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,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Callable, Dict, Optional, Mapping, List, Tuple
|
|
3
4
|
|
|
4
5
|
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
5
6
|
from opentrons.drivers.absorbance_reader import (
|
|
@@ -11,23 +12,84 @@ from opentrons.drivers.types import (
|
|
|
11
12
|
AbsorbanceReaderLidStatus,
|
|
12
13
|
AbsorbanceReaderPlatePresence,
|
|
13
14
|
AbsorbanceReaderDeviceState,
|
|
15
|
+
ABSMeasurementMode,
|
|
16
|
+
ABSMeasurementConfig,
|
|
14
17
|
)
|
|
15
18
|
|
|
16
19
|
from opentrons.hardware_control.execution_manager import ExecutionManager
|
|
17
|
-
from opentrons.hardware_control.poller import Reader
|
|
20
|
+
from opentrons.hardware_control.poller import Poller, Reader
|
|
18
21
|
from opentrons.hardware_control.modules import mod_abc
|
|
19
22
|
from opentrons.hardware_control.modules.types import (
|
|
23
|
+
ModuleDisconnectedCallback,
|
|
20
24
|
ModuleType,
|
|
21
25
|
AbsorbanceReaderStatus,
|
|
22
26
|
LiveData,
|
|
23
27
|
UploadFunction,
|
|
24
28
|
)
|
|
29
|
+
from opentrons.hardware_control.modules.errors import AbsorbanceReaderDisconnectedError
|
|
25
30
|
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
POLLING_FREQUENCY_SEC = 2.0
|
|
35
|
+
SIM_POLLING_FREQUENCY_SEC = POLLING_FREQUENCY_SEC / 50.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AbsorbanceReaderReader(Reader):
|
|
39
|
+
"""Read data from the Absorbance Reader.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
driver: A connected Absorbance Reader driver.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
device_state: AbsorbanceReaderDeviceState
|
|
46
|
+
lid_status: AbsorbanceReaderLidStatus
|
|
47
|
+
plate_presence: AbsorbanceReaderPlatePresence
|
|
48
|
+
supported_wavelengths: List[int]
|
|
49
|
+
error: Optional[str]
|
|
50
|
+
|
|
51
|
+
def __init__(self, driver: AbstractAbsorbanceReaderDriver) -> None:
|
|
52
|
+
self.device_state = AbsorbanceReaderDeviceState.UNKNOWN
|
|
53
|
+
self.lid_status = AbsorbanceReaderLidStatus.UNKNOWN
|
|
54
|
+
self.plate_presence = AbsorbanceReaderPlatePresence.UNKNOWN
|
|
55
|
+
self.supported_wavelengths = []
|
|
56
|
+
self.uptime = 0
|
|
57
|
+
self._driver = driver
|
|
58
|
+
self._handle_error: Optional[Callable[[Exception], None]] = None
|
|
59
|
+
|
|
60
|
+
async def read(self) -> None:
|
|
61
|
+
await self.get_lid_status()
|
|
62
|
+
await self.get_device_status()
|
|
63
|
+
await self.get_plate_presence()
|
|
64
|
+
if not self.supported_wavelengths:
|
|
65
|
+
await self.get_supported_wavelengths()
|
|
66
|
+
|
|
67
|
+
async def get_device_status(self) -> None:
|
|
68
|
+
"""Get the Absorbance Reader's current status."""
|
|
69
|
+
self.device_state = await self._driver.get_status()
|
|
70
|
+
|
|
71
|
+
async def get_device_uptime(self) -> None:
|
|
72
|
+
"""Get the device uptime in seconds."""
|
|
73
|
+
self.uptime = await self._driver.get_uptime()
|
|
74
|
+
|
|
75
|
+
async def get_supported_wavelengths(self) -> None:
|
|
76
|
+
"""Get the Absorbance Reader's supported wavelengths."""
|
|
77
|
+
self.supported_wavelengths = await self._driver.get_available_wavelengths()
|
|
78
|
+
|
|
79
|
+
async def get_lid_status(self) -> None:
|
|
80
|
+
"""Get the Absorbance Reader's lid status."""
|
|
81
|
+
self.lid_status = await self._driver.get_lid_status()
|
|
82
|
+
|
|
83
|
+
async def get_plate_presence(self) -> None:
|
|
84
|
+
"""Get the Absorbance Reader's plate presence."""
|
|
85
|
+
self.plate_presence = await self._driver.get_plate_presence()
|
|
86
|
+
|
|
87
|
+
def on_error(self, exception: Exception) -> None:
|
|
88
|
+
if self._handle_error is not None:
|
|
89
|
+
self._handle_error(exception)
|
|
90
|
+
|
|
91
|
+
def register_error_handler(self, handle_error: Callable[[Exception], None]) -> None:
|
|
92
|
+
self._handle_error = handle_error
|
|
31
93
|
|
|
32
94
|
|
|
33
95
|
class AbsorbanceReader(mod_abc.AbstractModule):
|
|
@@ -46,24 +108,56 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
46
108
|
simulating: bool = False,
|
|
47
109
|
sim_model: Optional[str] = None,
|
|
48
110
|
sim_serial_number: Optional[str] = None,
|
|
111
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
49
112
|
) -> "AbsorbanceReader":
|
|
50
|
-
"""
|
|
113
|
+
"""
|
|
114
|
+
Build and connect to an AbsorbanceReader
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
port: The port to connect to
|
|
118
|
+
usb_port: USB Port
|
|
119
|
+
execution_manager: Execution manager.
|
|
120
|
+
hw_control_loop: The event loop running in the hardware control thread.
|
|
121
|
+
poll_interval_seconds: Poll interval override.
|
|
122
|
+
simulating: whether to build a simulating driver
|
|
123
|
+
sim_model: The model name used by simulator
|
|
124
|
+
sim_serial_number: The serial number used by simulator
|
|
125
|
+
disconnected_callback: Callback to inform the module controller that the device was disconnected
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
AbsorbanceReader instance.
|
|
129
|
+
|
|
130
|
+
"""
|
|
51
131
|
driver: AbstractAbsorbanceReaderDriver
|
|
52
132
|
if not simulating:
|
|
53
133
|
driver = await AbsorbanceReaderDriver.create(
|
|
54
134
|
port, usb_port, hw_control_loop
|
|
55
135
|
)
|
|
136
|
+
await driver.connect()
|
|
137
|
+
poll_interval_seconds = poll_interval_seconds or POLLING_FREQUENCY_SEC
|
|
56
138
|
else:
|
|
57
|
-
driver = SimulatingDriver(serial_number=sim_serial_number)
|
|
139
|
+
driver = SimulatingDriver(model=sim_model, serial_number=sim_serial_number)
|
|
140
|
+
poll_interval_seconds = poll_interval_seconds or SIM_POLLING_FREQUENCY_SEC
|
|
141
|
+
|
|
142
|
+
reader = AbsorbanceReaderReader(driver=driver)
|
|
143
|
+
poller = Poller(reader=reader, interval=poll_interval_seconds)
|
|
58
144
|
module = cls(
|
|
59
145
|
port=port,
|
|
60
146
|
usb_port=usb_port,
|
|
61
|
-
device_info=await driver.get_device_info(),
|
|
62
|
-
execution_manager=execution_manager,
|
|
63
147
|
driver=driver,
|
|
148
|
+
reader=reader,
|
|
149
|
+
poller=poller,
|
|
150
|
+
device_info=await driver.get_device_info(),
|
|
64
151
|
hw_control_loop=hw_control_loop,
|
|
152
|
+
execution_manager=execution_manager,
|
|
153
|
+
disconnected_callback=disconnected_callback,
|
|
65
154
|
)
|
|
66
|
-
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
await poller.start()
|
|
158
|
+
except Exception:
|
|
159
|
+
log.exception(f"First read of AbsorbanceReader on port {port} failed")
|
|
160
|
+
|
|
67
161
|
return module
|
|
68
162
|
|
|
69
163
|
def __init__(
|
|
@@ -71,33 +165,74 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
71
165
|
port: str,
|
|
72
166
|
usb_port: USBPort,
|
|
73
167
|
driver: AbstractAbsorbanceReaderDriver,
|
|
168
|
+
reader: AbsorbanceReaderReader,
|
|
169
|
+
poller: Poller,
|
|
74
170
|
device_info: Mapping[str, str],
|
|
75
171
|
execution_manager: ExecutionManager,
|
|
76
172
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
173
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
77
174
|
) -> None:
|
|
78
|
-
|
|
175
|
+
"""
|
|
176
|
+
Constructor
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
port: The port the absorbance is connected to.
|
|
180
|
+
usb_port: The USB port.
|
|
181
|
+
execution_manager: The hardware execution manager.
|
|
182
|
+
driver: The Absorbance driver.
|
|
183
|
+
reader: An interface to read data from the Absorbance Reader.
|
|
184
|
+
poller: A poll controller for reads.
|
|
185
|
+
device_info: The Absorbance device info.
|
|
186
|
+
hw_control_loop: The event loop running in the hardware control thread.
|
|
187
|
+
"""
|
|
79
188
|
self._driver = driver
|
|
189
|
+
super().__init__(
|
|
190
|
+
port=port,
|
|
191
|
+
usb_port=usb_port,
|
|
192
|
+
hw_control_loop=hw_control_loop,
|
|
193
|
+
execution_manager=execution_manager,
|
|
194
|
+
disconnected_callback=disconnected_callback,
|
|
195
|
+
)
|
|
80
196
|
self._device_info = device_info
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await self._driver.disconnect()
|
|
197
|
+
self._reader = reader
|
|
198
|
+
self._poller = poller
|
|
199
|
+
self._measurement_config: Optional[ABSMeasurementConfig] = None
|
|
200
|
+
self._device_status = AbsorbanceReaderStatus.IDLE
|
|
201
|
+
self._error: Optional[str] = None
|
|
202
|
+
self._reader.register_error_handler(self._enter_error_state)
|
|
88
203
|
|
|
89
204
|
@property
|
|
90
205
|
def status(self) -> AbsorbanceReaderStatus:
|
|
91
|
-
"""Return some string describing status."""
|
|
92
|
-
|
|
206
|
+
"""Return some string describing the device status."""
|
|
207
|
+
state = self._reader.device_state
|
|
208
|
+
if state not in [
|
|
209
|
+
AbsorbanceReaderDeviceState.UNKNOWN,
|
|
210
|
+
AbsorbanceReaderDeviceState.OK,
|
|
211
|
+
]:
|
|
212
|
+
return AbsorbanceReaderStatus.ERROR
|
|
213
|
+
return self._device_status
|
|
93
214
|
|
|
94
215
|
@property
|
|
95
216
|
def lid_status(self) -> AbsorbanceReaderLidStatus:
|
|
96
|
-
return
|
|
217
|
+
return self._reader.lid_status
|
|
97
218
|
|
|
98
219
|
@property
|
|
99
220
|
def plate_presence(self) -> AbsorbanceReaderPlatePresence:
|
|
100
|
-
return
|
|
221
|
+
return self._reader.plate_presence
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def uptime(self) -> int:
|
|
225
|
+
"""Time in ms this device has been running for."""
|
|
226
|
+
return self._reader.uptime
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def supported_wavelengths(self) -> List[int]:
|
|
230
|
+
"""The wavelengths in nm this plate reader supports."""
|
|
231
|
+
return self._reader.supported_wavelengths
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def measurement_config(self) -> Optional[ABSMeasurementConfig]:
|
|
235
|
+
return self._measurement_config
|
|
101
236
|
|
|
102
237
|
@property
|
|
103
238
|
def device_info(self) -> Mapping[str, str]:
|
|
@@ -107,12 +242,17 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
107
242
|
@property
|
|
108
243
|
def live_data(self) -> LiveData:
|
|
109
244
|
"""Return a dict of the module's dynamic information"""
|
|
245
|
+
conf = self._measurement_config.data if self._measurement_config else dict()
|
|
110
246
|
return {
|
|
111
247
|
"status": self.status.value,
|
|
112
248
|
"data": {
|
|
249
|
+
"uptime": self.uptime,
|
|
250
|
+
"deviceStatus": self.status.value,
|
|
113
251
|
"lidStatus": self.lid_status.value,
|
|
114
252
|
"platePresence": self.plate_presence.value,
|
|
115
|
-
"
|
|
253
|
+
"measureMode": conf.get("measureMode", ""),
|
|
254
|
+
"sampleWavelengths": conf.get("sampleWavelengths", []),
|
|
255
|
+
"referenceWavelength": conf.get("referenceWavelength", 0),
|
|
116
256
|
},
|
|
117
257
|
}
|
|
118
258
|
|
|
@@ -131,6 +271,10 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
131
271
|
"""The physical port where the module is connected."""
|
|
132
272
|
return self._usb_port
|
|
133
273
|
|
|
274
|
+
async def deactivate(self, must_be_running: bool = True) -> None:
|
|
275
|
+
"""Deactivate the module."""
|
|
276
|
+
pass
|
|
277
|
+
|
|
134
278
|
async def wait_for_is_running(self) -> None:
|
|
135
279
|
if not self.is_simulated:
|
|
136
280
|
await self._execution_manager.wait_for_is_running()
|
|
@@ -145,25 +289,44 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
145
289
|
|
|
146
290
|
:returns str: The port we're running on.
|
|
147
291
|
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def model(self) -> str:
|
|
151
|
-
"""A name for this specific module, matching module defs"""
|
|
152
|
-
return "absorbanceReaderV1"
|
|
292
|
+
await self._poller.stop()
|
|
293
|
+
return self.port
|
|
153
294
|
|
|
154
295
|
@classmethod
|
|
155
296
|
def name(cls) -> str:
|
|
156
297
|
"""A shortname used for matching usb ports, among other things"""
|
|
157
298
|
return "absorbancereader"
|
|
158
299
|
|
|
300
|
+
def model(self) -> str:
|
|
301
|
+
"""A name for this specific module, matching module defs"""
|
|
302
|
+
return "absorbanceReaderV1"
|
|
303
|
+
|
|
159
304
|
def firmware_prefix(self) -> str:
|
|
160
305
|
"""The prefix used for looking up firmware"""
|
|
161
|
-
|
|
162
|
-
|
|
306
|
+
return "absorbance-96"
|
|
307
|
+
|
|
308
|
+
async def update_device(self, firmware_file_path: str) -> Tuple[bool, str]:
|
|
309
|
+
"""Updates the firmware on the device."""
|
|
310
|
+
if self._updating:
|
|
311
|
+
return False, f"Device {self.serial_number} already updating."
|
|
312
|
+
log.debug(f"Updating {self.name}: {self.port} with {firmware_file_path}")
|
|
313
|
+
self._updating = True
|
|
314
|
+
success, res = await self._driver.update_firmware(firmware_file_path)
|
|
315
|
+
# it takes time for the plate reader to re-init after an update.
|
|
316
|
+
await asyncio.sleep(10)
|
|
317
|
+
self._device_info = await self._driver.get_device_info()
|
|
318
|
+
await self._poller.start()
|
|
319
|
+
self._updating = False
|
|
320
|
+
return success, res
|
|
163
321
|
|
|
164
322
|
def bootloader(self) -> UploadFunction:
|
|
165
|
-
|
|
166
|
-
|
|
323
|
+
async def _update_function(
|
|
324
|
+
port: str, firmware_file_path: str, kwargs: Dict[str, Any]
|
|
325
|
+
) -> Tuple[bool, str]:
|
|
326
|
+
module: AbsorbanceReader = kwargs["module"]
|
|
327
|
+
return await module.update_device(firmware_file_path)
|
|
328
|
+
|
|
329
|
+
return _update_function
|
|
167
330
|
|
|
168
331
|
async def cleanup(self) -> None:
|
|
169
332
|
"""Clean up the module instance.
|
|
@@ -171,56 +334,42 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
171
334
|
Clean up, i.e. stop pollers, disconnect serial, etc in preparation for
|
|
172
335
|
object destruction.
|
|
173
336
|
"""
|
|
337
|
+
await self._poller.stop()
|
|
174
338
|
await self._driver.disconnect()
|
|
175
339
|
|
|
176
|
-
async def set_sample_wavelength(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
class AbsorbanceReaderReader(Reader):
|
|
196
|
-
device_state: AbsorbanceReaderDeviceState
|
|
197
|
-
lid_status: AbsorbanceReaderLidStatus
|
|
198
|
-
plate_presence: AbsorbanceReaderPlatePresence
|
|
199
|
-
supported_wavelengths: List[int]
|
|
200
|
-
|
|
201
|
-
def __init__(self, driver: AbsorbanceReaderDriver) -> None:
|
|
202
|
-
self.device_state = AbsorbanceReaderDeviceState.UNKNOWN
|
|
203
|
-
self.lid_status = AbsorbanceReaderLidStatus.UNKNOWN
|
|
204
|
-
self.plate_presence = AbsorbanceReaderPlatePresence.UNKNOWN
|
|
205
|
-
self.supported_wavelengths = []
|
|
206
|
-
self._driver = driver
|
|
207
|
-
|
|
208
|
-
async def read(self) -> None:
|
|
209
|
-
await self.get_device_status()
|
|
210
|
-
await self.get_supported_wavelengths()
|
|
211
|
-
|
|
212
|
-
async def get_device_status(self) -> None:
|
|
213
|
-
"""Get the Absorbance Reader's current status."""
|
|
214
|
-
self.device_state = await self._driver.get_status()
|
|
215
|
-
|
|
216
|
-
async def get_supported_wavelengths(self) -> None:
|
|
217
|
-
"""Get the Absorbance Reader's supported wavelengths."""
|
|
218
|
-
self.supported_wavelengths = await self._driver.get_available_wavelengths()
|
|
219
|
-
|
|
220
|
-
async def get_lid_status(self) -> None:
|
|
221
|
-
"""Get the Absorbance Reader's lid status."""
|
|
222
|
-
self.lid_status = await self._driver.get_lid_status()
|
|
340
|
+
async def set_sample_wavelength(
|
|
341
|
+
self,
|
|
342
|
+
mode: ABSMeasurementMode,
|
|
343
|
+
wavelengths: List[int],
|
|
344
|
+
reference_wavelength: Optional[int] = None,
|
|
345
|
+
) -> None:
|
|
346
|
+
"""Set the Absorbance Reader's measurement mode and active wavelength."""
|
|
347
|
+
if mode == ABSMeasurementMode.SINGLE:
|
|
348
|
+
assert (
|
|
349
|
+
len(wavelengths) == 1
|
|
350
|
+
), "Cannot initialize single read mode with more than 1 wavelength."
|
|
351
|
+
|
|
352
|
+
await self._driver.initialize_measurement(wavelengths, mode)
|
|
353
|
+
self._measurement_config = ABSMeasurementConfig(
|
|
354
|
+
measure_mode=mode,
|
|
355
|
+
sample_wavelengths=wavelengths,
|
|
356
|
+
reference_wavelength=reference_wavelength,
|
|
357
|
+
)
|
|
223
358
|
|
|
224
|
-
async def
|
|
225
|
-
"""
|
|
226
|
-
|
|
359
|
+
async def start_measure(self) -> List[List[float]]:
|
|
360
|
+
"""Initiate a measurement depending on the measurement mode."""
|
|
361
|
+
try:
|
|
362
|
+
self._device_status = AbsorbanceReaderStatus.MEASURING
|
|
363
|
+
return await self._driver.get_measurement()
|
|
364
|
+
finally:
|
|
365
|
+
self._device_status = AbsorbanceReaderStatus.IDLE
|
|
366
|
+
|
|
367
|
+
async def get_current_lid_status(self) -> AbsorbanceReaderLidStatus:
|
|
368
|
+
"""Get the Absorbance Reader's current lid status."""
|
|
369
|
+
await self._reader.get_lid_status()
|
|
370
|
+
return self._reader.lid_status
|
|
371
|
+
|
|
372
|
+
def _enter_error_state(self, error: Exception) -> None:
|
|
373
|
+
self._error = str(error)
|
|
374
|
+
if isinstance(error, AbsorbanceReaderDisconnectedError):
|
|
375
|
+
self.disconnected_callback()
|
|
@@ -14,6 +14,7 @@ from opentrons.hardware_control.execution_manager import ExecutionManager
|
|
|
14
14
|
from opentrons.hardware_control.poller import Reader, Poller
|
|
15
15
|
from opentrons.hardware_control.modules import mod_abc, update
|
|
16
16
|
from opentrons.hardware_control.modules.types import (
|
|
17
|
+
ModuleDisconnectedCallback,
|
|
17
18
|
ModuleType,
|
|
18
19
|
TemperatureStatus,
|
|
19
20
|
SpeedStatus,
|
|
@@ -50,6 +51,7 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
50
51
|
simulating: bool = False,
|
|
51
52
|
sim_model: Optional[str] = None,
|
|
52
53
|
sim_serial_number: Optional[str] = None,
|
|
54
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
53
55
|
) -> "HeaterShaker":
|
|
54
56
|
"""
|
|
55
57
|
Build a HeaterShaker
|
|
@@ -63,6 +65,7 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
63
65
|
simulating: whether to build a simulating driver
|
|
64
66
|
loop: Loop
|
|
65
67
|
sim_model: The model name used by simulator
|
|
68
|
+
disconnected_callback: Callback to inform the module controller that the device was disconnected
|
|
66
69
|
|
|
67
70
|
Returns:
|
|
68
71
|
HeaterShaker instance
|
|
@@ -80,12 +83,13 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
80
83
|
module = cls(
|
|
81
84
|
port=port,
|
|
82
85
|
usb_port=usb_port,
|
|
83
|
-
device_info=await driver.get_device_info(),
|
|
84
|
-
execution_manager=execution_manager,
|
|
85
86
|
driver=driver,
|
|
86
87
|
reader=reader,
|
|
87
88
|
poller=poller,
|
|
89
|
+
device_info=await driver.get_device_info(),
|
|
88
90
|
hw_control_loop=hw_control_loop,
|
|
91
|
+
execution_manager=execution_manager,
|
|
92
|
+
disconnected_callback=disconnected_callback,
|
|
89
93
|
)
|
|
90
94
|
|
|
91
95
|
try:
|
|
@@ -105,12 +109,14 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
105
109
|
poller: Poller,
|
|
106
110
|
device_info: Mapping[str, str],
|
|
107
111
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
112
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
108
113
|
):
|
|
109
114
|
super().__init__(
|
|
110
115
|
port=port,
|
|
111
116
|
usb_port=usb_port,
|
|
112
117
|
hw_control_loop=hw_control_loop,
|
|
113
118
|
execution_manager=execution_manager,
|
|
119
|
+
disconnected_callback=disconnected_callback,
|
|
114
120
|
)
|
|
115
121
|
self._device_info = device_info
|
|
116
122
|
self._driver = driver
|
|
@@ -195,7 +201,6 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
195
201
|
@property
|
|
196
202
|
def live_data(self) -> LiveData:
|
|
197
203
|
return {
|
|
198
|
-
# TODO (spp, 2022-2-22): Revise what status includes
|
|
199
204
|
"status": self.status.value,
|
|
200
205
|
"data": {
|
|
201
206
|
"temperatureStatus": self.temperature_status.value,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Dict, Optional
|
|
4
4
|
from opentrons.drivers.mag_deck import (
|
|
5
5
|
SimulatingDriver,
|
|
6
6
|
MagDeckDriver,
|
|
@@ -54,6 +54,7 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
54
54
|
simulating: bool = False,
|
|
55
55
|
sim_model: Optional[str] = None,
|
|
56
56
|
sim_serial_number: Optional[str] = None,
|
|
57
|
+
disconnected_callback: types.ModuleDisconnectedCallback = None,
|
|
57
58
|
) -> "MagDeck":
|
|
58
59
|
"""Factory function."""
|
|
59
60
|
driver: AbstractMagDeckDriver
|
|
@@ -71,6 +72,7 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
71
72
|
hw_control_loop=hw_control_loop,
|
|
72
73
|
device_info=await driver.get_device_info(),
|
|
73
74
|
driver=driver,
|
|
75
|
+
disconnected_callback=disconnected_callback,
|
|
74
76
|
)
|
|
75
77
|
return mod
|
|
76
78
|
|
|
@@ -81,7 +83,8 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
81
83
|
execution_manager: ExecutionManager,
|
|
82
84
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
83
85
|
driver: AbstractMagDeckDriver,
|
|
84
|
-
device_info:
|
|
86
|
+
device_info: Dict[str, str],
|
|
87
|
+
disconnected_callback: types.ModuleDisconnectedCallback = None,
|
|
85
88
|
) -> None:
|
|
86
89
|
"""Constructor"""
|
|
87
90
|
super().__init__(
|
|
@@ -89,6 +92,7 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
89
92
|
usb_port=usb_port,
|
|
90
93
|
hw_control_loop=hw_control_loop,
|
|
91
94
|
execution_manager=execution_manager,
|
|
95
|
+
disconnected_callback=disconnected_callback,
|
|
92
96
|
)
|
|
93
97
|
self._device_info = device_info
|
|
94
98
|
self._driver = driver
|
|
@@ -162,7 +166,7 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
162
166
|
return self._current_height
|
|
163
167
|
|
|
164
168
|
@property
|
|
165
|
-
def device_info(self) ->
|
|
169
|
+
def device_info(self) -> Dict[str, str]:
|
|
166
170
|
"""
|
|
167
171
|
|
|
168
172
|
Returns: a dict
|
|
@@ -171,6 +175,11 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
171
175
|
"""
|
|
172
176
|
return self._device_info
|
|
173
177
|
|
|
178
|
+
@property
|
|
179
|
+
def serial_number(self) -> Optional[str]:
|
|
180
|
+
"""The usb serial number of this device"""
|
|
181
|
+
return self._device_info.get("serial")
|
|
182
|
+
|
|
174
183
|
@property
|
|
175
184
|
def status(self) -> types.MagneticStatus:
|
|
176
185
|
if self.current_height > 0:
|
|
@@ -8,7 +8,13 @@ from opentrons.config import IS_ROBOT, ROBOT_FIRMWARE_DIR
|
|
|
8
8
|
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
9
9
|
|
|
10
10
|
from ..execution_manager import ExecutionManager
|
|
11
|
-
from .types import
|
|
11
|
+
from .types import (
|
|
12
|
+
BundledFirmware,
|
|
13
|
+
ModuleDisconnectedCallback,
|
|
14
|
+
UploadFunction,
|
|
15
|
+
LiveData,
|
|
16
|
+
ModuleType,
|
|
17
|
+
)
|
|
12
18
|
|
|
13
19
|
mod_log = logging.getLogger(__name__)
|
|
14
20
|
|
|
@@ -45,6 +51,7 @@ class AbstractModule(abc.ABC):
|
|
|
45
51
|
simulating: bool = False,
|
|
46
52
|
sim_model: Optional[str] = None,
|
|
47
53
|
sim_serial_number: Optional[str] = None,
|
|
54
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
48
55
|
) -> "AbstractModule":
|
|
49
56
|
"""Modules should always be created using this factory.
|
|
50
57
|
|
|
@@ -58,12 +65,15 @@ class AbstractModule(abc.ABC):
|
|
|
58
65
|
usb_port: USBPort,
|
|
59
66
|
execution_manager: ExecutionManager,
|
|
60
67
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
68
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
61
69
|
) -> None:
|
|
62
70
|
self._port = port
|
|
63
71
|
self._usb_port = usb_port
|
|
64
72
|
self._loop = hw_control_loop
|
|
65
73
|
self._execution_manager = execution_manager
|
|
66
74
|
self._bundled_fw: Optional[BundledFirmware] = self.get_bundled_fw()
|
|
75
|
+
self._disconnected_callback = disconnected_callback
|
|
76
|
+
self._updating = False
|
|
67
77
|
|
|
68
78
|
@staticmethod
|
|
69
79
|
def sort_key(inst: "AbstractModule") -> int:
|
|
@@ -82,13 +92,23 @@ class AbstractModule(abc.ABC):
|
|
|
82
92
|
def loop(self) -> asyncio.AbstractEventLoop:
|
|
83
93
|
return self._loop
|
|
84
94
|
|
|
95
|
+
@property
|
|
96
|
+
def updating(self) -> bool:
|
|
97
|
+
"""The device is updating is True."""
|
|
98
|
+
return self._updating
|
|
99
|
+
|
|
100
|
+
def disconnected_callback(self) -> None:
|
|
101
|
+
"""Called from within the module object to signify the object is no longer connected"""
|
|
102
|
+
if self._disconnected_callback is not None:
|
|
103
|
+
self._disconnected_callback(self.port, self.serial_number)
|
|
104
|
+
|
|
85
105
|
def get_bundled_fw(self) -> Optional[BundledFirmware]:
|
|
86
106
|
"""Get absolute path to bundled version of module fw if available."""
|
|
87
107
|
if not IS_ROBOT:
|
|
88
108
|
return None
|
|
89
109
|
file_prefix = self.firmware_prefix()
|
|
90
110
|
|
|
91
|
-
MODULE_FW_RE = re.compile(f"^{file_prefix}@v(.*)[.](hex|bin)$")
|
|
111
|
+
MODULE_FW_RE = re.compile(f"^{file_prefix}@v(.*)[.](hex|bin|byoup)$")
|
|
92
112
|
for fw_resource in ROBOT_FIRMWARE_DIR.iterdir(): # type: ignore
|
|
93
113
|
matches = MODULE_FW_RE.search(fw_resource.name)
|
|
94
114
|
if matches:
|
|
@@ -154,6 +174,11 @@ class AbstractModule(abc.ABC):
|
|
|
154
174
|
"""The physical port where the module is connected."""
|
|
155
175
|
return self._usb_port
|
|
156
176
|
|
|
177
|
+
@property
|
|
178
|
+
def serial_number(self) -> Optional[str]:
|
|
179
|
+
"""The usb serial number of this device."""
|
|
180
|
+
return self.device_info.get("serial")
|
|
181
|
+
|
|
157
182
|
@abc.abstractmethod
|
|
158
183
|
async def prep_for_update(self) -> str:
|
|
159
184
|
"""Prepare for an update.
|