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
+6 -5
View File
@@ -13,10 +13,10 @@ from secsgem_client import Equipment
eq = Equipment("localhost:50051")
@eq.on("START")
def start(cmd):
@eq.command
def START(cmd):
print("host says START", cmd.params)
eq.fire("ProcessStarted")
eq.fire(eq.names.event.ProcessStarted)
eq.listen(background=True)
@@ -25,7 +25,8 @@ while True:
pressure = round(random.uniform(1.0, 3.0), 3)
eq.set(ChamberPressure=pressure)
if pressure > 2.9:
eq.alarm("chiller_temp_high")
eq.alarm(eq.names.alarm.chiller_temp_high)
else:
eq.clear("chiller_temp_high")
eq.clear(eq.names.alarm.chiller_temp_high)
time.sleep(1)