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>
This commit is contained in:
2026-06-26 21:43:07 +02:00
parent 9876dd9b5a
commit a2ebbf7c65
14 changed files with 602 additions and 27 deletions
+22
View File
@@ -144,6 +144,28 @@ def run(grpc_addr: str, hsms_host: str, hsms_port: int) -> int:
check("handler's eq.fire reached the host (completion signal)",
ceid300.wait(timeout=10))
# ---- Describe-backed names + @eq.command binding ----
check("eq.names.event has ProcessStarted",
"ProcessStarted" in eq.names.event)
check("eq.names.command has START", "START" in eq.names.command)
try:
_ = eq.names.event.NoSuchEvent
check("typo on eq.names raises", False)
except AttributeError:
check("typo on eq.names raises", True)
eq.fire(eq.names.event.ProcessStarted)
check("eq.fire(eq.names.event.*) accepted", True)
# ---- E90/E157 material tracking ----
eq.report_substrate("WFR-PY-1", "ARRIVED")
eq.report_substrate("WFR-PY-1", "AT_WORK")
eq.report_substrate("WFR-PY-1", "PROCESSING")
eq.report_substrate("WFR-PY-1", "PROCESSED")
check("report_substrate journey accepted", True)
eq.report_module("MOD-PY-1", "GENERAL_EXECUTING")
eq.report_module("MOD-PY-1", "STEP_EXECUTING")
check("report_module accepted", True)
# ---- operator offline via the client ----
eq.request_control_state("HOST_OFFLINE")
check("request_control_state(HOST_OFFLINE)",