40df3067a4
Six more chapters finishing Part 2. Together with chapters 10–13 they document every SEMI standard this codebase implements. 14 — E40 + E94: process jobs (8-state lifecycle, S16F11/F5/F7/F9 on the wire) and control jobs (CJ wraps PJs with batch policy, S14F9/S16F27 messages). Worked cascade showing how CJSTART propagates through the PJ FSM and triggers S6F11 CEIDs at each transition. 15 — E87 carriers: three orthogonal sub-machines (CarrierID, SlotMap, CarrierAccess) per carrier and three more (Transfer, Reservation, Association) per load port. S3F17 CarrierAction strings + CAACK codes, S3F19 SlotMap verify, the 5-state slot encoding, multi-port concurrency. 16 — E90 + E157: substrate tracking via three orthogonal axes (STS / SPS / SubstrateIDStatus) and module process tracking (NotExecuting / GeneralExecuting / StepExecuting / StepCompleted). End-to-end PVD example showing E40 + E157 + E90 transitions cascading into CEIDs. 17 — E116 + E120 + E39: equipment performance time-buckets across six states, common equipment model object hierarchy, S14F1/F3 GetAttr/SetAttr as the uniform wire access for any object type across multiple standards. 18 — E84 parallel I/O: ten signal lines, the 9-state handshake FSM, the three TA1/TA2/TA3 timing-critical timers, why a physical handshake gets modeled in software (testability, timer enforcement, CEID emission, multi-port concurrency), the pure-FSM + asio-adapter split. 19 — E42 + E148 + S5F9–F18: formatted recipes (S7F23/F25 typed PPBODY), time synchronization with 16-char + 14-char accepted on set, exception recovery as a persistent multi-step host-supervised FSM (Posted → Recovering → Cleared with abort/retry). Revisits the auto-S9 family and contrasts S9 (transport) vs S5F9 (application). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
265 lines
10 KiB
Markdown
265 lines
10 KiB
Markdown
# 19 — E42 + E148 + S9 + exception recovery
|
||
|
||
← [18 E84 — Parallel I/O handoff](18_e84_parallel_io.md) | [Back to index](00_index.md) | Next: [30 Repository tour](30_repository_tour.md) →
|
||
|
||
Three remaining standards-shaped concerns to round out Part 2:
|
||
|
||
- **E42** — Formatted (enhanced) Process Programs.
|
||
- **E148** — Time synchronization.
|
||
- **S5F9–S5F18** — Exception recovery (E5 §13 + GEM Additional).
|
||
|
||
Each is narrow enough that a half-chapter would do. Together they
|
||
round out the GEM 300 picture.
|
||
|
||
---
|
||
|
||
## E42 — Formatted Process Programs
|
||
|
||
### What it is
|
||
|
||
E30's Process Program Management (chapter 13) covers **unformatted**
|
||
recipes: the PPBODY is opaque bytes that only the equipment knows
|
||
how to parse. E42 adds **formatted** PPs — the recipe has a typed
|
||
SECS-II structure the host can introspect.
|
||
|
||
In practice, formatted recipes are a fab-internal standard rather
|
||
than a SEMI-defined schema. E42 just gives the wire shape; the
|
||
fab agrees what the structure means.
|
||
|
||
### The messages
|
||
|
||
| S/F | Direction | Purpose |
|
||
|-------|-----------|---------------------------------------------------------------|
|
||
| S7F23 | H → E | Formatted PP Send. Body: PPID + typed SECS-II body. |
|
||
| S7F24 | E → H | ACKC7 reply. |
|
||
| S7F25 | H → E | Formatted PP Request. Body: PPID. |
|
||
| S7F26 | E → H | Formatted PP Send (back). Body: PPID + typed body. |
|
||
|
||
Compare to unformatted S7F3 / S7F5: same direction pattern, just a
|
||
typed body instead of opaque bytes.
|
||
|
||
### Implementation
|
||
|
||
[`RecipeStore`](../include/secsgem/gem/store/recipes.hpp) carries
|
||
**both** views per recipe: an unformatted PPBODY (opaque bytes)
|
||
and an optional formatted body (a `secs2::Item` tree). S7F3 sends
|
||
the unformatted; S7F23 sends the formatted; both are stored side
|
||
by side and the host can request either via S7F5 (unformatted) or
|
||
S7F25 (formatted).
|
||
|
||
Tests:
|
||
[`tests/test_e42_formatted_pp.cpp`](../tests/test_e42_formatted_pp.cpp)
|
||
(6 cases — send formatted, request back, round-trip integrity,
|
||
ACKC7 error paths).
|
||
|
||
### Why both?
|
||
|
||
Some MES — and some equipment — only speak unformatted PPs.
|
||
Coexistence lets a vendor ship one EAP that handles both.
|
||
COMPLIANCE §4j has the audit detail.
|
||
|
||
---
|
||
|
||
## E148 — Time synchronization
|
||
|
||
### What it does
|
||
|
||
In a multi-tool fab, the host and the equipment need a **common
|
||
notion of time** for timestamp correlation. If tool A logs an
|
||
alarm at 14:32:01 and tool B logs a related alarm at 14:31:58, did
|
||
B precede A or did the clocks drift?
|
||
|
||
E148 defines the wire mechanism for keeping equipment clocks in
|
||
sync with a host-authoritative time source — typically NTP behind
|
||
the scenes — and lets equipment **report clock quality** so the
|
||
host knows how much to trust the timestamps coming off the tool.
|
||
|
||
### The messages
|
||
|
||
E148 doesn't add new streams; it specialises the existing E30
|
||
clock messages:
|
||
|
||
| S/F | Direction | Purpose |
|
||
|-------|-----------|----------------------------------------------------------|
|
||
| S2F17 | H → E | Read clock. |
|
||
| S2F18 | E → H | Reply with current time string. |
|
||
| S2F31 | H → E | Set clock to specified time string. |
|
||
| S2F32 | E → H | TIACK reply. |
|
||
|
||
The time string is **16 ASCII chars `YYYYMMDDhhmmsscc`** (E148
|
||
extended form, including hundredths). 14-char `YYYYMMDDhhmmss`
|
||
(without hundredths) is the older E30 form; the codebase **accepts
|
||
both on set** but emits 16 chars on read by default. See
|
||
[`docs/COMPLIANCE.md`](COMPLIANCE.md) §4g.
|
||
|
||
### Clock store
|
||
|
||
[`include/secsgem/gem/store/clock.hpp`](../include/secsgem/gem/store/clock.hpp)
|
||
holds the wall-clock plus a **drift / quality** indicator:
|
||
|
||
- `Drift_ms`: cumulative drift since last set.
|
||
- `Quality`: enum from {Authoritative, GoodNTP, FreeRunning,
|
||
Unreliable}.
|
||
|
||
A host can read both via E120/E39 attribute access (chapter 17) or
|
||
via DVID exposures (the EAP wires them).
|
||
|
||
### Why this matters
|
||
|
||
Without clock sync, **alarm root-cause analysis is impossible**.
|
||
SPC charts get the X-axis wrong. Yield correlations across tools
|
||
fall apart. Most modern fabs run NTP on every tool's
|
||
control-plane host; E148 is the mechanism for *reporting* clock
|
||
state, not for synchronising it (that's NTP's job).
|
||
|
||
---
|
||
|
||
## Exception recovery — S5F9–S5F18
|
||
|
||
### What it adds beyond base alarms
|
||
|
||
E5 §13 + E30 Alarm Management (covered in chapter 13) handles
|
||
alarms as **set/clear** events: an alarm goes active, the
|
||
equipment fires S5F1; later it clears, equipment fires another
|
||
S5F1. Simple.
|
||
|
||
But some alarms aren't simple to clear. A vacuum leak that
|
||
required a chamber vent + manual seal replacement can't just
|
||
"go away" — the equipment has to run a recovery procedure with
|
||
the host's involvement. **S5F9–S5F18** is the **exception
|
||
recovery** family that handles that.
|
||
|
||
### The exception lifecycle
|
||
|
||
Defined in
|
||
[`include/secsgem/gem/exception_state.hpp`](../include/secsgem/gem/exception_state.hpp):
|
||
|
||
```cpp
|
||
enum class ExceptionState : uint8_t {
|
||
Posted = 0, // S5F9 sent; awaiting host action
|
||
Recovering = 1, // S5F13 accepted; recovery in progress
|
||
RecoverFailed = 2, // S5F15 reported failure; retry possible
|
||
Cleared = 3, // resolved; terminal
|
||
};
|
||
```
|
||
|
||
Events: `Created` (NoState → Posted), `Recover` (host's S5F13),
|
||
`RecoveryComplete`, `RecoveryFailed`, `RecoveryAbort` (host's
|
||
S5F17), `Clear`.
|
||
|
||
### The messages
|
||
|
||
| S/F | Direction | Purpose |
|
||
|-------|-----------|--------------------------------------------------------------|
|
||
| S5F9 | E → H | Exception Post. Equipment-initiated. Body: EXID + EXTYPE + EXMESSAGE + recovery-method list. |
|
||
| S5F10 | H → E | Ack. |
|
||
| S5F11 | E → H | Exception Clear. Equipment-initiated when condition resolves. |
|
||
| S5F12 | H → E | Ack. |
|
||
| S5F13 | H → E | Exception Recover. Body: EXID + which recovery method to attempt. |
|
||
| S5F14 | E → H | Recovery progress. |
|
||
| S5F15 | E → H | Recovery Complete (or Failed). |
|
||
| S5F16 | H → E | Ack. |
|
||
| S5F17 | H → E | Exception Recover Abort. Cancel an in-progress recovery. |
|
||
| S5F18 | E → H | Recovery Aborted reply. |
|
||
|
||
The flow:
|
||
|
||
```
|
||
1. Vacuum leak detected. EAP calls exceptions.post(EXID=42, recovery=["vent","seal","pump-down"]).
|
||
→ ExceptionState: NoState → Posted.
|
||
→ S5F9 fires.
|
||
2. Host sees S5F9 → operator decides to attempt recovery → host sends S5F13(EXID=42, method="vent").
|
||
→ ExceptionState: Posted → Recovering.
|
||
3. Recovery in progress. EAP fires S5F14 periodically with progress.
|
||
4. EAP completes the venting step. Fires recover_complete event.
|
||
→ ExceptionState: Recovering → Cleared.
|
||
→ S5F15 fires.
|
||
5. Host acknowledges (S5F16). EAP fires S5F11 to confirm the
|
||
underlying condition is gone.
|
||
```
|
||
|
||
Or, the abort path:
|
||
|
||
```
|
||
3'. Operator decides recovery isn't working → host sends S5F17.
|
||
4'. ExceptionState: Recovering → Posted.
|
||
5'. EAP can be re-instructed via another S5F13 with a different
|
||
method, or the condition can clear autonomously (Clear event
|
||
→ Cleared state).
|
||
```
|
||
|
||
### Code
|
||
|
||
State machine:
|
||
[`include/secsgem/gem/exception_state.hpp`](../include/secsgem/gem/exception_state.hpp).
|
||
Store:
|
||
[`include/secsgem/gem/store/exceptions.hpp`](../include/secsgem/gem/store/exceptions.hpp)
|
||
— persistent, so an exception in flight survives a power cycle.
|
||
Tests:
|
||
[`tests/test_exceptions.cpp`](../tests/test_exceptions.cpp) (11
|
||
cases) + persistence in
|
||
[`tests/test_exception_persistence.cpp`](../tests/test_exception_persistence.cpp)
|
||
(5 cases).
|
||
|
||
### Why this is its own family
|
||
|
||
Two reasons:
|
||
|
||
1. **State persistence.** Alarms come and go in seconds; exception
|
||
recovery can span hours and a few power cycles. The store
|
||
journal lets the equipment remember "we were halfway through
|
||
recovery method 2 of EXID=42" across restarts.
|
||
2. **Multi-step coordination.** Each step (`S5F13` → `S5F14` →
|
||
`S5F15`) is a host-supervised transaction. Base alarms can't
|
||
express "host, here are three recovery options, pick one."
|
||
|
||
Exception recovery is an Additional GEM capability — not every MES
|
||
asks for it — but the codebase implements it because it's
|
||
upstream-absent in `secsgem-py` (see [docs/COMPLIANCE.md](COMPLIANCE.md)
|
||
§4k) and because the persistent state machine is a nice example of
|
||
the spec-as-data pattern applied to a less-trivial FSM.
|
||
|
||
---
|
||
|
||
## The auto-S9 family (revisited)
|
||
|
||
We covered the S9 wire-error replies in chapter 11. Worth
|
||
re-listing here because S9 is part of the **error/exception layer**
|
||
even though it's transport-level rather than application-level:
|
||
|
||
| Function | Trigger |
|
||
|----------|----------------------------------------------------------|
|
||
| S9F1 | Unrecognized Device ID |
|
||
| S9F3 | Unrecognized Stream |
|
||
| S9F5 | Unrecognized Function |
|
||
| S9F7 | Illegal Data (body failed to decode) |
|
||
| S9F9 | Transaction Timer Timeout (T3 expired) |
|
||
| S9F11 | Data Too Long (body exceeded configured cap) |
|
||
| S9F13 | Conversation Timer Timeout (equipment-internal) |
|
||
|
||
Implementation:
|
||
[`hsms::Connection::emit_s9`](../include/secsgem/hsms/connection.hpp)
|
||
called from the connection's framing and routing paths. Tested
|
||
across [`tests/test_hsms_s9.cpp`](../tests/test_hsms_s9.cpp) and
|
||
[`tests/test_s9_fallback.cpp`](../tests/test_s9_fallback.cpp).
|
||
|
||
Difference from S5F9: S9 is **transport-level** (the bytes
|
||
themselves were wrong); S5F9 is **application-level** (the
|
||
equipment can't continue normal operation).
|
||
|
||
---
|
||
|
||
## End of Part 2
|
||
|
||
You now know every SECS/GEM and GEM 300 standard that this
|
||
codebase implements. Twelve standards across nine chapters, each
|
||
one mapped to its state machine, its messages, its store, and the
|
||
tests that hold it down.
|
||
|
||
Part 3 starts. We turn from "what the spec says" to "how this
|
||
codebase implements it" — repository tour, codegen, the data model
|
||
structure, transport internals, state-machine composition,
|
||
persistence mechanics.
|
||
|
||
Next: [→ 30 Repository tour](30_repository_tour.md)
|