opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__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 +207 -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/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 +230 -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 +126 -89
- 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/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 +10 -2
- opentrons/protocol_api/core/engine/module_core.py +129 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
- opentrons/protocol_api/core/engine/protocol.py +55 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -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 +5 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +68 -13
- opentrons/protocol_api/protocol_context.py +38 -4
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +5 -0
- opentrons/protocol_engine/actions/actions.py +42 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +7 -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 +161 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
- 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 +28 -17
- 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 +68 -15
- opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
- 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 +19 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +43 -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 +106 -19
- 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 +50 -29
- opentrons/protocol_engine/commands/pipetting_common.py +39 -15
- 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 +194 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
- 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 +41 -8
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +18 -0
- opentrons/protocol_engine/errors/exceptions.py +114 -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 +6 -3
- opentrons/protocol_engine/execution/movement.py +8 -3
- 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 +54 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
- opentrons/protocol_engine/resources/file_provider.py +157 -0
- opentrons/protocol_engine/resources/fixture_validation.py +5 -0
- 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 +359 -15
- opentrons/protocol_engine/state/labware.py +166 -63
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
- opentrons/protocol_engine/state/modules.py +167 -85
- opentrons/protocol_engine/state/motion.py +16 -9
- 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 +408 -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 +26 -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/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
- 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.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -1,26 +1,30 @@
|
|
|
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
|
+
VERSION_PARSER = re.compile(r"Absorbance (?P<version>V\d+\.\d+\.\d+)")
|
|
27
|
+
SERIAL_PARSER = re.compile(r"(?P<serial>BYO[A-Z]{3}[0-9]{5})")
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
class AsyncByonoy:
|
|
@@ -54,7 +58,7 @@ class AsyncByonoy:
|
|
|
54
58
|
port: str,
|
|
55
59
|
usb_port: USBPort,
|
|
56
60
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
57
|
-
) -> AsyncByonoy:
|
|
61
|
+
) -> "AsyncByonoy":
|
|
58
62
|
"""
|
|
59
63
|
Create an AsyncByonoy instance.
|
|
60
64
|
|
|
@@ -70,13 +74,13 @@ class AsyncByonoy:
|
|
|
70
74
|
loop = loop or asyncio.get_running_loop()
|
|
71
75
|
executor = ThreadPoolExecutor(max_workers=1)
|
|
72
76
|
|
|
73
|
-
import
|
|
77
|
+
import byonoy_devices as byonoy # type: ignore[import-not-found]
|
|
74
78
|
|
|
75
79
|
interface: AbsProtocol = byonoy
|
|
76
80
|
|
|
77
81
|
device_sn = cls.serial_number_from_port(usb_port.name)
|
|
78
82
|
found: List[AbsProtocol.Device] = await loop.run_in_executor(
|
|
79
|
-
executor=executor, func=byonoy.
|
|
83
|
+
executor=executor, func=byonoy.available_devices
|
|
80
84
|
)
|
|
81
85
|
device = cls.match_device_with_sn(device_sn, found)
|
|
82
86
|
|
|
@@ -108,191 +112,240 @@ class AsyncByonoy:
|
|
|
108
112
|
self._loop = loop
|
|
109
113
|
self._supported_wavelengths: Optional[list[int]] = None
|
|
110
114
|
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
|
|
115
|
+
self._current_config: Optional[MeasurementConfig] = None
|
|
181
116
|
|
|
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:
|
|
117
|
+
async def open(self) -> bool:
|
|
212
118
|
"""
|
|
213
119
|
Open the connection.
|
|
214
120
|
|
|
215
|
-
Returns:
|
|
121
|
+
Returns: boolean denoting connection success.
|
|
216
122
|
"""
|
|
217
|
-
|
|
218
|
-
|
|
123
|
+
|
|
124
|
+
err, device_handle = await self._loop.run_in_executor(
|
|
125
|
+
executor=self._executor,
|
|
126
|
+
func=partial(self._interface.open_device, self._device),
|
|
219
127
|
)
|
|
128
|
+
self._raise_if_error(err.name, f"Error opening device: {err}")
|
|
129
|
+
self._device_handle = device_handle
|
|
130
|
+
return bool(device_handle)
|
|
220
131
|
|
|
221
132
|
async def close(self) -> None:
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
133
|
+
"""Close the connection."""
|
|
134
|
+
handle = self._verify_device_handle()
|
|
135
|
+
await self._loop.run_in_executor(
|
|
136
|
+
executor=self._executor,
|
|
137
|
+
func=partial(self._interface.free_device, handle),
|
|
138
|
+
)
|
|
139
|
+
self._device_handle = None
|
|
228
140
|
|
|
229
141
|
async def is_open(self) -> bool:
|
|
230
|
-
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
142
|
+
"""True if connection is open."""
|
|
143
|
+
if self._device_handle is None:
|
|
144
|
+
return False
|
|
145
|
+
handle = self._verify_device_handle()
|
|
146
|
+
return await self._loop.run_in_executor(
|
|
147
|
+
executor=self._executor,
|
|
148
|
+
func=partial(self._interface.device_open, handle),
|
|
149
|
+
)
|
|
236
150
|
|
|
237
|
-
async def
|
|
238
|
-
|
|
239
|
-
|
|
151
|
+
async def get_device_information(self) -> Dict[str, str]:
|
|
152
|
+
"""Get serial number and version info."""
|
|
153
|
+
handle = self._verify_device_handle()
|
|
154
|
+
err, device_info = await self._loop.run_in_executor(
|
|
155
|
+
executor=self._executor,
|
|
156
|
+
func=partial(self._interface.get_device_information, handle),
|
|
157
|
+
)
|
|
158
|
+
self._raise_if_error(err.name, f"Error getting device information: {err}")
|
|
159
|
+
serial_match = SERIAL_PARSER.match(device_info.sn)
|
|
160
|
+
version_match = VERSION_PARSER.match(device_info.version)
|
|
161
|
+
serial = serial_match["serial"] if serial_match else "BYOMAA00000"
|
|
162
|
+
version = version_match["version"].lower() if version_match else "v0.0.0"
|
|
163
|
+
info = {
|
|
164
|
+
"serial": serial,
|
|
165
|
+
"version": version,
|
|
240
166
|
"model": "ABS96",
|
|
241
|
-
"version": "1.0",
|
|
242
167
|
}
|
|
168
|
+
return info
|
|
243
169
|
|
|
244
|
-
async def
|
|
245
|
-
|
|
246
|
-
|
|
170
|
+
async def get_device_status(self) -> AbsorbanceReaderDeviceState:
|
|
171
|
+
"""Get state information of the device."""
|
|
172
|
+
handle = self._verify_device_handle()
|
|
173
|
+
err, status = await self._loop.run_in_executor(
|
|
174
|
+
executor=self._executor,
|
|
175
|
+
func=partial(self._interface.get_device_status, handle),
|
|
247
176
|
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
177
|
+
self._raise_if_error(err.name, f"Error getting device status: {err}")
|
|
178
|
+
return self.convert_device_state(status.name)
|
|
179
|
+
|
|
180
|
+
async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]:
|
|
181
|
+
"""Updates the firmware of the device."""
|
|
182
|
+
handle = self._verify_device_handle()
|
|
183
|
+
if not os.path.exists(firmware_file_path):
|
|
184
|
+
return False, f"Firmware file not found: {firmware_file_path}"
|
|
185
|
+
err = await self._loop.run_in_executor(
|
|
186
|
+
executor=self._executor,
|
|
187
|
+
func=partial(self._interface.update_device, handle, firmware_file_path),
|
|
188
|
+
)
|
|
189
|
+
if err.name != "NO_ERROR":
|
|
190
|
+
return False, f"Byonoy update failed with error: {err}"
|
|
191
|
+
return True, ""
|
|
192
|
+
|
|
193
|
+
async def get_device_uptime(self) -> int:
|
|
194
|
+
"""Get how long in seconds the device has been running for."""
|
|
195
|
+
handle = self._verify_device_handle()
|
|
196
|
+
err, uptime = await self._loop.run_in_executor(
|
|
197
|
+
executor=self._executor,
|
|
198
|
+
func=partial(self._interface.get_device_uptime, handle),
|
|
199
|
+
)
|
|
200
|
+
self._raise_if_error(err.name, "Error getting device uptime: ")
|
|
201
|
+
return uptime
|
|
253
202
|
|
|
254
203
|
async def get_lid_status(self) -> AbsorbanceReaderLidStatus:
|
|
255
|
-
|
|
256
|
-
|
|
204
|
+
"""Get the state of the absorbance lid."""
|
|
205
|
+
handle = self._verify_device_handle()
|
|
206
|
+
err, lid_info = await self._loop.run_in_executor(
|
|
207
|
+
executor=self._executor,
|
|
208
|
+
func=partial(self._interface.get_device_parts_aligned, handle),
|
|
257
209
|
)
|
|
210
|
+
self._raise_if_error(err.name, f"Error getting lid status: {err}")
|
|
258
211
|
return (
|
|
259
212
|
AbsorbanceReaderLidStatus.ON if lid_info else AbsorbanceReaderLidStatus.OFF
|
|
260
213
|
)
|
|
261
214
|
|
|
262
215
|
async def get_supported_wavelengths(self) -> list[int]:
|
|
263
|
-
|
|
264
|
-
|
|
216
|
+
"""Get a list of the wavelength readings this device supports."""
|
|
217
|
+
handle = self._verify_device_handle()
|
|
218
|
+
err, wavelengths = await self._loop.run_in_executor(
|
|
219
|
+
executor=self._executor,
|
|
220
|
+
func=partial(self._interface.abs96_get_available_wavelengths, handle),
|
|
265
221
|
)
|
|
222
|
+
self._raise_if_error(err.name, "Error getting available wavelengths: ")
|
|
223
|
+
self._supported_wavelengths = wavelengths
|
|
224
|
+
return wavelengths
|
|
266
225
|
|
|
267
|
-
async def
|
|
268
|
-
|
|
269
|
-
|
|
226
|
+
async def get_measurement(self) -> List[List[float]]:
|
|
227
|
+
"""Gets one or more measurements based on the current configuration."""
|
|
228
|
+
handle = self._verify_device_handle()
|
|
229
|
+
assert (
|
|
230
|
+
self._current_config is not None
|
|
231
|
+
), "Cannot get measurement without initializing."
|
|
232
|
+
measure_func: Any = self._interface.abs96_single_measure
|
|
233
|
+
if isinstance(self._current_config, AbsProtocol.MultiMeasurementConfig):
|
|
234
|
+
measure_func = self._interface.abs96_multiple_measure
|
|
235
|
+
err, measurements = await self._loop.run_in_executor(
|
|
236
|
+
executor=self._executor,
|
|
237
|
+
func=partial(
|
|
238
|
+
measure_func,
|
|
239
|
+
handle,
|
|
240
|
+
self._current_config,
|
|
241
|
+
),
|
|
270
242
|
)
|
|
243
|
+
self._raise_if_error(err.name, f"Error getting measurement: {err}")
|
|
244
|
+
return measurements if isinstance(measurements[0], List) else [measurements] # type: ignore
|
|
271
245
|
|
|
272
|
-
async def
|
|
273
|
-
|
|
246
|
+
async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence:
|
|
247
|
+
"""Get the state of the plate for the reader."""
|
|
248
|
+
handle = self._verify_device_handle()
|
|
249
|
+
err, presence = await self._loop.run_in_executor(
|
|
274
250
|
executor=self._executor,
|
|
275
|
-
func=partial(self.
|
|
251
|
+
func=partial(self._interface.get_device_slot_status, handle),
|
|
276
252
|
)
|
|
253
|
+
self._raise_if_error(err.name, f"Error getting slot status: {err}")
|
|
254
|
+
return self.convert_plate_presence(presence.name)
|
|
277
255
|
|
|
278
|
-
|
|
279
|
-
|
|
256
|
+
def _get_supported_wavelengths(self) -> List[int]:
|
|
257
|
+
handle = self._verify_device_handle()
|
|
258
|
+
wavelengths: List[int]
|
|
259
|
+
err, wavelengths = self._interface.abs96_get_available_wavelengths(handle)
|
|
260
|
+
self._raise_if_error(err.name, f"Error getting available wavelengths: {err}")
|
|
261
|
+
self._supported_wavelengths = wavelengths
|
|
262
|
+
return wavelengths
|
|
280
263
|
|
|
281
|
-
|
|
282
|
-
|
|
264
|
+
def _initialize_measurement(self, conf: MeasurementConfig) -> None:
|
|
265
|
+
handle = self._verify_device_handle()
|
|
266
|
+
if isinstance(conf, AbsProtocol.SingleMeasurementConfig):
|
|
267
|
+
err = self._interface.abs96_initialize_single_measurement(handle, conf)
|
|
268
|
+
else:
|
|
269
|
+
err = self._interface.abs96_initialize_multiple_measurement(handle, conf)
|
|
270
|
+
self._raise_if_error(err.name, f"Error initializing measurement: {err}")
|
|
271
|
+
self._current_config = conf
|
|
272
|
+
|
|
273
|
+
def _initialize(
|
|
274
|
+
self,
|
|
275
|
+
mode: ABSMeasurementMode,
|
|
276
|
+
wavelengths: List[int],
|
|
277
|
+
reference_wavelength: Optional[int] = None,
|
|
278
|
+
) -> None:
|
|
279
|
+
if not self._supported_wavelengths:
|
|
280
|
+
self._get_supported_wavelengths()
|
|
281
|
+
assert self._supported_wavelengths
|
|
282
|
+
conf: MeasurementConfig
|
|
283
|
+
if set(wavelengths).issubset(self._supported_wavelengths):
|
|
284
|
+
if mode == ABSMeasurementMode.SINGLE:
|
|
285
|
+
conf = self._interface.Abs96SingleMeasurementConfig()
|
|
286
|
+
conf.sample_wavelength = wavelengths[0] or 0
|
|
287
|
+
conf.reference_wavelength = reference_wavelength or 0
|
|
288
|
+
else:
|
|
289
|
+
conf = self._interface.Abs96MultipleMeasurementConfig()
|
|
290
|
+
conf.sample_wavelengths = wavelengths
|
|
291
|
+
else:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
f"Unsupported wavelength: {wavelengths}, expected: {self._supported_wavelengths}"
|
|
294
|
+
)
|
|
295
|
+
self._initialize_measurement(conf)
|
|
296
|
+
|
|
297
|
+
async def initialize(
|
|
298
|
+
self,
|
|
299
|
+
mode: ABSMeasurementMode,
|
|
300
|
+
wavelengths: List[int],
|
|
301
|
+
reference_wavelength: Optional[int] = None,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""initialize the device so we can start reading samples from it."""
|
|
304
|
+
await self._loop.run_in_executor(
|
|
283
305
|
executor=self._executor,
|
|
284
|
-
func=self.
|
|
306
|
+
func=partial(self._initialize, mode, wavelengths, reference_wavelength),
|
|
285
307
|
)
|
|
286
|
-
|
|
308
|
+
|
|
309
|
+
def _verify_device_handle(self) -> int:
|
|
310
|
+
assert self._device_handle is not None, RuntimeError(
|
|
311
|
+
"Device handle not set up."
|
|
312
|
+
)
|
|
313
|
+
return self._device_handle
|
|
314
|
+
|
|
315
|
+
def _raise_if_error(
|
|
316
|
+
self,
|
|
317
|
+
err_name: ErrorCodeNames,
|
|
318
|
+
msg: str = "Error occurred: ",
|
|
319
|
+
) -> None:
|
|
320
|
+
if err_name in [
|
|
321
|
+
"DEVICE_CLOSED",
|
|
322
|
+
"DEVICE_COMMUNICATION_FAILURE",
|
|
323
|
+
"UNSUPPORTED_OPERATION",
|
|
324
|
+
]:
|
|
325
|
+
raise AbsorbanceReaderDisconnectedError(self._device.sn)
|
|
326
|
+
if err_name != "NO_ERROR":
|
|
327
|
+
raise RuntimeError(msg, err_name)
|
|
287
328
|
|
|
288
329
|
@staticmethod
|
|
289
330
|
def convert_device_state(
|
|
290
331
|
device_state: DeviceStateNames,
|
|
291
332
|
) -> AbsorbanceReaderDeviceState:
|
|
292
333
|
state_map: Dict[DeviceStateNames, AbsorbanceReaderDeviceState] = {
|
|
293
|
-
"
|
|
294
|
-
"
|
|
295
|
-
"
|
|
296
|
-
"
|
|
334
|
+
"UNKNOWN": AbsorbanceReaderDeviceState.UNKNOWN,
|
|
335
|
+
"OK": AbsorbanceReaderDeviceState.OK,
|
|
336
|
+
"BROKEN_FW": AbsorbanceReaderDeviceState.BROKEN_FW,
|
|
337
|
+
"ERROR": AbsorbanceReaderDeviceState.ERROR,
|
|
297
338
|
}
|
|
298
339
|
return state_map[device_state]
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
def convert_plate_presence(
|
|
343
|
+
slot_state: SlotStateNames,
|
|
344
|
+
) -> AbsorbanceReaderPlatePresence:
|
|
345
|
+
state_map: Dict[SlotStateNames, AbsorbanceReaderPlatePresence] = {
|
|
346
|
+
"UNKNOWN": AbsorbanceReaderPlatePresence.UNKNOWN,
|
|
347
|
+
"EMPTY": AbsorbanceReaderPlatePresence.ABSENT,
|
|
348
|
+
"OCCUPIED": AbsorbanceReaderPlatePresence.PRESENT,
|
|
349
|
+
"UNDETERMINED": AbsorbanceReaderPlatePresence.UNKNOWN,
|
|
350
|
+
}
|
|
351
|
+
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()
|