Files
secs-gem/docs/12_e4_secs_i.md
raphael 858ca22975 docs: chapters 11–13 — HSMS, SECS-I, GEM
Three more chapters of Part 2:

11 — E37 HSMS.  4-byte length prefix + 10-byte header (R-bit + session
id + W-bit + stream + function + PType + SType + system_bytes), the
9 SType control messages, the NOT-SELECTED → SELECTED state machine,
T3/T5/T6/T7/T8 with what each one bounds, the auto-S9 paths
(S9F1/F3/F5/F7/F9/F11), HSMS-SS vs HSMS-GS, the asio
single-threaded contract.

12 — E4 SECS-I.  Half-duplex line turnaround (ENQ/EOT/ACK/NAK), the
10-byte block header bit-packing (R-bit / W-bit / E-bit / system
bytes), the 244-byte block cap and multi-block split/assemble, the
event-driven IO-free FSM with its Action / Event variants, T1/T2/T3/T4
with semantics + defaults, master/slave contention.  Notes the
deferred asio serial_port adapter; explains why this chapter
matters even for HSMS-only readers.

13 — E30 GEM.  Disambiguates the three state machines (HSMS transport
vs GEM communication vs GEM control), walks the comm-state FSM
(DISABLED → WAIT-CRA → COMMUNICATING with T_CRA / T_DELAY) and the
control-state FSM (5 states + the YAML transition table).  Lists
every Fundamental and Additional capability with its messages, code
locations, and store assignments.  One worked Event-Notification
scenario tracing seven on-wire steps to their EquipmentDataModel
internals.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 20:07:31 +02:00

9.7 KiB
Raw Permalink Blame History

12 — E4: SECS-I — the serial origin

11 E37 — HSMS transport | Back to index | Next: 13 E30 — GEM

HSMS (chapter 11) is what every modern 300 mm tool runs. But SECS-I — published 1980, the original SECS transport — is still on the wire. Older 200 mm fabs, smaller specialty tools (e.g. inspection microscopes, simple metrology), some legacy lithography steppers, even some new equipment shipping into mixed-fleet fabs all speak SECS-I over RS-232 or RS-422.

This chapter is short, because the protocol is small. By the end:

  • You'll understand half-duplex line turnaround.
  • You'll know the ENQ / EOT / ACK / NAK handshake.
  • You'll be able to read the 10-byte block header.
  • You'll know exactly what's implemented here, what isn't, and why.

What SECS-I actually is

A half-duplex block protocol for RS-232 / RS-422 serial links. Half-duplex means: at any moment, exactly one side is allowed to be transmitting. Switching direction requires an explicit handshake.

The handshake uses four single-byte control codes:

Byte Mnemonic Meaning
0x05 ENQ I want to send
0x04 EOT Go ahead, send
0x06 ACK Block received OK
0x15 NAK Block bad, retry

A successful transmission looks like:

sender            receiver
──────            ────────
 ENQ ──────────────►        "I want to send"
       ◄────────── EOT      "go ahead"
 <block bytes> ────►         "here you go"
       ◄────────── ACK      "got it"

Compare to HSMS: HSMS gets all this for free from TCP. TCP is full-duplex, segments are framed by the operating system, and the ACK semantics are at the byte level not the message level. SECS-I predates that affordance — it was designed for a UART straight on the equipment's serial port.


The 10-byte block header

A block carries the same logical information as an HSMS data message — session, stream, function, W-bit, system bytes — packed slightly differently:

 byte  0  1     2         3       4   5      6  7  8  9
      ┌──────┬──────────┬──────────┬────────┬───────────┐
      │R+sid │ W+stream │ function │E+block#│ sys bytes │
      └──────┴──────────┴──────────┴────────┴───────────┘
        u16    bit7 W    func id    bit15 E     u32
        bit15 R bits6-0  (byte 3)   bits14-0   (BE)
        bits14-0  stream            block #
        device id

Three bit-packings, all in Header::encode/decode:

  • Byte 01: R-bit (bit 15) + 15-bit device ID. R=1 means "host → equipment", R=0 means "equipment → host".
  • Byte 2: W-bit (bit 7) + 7-bit stream. Same W-bit semantics as HSMS.
  • Byte 45: E-bit (bit 15) + 15-bit block number. E=1 marks the last block of a multi-block message. Block numbers are 1-based.
  • Byte 69: 32-bit system_bytes correlation token.

Defined in include/secsgem/secsi/header.hpp.


Multi-block messages

A single SECS-I block carries at most 244 bytes of body:

// include/secsgem/secsi/block.hpp
inline constexpr std::size_t kMaxBlockBody = 244;

Why 244? The framing uses a one-byte length field that counts 10 (header) + body, with byte values 10254 valid (255 reserved). 254 10 = 244.

