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

260 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 12 — E4: SECS-I — the serial origin
← [11 E37 — HSMS transport](11_e37_hsms.md) | [Back to index](00_index.md) | Next: [13 E30 — GEM](13_e30_gem.md) →
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`](../include/secsgem/secsi/header.hpp):
- **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`](../include/secsgem/secsi/header.hpp).
---
## Multi-block messages
A single SECS-I block carries at most **244 bytes** of body:
```cpp
// 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`](../include/secsgem/secsi/block.hpp) does
the split; [`secsi::assemble_message`](../include/secsgem/secsi/block.hpp)
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`](../tests/test_secsi.cpp)
(15 cases) and [`tests/test_secsi_tcp.cpp`](../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:
```cpp
// 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`](../include/secsgem/secsi/protocol.hpp):
```
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`](../tests/test_secsi.cpp) and
[`tests/test_secsi_timers.cpp`](../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`](../include/secsgem/secsi/protocol.hpp):
```cpp
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`](../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](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`](../include/secsgem/secsi/tcp_transport.hpp)
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](13_e30_gem.md).
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](13_e30_gem.md) 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](13_e30_gem.md)