Skip to content

Rework Inheco ODTC driver (builds on #1026): share SiLA-1 with SCILA, mock server + e2e tests#1144

Open
c-reiter wants to merge 13 commits into
PyLabRobot:v1b1from
c-reiter:odtc-v1b1-rework
Open

Rework Inheco ODTC driver (builds on #1026): share SiLA-1 with SCILA, mock server + e2e tests#1144
c-reiter wants to merge 13 commits into
PyLabRobot:v1b1from
c-reiter:odtc-v1b1-rework

Conversation

@c-reiter

@c-reiter c-reiter commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Builds on @cmoscy's #1026 (base v1b1), reworked per @rickwierenga's review — share code with SCILA, and prove it works. Since #1026 isn't merged yet, this PR includes its commits plus the rework on top, and is intended to supersede it.

Share the SiLA-1 transport with SCILA (kill the fork)

The ODTC and SCILA both speak SiLA 1.x (SOAP/XML over HTTP) — distinct from pylabrobot/io/sila, which is SiLA 2 (gRPC). Moved the shared client (inheco_sila_interface.py + soap.py) into a neutral pylabrobot/inheco/sila/ package that both SCILA and ODTC import (all import sites + legacy shims updated).

Trim overengineering (measured)

Removed the generic XMLField reflection framework (used by only ODTCPID/ODTCSensorValues) in favor of explicit XML serialization. Kept the tested overshoot / protocol-compilation / progress / roundtrip logic intact — deliberately not an aggressive cut, since that would delete working features.

Prove it works — mock SiLA-1 device + end-to-end tests

New pylabrobot/inheco/odtc/mock_server.py: an in-process SiLA-1 SOAP device with async ResponseEvent + DataEvent callbacks (MicroSpin-style). New tests/mock_server_tests.py: 14 end-to-end tests over a real socket driving the full stack (ODTC → Thermocycler/LoadingTray → backend → driver → SOAP → device): setup lifecycle, door, temperatures, run_protocol, premethod, stored-protocol XML roundtrip, device-error path, progress-from-DataEvents, and a 384-well run. Added machine_port/odtc_port so a driver can target the mock's ephemeral port.

96 vs 384

Both variants are mechanically supported and covered by tests. The only variant-specific code is a small constraints table (max heating slope 4.4→5.0 °C/s, max lid 110→115 °C, plate types) plus the <Variant> device code. 96 is hardware-validated (via #841/#1026); 384 is validated against the mock only — real-384 hardware validation and 384-specific overshoot-coefficient tuning are follow-ups (the overshoot model is currently shared across variants).

Testing

All ODTC + thermocycling-capability + SCILA tests green; ruff + ruff format clean.

cc @rickwierenga @cmoscy

cmoscy and others added 13 commits May 3, 2026 21:39
… server + e2e tests

Builds on @cmoscy's PR PyLabRobot#1026 (base v1b1), per @rickwierenga's review (share
code with SCILA; prove it works).

- Share the SiLA-1 SOAP transport with SCILA: move inheco_sila_interface.py +
  soap.py into a neutral pylabrobot/inheco/sila/ package that both SCILA and ODTC
  import. (This is SiLA 1.x SOAP over HTTP, distinct from pylabrobot/io/sila,
  which is SiLA 2 over gRPC.)
- Remove the generic XMLField reflection framework (used by only ODTCPID and
  ODTCSensorValues); replace with explicit XML serialization.
- Add pylabrobot/inheco/odtc/mock_server.py: an in-process SiLA-1 SOAP device
  with async ResponseEvent callbacks, plus tests/mock_server_tests.py — 12
  end-to-end tests over a real socket exercising the full ODTC stack (setup
  lifecycle, door, temperatures, run_protocol, premethod, stored-protocol XML
  roundtrip, device-error path).
- Add machine_port / odtc_port so a driver can target the mock's ephemeral port.

96-well first; 384-well support to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…end test

The mock now emits SiLA DataEvents (nested AnyData layout, temps in 1/100 °C)
during an ExecuteMethod run, and auto_complete=False keeps the method running so
request_progress()/wait_for_first_progress() can be exercised deterministically.
Adds a test asserting ODTCProgress fields (elapsed, current/target/lid temps)
parsed from a DataEvent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Demonstrates the full stack runs for variant=384 and emits the 384 device code
(<Variant>384000</Variant>). Mechanical 384 support; overshoot tuning and
real-hardware validation for the 384 block remain follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ODTCDoorBackend.close() now accepts (and ignores) the resource kwarg the
  updated LoadingTray capability passes.
- Clear ruff findings in the odtc package (unused imports, unused var,
  ambiguous name) surfaced by CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes surfaced by CI 'make typecheck' (mypy --check-untyped-defs, warn_return_any):
- xml.py: coerce parsed fluid_quantity to FluidQuantity; default premethod datetime.
- odtc.py: cast variant property; type: ignore the intentionally-richer setup override.
- sila/interface.py: repr bytes in error log.
- tests: narrow Optionals (assert / cast), use FluidQuantity enums, and
  type: ignore mock method-assignment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's make format-check runs 'ruff check --select I' (isort), which is not in the
default ruff select and so was missed locally. Reorders imports in the odtc/scila
files touched by this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cast the chatterbox backend for _block_temperature access, narrow Optional
progress/overshoot in assertions. (capabilities/thermocycling is added by this
PR — absent on v1b1.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@c-reiter

c-reiter commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

CI status note: failing checks are related to other v1b1 stuff, not to the work in this PR

For context: this branch merges current v1b1 in, which is why the diff is large. Merging also surfaced one real issue that is fixed here — the updated LoadingTray contract now passes close(resource=…), so ODTCDoorBackend.close() was updated to accept it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants