Skip to content

Add thermocycling capability and Opentrons vendor module#959

Open
rickwierenga wants to merge 13 commits intov1b1from
thermocycling-capability-v2
Open

Add thermocycling capability and Opentrons vendor module#959
rickwierenga wants to merge 13 commits intov1b1from
thermocycling-capability-v2

Conversation

@rickwierenga
Copy link
Copy Markdown
Member

Summary

  • Add pylabrobot/capabilities/thermocycling/ with thin ThermocyclingBackend (lid control, protocol execution, profile progress only — no temperature methods)
  • ThermocyclingCapability composes with block/lid TemperatureControlCapability instances
  • Own copies of Step/Stage/Protocol data types; translation functions in legacy standard.py
  • Legacy Thermocycler frontend delegates to capabilities via three adapters (_BlockTempAdapter, _LidTempAdapter, _ThermocyclingAdapter)
  • pylabrobot/opentrons/ vendor module with driver + 3 capability backends pattern:
    • OpentronsThermocyclerDriver — single class for all OT HTTP API calls
    • OpentronsBlockBackend, OpentronsLidBackend, OpentronsThermocyclingBackend
    • OpentronsThermocyclerV1 and V2 device classes
  • Legacy opentrons_backend.py delegates to new module

Usage (new API)

tc = OpentronsThermocyclerV1(name="tc", opentrons_id="...")
await tc.setup()

# Ad-hoc temperature control
await tc.block.set_temperature(95.0)
await tc.lid.set_temperature(105.0)

# Protocol execution
await tc.thermocycling.run_pcr_profile(
  denaturation_temp=[95.0], denaturation_time=30,
  annealing_temp=[55.0], annealing_time=30,
  extension_temp=[72.0], extension_time=60,
  num_cycles=30, block_max_volume=50,
  lid_temperature=105.0,
)
await tc.thermocycling.wait_for_profile_completion()

Test plan

  • All 4 legacy thermocycler tests pass
  • All imports verified (new + legacy)
  • Pre-commit hooks pass
  • CI tests pass

🤖 Generated with Claude Code

Introduces PumpingCapability under the new capability composition architecture.
Pump arrays (Agrow) are modeled as a list of PumpingCapability instances, one
per channel, each backed by a per-channel adapter over the shared hardware driver.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rickwierenga rickwierenga changed the base branch from main to v1b1 March 23, 2026 23:13
rickwierenga and others added 11 commits March 26, 2026 17:44
- Fix PumpBackend to extend CapabilityBackend instead of removed DeviceBackend
- Fix AgrowDriver to extend Driver, fix type annotations and attribute access
- Fix MasterflexPump to use driver= kwarg in Device.__init__

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Capability:
- ThermocyclingBackend(DeviceBackend) — thin: lid control, protocol
  execution, and profile progress queries only. Block/lid temperature
  handled by separate TemperatureControllerBackend instances.
- ThermocyclingCapability composes with block/lid TemperatureControlCapability
- Own copies of Step/Stage/Protocol data types
- run_pcr_profile convenience method

Legacy:
- Translation functions (protocol_to_new, protocol_from_new) in legacy standard.py
- Three adapters in legacy thermocycler.py: _BlockTempAdapter,
  _LidTempAdapter, _ThermocyclingAdapter — wrap legacy ThermocyclerBackend
  for the new capability interfaces
- Legacy Thermocycler frontend delegates to capabilities via adapters
- All 4 legacy tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OpentronsThermocyclerDriver: single class for all OT HTTP API calls
- Three capability backends backed by the driver:
  - OpentronsBlockBackend (TemperatureControllerBackend)
  - OpentronsLidBackend (TemperatureControllerBackend)
  - OpentronsThermocyclingBackend (ThermocyclingBackend)
- OpentronsThermocyclerV1 and V2 device classes with self._driver
- Legacy opentrons_backend.py delegates to new module via driver

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ThermoFisherThermocyclerDriver: single class for all SCPI I/O, auth,
  block discovery, power, temperature control, protocol execution