A SECS-II body larger than 244 bytes is split into multiple blocks, each with the same header except for the incrementing block number and the E-bit (set only on the last block). secsi::split_message does the split; secsi::assemble_message recombines them.

Each block has a 2-byte checksum after the body — sum of every byte in the header + body modulo 2^16, big-endian.

Multi-block round-trip is verified by tests/test_secsi.cpp (15 cases) and tests/test_secsi_tcp.cpp (3 cases — an end-to-end split / send / reassemble over the test TCP transport).

Why HSMS doesn't need this. HSMS frames have a 4-byte length prefix, so a single frame can carry up to 4 GiB. Multi-block is a SECS-I concept that simply doesn't apply on TCP.


The line-turnaround FSM

The interesting part of SECS-I — and the part that bites every implementer — is the half-duplex state machine. Both sides might want to send at the same time. Both might ENQ simultaneously. Both must agree on who yields.

E4 §7.1.4: the master holds, the slave yields. By convention the host is master and the equipment is slave, but this is configurable. In code:

// include/secsgem/secsi/protocol.hpp
enum class Role { Master, Slave };

The FSM is event-driven and IO-free. It takes:

  • EventByte — one received byte.
  • EventSend — the application wants to send a block.
  • EventTimeout — a previously-armed timer fired.

And produces a sequence of actions:

  • ActionTransmit — push these bytes onto the wire.
  • ActionStartTimer / ActionCancelTimer — arm or cancel one of T1/T2/T3/T4.
  • ActionDeliverBlock — pass this received block up to the application.
  • ActionRaiseError — fatal: retries exhausted, line protocol violated, etc.

State names from Protocol::State:

Idle ──ENQ─► SendEnqSent ──EOT─► SendBlock ──bytes─► WaitAck ──ACK─► Idle
                                                              └─NAK─► retry (RTY budget)

Idle ──ENQ(rx)─► RecvEnq ──EOT(tx)─► RecvBlock ──bytes(rx)─► RecvAck ──ACK(tx)─► Idle
                                              └─bad checksum─► NAK(tx) → RecvBlock (retry)

Tests in tests/test_secsi.cpp and tests/test_secsi_timers.cpp walk every transition.


The four SECS-I T-timers

Distinct from HSMS T-timers despite the name overlap:

Name Default Bounds
T1 500 ms Inter-character — gap between bytes in one block
T2 10 s Protocol — waiting for EOT after our ENQ, or vice versa
T3 45 s Reply — primary (W=1) waiting for the reply block
T4 45 s Inter-block — gap between blocks of a multi-block message

Defaults in secsi::Timers:

struct Timers {
  std::chrono::milliseconds t1{500};
  std::chrono::milliseconds t2{10000};
  std::chrono::milliseconds t3{45000};
  std::chrono::milliseconds t4{45000};
  uint8_t rty = 3;
};

Each timer is armed by the FSM via ActionStartTimer, cancelled by ActionCancelTimer, and fired by the wrapping host's wall clock. The FSM itself has no wall clock — it only sees EventTimeout when the host tells it the timer fired.

Tested independently in tests/test_secsi_timers.cpp (9 cases — every armed-and-cancelled scenario, every expiry).


What's implemented here vs. what isn't

Per docs/COMPLIANCE.md §1a:

Item Status
10-byte block header bit-packing/unpacking
Length-prefixed block + 2-byte checksum
Multi-block split / assemble (E-bit, block#)
ENQ/EOT/ACK/NAK half-duplex handshake
RTY retry budget
T1/T2/T3/T4 timer hooks (event-driven)
Master/slave contention resolution
TCP tunnel for testing
Serial-port driver (asio serial_port) deferred

The FSM is complete and tested end-to-end over a TCP transport: secsi::TcpTransport wraps the FSM behind an asio TCP socket. This is enough for testing and for the docker-compose interop flows, but it's not a real serial port.

The remaining piece — a serial driver that pumps bytes between the FSM and an asio::serial_port — has not been written. Most modern GEM equipment runs HSMS; the deferral is documented in the README "Deferred follow-ups" section. Mirror TcpTransport to add it.


Why this matters even if you only run HSMS

Two reasons to read this chapter even if you'll never touch serial:

  1. The line-turnaround FSM informs the GEM communication state machine. E30 §6.5 reuses the establish-comms pattern that originated in SECS-I — T_CRA / T_DELAY echo T3 / T2. See chapter 13.
  2. Block-level error recovery is a useful mental model. Even on HSMS, the per-message correlation by system_bytes and the T3 reply timer are direct descendants of SECS-I's block-level tracking. Understanding one helps you read the other.

Where to go next

Now you know both transports. Chapter 13 lifts up one level: E30 — GEM behaviour. This is where the protocol stops being plumbing and starts encoding what equipment is supposed to do: communication state, control state, the GEM Fundamental + Additional capabilities, scenarios for every typical interaction.

Next: → 13 E30 — GEM behaviour