Files
secs-gem/clients/python
raphael 8a55137e57 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>
2026-06-26 22:09:48 +02:00
..

secsgem-client

A complete GEM tool integration in plain Python. The secs_gemd daemon owns everything SEMI — the HSMS link to the host, the GEM state machines, formats, timers, spooling; this client tells it about your tool and reacts to the host.

from secsgem_client import Equipment

eq = Equipment("localhost:50051")

eq.set(ChamberPressure=2.5)        # host sees it on its next poll
eq.fire("ProcessStarted")          # S6F11 to the host, report auto-assembled
eq.alarm("chiller_temp_high")      # S5F1 (set), eq.clear(...) for clear

@eq.on("START")                    # host remote commands -> your function
def start(cmd):
    run_recipe(cmd.params.get("PPID"))
    eq.fire("ProcessStarted")      # the host's real completion signal

eq.listen()                        # block and dispatch (background=True for a thread)

Names are the ones from your equipment.yaml; values are plain Python (float, int, bool, str, bytes, lists). Errors raise SecsGemError with the daemon's explanation ("no variable named ...").

No compiled extension, no SEMI knowledge, no C++ toolchain — pip install and a running daemon is the whole setup.