opentrons 8.1.0__py2.py3-none-any.whl → 8.2.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opentrons/cli/analyze.py +71 -7
- opentrons/config/__init__.py +9 -0
- opentrons/config/advanced_settings.py +22 -0
- opentrons/config/defaults_ot3.py +14 -36
- opentrons/config/feature_flags.py +4 -0
- opentrons/config/types.py +6 -17
- opentrons/drivers/absorbance_reader/abstract.py +27 -3
- opentrons/drivers/absorbance_reader/async_byonoy.py +208 -154
- opentrons/drivers/absorbance_reader/driver.py +24 -15
- opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
- opentrons/drivers/absorbance_reader/simulator.py +32 -6
- opentrons/drivers/types.py +23 -1
- opentrons/execute.py +2 -2
- opentrons/hardware_control/api.py +18 -10
- opentrons/hardware_control/backends/controller.py +3 -2
- opentrons/hardware_control/backends/flex_protocol.py +11 -5
- opentrons/hardware_control/backends/ot3controller.py +18 -50
- opentrons/hardware_control/backends/ot3simulator.py +7 -6
- opentrons/hardware_control/backends/ot3utils.py +1 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
- opentrons/hardware_control/module_control.py +43 -2
- opentrons/hardware_control/modules/__init__.py +7 -1
- opentrons/hardware_control/modules/absorbance_reader.py +232 -83
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/heater_shaker.py +8 -3
- opentrons/hardware_control/modules/magdeck.py +12 -3
- opentrons/hardware_control/modules/mod_abc.py +27 -2
- opentrons/hardware_control/modules/tempdeck.py +15 -7
- opentrons/hardware_control/modules/thermocycler.py +69 -3
- opentrons/hardware_control/modules/types.py +11 -5
- opentrons/hardware_control/modules/update.py +11 -5
- opentrons/hardware_control/modules/utils.py +3 -1
- opentrons/hardware_control/ot3_calibration.py +6 -6
- opentrons/hardware_control/ot3api.py +131 -94
- opentrons/hardware_control/poller.py +15 -11
- opentrons/hardware_control/protocols/__init__.py +1 -7
- opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
- opentrons/hardware_control/protocols/liquid_handler.py +5 -0
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/hardware_control/types.py +2 -0
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/motion_planning/__init__.py +2 -0
- opentrons/motion_planning/waypoints.py +32 -0
- opentrons/protocol_api/__init__.py +2 -1
- opentrons/protocol_api/_liquid.py +87 -1
- opentrons/protocol_api/_parameter_context.py +10 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
- opentrons/protocol_api/core/engine/instrument.py +29 -25
- opentrons/protocol_api/core/engine/labware.py +20 -4
- opentrons/protocol_api/core/engine/module_core.py +166 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
- opentrons/protocol_api/core/engine/protocol.py +30 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/module.py +22 -4
- opentrons/protocol_api/core/protocol.py +6 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +115 -17
- opentrons/protocol_api/protocol_context.py +49 -5
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +3 -0
- opentrons/protocol_engine/actions/actions.py +30 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +1 -1
- opentrons/protocol_engine/clients/transports.py +1 -1
- opentrons/protocol_engine/commands/__init__.py +0 -4
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
- opentrons/protocol_engine/commands/blow_out.py +63 -14
- opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
- opentrons/protocol_engine/commands/command.py +31 -18
- opentrons/protocol_engine/commands/command_unions.py +37 -24
- opentrons/protocol_engine/commands/comment.py +5 -3
- opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
- opentrons/protocol_engine/commands/custom.py +5 -3
- opentrons/protocol_engine/commands/dispense.py +42 -20
- opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
- opentrons/protocol_engine/commands/drop_tip.py +70 -16
- opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
- opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/home.py +11 -5
- opentrons/protocol_engine/commands/liquid_probe.py +146 -88
- opentrons/protocol_engine/commands/load_labware.py +28 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +4 -6
- opentrons/protocol_engine/commands/load_pipette.py +18 -17
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
- opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
- opentrons/protocol_engine/commands/move_labware.py +155 -23
- opentrons/protocol_engine/commands/move_relative.py +15 -3
- opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
- opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
- opentrons/protocol_engine/commands/move_to_well.py +37 -10
- opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
- opentrons/protocol_engine/commands/pipetting_common.py +47 -16
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
- opentrons/protocol_engine/commands/reload_labware.py +13 -4
- opentrons/protocol_engine/commands/retract_axis.py +6 -3
- opentrons/protocol_engine/commands/save_position.py +2 -3
- opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
- opentrons/protocol_engine/commands/set_status_bar.py +5 -3
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
- opentrons/protocol_engine/commands/touch_tip.py +19 -7
- opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
- opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
- opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
- opentrons/protocol_engine/create_protocol_engine.py +60 -10
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +20 -0
- opentrons/protocol_engine/errors/error_occurrence.py +8 -3
- opentrons/protocol_engine/errors/exceptions.py +127 -2
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +22 -13
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/door_watcher.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +2 -1
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +4 -2
- opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
- opentrons/protocol_engine/execution/labware_movement.py +73 -22
- opentrons/protocol_engine/execution/movement.py +17 -7
- opentrons/protocol_engine/execution/pipetting.py +7 -4
- opentrons/protocol_engine/execution/queue_worker.py +6 -2
- opentrons/protocol_engine/execution/run_control.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
- opentrons/protocol_engine/execution/tip_handler.py +77 -43
- opentrons/protocol_engine/notes/__init__.py +14 -2
- opentrons/protocol_engine/notes/notes.py +18 -1
- opentrons/protocol_engine/plugins.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +47 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +11 -1
- opentrons/protocol_engine/resources/labware_validation.py +10 -0
- opentrons/protocol_engine/state/__init__.py +0 -70
- opentrons/protocol_engine/state/addressable_areas.py +1 -1
- opentrons/protocol_engine/state/command_history.py +21 -2
- opentrons/protocol_engine/state/commands.py +110 -31
- opentrons/protocol_engine/state/files.py +59 -0
- opentrons/protocol_engine/state/frustum_helpers.py +440 -0
- opentrons/protocol_engine/state/geometry.py +445 -59
- opentrons/protocol_engine/state/labware.py +264 -84
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
- opentrons/protocol_engine/state/modules.py +145 -90
- opentrons/protocol_engine/state/motion.py +33 -14
- opentrons/protocol_engine/state/pipettes.py +157 -317
- opentrons/protocol_engine/state/state.py +30 -1
- opentrons/protocol_engine/state/state_summary.py +3 -0
- opentrons/protocol_engine/state/tips.py +69 -114
- opentrons/protocol_engine/state/update_types.py +424 -0
- opentrons/protocol_engine/state/wells.py +236 -0
- opentrons/protocol_engine/types.py +90 -0
- opentrons/protocol_reader/file_format_validator.py +83 -15
- opentrons/protocol_runner/json_translator.py +21 -5
- opentrons/protocol_runner/legacy_command_mapper.py +27 -6
- opentrons/protocol_runner/legacy_context_plugin.py +27 -71
- opentrons/protocol_runner/protocol_runner.py +6 -3
- opentrons/protocol_runner/run_orchestrator.py +41 -6
- opentrons/protocols/advanced_control/mix.py +3 -5
- opentrons/protocols/advanced_control/transfers.py +125 -56
- opentrons/protocols/api_support/constants.py +1 -1
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/labware_like.py +4 -4
- opentrons/protocols/api_support/tip_tracker.py +2 -2
- opentrons/protocols/api_support/types.py +15 -2
- opentrons/protocols/api_support/util.py +30 -42
- opentrons/protocols/duration/errors.py +1 -1
- opentrons/protocols/duration/estimator.py +50 -29
- opentrons/protocols/execution/dev_types.py +2 -2
- opentrons/protocols/execution/execute_json_v4.py +15 -10
- opentrons/protocols/execution/execute_python.py +8 -3
- opentrons/protocols/geometry/planning.py +12 -12
- opentrons/protocols/labware.py +17 -33
- opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
- opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
- opentrons/protocol_engine/commands/configuring_common.py +0 -26
- opentrons/protocol_runner/thread_async_queue.py +0 -174
- /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
- /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import asyncio
|
|
2
|
+
import os
|
|
4
3
|
import re
|
|
5
4
|
from concurrent.futures.thread import ThreadPoolExecutor
|
|
6
5
|
from functools import partial
|
|
7
|
-
from typing import Optional, List, Dict
|
|
8
|
-
|
|
6
|
+
from typing import Any, Optional, List, Dict, Tuple
|
|
9
7
|
|
|
10
8
|
from .hid_protocol import (
|
|
11
9
|
AbsorbanceHidInterface as AbsProtocol,
|
|
12
10
|
ErrorCodeNames,
|
|
13
11
|
DeviceStateNames,
|
|
12
|
+
SlotStateNames,
|
|
13
|
+
MeasurementConfig,
|
|
14
14
|
)
|
|
15
15
|
from opentrons.drivers.types import (
|
|
16
16
|
AbsorbanceReaderLidStatus,
|
|
17
17
|
AbsorbanceReaderPlatePresence,
|
|
18
18
|
AbsorbanceReaderDeviceState,
|
|
19
|
+
ABSMeasurementMode,
|
|
19
20
|
)
|
|
20
21
|
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
22
|
+
from opentrons.hardware_control.modules.errors import AbsorbanceReaderDisconnectedError
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
SN_PARSER = re.compile(r'ATTRS{serial}=="(?P<serial>.+?)"')
|
|
26
|
+
# match semver V0.0.0 (old format) or one integer (latest format)
|
|
27
|
+
VERSION_PARSER = re.compile(r"(?P<version>(V\d+\.\d+\.\d+|^\d+$))")
|
|
28
|
+
SERIAL_PARSER = re.compile(r"(?P<serial>(OPT|BYO)[A-Z]{3}[0-9]+)")
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
class AsyncByonoy:
|
|
@@ -54,7 +59,7 @@ class AsyncByonoy:
|
|
|
54
59
|
port: str,
|
|
55
60
|
usb_port: USBPort,
|
|
56
61
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
57
|
-
) -> AsyncByonoy:
|
|
62
|
+
) -> "AsyncByonoy":
|
|
58
63
|
"""
|
|
59
64
|
Create an AsyncByonoy instance.
|
|
60
65
|
|
|
@@ -70,13 +75,13 @@ class AsyncByonoy:
|
|
|
70
75
|
loop = loop or asyncio.get_running_loop()
|
|
71
76
|
executor = ThreadPoolExecutor(max_workers=1)
|
|
72
77
|
|
|
73
|
-
import
|
|
78
|
+
import byonoy_devices as byonoy # type: ignore[import-not-found]
|
|
74
79
|
|
|
75
80
|
interface: AbsProtocol = byonoy
|
|
76
81
|
|
|
77
82
|
device_sn = cls.serial_number_from_port(usb_port.name)
|
|
78
83
|
found: List[AbsProtocol.Device] = await loop.run_in_executor(
|
|
79
|
-
executor=executor, func=byonoy.
|
|
84
|
+
executor=executor, func=byonoy.available_devices
|
|
80
85
|
)
|
|
81
86
|
device = cls.match_device_with_sn(device_sn, found)
|
|
82
87
|
|
|
@@ -108,191 +113,240 @@ class AsyncByonoy:
|
|
|
108
113
|
self._loop = loop
|
|
109
114
|
self._supported_wavelengths: Optional[list[int]] = None
|
|
110
115
|
self._device_handle: Optional[int] = None
|
|
111
|
-
self._current_config: Optional[
|
|
112
|
-
|
|
113
|
-
def _cleanup(self) -> None:
|
|
114
|
-
self._device_handle = None
|
|
115
|
-
|
|
116
|
-
def _open(self) -> None:
|
|
117
|
-
err, device_handle = self._interface.byonoy_open_device(self._device)
|
|
118
|
-
if err.name != "BYONOY_ERROR_NO_ERROR":
|
|
119
|
-
raise RuntimeError(f"Error opening device: {err}")
|
|
120
|
-
self._device_handle = device_handle
|
|
121
|
-
|
|
122
|
-
def _free(self) -> None:
|
|
123
|
-
if self._device_handle:
|
|
124
|
-
self._interface.byonoy_free_device(self._device_handle)
|
|
125
|
-
self._cleanup()
|
|
126
|
-
|
|
127
|
-
def verify_device_handle(self) -> int:
|
|
128
|
-
assert self._device_handle is not None, RuntimeError(
|
|
129
|
-
"Device handle not set up."
|
|
130
|
-
)
|
|
131
|
-
return self._device_handle
|
|
132
|
-
|
|
133
|
-
def _raise_if_error(
|
|
134
|
-
self,
|
|
135
|
-
err_name: ErrorCodeNames,
|
|
136
|
-
msg: str = "Error occurred: ",
|
|
137
|
-
) -> None:
|
|
138
|
-
if err_name != "BYONOY_ERROR_NO_ERROR":
|
|
139
|
-
raise RuntimeError(msg, err_name)
|
|
140
|
-
|
|
141
|
-
def _get_device_information(self) -> AbsProtocol.DeviceInfo:
|
|
142
|
-
handle = self.verify_device_handle()
|
|
143
|
-
err, device_info = self._interface.byonoy_get_device_information(handle)
|
|
144
|
-
self._raise_if_error(err.name, "Error getting device information: ")
|
|
145
|
-
return device_info
|
|
146
|
-
|
|
147
|
-
def _get_device_status(self) -> AbsProtocol.DeviceState:
|
|
148
|
-
handle = self.verify_device_handle()
|
|
149
|
-
err, status = self._interface.byonoy_get_device_status(handle)
|
|
150
|
-
self._raise_if_error(err.name, "Error getting device status: ")
|
|
151
|
-
return status
|
|
152
|
-
|
|
153
|
-
def _get_slot_status(self) -> AbsProtocol.SlotState:
|
|
154
|
-
handle = self.verify_device_handle()
|
|
155
|
-
err, slot_status = self._interface.byonoy_get_device_slot_status(handle)
|
|
156
|
-
self._raise_if_error(err.name, "Error getting slot status: ")
|
|
157
|
-
return slot_status
|
|
158
|
-
|
|
159
|
-
def _get_lid_status(self) -> bool:
|
|
160
|
-
handle = self.verify_device_handle()
|
|
161
|
-
lid_on: bool
|
|
162
|
-
err, lid_on = self._interface.byonoy_get_device_parts_aligned(handle)
|
|
163
|
-
self._raise_if_error(err.name, "Error getting lid status: ")
|
|
164
|
-
return lid_on
|
|
165
|
-
|
|
166
|
-
def _get_supported_wavelengths(self) -> List[int]:
|
|
167
|
-
handle = self.verify_device_handle()
|
|
168
|
-
wavelengths: List[int]
|
|
169
|
-
err, wavelengths = self._interface.byonoy_abs96_get_available_wavelengths(
|
|
170
|
-
handle
|
|
171
|
-
)
|
|
172
|
-
self._raise_if_error(err.name, "Error getting available wavelengths: ")
|
|
173
|
-
self._supported_wavelengths = wavelengths
|
|
174
|
-
return wavelengths
|
|
175
|
-
|
|
176
|
-
def _initialize_measurement(self, conf: AbsProtocol.MeasurementConfig) -> None:
|
|
177
|
-
handle = self.verify_device_handle()
|
|
178
|
-
err = self._interface.byonoy_abs96_initialize_single_measurement(handle, conf)
|
|
179
|
-
self._raise_if_error(err.name, "Error initializing measurement: ")
|
|
180
|
-
self._current_config = conf
|
|
116
|
+
self._current_config: Optional[MeasurementConfig] = None
|
|
181
117
|
|
|
182
|
-
def
|
|
183
|
-
handle = self.verify_device_handle()
|
|
184
|
-
measurements: List[float]
|
|
185
|
-
err, measurements = self._interface.byonoy_abs96_single_measure(handle, conf)
|
|
186
|
-
self._raise_if_error(err.name, "Error getting single measurement: ")
|
|
187
|
-
return measurements
|
|
188
|
-
|
|
189
|
-
def _set_sample_wavelength(self, wavelength: int) -> AbsProtocol.MeasurementConfig:
|
|
190
|
-
if not self._supported_wavelengths:
|
|
191
|
-
self._get_supported_wavelengths()
|
|
192
|
-
assert self._supported_wavelengths
|
|
193
|
-
if wavelength in self._supported_wavelengths:
|
|
194
|
-
conf = self._interface.ByonoyAbs96SingleMeasurementConfig()
|
|
195
|
-
conf.sample_wavelength = wavelength
|
|
196
|
-
return conf
|
|
197
|
-
else:
|
|
198
|
-
raise ValueError(
|
|
199
|
-
f"Unsupported wavelength: {wavelength}, expected: {self._supported_wavelengths}"
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
def _initialize(self, wavelength: int) -> None:
|
|
203
|
-
conf = self._set_sample_wavelength(wavelength)
|
|
204
|
-
self._initialize_measurement(conf)
|
|
205
|
-
|
|
206
|
-
def _get_single_measurement(self, wavelength: int) -> List[float]:
|
|
207
|
-
initialized = self._current_config
|
|
208
|
-
assert initialized and initialized.sample_wavelength == wavelength
|
|
209
|
-
return self._single_measurement(initialized)
|
|
210
|
-
|
|
211
|
-
async def open(self) -> None:
|
|
118
|
+
async def open(self) -> bool:
|
|
212
119
|
"""
|
|
213
120
|
Open the connection.
|
|
214
121
|
|
|
215
|
-
Returns:
|
|
122
|
+
Returns: boolean denoting connection success.
|
|
216
123
|
"""
|
|
217
|
-
|
|
218
|
-
|
|
124
|
+
|
|
125
|
+
err, device_handle = await self._loop.run_in_executor(
|
|
126
|
+
executor=self._executor,
|
|
127
|
+
func=partial(self._interface.open_device, self._device),
|
|
219
128
|
)
|
|
129
|
+
self._raise_if_error(err.name, f"Error opening device: {err}")
|
|
130
|
+
self._device_handle = device_handle
|
|
131
|
+
return bool(device_handle)
|
|
220
132
|
|
|
221
133
|
async def close(self) -> None:
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
134
|
+
"""Close the connection."""
|
|
135
|
+
handle = self._verify_device_handle()
|
|
136
|
+
await self._loop.run_in_executor(
|
|
137
|
+
executor=self._executor,
|
|
138
|
+
func=partial(self._interface.free_device, handle),
|
|
139
|
+
)
|
|
140
|
+
self._device_handle = None
|
|
228
141
|
|
|
229
142
|
async def is_open(self) -> bool:
|
|
230
|
-
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
143
|
+
"""True if connection is open."""
|
|
144
|
+
if self._device_handle is None:
|
|
145
|
+
return False
|
|
146
|
+
handle = self._verify_device_handle()
|
|
147
|
+
return await self._loop.run_in_executor(
|
|
148
|
+
executor=self._executor,
|
|
149
|
+
func=partial(self._interface.device_open, handle),
|
|
150
|
+
)
|
|
236
151
|
|
|
237
|
-
async def
|
|
238
|
-
|
|
239
|
-
|
|
152
|
+
async def get_device_information(self) -> Dict[str, str]:
|
|
153
|
+
"""Get serial number and version info."""
|
|
154
|
+
handle = self._verify_device_handle()
|
|
155
|
+
err, device_info = await self._loop.run_in_executor(
|
|
156
|
+
executor=self._executor,
|
|
157
|
+
func=partial(self._interface.get_device_information, handle),
|
|
158
|
+
)
|
|
159
|
+
self._raise_if_error(err.name, f"Error getting device information: {err}")
|
|
160
|
+
serial_match = SERIAL_PARSER.match(device_info.sn)
|
|
161
|
+
version_match = VERSION_PARSER.search(device_info.version)
|
|
162
|
+
serial = serial_match["serial"].strip() if serial_match else "OPTMAA00000"
|
|
163
|
+
version = version_match["version"].lower() if version_match else "v0"
|
|
164
|
+
info = {
|
|
165
|
+
"serial": serial,
|
|
166
|
+
"version": version,
|
|
240
167
|
"model": "ABS96",
|
|
241
|
-
"version": "1.0",
|
|
242
168
|
}
|
|
169
|
+
return info
|
|
243
170
|
|
|
244
|
-
async def
|
|
245
|
-
|
|
246
|
-
|
|
171
|
+
async def get_device_status(self) -> AbsorbanceReaderDeviceState:
|
|
172
|
+
"""Get state information of the device."""
|
|
173
|
+
handle = self._verify_device_handle()
|
|
174
|
+
err, status = await self._loop.run_in_executor(
|
|
175
|
+
executor=self._executor,
|
|
176
|
+
func=partial(self._interface.get_device_status, handle),
|
|
247
177
|
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
178
|
+
self._raise_if_error(err.name, f"Error getting device status: {err}")
|
|
179
|
+
return self.convert_device_state(status.name)
|
|
180
|
+
|
|
181
|
+
async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]:
|
|
182
|
+
"""Updates the firmware of the device."""
|
|
183
|
+
handle = self._verify_device_handle()
|
|
184
|
+
if not os.path.exists(firmware_file_path):
|
|
185
|
+
return False, f"Firmware file not found: {firmware_file_path}"
|
|
186
|
+
err = await self._loop.run_in_executor(
|
|
187
|
+
executor=self._executor,
|
|
188
|
+
func=partial(self._interface.update_device, handle, firmware_file_path),
|
|
189
|
+
)
|
|
190
|
+
if err.name != "NO_ERROR":
|
|
191
|
+
return False, f"Byonoy update failed with error: {err}"
|
|
192
|
+
return True, ""
|
|
193
|
+
|
|
194
|
+
async def get_device_uptime(self) -> int:
|
|
195
|
+
"""Get how long in seconds the device has been running for."""
|
|
196
|
+
handle = self._verify_device_handle()
|
|
197
|
+
err, uptime = await self._loop.run_in_executor(
|
|
198
|
+
executor=self._executor,
|
|
199
|
+
func=partial(self._interface.get_device_uptime, handle),
|
|
200
|
+
)
|
|
201
|
+
self._raise_if_error(err.name, "Error getting device uptime: ")
|
|
202
|
+
return uptime
|
|
253
203
|
|
|
254
204
|
async def get_lid_status(self) -> AbsorbanceReaderLidStatus:
|
|
255
|
-
|
|
256
|
-
|
|
205
|
+
"""Get the state of the absorbance lid."""
|
|
206
|
+
handle = self._verify_device_handle()
|
|
207
|
+
err, lid_info = await self._loop.run_in_executor(
|
|
208
|
+
executor=self._executor,
|
|
209
|
+
func=partial(self._interface.get_device_parts_aligned, handle),
|
|
257
210
|
)
|
|
211
|
+
self._raise_if_error(err.name, f"Error getting lid status: {err}")
|
|
258
212
|
return (
|
|
259
213
|
AbsorbanceReaderLidStatus.ON if lid_info else AbsorbanceReaderLidStatus.OFF
|
|
260
214
|
)
|
|
261
215
|
|
|
262
216
|
async def get_supported_wavelengths(self) -> list[int]:
|
|
263
|
-
|
|
264
|
-
|
|
217
|
+
"""Get a list of the wavelength readings this device supports."""
|
|
218
|
+
handle = self._verify_device_handle()
|
|
219
|
+
err, wavelengths = await self._loop.run_in_executor(
|
|
220
|
+
executor=self._executor,
|
|
221
|
+
func=partial(self._interface.abs96_get_available_wavelengths, handle),
|
|
265
222
|
)
|
|
223
|
+
self._raise_if_error(err.name, "Error getting available wavelengths: ")
|
|
224
|
+
self._supported_wavelengths = wavelengths
|
|
225
|
+
return wavelengths
|
|
266
226
|
|
|
267
|
-
async def
|
|
268
|
-
|
|
269
|
-
|
|
227
|
+
async def get_measurement(self) -> List[List[float]]:
|
|
228
|
+
"""Gets one or more measurements based on the current configuration."""
|
|
229
|
+
handle = self._verify_device_handle()
|
|
230
|
+
assert (
|
|
231
|
+
self._current_config is not None
|
|
232
|
+
), "Cannot get measurement without initializing."
|
|
233
|
+
measure_func: Any = self._interface.abs96_single_measure
|
|
234
|
+
if isinstance(self._current_config, AbsProtocol.MultiMeasurementConfig):
|
|
235
|
+
measure_func = self._interface.abs96_multiple_measure
|
|
236
|
+
err, measurements = await self._loop.run_in_executor(
|
|
237
|
+
executor=self._executor,
|
|
238
|
+
func=partial(
|
|
239
|
+
measure_func,
|
|
240
|
+
handle,
|
|
241
|
+
self._current_config,
|
|
242
|
+
),
|
|
270
243
|
)
|
|
244
|
+
self._raise_if_error(err.name, f"Error getting measurement: {err}")
|
|
245
|
+
return measurements if isinstance(measurements[0], List) else [measurements] # type: ignore
|
|
271
246
|
|
|
272
|
-
async def
|
|
273
|
-
|
|
247
|
+
async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence:
|
|
248
|
+
"""Get the state of the plate for the reader."""
|
|
249
|
+
handle = self._verify_device_handle()
|
|
250
|
+
err, presence = await self._loop.run_in_executor(
|
|
274
251
|
executor=self._executor,
|
|
275
|
-
func=partial(self.
|
|
252
|
+
func=partial(self._interface.get_device_slot_status, handle),
|
|
276
253
|
)
|
|
254
|
+
self._raise_if_error(err.name, f"Error getting slot status: {err}")
|
|
255
|
+
return self.convert_plate_presence(presence.name)
|
|
277
256
|
|
|
278
|
-
|
|
279
|
-
|
|
257
|
+
def _get_supported_wavelengths(self) -> List[int]:
|
|
258
|
+
handle = self._verify_device_handle()
|
|
259
|
+
wavelengths: List[int]
|
|
260
|
+
err, wavelengths = self._interface.abs96_get_available_wavelengths(handle)
|
|
261
|
+
self._raise_if_error(err.name, f"Error getting available wavelengths: {err}")
|
|
262
|
+
self._supported_wavelengths = wavelengths
|
|
263
|
+
return wavelengths
|
|
280
264
|
|
|
281
|
-
|
|
282
|
-
|
|
265
|
+
def _initialize_measurement(self, conf: MeasurementConfig) -> None:
|
|
266
|
+
handle = self._verify_device_handle()
|
|
267
|
+
if isinstance(conf, AbsProtocol.SingleMeasurementConfig):
|
|
268
|
+
err = self._interface.abs96_initialize_single_measurement(handle, conf)
|
|
269
|
+
else:
|
|
270
|
+
err = self._interface.abs96_initialize_multiple_measurement(handle, conf)
|
|
271
|
+
self._raise_if_error(err.name, f"Error initializing measurement: {err}")
|
|
272
|
+
self._current_config = conf
|
|
273
|
+
|
|
274
|
+
def _initialize(
|
|
275
|
+
self,
|
|
276
|
+
mode: ABSMeasurementMode,
|
|
277
|
+
wavelengths: List[int],
|
|
278
|
+
reference_wavelength: Optional[int] = None,
|
|
279
|
+
) -> None:
|
|
280
|
+
if not self._supported_wavelengths:
|
|
281
|
+
self._get_supported_wavelengths()
|
|
282
|
+
assert self._supported_wavelengths
|
|
283
|
+
conf: MeasurementConfig
|
|
284
|
+
if set(wavelengths).issubset(self._supported_wavelengths):
|
|
285
|
+
if mode == ABSMeasurementMode.SINGLE:
|
|
286
|
+
conf = self._interface.Abs96SingleMeasurementConfig()
|
|
287
|
+
conf.sample_wavelength = wavelengths[0] or 0
|
|
288
|
+
conf.reference_wavelength = reference_wavelength or 0
|
|
289
|
+
else:
|
|
290
|
+
conf = self._interface.Abs96MultipleMeasurementConfig()
|
|
291
|
+
conf.sample_wavelengths = wavelengths
|
|
292
|
+
else:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"Unsupported wavelength: {wavelengths}, expected: {self._supported_wavelengths}"
|
|
295
|
+
)
|
|
296
|
+
self._initialize_measurement(conf)
|
|
297
|
+
|
|
298
|
+
async def initialize(
|
|
299
|
+
self,
|
|
300
|
+
mode: ABSMeasurementMode,
|
|
301
|
+
wavelengths: List[int],
|
|
302
|
+
reference_wavelength: Optional[int] = None,
|
|
303
|
+
) -> None:
|
|
304
|
+
"""initialize the device so we can start reading samples from it."""
|
|
305
|
+
await self._loop.run_in_executor(
|
|
283
306
|
executor=self._executor,
|
|
284
|
-
func=self.
|
|
307
|
+
func=partial(self._initialize, mode, wavelengths, reference_wavelength),
|
|
285
308
|
)
|
|
286
|
-
|
|
309
|
+
|
|
310
|
+
def _verify_device_handle(self) -> int:
|
|
311
|
+
assert self._device_handle is not None, RuntimeError(
|
|
312
|
+
"Device handle not set up."
|
|
313
|
+
)
|
|
314
|
+
return self._device_handle
|
|
315
|
+
|
|
316
|
+
def _raise_if_error(
|
|
317
|
+
self,
|
|
318
|
+
err_name: ErrorCodeNames,
|
|
319
|
+
msg: str = "Error occurred: ",
|
|
320
|
+
) -> None:
|
|
321
|
+
if err_name in [
|
|
322
|
+
"DEVICE_CLOSED",
|
|
323
|
+
"DEVICE_COMMUNICATION_FAILURE",
|
|
324
|
+
"UNSUPPORTED_OPERATION",
|
|
325
|
+
]:
|
|
326
|
+
raise AbsorbanceReaderDisconnectedError(self._device.sn)
|
|
327
|
+
if err_name != "NO_ERROR":
|
|
328
|
+
raise RuntimeError(msg, err_name)
|
|
287
329
|
|
|
288
330
|
@staticmethod
|
|
289
331
|
def convert_device_state(
|
|
290
332
|
device_state: DeviceStateNames,
|
|
291
333
|
) -> AbsorbanceReaderDeviceState:
|
|
292
334
|
state_map: Dict[DeviceStateNames, AbsorbanceReaderDeviceState] = {
|
|
293
|
-
"
|
|
294
|
-
"
|
|
295
|
-
"
|
|
296
|
-
"
|
|
335
|
+
"UNKNOWN": AbsorbanceReaderDeviceState.UNKNOWN,
|
|
336
|
+
"OK": AbsorbanceReaderDeviceState.OK,
|
|
337
|
+
"BROKEN_FW": AbsorbanceReaderDeviceState.BROKEN_FW,
|
|
338
|
+
"ERROR": AbsorbanceReaderDeviceState.ERROR,
|
|
297
339
|
}
|
|
298
340
|
return state_map[device_state]
|
|
341
|
+
|
|
342
|
+
@staticmethod
|
|
343
|
+
def convert_plate_presence(
|
|
344
|
+
slot_state: SlotStateNames,
|
|
345
|
+
) -> AbsorbanceReaderPlatePresence:
|
|
346
|
+
state_map: Dict[SlotStateNames, AbsorbanceReaderPlatePresence] = {
|
|
347
|
+
"UNKNOWN": AbsorbanceReaderPlatePresence.UNKNOWN,
|
|
348
|
+
"EMPTY": AbsorbanceReaderPlatePresence.ABSENT,
|
|
349
|
+
"OCCUPIED": AbsorbanceReaderPlatePresence.PRESENT,
|
|
350
|
+
"UNDETERMINED": AbsorbanceReaderPlatePresence.UNKNOWN,
|
|
351
|
+
}
|
|
352
|
+
return state_map[slot_state]
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from typing import Dict, Optional, List, TYPE_CHECKING
|
|
4
|
+
from typing import Dict, Optional, List, Tuple, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from opentrons.drivers.types import (
|
|
7
|
+
ABSMeasurementMode,
|
|
7
8
|
AbsorbanceReaderLidStatus,
|
|
8
9
|
AbsorbanceReaderDeviceState,
|
|
9
10
|
AbsorbanceReaderPlatePresence,
|
|
10
11
|
)
|
|
11
|
-
from opentrons.drivers.absorbance_reader.abstract import
|
|
12
|
+
from opentrons.drivers.absorbance_reader.abstract import (
|
|
13
|
+
AbstractAbsorbanceReaderDriver,
|
|
14
|
+
)
|
|
12
15
|
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
13
16
|
|
|
14
17
|
if TYPE_CHECKING:
|
|
@@ -32,15 +35,6 @@ class AbsorbanceReaderDriver(AbstractAbsorbanceReaderDriver):
|
|
|
32
35
|
def __init__(self, connection: AsyncByonoyType) -> None:
|
|
33
36
|
self._connection = connection
|
|
34
37
|
|
|
35
|
-
async def get_device_info(self) -> Dict[str, str]:
|
|
36
|
-
"""Get device info"""
|
|
37
|
-
connected = await self.is_connected()
|
|
38
|
-
if not connected:
|
|
39
|
-
info = await self._connection.get_device_static_info()
|
|
40
|
-
else:
|
|
41
|
-
info = await self._connection.get_device_information()
|
|
42
|
-
return info
|
|
43
|
-
|
|
44
38
|
async def connect(self) -> None:
|
|
45
39
|
"""Connect to absorbance reader"""
|
|
46
40
|
await self._connection.open()
|
|
@@ -53,17 +47,32 @@ class AbsorbanceReaderDriver(AbstractAbsorbanceReaderDriver):
|
|
|
53
47
|
"""Check connection to absorbance reader"""
|
|
54
48
|
return await self._connection.is_open()
|
|
55
49
|
|
|
50
|
+
async def get_device_info(self) -> Dict[str, str]:
|
|
51
|
+
"""Get device info"""
|
|
52
|
+
return await self._connection.get_device_information()
|
|
53
|
+
|
|
54
|
+
async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]:
|
|
55
|
+
return await self._connection.update_firmware(firmware_file_path)
|
|
56
|
+
|
|
57
|
+
async def get_uptime(self) -> int:
|
|
58
|
+
return await self._connection.get_device_uptime()
|
|
59
|
+
|
|
56
60
|
async def get_lid_status(self) -> AbsorbanceReaderLidStatus:
|
|
57
61
|
return await self._connection.get_lid_status()
|
|
58
62
|
|
|
59
63
|
async def get_available_wavelengths(self) -> List[int]:
|
|
60
64
|
return await self._connection.get_supported_wavelengths()
|
|
61
65
|
|
|
62
|
-
async def
|
|
63
|
-
|
|
66
|
+
async def initialize_measurement(
|
|
67
|
+
self,
|
|
68
|
+
wavelengths: List[int],
|
|
69
|
+
mode: ABSMeasurementMode = ABSMeasurementMode.SINGLE,
|
|
70
|
+
reference_wavelength: Optional[int] = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
await self._connection.initialize(mode, wavelengths, reference_wavelength)
|
|
64
73
|
|
|
65
|
-
async def
|
|
66
|
-
await self._connection.
|
|
74
|
+
async def get_measurement(self) -> List[List[float]]:
|
|
75
|
+
return await self._connection.get_measurement()
|
|
67
76
|
|
|
68
77
|
async def get_status(self) -> AbsorbanceReaderDeviceState:
|
|
69
78
|
return await self._connection.get_device_status()
|