Files
secs-gem/docs/16_e90_e157_substrates_modules.md
T
raphael a2ebbf7c65 feat(client)+feat(daemon): eq.names, @eq.command, E90/E157 RPCs
Python client:
- eq.names.event.* / .alarm.* / .command.* / .var.* / .constant.*  —
  autocomplete-able, typo-safe name lookup backed by the Describe RPC
  (lazy, cached; AttributeError on bad name with close-match hints)
- @eq.command decorator — binds a handler by function name, validated
  against the equipment's real command set at decoration time
- eq.report_substrate() — E90 wafer milestone reporting
- eq.report_module() — E157 module state reporting (auto-create)

Daemon (C++ service):
- ReportSubstrate RPC — drives E90 location + processing FSMs
- ReportModule RPC — drives E157 module FSM (auto-create on first report)
- ack_from_outcome() helper — consistent Ack mapping for read_sync results

Proto: SubstrateReport, ModuleReport, EquipmentDescription,
       SpoolFlushRequest, TerminalMessage; Describe, FlushSpool,
       SendTerminalMessage RPCs

Tests: C++ FSM test (journey + ghost rejection + E157 illegal jump);
       interop coverage for names API and E90/E157 round-trip

Docs: ch42 RPC table + Python example updated; ch16 daemon-path section added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 21:43:07 +02:00

244 lines
7.8 KiB
Markdown
Raw 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.
# 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 2550 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)