opentrons 8.7.0a1__py3-none-any.whl → 8.7.0a2__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.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/_version.py +2 -2
- opentrons/drivers/thermocycler/abstract.py +1 -0
- opentrons/drivers/thermocycler/driver.py +33 -4
- opentrons/drivers/thermocycler/simulator.py +2 -0
- opentrons/hardware_control/api.py +24 -5
- opentrons/hardware_control/backends/controller.py +8 -2
- opentrons/hardware_control/backends/ot3controller.py +3 -0
- opentrons/hardware_control/backends/ot3simulator.py +2 -1
- opentrons/hardware_control/backends/simulator.py +2 -1
- opentrons/hardware_control/backends/subsystem_manager.py +5 -2
- opentrons/hardware_control/module_control.py +82 -8
- opentrons/hardware_control/modules/__init__.py +3 -0
- opentrons/hardware_control/modules/absorbance_reader.py +11 -4
- opentrons/hardware_control/modules/flex_stacker.py +38 -9
- opentrons/hardware_control/modules/heater_shaker.py +30 -5
- opentrons/hardware_control/modules/magdeck.py +8 -4
- opentrons/hardware_control/modules/mod_abc.py +13 -5
- opentrons/hardware_control/modules/tempdeck.py +25 -5
- opentrons/hardware_control/modules/thermocycler.py +56 -10
- opentrons/hardware_control/modules/types.py +20 -1
- opentrons/hardware_control/modules/utils.py +11 -4
- opentrons/hardware_control/nozzle_manager.py +3 -0
- opentrons/hardware_control/ot3api.py +26 -5
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +31 -2
- opentrons/legacy_commands/protocol_commands.py +20 -0
- opentrons/legacy_commands/types.py +42 -0
- opentrons/motion_planning/waypoints.py +15 -29
- opentrons/protocol_api/__init__.py +5 -0
- opentrons/protocol_api/_types.py +6 -1
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
- opentrons/protocol_api/core/engine/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +4 -0
- opentrons/protocol_api/core/engine/protocol.py +18 -1
- opentrons/protocol_api/core/engine/tasks.py +35 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
- opentrons/protocol_api/core/legacy/tasks.py +19 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
- opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
- opentrons/protocol_api/core/module.py +1 -0
- opentrons/protocol_api/core/protocol.py +11 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/module_contexts.py +1 -0
- opentrons/protocol_api/protocol_context.py +26 -4
- opentrons/protocol_api/robot_context.py +38 -21
- opentrons/protocol_api/tasks.py +48 -0
- opentrons/protocol_api/validation.py +6 -1
- opentrons/protocol_engine/actions/__init__.py +4 -2
- opentrons/protocol_engine/actions/actions.py +22 -9
- opentrons/protocol_engine/clients/sync_client.py +6 -7
- opentrons/protocol_engine/commands/__init__.py +42 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
- opentrons/protocol_engine/commands/aspirate.py +1 -0
- opentrons/protocol_engine/commands/command.py +1 -0
- opentrons/protocol_engine/commands/command_unions.py +39 -0
- opentrons/protocol_engine/commands/create_timer.py +83 -0
- opentrons/protocol_engine/commands/dispense.py +1 -0
- opentrons/protocol_engine/commands/drop_tip.py +32 -8
- opentrons/protocol_engine/commands/movement_common.py +2 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
- opentrons/protocol_engine/commands/set_tip_state.py +97 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +17 -1
- opentrons/protocol_engine/commands/touch_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
- opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
- opentrons/protocol_engine/errors/__init__.py +4 -0
- opentrons/protocol_engine/errors/exceptions.py +55 -0
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +8 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/labware_movement.py +9 -12
- opentrons/protocol_engine/execution/movement.py +2 -0
- opentrons/protocol_engine/execution/queue_worker.py +4 -0
- opentrons/protocol_engine/execution/run_control.py +8 -0
- opentrons/protocol_engine/execution/task_handler.py +157 -0
- opentrons/protocol_engine/protocol_engine.py +67 -33
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
- opentrons/protocol_engine/resources/labware_validation.py +10 -6
- opentrons/protocol_engine/state/_well_math.py +60 -18
- opentrons/protocol_engine/state/addressable_areas.py +2 -0
- opentrons/protocol_engine/state/commands.py +7 -7
- opentrons/protocol_engine/state/geometry.py +204 -374
- opentrons/protocol_engine/state/labware.py +52 -102
- opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
- opentrons/protocol_engine/state/modules.py +21 -8
- opentrons/protocol_engine/state/motion.py +44 -0
- opentrons/protocol_engine/state/state.py +14 -0
- opentrons/protocol_engine/state/state_summary.py +2 -0
- opentrons/protocol_engine/state/tasks.py +139 -0
- opentrons/protocol_engine/state/tips.py +177 -258
- opentrons/protocol_engine/state/update_types.py +16 -9
- opentrons/protocol_engine/types/__init__.py +9 -3
- opentrons/protocol_engine/types/deck_configuration.py +5 -1
- opentrons/protocol_engine/types/instrument.py +8 -1
- opentrons/protocol_engine/types/labware.py +1 -13
- opentrons/protocol_engine/types/module.py +10 -0
- opentrons/protocol_engine/types/tasks.py +38 -0
- opentrons/protocol_engine/types/tip.py +9 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
- opentrons/protocol_runner/run_orchestrator.py +18 -2
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +2 -1
- opentrons/simulate.py +48 -15
- opentrons/system/camera.py +1 -1
- {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/RECORD +118 -105
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/licenses/LICENSE +0 -0
opentrons/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '8.7.
|
|
32
|
-
__version_tuple__ = version_tuple = (8, 7, 0, '
|
|
31
|
+
__version__ = version = '8.7.0a2'
|
|
32
|
+
__version_tuple__ = version_tuple = (8, 7, 0, 'a2')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -226,6 +226,7 @@ class ThermocyclerDriver(AbstractThermocyclerDriver):
|
|
|
226
226
|
temp: float,
|
|
227
227
|
hold_time: Optional[float] = None,
|
|
228
228
|
volume: Optional[float] = None,
|
|
229
|
+
ramp_rate: Optional[float] = None,
|
|
229
230
|
) -> None:
|
|
230
231
|
"""Send set plate temperature command"""
|
|
231
232
|
temp = min(BLOCK_TARGET_MAX, max(BLOCK_TARGET_MIN, temp))
|
|
@@ -343,10 +344,38 @@ class ThermocyclerDriverV2(ThermocyclerDriver):
|
|
|
343
344
|
"""
|
|
344
345
|
super().__init__(connection)
|
|
345
346
|
|
|
346
|
-
async def
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
async def set_plate_temperature(
|
|
348
|
+
self,
|
|
349
|
+
temp: float,
|
|
350
|
+
hold_time: Optional[float] = None,
|
|
351
|
+
volume: Optional[float] = None,
|
|
352
|
+
ramp_rate: Optional[float] = None,
|
|
353
|
+
) -> None:
|
|
354
|
+
"""Send set plate temperature command"""
|
|
355
|
+
temp = min(BLOCK_TARGET_MAX, max(BLOCK_TARGET_MIN, temp))
|
|
356
|
+
|
|
357
|
+
c = (
|
|
358
|
+
CommandBuilder(terminator=TC_COMMAND_TERMINATOR)
|
|
359
|
+
.add_gcode(gcode=GCODE.SET_PLATE_TEMP)
|
|
360
|
+
.add_float(
|
|
361
|
+
prefix="S", value=temp, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
if hold_time is not None:
|
|
365
|
+
c = c.add_float(
|
|
366
|
+
prefix="H", value=hold_time, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
367
|
+
)
|
|
368
|
+
if volume is not None:
|
|
369
|
+
c = c.add_float(
|
|
370
|
+
prefix="V", value=volume, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if ramp_rate is not None:
|
|
374
|
+
c = c.add_float(
|
|
375
|
+
prefix="R", value=ramp_rate, precision=utils.TC_GCODE_ROUNDING_PRECISION
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
await self._connection.send_command(command=c, retries=DEFAULT_COMMAND_RETRIES)
|
|
350
379
|
|
|
351
380
|
async def get_device_info(self) -> Dict[str, str]:
|
|
352
381
|
"""Send get device info command"""
|
|
@@ -67,10 +67,12 @@ class SimulatingDriver(AbstractThermocyclerDriver):
|
|
|
67
67
|
temp: float,
|
|
68
68
|
hold_time: Optional[float] = None,
|
|
69
69
|
volume: Optional[float] = None,
|
|
70
|
+
ramp_rate: Optional[float] = None,
|
|
70
71
|
) -> None:
|
|
71
72
|
self._plate_temperature.target = temp
|
|
72
73
|
self._plate_temperature.current = temp
|
|
73
74
|
self._plate_temperature.hold = 0
|
|
75
|
+
self._ramp_rate = ramp_rate
|
|
74
76
|
|
|
75
77
|
@ensure_yield
|
|
76
78
|
async def get_plate_temperature(self) -> PlateTemperature:
|
|
@@ -44,6 +44,7 @@ from .execution_manager import ExecutionManagerProvider
|
|
|
44
44
|
from .pause_manager import PauseManager
|
|
45
45
|
from .module_control import AttachedModulesControl
|
|
46
46
|
from .types import (
|
|
47
|
+
AsynchronousModuleErrorNotification,
|
|
47
48
|
Axis,
|
|
48
49
|
CriticalPoint,
|
|
49
50
|
DoorState,
|
|
@@ -51,6 +52,7 @@ from .types import (
|
|
|
51
52
|
ErrorMessageNotification,
|
|
52
53
|
HardwareEventHandler,
|
|
53
54
|
HardwareAction,
|
|
55
|
+
HardwareEvent,
|
|
54
56
|
MotionChecks,
|
|
55
57
|
PauseType,
|
|
56
58
|
StatusBarState,
|
|
@@ -167,6 +169,18 @@ class API(
|
|
|
167
169
|
except Exception:
|
|
168
170
|
mod_log.exception("Errored during door state event callback")
|
|
169
171
|
|
|
172
|
+
def _send_module_notification(self, event: HardwareEvent) -> None:
|
|
173
|
+
if not isinstance(event, AsynchronousModuleErrorNotification):
|
|
174
|
+
return
|
|
175
|
+
mod_log.info(
|
|
176
|
+
f"Forwarding module event {event.event} for {event.module_model} {event.module_serial} at {event.port}"
|
|
177
|
+
)
|
|
178
|
+
for cb in self._callbacks:
|
|
179
|
+
try:
|
|
180
|
+
cb(event)
|
|
181
|
+
except Exception:
|
|
182
|
+
mod_log.exception("Errored during module asynchronous callback")
|
|
183
|
+
|
|
170
184
|
def _reset_last_mount(self) -> None:
|
|
171
185
|
self._last_moved_mount = None
|
|
172
186
|
|
|
@@ -247,7 +261,9 @@ class API(
|
|
|
247
261
|
)
|
|
248
262
|
await api_instance.cache_instruments()
|
|
249
263
|
module_controls = await AttachedModulesControl.build(
|
|
250
|
-
api_instance,
|
|
264
|
+
api_instance,
|
|
265
|
+
board_revision=backend.board_revision,
|
|
266
|
+
event_callback=api_instance._send_module_notification,
|
|
251
267
|
)
|
|
252
268
|
backend.module_controls = module_controls
|
|
253
269
|
checked_loop.create_task(backend.watch(loop=checked_loop))
|
|
@@ -306,7 +322,9 @@ class API(
|
|
|
306
322
|
)
|
|
307
323
|
await api_instance.cache_instruments()
|
|
308
324
|
module_controls = await AttachedModulesControl.build(
|
|
309
|
-
api_instance,
|
|
325
|
+
api_instance,
|
|
326
|
+
board_revision=backend.board_revision,
|
|
327
|
+
event_callback=api_instance._send_module_notification,
|
|
310
328
|
)
|
|
311
329
|
backend.module_controls = module_controls
|
|
312
330
|
await backend.watch()
|
|
@@ -1312,9 +1330,10 @@ class API(
|
|
|
1312
1330
|
self.is_simulator
|
|
1313
1331
|
), "Cannot build simulating module from non-simulating hardware control API"
|
|
1314
1332
|
|
|
1315
|
-
return await self._backend.module_controls.
|
|
1316
|
-
|
|
1317
|
-
|
|
1333
|
+
return await self._backend.module_controls.register_simulated_module(
|
|
1334
|
+
simulated_usb_port=USBPort(
|
|
1335
|
+
name="", port_number=1, port_group=PortGroup.MAIN
|
|
1336
|
+
),
|
|
1318
1337
|
type=modules.ModuleType.from_model(model),
|
|
1319
1338
|
sim_model=model.value,
|
|
1320
1339
|
)
|
|
@@ -360,13 +360,19 @@ class Controller:
|
|
|
360
360
|
"""Run a probe and return the new position dict"""
|
|
361
361
|
return await self._smoothie_driver.probe_axis(axis, distance)
|
|
362
362
|
|
|
363
|
-
async def clean_up(self) -> None:
|
|
363
|
+
async def clean_up(self) -> None: # noqa: C901
|
|
364
364
|
try:
|
|
365
365
|
loop = asyncio.get_event_loop()
|
|
366
366
|
except RuntimeError:
|
|
367
367
|
return
|
|
368
|
+
if hasattr(self, "_module_controls") and self._module_controls is not None:
|
|
369
|
+
await self._module_controls.clean_up()
|
|
368
370
|
if hasattr(self, "_event_watcher"):
|
|
369
|
-
if
|
|
371
|
+
if (
|
|
372
|
+
loop.is_running()
|
|
373
|
+
and self._event_watcher
|
|
374
|
+
and not self._event_watcher.closed
|
|
375
|
+
):
|
|
370
376
|
self._event_watcher.close()
|
|
371
377
|
if hasattr(self, "gpio_chardev"):
|
|
372
378
|
try:
|
|
@@ -1398,6 +1398,9 @@ class OT3Controller(FlexBackend):
|
|
|
1398
1398
|
except RuntimeError:
|
|
1399
1399
|
return
|
|
1400
1400
|
|
|
1401
|
+
if hasattr(self, "_module_controls") and self._module_controls is not None:
|
|
1402
|
+
await self._module_controls.clean_up()
|
|
1403
|
+
|
|
1401
1404
|
if hasattr(self, "_event_watcher"):
|
|
1402
1405
|
if (
|
|
1403
1406
|
loop.is_running()
|
|
@@ -728,7 +728,8 @@ class OT3Simulator(FlexBackend):
|
|
|
728
728
|
@ensure_yield
|
|
729
729
|
async def clean_up(self) -> None:
|
|
730
730
|
"""Clean up."""
|
|
731
|
-
|
|
731
|
+
if hasattr(self, "_module_controls") and self._module_controls is not None:
|
|
732
|
+
await self._module_controls.clean_up()
|
|
732
733
|
|
|
733
734
|
@staticmethod
|
|
734
735
|
def _get_home_position() -> Dict[Axis, float]:
|
|
@@ -414,7 +414,8 @@ class Simulator:
|
|
|
414
414
|
|
|
415
415
|
@ensure_yield
|
|
416
416
|
async def clean_up(self) -> None:
|
|
417
|
-
|
|
417
|
+
if hasattr(self, "_module_controls") and self._module_controls is not None:
|
|
418
|
+
await self._module_controls.clean_up()
|
|
418
419
|
|
|
419
420
|
@ensure_yield
|
|
420
421
|
async def configure_mount(
|
|
@@ -91,6 +91,8 @@ class SubsystemManager:
|
|
|
91
91
|
self._present_tools = tools.types.ToolSummary(
|
|
92
92
|
left=None, right=None, gripper=None
|
|
93
93
|
)
|
|
94
|
+
# This is intended to be an internal variable but is modified in unit tests to avoid long timeouts
|
|
95
|
+
self._check_device_update_timeout = 10.0
|
|
94
96
|
|
|
95
97
|
@property
|
|
96
98
|
def ok(self) -> bool:
|
|
@@ -183,11 +185,12 @@ class SubsystemManager:
|
|
|
183
185
|
return self._tool_task_state is True
|
|
184
186
|
|
|
185
187
|
async def _check_devices_after_update(
|
|
186
|
-
self, devices: Set[SubSystem], timeout_sec: float =
|
|
188
|
+
self, devices: Set[SubSystem], timeout_sec: Optional[float] = None
|
|
187
189
|
) -> None:
|
|
188
190
|
try:
|
|
189
191
|
await asyncio.wait_for(
|
|
190
|
-
self._do_check_devices_after_update(devices),
|
|
192
|
+
self._do_check_devices_after_update(devices),
|
|
193
|
+
timeout=timeout_sec or self._check_device_update_timeout,
|
|
191
194
|
)
|
|
192
195
|
except asyncio.TimeoutError:
|
|
193
196
|
raise RuntimeError("Device failed to come back after firmware update")
|
|
@@ -2,9 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
|
-
from typing import TYPE_CHECKING, List, Optional, Union
|
|
5
|
+
from typing import TYPE_CHECKING, List, Optional, Union, Callable
|
|
6
6
|
from glob import glob
|
|
7
7
|
|
|
8
|
+
from opentrons_shared_data.errors.exceptions import EnumeratedError
|
|
9
|
+
|
|
8
10
|
from opentrons.config import IS_ROBOT, IS_LINUX
|
|
9
11
|
from opentrons.drivers.rpi_drivers import types, interfaces, usb, usb_simulator
|
|
10
12
|
from opentrons.hardware_control.emulation.module_server.helpers import (
|
|
@@ -19,8 +21,16 @@ from opentrons.hardware_control.modules.module_calibration import (
|
|
|
19
21
|
from opentrons.hardware_control.modules.types import ModuleAtPort, ModuleType
|
|
20
22
|
from opentrons.hardware_control.modules import SimulatingModuleAtPort
|
|
21
23
|
|
|
24
|
+
|
|
22
25
|
from opentrons.types import Point
|
|
23
|
-
from .types import
|
|
26
|
+
from .types import (
|
|
27
|
+
AionotifyEvent,
|
|
28
|
+
BoardRevision,
|
|
29
|
+
OT3Mount,
|
|
30
|
+
StatusBarUpdateEvent,
|
|
31
|
+
HardwareEvent,
|
|
32
|
+
AsynchronousModuleErrorNotification,
|
|
33
|
+
)
|
|
24
34
|
from . import modules
|
|
25
35
|
|
|
26
36
|
if TYPE_CHECKING:
|
|
@@ -51,10 +61,21 @@ class AttachedModulesControl:
|
|
|
51
61
|
self,
|
|
52
62
|
api: Union["API", "OT3API"],
|
|
53
63
|
usb: interfaces.USBDriverInterface,
|
|
64
|
+
event_callback: Callable[[HardwareEvent], None],
|
|
54
65
|
) -> None:
|
|
55
66
|
self._available_modules: List[modules.AbstractModule] = []
|
|
56
67
|
self._api = api
|
|
57
68
|
self._usb = usb
|
|
69
|
+
self._event_callback = event_callback
|
|
70
|
+
if not IS_ROBOT and not api.is_simulator:
|
|
71
|
+
# Start task that registers emulated modules.
|
|
72
|
+
self._emulation_listen_task: asyncio.Task[
|
|
73
|
+
None
|
|
74
|
+
] | None = api.loop.create_task(
|
|
75
|
+
listen_module_connection(self.register_modules)
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
self._emulation_listen_task = None
|
|
58
79
|
|
|
59
80
|
def subscribe_to_api_event(self, module: modules.AbstractModule) -> None:
|
|
60
81
|
self._api.add_status_bar_listener(module.event_listener)
|
|
@@ -64,22 +85,20 @@ class AttachedModulesControl:
|
|
|
64
85
|
cls,
|
|
65
86
|
api_instance: Union["API", "OT3API"],
|
|
66
87
|
board_revision: BoardRevision,
|
|
88
|
+
event_callback: Callable[[HardwareEvent], None],
|
|
67
89
|
) -> AttachedModulesControl:
|
|
68
90
|
usb_instance = (
|
|
69
91
|
usb.USBBus(board_revision)
|
|
70
92
|
if not api_instance.is_simulator and IS_ROBOT
|
|
71
93
|
else usb_simulator.USBBusSimulator()
|
|
72
94
|
)
|
|
73
|
-
mc_instance = cls(
|
|
95
|
+
mc_instance = cls(
|
|
96
|
+
api=api_instance, usb=usb_instance, event_callback=event_callback
|
|
97
|
+
)
|
|
74
98
|
|
|
75
99
|
if not api_instance.is_simulator:
|
|
76
100
|
# Do an initial scan of modules.
|
|
77
101
|
await mc_instance.register_modules(mc_instance.scan())
|
|
78
|
-
if not IS_ROBOT:
|
|
79
|
-
# Start task that registers emulated modules.
|
|
80
|
-
api_instance.loop.create_task(
|
|
81
|
-
listen_module_connection(mc_instance.register_modules)
|
|
82
|
-
)
|
|
83
102
|
|
|
84
103
|
return mc_instance
|
|
85
104
|
|
|
@@ -87,6 +106,37 @@ class AttachedModulesControl:
|
|
|
87
106
|
def available_modules(self) -> List[modules.AbstractModule]:
|
|
88
107
|
return self._available_modules
|
|
89
108
|
|
|
109
|
+
async def clean_up(self) -> None:
|
|
110
|
+
"""Clean up all registered modules and emulator scanning tasks (if any)."""
|
|
111
|
+
for module in self._available_modules:
|
|
112
|
+
await module.cleanup()
|
|
113
|
+
if self._emulation_listen_task is not None:
|
|
114
|
+
self._emulation_listen_task.cancel("cleanup")
|
|
115
|
+
try:
|
|
116
|
+
await self._emulation_listen_task
|
|
117
|
+
except asyncio.CancelledError:
|
|
118
|
+
pass
|
|
119
|
+
except Exception:
|
|
120
|
+
log.exception("Exception cleaning up emulation listen task")
|
|
121
|
+
finally:
|
|
122
|
+
self._emulation_listen_task = None
|
|
123
|
+
|
|
124
|
+
async def register_simulated_module(
|
|
125
|
+
self,
|
|
126
|
+
simulated_usb_port: types.USBPort,
|
|
127
|
+
type: modules.ModuleType,
|
|
128
|
+
sim_model: str,
|
|
129
|
+
) -> modules.AbstractModule:
|
|
130
|
+
"""Register a simulated module."""
|
|
131
|
+
module = await self.build_module(
|
|
132
|
+
"", simulated_usb_port, type, sim_model, sim_serial_number=None
|
|
133
|
+
)
|
|
134
|
+
self._available_modules.append(module)
|
|
135
|
+
self._available_modules = sorted(
|
|
136
|
+
self._available_modules, key=modules.AbstractModule.sort_key
|
|
137
|
+
)
|
|
138
|
+
return module
|
|
139
|
+
|
|
90
140
|
async def build_module(
|
|
91
141
|
self,
|
|
92
142
|
port: str,
|
|
@@ -105,6 +155,7 @@ class AttachedModulesControl:
|
|
|
105
155
|
sim_model=sim_model,
|
|
106
156
|
sim_serial_number=sim_serial_number,
|
|
107
157
|
disconnected_callback=self._disconnected_callback,
|
|
158
|
+
error_callback=self._async_error_callback,
|
|
108
159
|
)
|
|
109
160
|
last_event = StatusBarUpdateEvent(
|
|
110
161
|
self._api.get_status_bar_state(), self._api.get_status_bar_enabled()
|
|
@@ -121,6 +172,29 @@ class AttachedModulesControl:
|
|
|
121
172
|
self._api.loop,
|
|
122
173
|
)
|
|
123
174
|
|
|
175
|
+
def _async_error_callback(
|
|
176
|
+
self,
|
|
177
|
+
exc: Exception,
|
|
178
|
+
model: str,
|
|
179
|
+
port: str,
|
|
180
|
+
serial: str | None,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Used by the module to indicate it saw an error from its data poller."""
|
|
183
|
+
try:
|
|
184
|
+
self._api.loop.call_soon(
|
|
185
|
+
self._event_callback,
|
|
186
|
+
AsynchronousModuleErrorNotification(
|
|
187
|
+
exception=EnumeratedError.ensure(exc),
|
|
188
|
+
module_serial=serial,
|
|
189
|
+
module_model=modules.module_model_from_string(model),
|
|
190
|
+
port=port,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
except Exception:
|
|
194
|
+
log.exception(
|
|
195
|
+
f"Async error callback for module {model} {serial} at {port} for exc {exc} failed"
|
|
196
|
+
)
|
|
197
|
+
|
|
124
198
|
async def unregister_modules(
|
|
125
199
|
self,
|
|
126
200
|
mods_at_ports: Union[
|
|
@@ -27,7 +27,9 @@ from .types import (
|
|
|
27
27
|
LiveData,
|
|
28
28
|
ModuleData,
|
|
29
29
|
ModuleDataValidator,
|
|
30
|
+
module_model_from_string,
|
|
30
31
|
)
|
|
32
|
+
|
|
31
33
|
from .errors import (
|
|
32
34
|
UpdateError,
|
|
33
35
|
AbsorbanceReaderDisconnectedError,
|
|
@@ -66,4 +68,5 @@ __all__ = [
|
|
|
66
68
|
"FlexStackerStatus",
|
|
67
69
|
"PlatformState",
|
|
68
70
|
"StackerAxisState",
|
|
71
|
+
"module_model_from_string",
|
|
69
72
|
]
|
|
@@ -21,6 +21,7 @@ from opentrons.hardware_control.poller import Poller, Reader
|
|
|
21
21
|
from opentrons.hardware_control.modules import mod_abc
|
|
22
22
|
from opentrons.hardware_control.modules.types import (
|
|
23
23
|
ModuleDisconnectedCallback,
|
|
24
|
+
ModuleErrorCallback,
|
|
24
25
|
ModuleType,
|
|
25
26
|
AbsorbanceReaderStatus,
|
|
26
27
|
LiveData,
|
|
@@ -104,12 +105,13 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
104
105
|
port: str,
|
|
105
106
|
usb_port: USBPort,
|
|
106
107
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
107
|
-
execution_manager:
|
|
108
|
+
execution_manager: ExecutionManager,
|
|
109
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
110
|
+
error_callback: ModuleErrorCallback,
|
|
108
111
|
poll_interval_seconds: Optional[float] = None,
|
|
109
112
|
simulating: bool = False,
|
|
110
113
|
sim_model: Optional[str] = None,
|
|
111
114
|
sim_serial_number: Optional[str] = None,
|
|
112
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
113
115
|
) -> "AbsorbanceReader":
|
|
114
116
|
"""
|
|
115
117
|
Build and connect to an AbsorbanceReader
|
|
@@ -152,6 +154,7 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
152
154
|
hw_control_loop=hw_control_loop,
|
|
153
155
|
execution_manager=execution_manager,
|
|
154
156
|
disconnected_callback=disconnected_callback,
|
|
157
|
+
error_callback=error_callback,
|
|
155
158
|
)
|
|
156
159
|
|
|
157
160
|
try:
|
|
@@ -170,8 +173,9 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
170
173
|
poller: Poller,
|
|
171
174
|
device_info: Mapping[str, str],
|
|
172
175
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
173
|
-
execution_manager:
|
|
174
|
-
disconnected_callback: ModuleDisconnectedCallback
|
|
176
|
+
execution_manager: ExecutionManager,
|
|
177
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
178
|
+
error_callback: ModuleErrorCallback,
|
|
175
179
|
) -> None:
|
|
176
180
|
"""
|
|
177
181
|
Constructor
|
|
@@ -193,6 +197,7 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
193
197
|
hw_control_loop=hw_control_loop,
|
|
194
198
|
execution_manager=execution_manager,
|
|
195
199
|
disconnected_callback=disconnected_callback,
|
|
200
|
+
error_callback=error_callback,
|
|
196
201
|
)
|
|
197
202
|
self._device_info = device_info
|
|
198
203
|
self._reader = reader
|
|
@@ -371,3 +376,5 @@ class AbsorbanceReader(mod_abc.AbstractModule):
|
|
|
371
376
|
self._error = str(error)
|
|
372
377
|
if isinstance(error, AbsorbanceReaderDisconnectedError):
|
|
373
378
|
self.disconnected_callback()
|
|
379
|
+
else:
|
|
380
|
+
self.error_callback(error)
|
|
@@ -44,6 +44,7 @@ from opentrons.hardware_control.modules.types import (
|
|
|
44
44
|
FlexStackerStatus,
|
|
45
45
|
HopperDoorState,
|
|
46
46
|
LatchState,
|
|
47
|
+
ModuleErrorCallback,
|
|
47
48
|
ModuleDisconnectedCallback,
|
|
48
49
|
ModuleType,
|
|
49
50
|
PlatformState,
|
|
@@ -215,12 +216,13 @@ class FlexStacker(mod_abc.AbstractModule):
|
|
|
215
216
|
port: str,
|
|
216
217
|
usb_port: USBPort,
|
|
217
218
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
218
|
-
execution_manager:
|
|
219
|
-
|
|
219
|
+
execution_manager: ExecutionManager,
|
|
220
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
221
|
+
error_callback: ModuleErrorCallback,
|
|
222
|
+
poll_interval_seconds: float | None = None,
|
|
220
223
|
simulating: bool = False,
|
|
221
224
|
sim_model: Optional[str] = None,
|
|
222
225
|
sim_serial_number: Optional[str] = None,
|
|
223
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
224
226
|
) -> "FlexStacker":
|
|
225
227
|
"""
|
|
226
228
|
Build a FlexStacker
|
|
@@ -259,11 +261,9 @@ class FlexStacker(mod_abc.AbstractModule):
|
|
|
259
261
|
hw_control_loop=hw_control_loop,
|
|
260
262
|
execution_manager=execution_manager,
|
|
261
263
|
disconnected_callback=disconnected_callback,
|
|
264
|
+
error_callback=error_callback,
|
|
262
265
|
)
|
|
263
266
|
|
|
264
|
-
# Set initialized callback
|
|
265
|
-
reader.set_initialized_callback(module._initialized_callback)
|
|
266
|
-
|
|
267
267
|
# Enable stallguard
|
|
268
268
|
for axis, config in STALLGUARD_CONFIG.items():
|
|
269
269
|
await driver.set_stallguard_threshold(
|
|
@@ -286,8 +286,9 @@ class FlexStacker(mod_abc.AbstractModule):
|
|
|
286
286
|
poller: Poller,
|
|
287
287
|
device_info: Mapping[str, str],
|
|
288
288
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
289
|
-
execution_manager:
|
|
290
|
-
disconnected_callback: ModuleDisconnectedCallback
|
|
289
|
+
execution_manager: ExecutionManager,
|
|
290
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
291
|
+
error_callback: ModuleErrorCallback,
|
|
291
292
|
):
|
|
292
293
|
super().__init__(
|
|
293
294
|
port=port,
|
|
@@ -295,6 +296,7 @@ class FlexStacker(mod_abc.AbstractModule):
|
|
|
295
296
|
hw_control_loop=hw_control_loop,
|
|
296
297
|
execution_manager=execution_manager,
|
|
297
298
|
disconnected_callback=disconnected_callback,
|
|
299
|
+
error_callback=error_callback,
|
|
298
300
|
)
|
|
299
301
|
self._device_info = device_info
|
|
300
302
|
self._driver = driver
|
|
@@ -304,12 +306,20 @@ class FlexStacker(mod_abc.AbstractModule):
|
|
|
304
306
|
self._stacker_status = FlexStackerStatus.IDLE
|
|
305
307
|
self._last_status_bar_event: Optional[StatusBarUpdateEvent] = None
|
|
306
308
|
self._should_identify = False
|
|
309
|
+
# Set initialized callback
|
|
310
|
+
self._unsubscribe_init = reader.set_initialized_callback(
|
|
311
|
+
self._initialized_callback
|
|
312
|
+
)
|
|
313
|
+
self._unsubscribe_error = reader.set_error_callback(self._async_error_callback)
|
|
307
314
|
|
|
308
315
|
async def _initialized_callback(self) -> None:
|
|
309
316
|
"""Called by the reader once the module is initialized."""
|
|
310
317
|
if self._last_status_bar_event:
|
|
311
318
|
await self._handle_status_bar_event(self._last_status_bar_event)
|
|
312
319
|
|
|
320
|
+
def _async_error_callback(self, exception: Exception) -> None:
|
|
321
|
+
self.error_callback(exception)
|
|
322
|
+
|
|
313
323
|
async def cleanup(self) -> None:
|
|
314
324
|
"""Stop the poller task"""
|
|
315
325
|
await self._poller.stop()
|
|
@@ -864,10 +874,27 @@ class FlexStackerReader(Reader):
|
|
|
864
874
|
self.installation_detected = False
|
|
865
875
|
self._refresh_state = False
|
|
866
876
|
self._initialized_callback: Optional[Callable[[], Awaitable[None]]] = None
|
|
877
|
+
self._error_callback: Optional[Callable[[Exception], None]] = None
|
|
867
878
|
|
|
868
|
-
def set_initialized_callback(
|
|
879
|
+
def set_initialized_callback(
|
|
880
|
+
self, callback: Callable[[], Awaitable[None]]
|
|
881
|
+
) -> Callable[[], None]:
|
|
869
882
|
"""Sets the callback used when done initializing the module."""
|
|
870
883
|
self._initialized_callback = callback
|
|
884
|
+
return self._remove_init_callback
|
|
885
|
+
|
|
886
|
+
def _remove_init_callback(self) -> None:
|
|
887
|
+
self._initialized_callback = None
|
|
888
|
+
|
|
889
|
+
def set_error_callback(
|
|
890
|
+
self, error_callback: Callable[[Exception], None]
|
|
891
|
+
) -> Callable[[], None]:
|
|
892
|
+
"""Register a handler for asynchronous hardware errors."""
|
|
893
|
+
self._error_callback = error_callback
|
|
894
|
+
return self._remove_error_callback
|
|
895
|
+
|
|
896
|
+
def _remove_error_callback(self) -> None:
|
|
897
|
+
self._error_callback = None
|
|
871
898
|
|
|
872
899
|
async def read(self) -> None:
|
|
873
900
|
await self.get_door_closed()
|
|
@@ -942,6 +969,8 @@ class FlexStackerReader(Reader):
|
|
|
942
969
|
if exception is None:
|
|
943
970
|
self.error = None
|
|
944
971
|
else:
|
|
972
|
+
if self._error_callback:
|
|
973
|
+
self._error_callback(exception)
|
|
945
974
|
try:
|
|
946
975
|
self.error = str(exception.args[0])
|
|
947
976
|
except Exception:
|