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>
13 KiB
13 — E30: GEM — the behavioural model
← 12 E4 — SECS-I | Back to index | Next: 14 E40 + E94 — Process and control jobs →
E5 (chapter 10) is the data encoding. E37 / E4 (chapters 11–12) 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:
- Two state machines — communication state (above HSMS) and control state (governs who's allowed to issue commands).
- 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;
tests in
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.
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
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++.
// 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 for the wider story.
Tests: 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 §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 | S7F1–F6, S7F17–F20, S7F23–F26 | RecipeStore |
| Material Movement | (handled by E40 + E94 + E87 + E90 + E157) | see chapters 14–16 |
| Equipment Terminal Services | S10F1/F2, S10F3/F4, S10F5/F6 | S10F1–F6 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 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; store internals in chapter 32.
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:
// 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).
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.