# 16 — E90 + E157: Substrate and module tracking ← [15 E87 — Carriers and load ports](15_e87_carriers.md) | [Back to index](00_index.md) | Next: [17 E116 + E120 + E39 — Performance, CEM, objects](17_e116_e120_e39_objects.md) → E87 (chapter 15) tracks the **container**. E90 and E157 track what's *inside* the container — every individual wafer (substrate) and every process module the wafer passes through. This chapter is shorter than the others in Part 2 because the ideas overlap E87 (the same three-orthogonal-axes pattern repeats) and E40 (state events drive S6F11 CEIDs). --- ## E90 — Substrate tracking ### What it tracks One state-bearing record **per wafer**, identified by a substrate ID (an ASCII string, often the laser-etched serial number). E90 has **three orthogonal axes** — the same pattern as E87's carrier (chapter 15 §3): #### 1. Substrate State (STS) — location ```cpp // include/secsgem/gem/substrate_state.hpp:26 enum class SubstrateState : uint8_t { AtSource = 0, // in its origin carrier slot AtWork = 1, // in-process at a module AtDestination = 2, // delivered to final location NoState = 255, }; ``` Events: `Acquire` (Source→Work), `Release` (Work→Destination), `Return` (Work→Source, for unprocessed return). #### 2. Substrate Processing State (SPS) — lifecycle ```cpp enum class SubstrateProcessingState : uint8_t { NeedsProcessing = 0, InProcess = 1, Processed = 2, Aborted = 3, Stopped = 4, Rejected = 5, Lost = 6, Skipped = 7, NoState = 255, }; ``` Events: `StartProcessing`, `EndProcessing`, `Abort`, `Stop`, `Reject`, `ReportLost`, `Skip`. #### 3. Substrate ID Status — identity confidence ```cpp enum class SubstrateIDStatus : uint8_t { NotConfirmed = 0, WaitingForHost = 1, Confirmed = 2, Mismatched = 3, NoState = 255, }; ``` Mirrors the `CarrierIDStatus` pattern from E87 — same problem (equipment reads ID, host may need to verify), same shape of solution. ### Why three axes? The same reason E87 has three: these aspects evolve **at different times** and **for different reasons**. - A wafer can be `AtWork` + `NeedsProcessing` (just arrived, recipe hasn't started). - A wafer can be `AtWork` + `InProcess` (recipe running). - A wafer can be `AtSource` + `Processed` + `Confirmed` (back in its carrier slot after processing — typical end state). Putting all three in one enum would multiply to ~30 valid combinations. Three independent FSMs with ~3 events each is much cleaner. ### Code State machines: [`include/secsgem/gem/substrate_state.hpp`](../include/secsgem/gem/substrate_state.hpp) defines `SubstrateStateMachine`, which composes the three. Store: [`include/secsgem/gem/store/substrates.hpp`](../include/secsgem/gem/store/substrates.hpp) holds one record per substrate ID, with a Location string the application updates as the wafer moves. Tests: [`tests/test_substrates.cpp`](../tests/test_substrates.cpp) (14 cases — every axis, every event); persistence in [`tests/test_substrate_persistence.cpp`](../tests/test_substrate_persistence.cpp) (7 cases). CEID-on-wire emission ("Substrate StartProcessing fires the configured SubstrateInProcess CEID") is verified by [`tests/test_wire_ceid_emission.cpp`](../tests/test_wire_ceid_emission.cpp). ### Wire interaction E90 doesn't define its own S/F messages — substrate state changes fire as **CEIDs** that the host has subscribed to via the standard E30 §6.6 Dynamic Event Report Configuration (chapter 13). So: - Equipment fires `Acquire` event on substrate `W-2026-06-09-A47`. - `SubstrateStateMachine` transitions Source → Work. - The state-change handler looks up the configured CEID for "SubstrateInProcess" (from `data/equipment.yaml`). - That CEID fires → `compose_reports_for(ceid)` → `S6F11`. Host gets one S6F11 per wafer transition. In a 25-wafer FOUP that's 25–50 events per processing pass. Persistent reports + spool (chapter 13 Additionals) handle the burst. --- ## E157 — Module Process Tracking ### What it tracks One state-bearing record **per process module**. A cluster tool has multiple modules (Chamber A, Chamber B, Pre-clean, …); each runs its own recipe step in parallel or sequence. E157 lets the host see *which module is in which step of which recipe right now*. ### The states ```cpp // include/secsgem/gem/module_state.hpp:20 enum class ModuleState : uint8_t { NotExecuting = 0, GeneralExecuting = 1, // setup, pre-process, post-process StepExecuting = 2, // actively running a recipe step StepCompleted = 3, NoState = 255, }; ``` Events: `StartGeneral`, `StartStep`, `CompleteStep`, `Reset`, `Abort`. Notice this is a much simpler FSM than E90 — one axis only. That's because modules are more deterministic than substrates: a module is either running a step or it isn't; substrates can be in many overlapping conditions. ### Code [`include/secsgem/gem/module_state.hpp`](../include/secsgem/gem/module_state.hpp) defines `ModuleStateMachine`. Store: [`include/secsgem/gem/store/modules.hpp`](../include/secsgem/gem/store/modules.hpp). Tests: [`tests/test_modules.cpp`](../tests/test_modules.cpp) (5 cases). ### How E157 plays with E40 and E90 Concrete example. A PVD tool with three modules (Chamber A, B, C); host submits PJ for wafer W-1, recipe says "process at Chamber B for 90 seconds": ``` 1. PJ-1 transitions Queued → SettingUp → WaitingForStart → Processing. (E40 FSM, chapter 14) 2. Equipment fires ModuleEvent::StartGeneral on Chamber B. ModuleState: NotExecuting → GeneralExecuting. (E157 FSM) 3. Equipment fires SubstrateEvent::Acquire on W-1. Substrate STS: AtSource → AtWork. (E90 FSM) 4. Recipe step begins. ModuleEvent::StartStep on Chamber B. ModuleState: GeneralExecuting → StepExecuting. Substrate SPS: NeedsProcessing → InProcess. 5. ...90 seconds pass... 6. Recipe step ends. ModuleEvent::CompleteStep on Chamber B. ModuleState: StepExecuting → StepCompleted. Substrate SPS: InProcess → Processed. 7. Substrate released. SubstrateEvent::Release on W-1. Substrate STS: AtWork → AtDestination. 8. PJ-1: ProcessComplete. ``` Each of the eight transitions fires a CEID, which fires an S6F11 event report. The host sees the **complete trace** of where every wafer was at every moment. --- ## Daemon path (Python client) If your tool uses the daemon (`secs_gemd`) and the Python client, the E90 and E157 RPCs are wrapped as two methods: ```python from secsgem_client import Equipment eq = Equipment("localhost:50051") # E90 — substrate journey (daemon drives FSMs, fires CEIDs automatically) eq.report_substrate("WFR-001", "ARRIVED", carrier_id="FOUP-7", slot=3) eq.report_substrate("WFR-001", "AT_WORK") eq.report_substrate("WFR-001", "PROCESSING") eq.report_substrate("WFR-001", "PROCESSED") eq.report_substrate("WFR-001", "AT_DESTINATION") # E157 — module state (module is auto-created on first report) eq.report_module("CHAMBER-A", "GENERAL_EXECUTING") eq.report_module("CHAMBER-A", "STEP_EXECUTING") eq.report_module("CHAMBER-A", "STEP_COMPLETED") eq.report_module("CHAMBER-A", "NOT_EXECUTING") ``` Milestones map to the `SubstrateReport.Milestone` protobuf enum; module states to `ModuleReport.State`. The daemon's `ReportSubstrate` handler validates FSM transitions and returns `INVALID_OBJECT` if the substrate was never `ARRIVED` (which guarantees the daemon owns the substrate record). --- ## Where to go next You now know how every component of in-flight material is tracked. The next chapter covers the three smaller GEM 300 standards that round out the suite: equipment performance time tracking, the common equipment model, and generic object services. Next: [→ 17 E116 + E120 + E39 — Performance, CEM, objects](17_e116_e120_e39_objects.md)