Skip to content

Capability composition architecture#931

Merged
rickwierenga merged 31 commits intov1b1from
capability-architecture
Mar 23, 2026
Merged

Capability composition architecture#931
rickwierenga merged 31 commits intov1b1from
capability-architecture

Conversation

@rickwierenga
Copy link
Member

rickwierenga and others added 23 commits March 10, 2026 12:17
Move 18 device module folders (arms, centrifuge, heating_shaking,
liquid_handling, microscopes, only_fans, peeling, plate_reading,
plate_washing, powder_dispensing, pumps, scales, sealing, shaking,
storage, temperature_controlling, thermocycling, tilting) into
pylabrobot/legacy/ and update all intra-legacy imports from
pylabrobot.<module> to pylabrobot.legacy.<module>.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce Capability and need_capability_ready decorator in
pylabrobot/machines/capability.py. Capabilities are owned by a
Machine, share its backend, and track setup state via _on_setup()
and _on_stop() lifecycle hooks called by the parent Machine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create pylabrobot/capabilities/temperature_controlling/ with
TemperatureControlCapability and TemperatureControllerBackend.
Legacy TemperatureController now delegates to the capability,
keeping its API fully backwards compatible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y wrappers

- Create pylabrobot.inheco module with InhecoCPAC, InhecoCPACBackend,
  InhecoTemperatureControllerBackend, and InhecoTECControlBox
- Make Machine.backend private (_backend) across codebase
- Legacy inheco backends use composition: inherit from legacy interface,
  delegate to new implementation via self._new
- Legacy control_box re-exports from new module (no duplication)
- Add legacy/__init__.py to fix pytest dual-import issue
- Fix bug in MachineBackend.deserialize() accessing popped key
- Remove duplicated docstrings from legacy wrappers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create pylabrobot.capabilities.shaking with ShakerBackend and
  ShakingCapability
- Create pylabrobot.inheco.thermoshake with InhecoThermoshakeBackend,
  InhecoThermoShake machine, and factory functions (ac, classic, rm)
- Legacy thermoshake backend uses composition, delegating to new impl
- Legacy ShakerBackend re-exports from new capability module
- Legacy Shaker delegates to ShakingCapability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…abilities

Each BioShake model is its own class composing only the capabilities its
hardware supports (shaker, tc, or both). supports_active_cooling is accurate
per model. All classes raise NotImplementedError until resource definitions
are filled in. Legacy bioshake_backend now delegates to the new implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SealingCapability covers seal/open/close; temperature uses the existing
TemperatureControlCapability. A4SBackend implements both interfaces.
Legacy sealer wrappers now delegate to the new implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Machine.setup() now iterates _capabilities forward calling _on_setup(),
and Machine.stop() iterates reversed calling _on_stop(). Removes
duplicated boilerplate from BioShake, InhecoThermoShake, InhecoCPAC, A4S.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Box (USB interface) in separate file. Backend implements
TemperatureControllerBackend + ShakerBackend. Machine class composes
tc + shaker capabilities. Legacy wrapper delegates to new implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move SCILA backend, SiLA interface, and SOAP utilities to
pylabrobot/inheco/scila/. SCILABackend now implements
TemperatureControllerBackend. Resource definition deferred.
Legacy wrappers delegate to new module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScaleBackend with zero/tare/read_weight. MettlerToledoWXS205SDUBackend
moved to pylabrobot/mettler_toledo/. Legacy wrappers delegate to new
implementation and keep deprecated aliases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Liconic vendor modules

New capabilities:
- AutomatedRetrievalBackend/Capability for plate fetch/store operations
- HumidityControllerBackend/Capability for humidity monitoring and control

New vendor modules with multi-capability backends:
- pylabrobot.thermo_fisher.cytomat: CytomatBackend, HeraeusCytomatBackend,
  CytomatChatterbox, Cytomat machine class
- pylabrobot.liconic: LiconicBackend, Liconic machine class

Legacy support files (constants, errors, schemas, utils, racks) moved to
vendor modules; legacy paths re-export via wildcard imports so existing
code works with just inserting .legacy. in import paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Barcode scanning is now a capability (not a standalone Machine). The
canonical location is pylabrobot/capabilities/barcode_scanning/ with
BarcodeScannerBackend and BarcodeScanningCapability. Keyence vendor
module implements the backend. Legacy barcode_scanners module provides
backward-compatible BarcodeScanner(Machine) wrapper.

