feat(client): the Python client — a GEM tool in plain Python (Phase C)
clients/python: pip-installable "secsgem-client", pure Python (stubs
pre-generated from equipment.proto, import made package-relative; no
compiled extension, no SEMI knowledge, no C++ toolchain). The API the whole
effort aimed at:
eq = Equipment("localhost:50051")
eq.set(ChamberPressure=2.5); eq["WaferCounter"] = 7
eq.fire("ProcessStarted", ChamberPressure=2.75)
eq.alarm("chiller_temp_high"); eq.clear("chiller_temp_high")
@eq.on("START")
def start(cmd): ... # auto-CompleteCommand after return
eq.listen(background=True)
eq.control_state; eq.request_control_state("HOST_OFFLINE"); eq.health()
Errors raise SecsGemError carrying the daemon's message ("no variable named
..."). bool checked before int in conversion (isinstance(True, int)).
examples/mini_tool.py is a complete GEM tool in ~25 lines.
PROOF — interop/pyclient_interop.py drives the PUBLISHED package (not raw
stubs) against a live secs_gemd with secsgem-py as the fab host: 13 checks
all green on first run — set/get round-trips, item syntax, SecsGemError on
unknown names, control state, health, fire->S6F11 on the host's wire,
alarm/clear->S5F1 with correct set bit, the full command loop (host S2F41 ->
HCACK=4 -> @eq.on handler -> completion event back at the host), operator
offline. Conversion layer unit-tested standalone; both wired into
tools/run_interop.sh as the pyclient step.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# secsgem-client
|
||||
|
||||
A complete GEM tool integration in plain Python. The
|
||||
[`secs_gemd`](../../docs/DAEMON_ROADMAP.md) 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.
|
||||
|
||||
```python
|
||||
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.
|
||||
Reference in New Issue
Block a user