Files
secs-gem/docs/13_e30_gem.md
raphael 4b4b2ac690 docs: correct drifted and fabricated APIs in chapters 13/17/35/51
An audit of doc code blocks against the real headers found APIs that do
not exist in the codebase, presented as authoritative walkthroughs:

- ch35 (dispatch): an entirely fabricated callback architecture —
  HostCommandRegistry::set_emit_ceid_handler, CommandOutcome, emit_ceids.
  Rewritten to the real Spec/Result/dispatch + the new set_handler hook.
- ch13 (E30): wrong store names — EventStore/ReportStore -> EventReportSubscriptions,
  SvidStore -> StatusVariableStore, AlarmStore/AlarmDispatcher -> AlarmRegistry,
  ClockStore -> Clock, TerminalServiceStore -> (no store), in both the
  capability tables and the worked S2F33 example.
- ch17 (E116): EptStore/seconds/bucket_ -> EptStateMachine/milliseconds/buckets_.
- ch51 (extending): stale host-command handler -> the real set_handler signature.

Verified clean by grep: no fabricated symbols remain in docs/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:00:58 +02:00

260 lines
13 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.
# 13 — E30: GEM — the behavioural model
← [12 E4 — SECS-I](12_e4_secs_i.md) | [Back to index](00_index.md) | Next: [14 E40 + E94 — Process and control jobs](14_e40_e94_jobs.md) →
E5 (chapter 10) is the data encoding. E37 / E4 (chapters 1112)
move the encoded bytes. This chapter is the first one where
*behaviour* enters the picture.
**SEMI E30 — Generic Equipment Model (GEM)**, published 1992,
specifies what an equipment must *do* when its host sends specific
messages. E5 says how to encode S1F13; E30 says what state must
change when an S1F13 arrives, what reply must come back, and under
what conditions either side may refuse.
E30 has two top-level concepts:
1. **Two state machines** — communication state (above HSMS) and
control state (governs who's allowed to issue commands).
2. **GEM Capabilities** — Fundamentals (mandatory) and Additionals
(optional but de-facto required). Each capability defines its
own scenarios + messages.
By the end of this chapter you'll know both state machines, the
14 Fundamentals + Additionals, and where each one lives in code.
---
## The two GEM state machines
GEM has **two** state machines that live on top of HSMS's transport
state machine. Read carefully — beginners conflate these all the
time:
| State machine | Lives where | Concerns |
|----------------------|---------------------------------------------------|-----------------------------------------------|
| **HSMS transport** | `secsgem::hsms::Connection` | NOT-CONNECTED → NOT-SELECTED → SELECTED |
| **GEM communication**| `secsgem::gem::CommunicationStateMachine` | DISABLED / WAIT-CRA / WAIT-DELAY / COMMUNICATING |
| **GEM control** | `secsgem::gem::ControlStateMachine` | EquipmentOffline / OnlineLocal / OnlineRemote / … |
All three can be in independent states. `SELECTED` (HSMS) doesn't
imply `COMMUNICATING` (GEM-comm); `COMMUNICATING` doesn't imply
`OnlineRemote` (control).
### Communication state (E30 §6.5)
What it answers: **have host and equipment agreed they can talk to
each other at the GEM level?** This is *above* HSMS — even after
HSMS is SELECTED, GEM-comm starts at WAIT-CRA and only reaches
COMMUNICATING after a successful `S1F13 / S1F14 (COMMACK=Accept)`
exchange.
```
wire: S1F13 →
DISABLED ─enable──► WAIT-CRA ─────────────► COMMUNICATING
│ ◄─ S1F14(Accept)
│ S1F14(Deny) or T_CRA expires
WAIT-DELAY
│ T_DELAY expires
WAIT-CRA (retry)
```
Two timers, both in `gem::CommunicationStateMachine`:
- **T_CRA** (default 45 s): how long to wait for the S1F14 reply
after sending S1F13.
- **T_DELAY** (default 10 s): how long to back off after a
rejected S1F14 before retrying.
Code:
[`include/secsgem/gem/communication_state.hpp`](../include/secsgem/gem/communication_state.hpp);
tests in
[`tests/test_communication_state.cpp`](../tests/test_communication_state.cpp)
(12 cases — every transition, every timer expiry).
The state machine is **IO-free** — it raises actions (send S1F13,
arm T_CRA, …) that the caller translates into asio work. This
makes it unit-testable without spinning up a TCP socket. Same
design pattern as `secsi::Protocol` from chapter 12.
### Control state (E30 §6.2)
What it answers: **who's allowed to issue commands right now?**
Five states:
| State | Meaning |
|------------------|------------------------------------------------------------|
| `EquipmentOffline` | Off-network. Both panel and host commands disabled. |
| `AttemptOnline` | Transient: equipment is dialing host. Rare. |
| `HostOffline` | Host disconnected (or never connected). Operator can act, host cannot. |
| `OnlineLocal` | Operator at the local panel has control. Host can read, not act. |
| `OnlineRemote` | Host has full control. |
Defined in
[`include/secsgem/gem/control_state.hpp`](../include/secsgem/gem/control_state.hpp).
Transitions are driven by **events** (operator pressed Online,
host sent S1F17, AttemptOnline succeeded or failed, …) and
encoded as a transition table loaded from
[`data/control_state.yaml`](../data/control_state.yaml):
```yaml
# data/control_state.yaml
transitions:
- {from: EquipmentOffline, on: operator_switch_online, to: AttemptOnline, then: OnlineRemote}
- {from: OnlineRemote, on: host_request_offline, to: HostOffline, ack: Accept}
- {from: OnlineLocal, on: host_request_remote, ack: NotAccept}
...
```
The table is **pure data**. `ControlTransitionTable` looks up
rows; `ControlStateMachine` applies them. No `if/else` ladders
embedded in C++.
```cpp
// include/secsgem/gem/control_state.hpp:53
struct ControlTransition {
ControlState from;
ControlEvent on;
std::optional<ControlState> to;
std::optional<ControlState> then; // chain through AttemptOnline
std::optional<uint8_t> ack_code;
};
```
This is **spec-as-data** in its purest form: the SEMI standard
section 6.2 is one YAML file. Add a state, add a transition, edit
the YAML — no recompile, no C++ change. See chapter
[31](31_spec_as_data_and_codegen.md) for the wider story.
Tests: [`tests/test_control_state.cpp`](../tests/test_control_state.cpp)
(15 cases — every YAML-defined transition, both ACK codes).
---
## GEM Fundamentals (E30 §5.2)
The **mandatory** capabilities. An equipment that doesn't ship
these isn't GEM-compliant, end of story.
| Fundamental | Messages | Code |
|---------------------------------------------|---------------------------------------------------------|---------------------------------------------------------------|
| State models | — | `ControlStateMachine`, `CommunicationStateMachine` |
| Equipment Processing States | — | `ControlTransitionTable` (vendor supplies concrete states) |
| Host-Initiated S1F13/F14 | S1F13 / S1F14 | `gem::CommunicationStateMachine` |
| Event Notification | S6F11 / S6F12 | `EventReportSubscriptions` + `EquipmentDataModel::compose_reports_for` |
| On-Line Identification | S1F1 / S1F2 | Router handler in `apps/secs_server.cpp` |
| Error Messages | S9F1/F3/F5/F7/F9/F11 | `Connection::emit_s9` + `Router::dispatch_with_s9` |
| Documentation | S1F19/F20, S1F21/F22, S1F23/F24 | `gem::compliance` / namelist handlers |
| Control (Operator-Initiated) | — | `ControlStateMachine::operator_online/offline/local/remote` |
Full per-capability accounting with status + spec section + code
ref: [docs/COMPLIANCE.md](COMPLIANCE.md) §3.
---
## GEM Additionals (E30 §5.3)
The **optional** capabilities — but every commercial MES will
require all of them. In practice "Additional" means "optional per
the SEMI spec, but mandatory for procurement."
| Additional | Messages | Code |
|---------------------------------------|---------------------------------------------------------------------------|-----------------------------------------------|
| Establish Communications | S1F13/F14 | `CommunicationStateMachine` (also in Fundamentals) |
| Dynamic Event Report Configuration | S2F33/F34, S2F35/F36, S2F37/F38 | `EventReportSubscriptions` |
| Variable Data Collection | S1F21/F22 + DVID values via `vid_value` | `DataVariableStore` |
| Trace Data Collection | S2F23/F24, S6F1/F2 | `TraceStore` |
| Status Data Collection | S1F3/F4, S1F11/F12 | `StatusVariableStore` |
| Alarm Management | S5F1/F2, S5F3/F4, S5F5/F6, S5F7/F8 | `AlarmRegistry` |
| Remote Control | S2F41/F42, S2F49/F50, S2F21/F22 | `HostCommandRegistry` |
| Equipment Constants | S2F13/F14, S2F15/F16, S2F29/F30 | `EquipmentConstantStore` |
| Process Program Management | S7F1F6, S7F17F20, S7F23F26 | `RecipeStore` |
| Material Movement | (handled by E40 + E94 + E87 + E90 + E157) | see chapters 1416 |
| Equipment Terminal Services | S10F1/F2, S10F3/F4, S10F5/F6 | S10F1F6 Router handlers (no dedicated store) |
| Clock | S2F17/F18, S2F31/F32 | `Clock` (+ E148 in chapter 19) |
| Limits Monitoring | S2F45/F46, S2F47/F48 | `LimitMonitorStore` |
| Spooling | S2F43/F44, S6F23/F24, S6F25/F26 | `SpoolStore` (persistent file-backed journal) |
Every capability has its **own store** (a namespace bundle of
state + behaviour) and its **own Router handlers** for the messages
that drive it. Stores compose into `EquipmentDataModel`. Chapter
[32](32_stores_and_the_data_model.md) is the deep dive.
---
## How a typical scenario lands in code
Pick **Event Notification** — the canonical GEM scenario:
```
1. Host sends S2F33 (DefineReport): "RPTID 100 = [SVID 1, SVID 5]"
2. Equipment stores the definition in EventReportSubscriptions; replies S2F34(DRACK=0).
3. Host sends S2F35 (LinkEvent): "CEID 300 → RPTID 100"
4. Equipment stores the link in EventReportSubscriptions; replies S2F36(LRACK=0).
5. Host sends S2F37 (EnableEvent CEED=true, CEID=[300])
6. Equipment marks CEID 300 enabled in EventReportSubscriptions; replies S2F38(ERACK=0).
7. Later: some FSM transition decides to fire CEID 300.
compose_reports_for(300) walks EventReportSubscriptions → StatusVariableStore
and assembles {RPTID=100, V=[svid1_val, svid5_val]}.
8. Equipment emits S6F11 with the assembled body.
9. Host replies S6F12(ACKC6=0).
```
Steps 1, 3, 5 are inbound — `gem::Router` dispatches by
`(stream, function)` to a registered handler. Steps 2, 4, 6, 8
are outbound — the handler or the FSM hands a built `secs2::Message`
to the Connection. Step 7 is *internal* — the EquipmentDataModel
walks its own stores; nothing on the wire happens until step 8.
Router and dispatch is in chapter
[35](35_state_machines_and_dispatch.md); store internals in
chapter [32](32_stores_and_the_data_model.md).
---
## The host-side analogue
Everything above describes the equipment side. The host side has
its own E30 state — every Additional capability has a host-side
view too (the host can disable an alarm, change a host command,
etc.). This codebase implements the host-side as a thin module:
```cpp
// include/secsgem/gem/host_handler.hpp
class HostHandler {
// Decode equipment-initiated S5F1 / S6F11 / S9Fx.
// Maintain the host's view of CEID enables, alarm enables, …
};
```
`apps/secs_client.cpp` is the canonical host binary. In the
two-container demo it walks ~24 transactions against
`apps/secs_server.cpp` — the host side mostly *reads* what the
equipment reports and acknowledges. Driving an MES is a much
bigger story (see chapter [41](41_integration_hardware_mes_production.md)).
---
## Where to go next
You now have:
- E5 codec.
- E37/E4 transport.
- E30 state machines and capabilities.
That's the complete **base GEM stack**. Modern fab automation
needs more — process job lifecycles, carrier management, substrate
tracking — and that's what **GEM 300** adds.
The next six chapters tackle the GEM 300 standards one family at a
time. Each one fits on top of E30 in the same way: a state
machine + a store + Router handlers + per-CEID emissions.
Next: [→ 14 E40 + E94 — Process and control jobs](14_e40_e94_jobs.md)