Liconic frontend now instantiates its own backend from model+port and
conditionally creates capabilities based on LiconicType properties:
- retrieval: always
- tc: all except NC
- humidity: only DC2/DR2/AR/DH (independent control)
- shaker: opt-in via has_shaker parameter
- barcode_scanner: opt-in via BarcodeScanningCapability parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TiltingCapability tracks absolute angle and provides set_angle/tilt.
Hamilton tilt module moved to pylabrobot/hamilton/tilt_module/ with
full serial protocol, geometry methods (rotate_coordinate_around_hinge,
get_plate_drain_offsets, get_well_drain_offsets), and chatterbox.
Legacy tilting module now re-exports from new locations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate XPeel from legacy monolithic peeler into capability-based
architecture. Legacy peeling module now uses composition wrappers
delegating to the new implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate fan control from legacy monolithic Fan into capability-based
architecture. Legacy only_fans module now uses composition wrappers
delegating to the new implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add pylabrobot/device.py with Device and DeviceBackend base classes for
the new capability architecture. Move Capability from pylabrobot/machines/
to pylabrobot/capabilities/capability.py. Move Machine and MachineBackend
to pylabrobot/legacy/machines/ as independent copies. Delete
pylabrobot/machines/.

Update all capability backends and vendor modules to import DeviceBackend
from pylabrobot.device. Update all legacy modules to import from
pylabrobot.legacy.machines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate Pico microscope from legacy monolithic imager into
capability-based architecture. Legacy imaging module now delegates
to MicroscopyCapability via an adapter backend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y vendor module

Three separate plate reading capabilities with typed result dataclasses,
replacing the legacy monolithic PlateReaderBackend. Each capability has its
own backend ABC, chatterbox, and tests. Shared well-masking logic lives in
plate_reading/utils.py.

New pylabrobot/byonoy/ vendor module extracts the real HID backends from
legacy, with resource definitions and factory functions. Legacy Byonoy
module delegates to the new backends via self._new wrappers, preserving
full PlateReader API compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create pylabrobot/agilent/biotek/ with BioTekBackend base, CytationBackend
(with MicroscopyBackend), SynergyH1Backend, and Device classes (Cytation5,
Cytation1, SynergyH1) with PlateHolder resources. Legacy wrappers delegate
to new backends; deprecated aliases (Cytation5Backend, Cytation5ImagingConfig)
and re-export shims removed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move Molecular Devices SpectraMax serial protocol code to
pylabrobot/molecular_devices/spectramax/ with capability-based backends
(AbsorbanceBackend, FluorescenceBackend, LuminescenceBackend,
TemperatureControllerBackend). Legacy wrappers delegate to _new backend
and convert typed results back to List[Dict].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rickwierenga rickwierenga force-pushed the capability-architecture branch 5 times, most recently from 79bcf2c to 2b430b5 Compare March 22, 2026 05:40
@rickwierenga rickwierenga changed the base branch from main to v1b1 March 23, 2026 04:21
@rickwierenga rickwierenga force-pushed the capability-architecture branch from 2b430b5 to 226e6d4 Compare March 23, 2026 05:14
rickwierenga and others added 7 commits March 22, 2026 22:20
# Conflicts:
#	pylabrobot/agilent/biotek/biotek_tests.py
#	pylabrobot/azenta/a4s.py
#	pylabrobot/azenta/xpeel.py
#	pylabrobot/legacy/liquid_handling/backends/opentrons_backend_tests.py
#	pylabrobot/legacy/liquid_handling/backends/opentrons_simulator.py
#	pylabrobot/legacy/plate_reading/tecan/__init__.py
#	pylabrobot/legacy/plate_reading/tecan/infinite_backend.py
#	pylabrobot/legacy/plate_reading/tecan/infinite_backend_tests.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/__init__.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/__init__.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/base_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/camera_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/config_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/data_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/gas_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/injector_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/measurement_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/movement_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/optics_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/plate_transport_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/sensor_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/spark_enums.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/controls/system_control.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/enums.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_backend.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_backend_tests.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_packet_parser.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_processor.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_processor_tests.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_reader_async.py
#	pylabrobot/legacy/plate_reading/tecan/spark20m/spark_reader_async_tests.py
#	pylabrobot/legacy/plate_reading/utils.py
#	pylabrobot/plate_reading/agilent/biotek_synergyh1_backend.py
…ppers

- Restore legacy backends to original MachineBackend definitions (shaking,
  temperature_controlling, tilting, scales, barcode_scanners)
- Add adapters in legacy frontends to bridge between MachineBackend and
  DeviceBackend when delegating to capabilities
- Fix IO constructor calls missing human_readable_device_name
- Fix broken import paths in legacy modules (plate_reading, liquid_handling,
  microscopes)
- Fix Liconic legacy wrapper barcode read ordering to match original hardware
  command sequence
- Restore SiLAError class dropped during migration
- Add class-level type annotations for Cytomat unreachable __init__
- Narrow CytationBackend._new type annotation
- Fix test type annotations for plate reading results
- Add creating-capabilities.md documenting the migration pattern

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

Update installation docs to match new extra names.

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

The time.time patch cleanup was calling stop() on the mock object instead
of the patcher, causing time.time to remain frozen for all subsequent tests.
This made the Hamilton reader thread timeout check never fire, hanging the
STAR USB comms tests.

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 marked this pull request as ready for review March 23, 2026 19:40
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rickwierenga rickwierenga merged commit 46c0125 into v1b1 Mar 23, 2026
19 checks passed
@rickwierenga rickwierenga deleted the capability-architecture branch March 23, 2026 19:42
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