a2ebbf7c65
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>
244 lines
7.8 KiB
Markdown
244 lines
7.8 KiB
Markdown
# 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)
|