feat(client): typo-safe protocol enums + context manager; add wafer_tool example
Interface cleanup so the report_* family matches the typo-safe ethos of eq.names instead of leaking raw protobuf errors on a misspelled value. - Milestone / ModuleState / JobState: importable str-enums (member == its wire name, so plain strings still work) — autocomplete + a typo-checked happy path. The clean rule: equipment-specific *names* live on eq.names; fixed protocol *value-sets* are enums. - _enum_value(): resolves an enum-or-string arg client-side and, on a bad value, raises ValueError with a close-match hint *before* the wire. Wired into report_job / report_substrate / report_module / request_control_state (all previously raised a raw protobuf ValueError). - Equipment is now a context manager (with Equipment(...) as eq: ...). - examples/wafer_tool.py: a cluster tool tracking one wafer through one module end-to-end (E90 + E157), showing the enums + context manager. - tests/test_enums.py: asserts the enums stay in lockstep with the proto and that the typo path is helpful. Wired into run_interop.sh (pyclient step). - Interop drives both the enum and string forms on the wire + the ValueError typo path. Docs (ch16/ch42) updated; names-vs-enums rule documented. All Python unit tests + 25 pyclient interop checks pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-14
@@ -33,7 +33,7 @@ import secsgem.gem
|
||||
import secsgem.hsms
|
||||
import secsgem.secs
|
||||
|
||||
from secsgem_client import Equipment, SecsGemError
|
||||
from secsgem_client import Equipment, Milestone, ModuleState, SecsGemError
|
||||
|
||||
LOG = logging.getLogger("pyclient-interop")
|
||||
F = secsgem.secs.functions
|
||||
@@ -175,27 +175,33 @@ def run(grpc_addr: str, hsms_host: str, hsms_port: int) -> int:
|
||||
except NameError:
|
||||
check("@eq.command on unknown name raises NameError", True)
|
||||
|
||||
# ---- E90 substrate tracking: full journey, then the error path ----
|
||||
eq.report_substrate("WFR-PY-1", "ARRIVED", carrier_id="FOUP-PY", slot=4)
|
||||
eq.report_substrate("WFR-PY-1", "AT_WORK")
|
||||
eq.report_substrate("WFR-PY-1", "PROCESSING")
|
||||
eq.report_substrate("WFR-PY-1", "PROCESSED")
|
||||
eq.report_substrate("WFR-PY-1", "AT_DESTINATION")
|
||||
check("report_substrate full journey accepted (no raise)", True)
|
||||
# ---- E90 substrate tracking: enum + string forms, then error paths ----
|
||||
eq.report_substrate("WFR-PY-1", Milestone.ARRIVED,
|
||||
carrier_id="FOUP-PY", slot=4) # enum form
|
||||
eq.report_substrate("WFR-PY-1", "AT_WORK") # plain string, same thing
|
||||
eq.report_substrate("WFR-PY-1", Milestone.PROCESSING)
|
||||
eq.report_substrate("WFR-PY-1", Milestone.PROCESSED)
|
||||
eq.report_substrate("WFR-PY-1", Milestone.AT_DESTINATION)
|
||||
check("report_substrate full journey (enum + string) accepted", True)
|
||||
try:
|
||||
eq.report_substrate("WFR-GHOST", "AT_WORK") # never ARRIVED
|
||||
eq.report_substrate("WFR-PY-1", "AT_WROK") # typo: client-side reject
|
||||
check("misspelled milestone raises ValueError", False)
|
||||
except ValueError as e:
|
||||
check("misspelled milestone raises ValueError", "AT_WORK" in str(e))
|
||||
try:
|
||||
eq.report_substrate("WFR-GHOST", Milestone.AT_WORK) # never ARRIVED
|
||||
check("report_substrate on unknown wafer raises", False)
|
||||
except SecsGemError as e:
|
||||
check("report_substrate on unknown wafer raises", "WFR-GHOST" in str(e))
|
||||
|
||||
# ---- E157 module tracking: walk and reset ----
|
||||
eq.report_module("MOD-PY-1", "GENERAL_EXECUTING")
|
||||
eq.report_module("MOD-PY-1", "STEP_EXECUTING")
|
||||
eq.report_module("MOD-PY-1", "STEP_COMPLETED")
|
||||
eq.report_module("MOD-PY-1", "NOT_EXECUTING")
|
||||
eq.report_module("MOD-PY-1", ModuleState.GENERAL_EXECUTING)
|
||||
eq.report_module("MOD-PY-1", ModuleState.STEP_EXECUTING)
|
||||
eq.report_module("MOD-PY-1", ModuleState.STEP_COMPLETED)
|
||||
eq.report_module("MOD-PY-1", ModuleState.NOT_EXECUTING)
|
||||
check("report_module walk + reset accepted (no raise)", True)
|
||||
try:
|
||||
eq.report_module("MOD-PY-2", "STEP_EXECUTING") # illegal from idle
|
||||
eq.report_module("MOD-PY-2", ModuleState.STEP_EXECUTING) # illegal from idle
|
||||
check("report_module illegal jump raises", False)
|
||||
except SecsGemError:
|
||||
check("report_module illegal jump raises", True)
|
||||
|
||||
Reference in New Issue
Block a user