- Three capability backends per block:
  - ThermoFisherBlockBackend (TemperatureControllerBackend)
  - ThermoFisherLidBackend (TemperatureControllerBackend)
  - ThermoFisherThermocyclingBackend (ThermocyclingBackend)
- ThermoFisherThermocycler device with factory classmethods:
  proflex_single_block (1 block/6 zones), proflex_three_block (3 blocks/2 zones),
  atc (1 block/3 zones + lid control)
- Each block gets its own ThermocyclingCapability — run 3 PCR protocols simultaneously
- Legacy ThermoFisherThermocyclerBackend gutted to thin adapter (~275 lines, was ~1050)
- All 7 legacy tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ThermoFisherThermocyclerDriver now extends DeviceBackend — passed
  directly to Device.__init__, no first_tc_backend hack
- Remove _supports_lid_control from device class (backend concern only)
- Split factory functions into separate files:
  - proflex.py: ProFlexSingleBlock(), ProFlexThreeBlock()
  - atc.py: ATC()
- Simplify _capabilities list construction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ODTCDriver(DeviceBackend): SiLA communication, setup/stop, sensor reading
- ODTCBlockBackend: block temperature control via PreMethod
- ODTCLidBackend: lid temperature control (coupled with block via PreMethod)
- ODTCThermocyclingBackend: protocol execution, lid open/close
- ODTC device class with block, lid, and thermocycling capabilities
- Legacy ExperimentalODTCBackend delegates to new module

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split pylabrobot/thermo_fisher/thermocycler.py into:
- thermocyclers/driver.py — raw SCPI IO, auth, block discovery, power
- thermocyclers/block_backend.py — per-block temp control via send_command
- thermocyclers/lid_backend.py — per-block lid temp control via send_command
- thermocyclers/thermocycling_backend.py — protocol execution, run progress
- thermocyclers/thermocycler.py — device class
- thermocyclers/utils.py — XML/SCPI helpers, RunProgress
- thermocyclers/proflex.py, atc.py — factory functions

Backends call driver.send_command() directly — driver is pure IO layer.
Backward-compat shim at thermocycler.py re-exports from package.
All 3 legacy proflex tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OT legacy backend: now delegates to new driver/backend (was self-contained)
- OT tests: updated patch targets to new module location
- TF legacy backend: delegates to per-block backends via lazy cache
- Move check_run_exists, create_run, get_log_by_runname,
  get_elapsed_run_time_from_log to TF driver (device-level, not per-block)
- All 19 legacy tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix empty backward compat shims (thermo_fisher/atc.py, proflex.py)
- Make OT driver a DeviceBackend (consistent with TF)
- OT device classes now pass driver to Device.__init__()
- Remove duplicate deactivate calls from OT thermocycling backend stop()
- Move dynamic imports to module level (ODTC legacy, thermocycler frontend)
- All 19 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rickwierenga rickwierenga force-pushed the thermocycling-capability-v2 branch from 5732d7b to 3a31244 Compare March 27, 2026 02:54
… BackendParams)

- DeviceBackend → Driver for ODTC and ThermoFisher drivers
- Remove setup()/stop()/super().__init__() from capability backends (CapabilityBackend pattern)
- Add BackendParams: ODTCRunProtocolParams for ODTC-specific run_protocol params
- Thread backend_params through ThermocyclingBackend → ThermocyclingCapability → backends
- Fix _run_pre_method: restore dynamic_time parameter (was hardcoded to true)
- Fix legacy ODTC adapter: cache ODTCBlockBackend instead of creating throwaway instances
- Fix is_profile_running: simplify hold > 0 check
- Fix wait_for_block/wait_for_lid: restore polling logic, handle missing target temps gracefully
- Replace print() with logging in ODTC module

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rickwierenga rickwierenga force-pushed the v1b1 branch 5 times, most recently from 475dbab to 588abbb Compare April 2, 2026 21:12
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.

1 participant