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:
2026-06-10 22:57:55 +02:00
parent 912304966f
commit 8686654b15
19 changed files with 1235 additions and 7 deletions
+14
View File
@@ -89,6 +89,20 @@ compose run --rm --no-deps interop \
record daemon $?
compose stop gemd >/dev/null 2>&1
# ---- Python client package vs secs_gemd --------------------------------------
note "pyclient: secsgem_client package + secsgem-py host vs secs_gemd"
(
set -e
compose run --rm --no-deps -e PYTHONPATH=/app/clients/python interop \
python3 /app/clients/python/tests/test_values.py
compose up -d --no-deps gemd
sleep 2
compose run --rm --no-deps -e PYTHONPATH=/app/clients/python interop \
python3 pyclient_interop.py --grpc gemd:50051 --hsms-host gemd
)
record pyclient $?
compose stop gemd >/dev/null 2>&1
# ---- spool persistence across restart ---------------------------------------
note "spool: persistence across server restart"
(