opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a8__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/_version.py +2 -2
- opentrons/cli/analyze.py +4 -1
- opentrons/config/__init__.py +7 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +126 -49
- opentrons/drivers/heater_shaker/abstract.py +5 -0
- opentrons/drivers/heater_shaker/driver.py +10 -0
- opentrons/drivers/heater_shaker/simulator.py +4 -0
- opentrons/drivers/thermocycler/abstract.py +6 -0
- opentrons/drivers/thermocycler/driver.py +61 -10
- opentrons/drivers/thermocycler/simulator.py +6 -0
- opentrons/drivers/vacuum_module/__init__.py +5 -0
- opentrons/drivers/vacuum_module/abstract.py +93 -0
- opentrons/drivers/vacuum_module/driver.py +208 -0
- opentrons/drivers/vacuum_module/errors.py +39 -0
- opentrons/drivers/vacuum_module/simulator.py +85 -0
- opentrons/drivers/vacuum_module/types.py +79 -0
- opentrons/execute.py +3 -0
- opentrons/hardware_control/api.py +24 -5
- opentrons/hardware_control/backends/controller.py +8 -2
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +35 -2
- opentrons/hardware_control/backends/ot3simulator.py +3 -1
- opentrons/hardware_control/backends/ot3utils.py +37 -0
- opentrons/hardware_control/backends/simulator.py +2 -1
- opentrons/hardware_control/backends/subsystem_manager.py +5 -2
- opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
- opentrons/hardware_control/emulation/connection_handler.py +8 -5
- opentrons/hardware_control/emulation/heater_shaker.py +12 -3
- opentrons/hardware_control/emulation/settings.py +1 -1
- opentrons/hardware_control/emulation/thermocycler.py +67 -15
- opentrons/hardware_control/module_control.py +105 -10
- 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 +42 -5
- opentrons/hardware_control/modules/magdeck.py +8 -4
- opentrons/hardware_control/modules/mod_abc.py +14 -6
- opentrons/hardware_control/modules/tempdeck.py +25 -5
- opentrons/hardware_control/modules/thermocycler.py +68 -11
- opentrons/hardware_control/modules/types.py +20 -1
- opentrons/hardware_control/modules/utils.py +11 -4
- opentrons/hardware_control/motion_utilities.py +6 -6
- opentrons/hardware_control/nozzle_manager.py +3 -0
- opentrons/hardware_control/ot3api.py +92 -17
- opentrons/hardware_control/poller.py +22 -8
- opentrons/hardware_control/protocols/liquid_handler.py +12 -4
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +43 -2
- opentrons/legacy_commands/commands.py +58 -5
- opentrons/legacy_commands/module_commands.py +52 -0
- opentrons/legacy_commands/protocol_commands.py +53 -1
- opentrons/legacy_commands/types.py +155 -1
- opentrons/motion_planning/deck_conflict.py +17 -12
- opentrons/motion_planning/waypoints.py +15 -29
- opentrons/protocol_api/__init__.py +5 -1
- opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
- opentrons/protocol_api/_types.py +8 -1
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/_default_labware_versions.py +33 -11
- opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
- opentrons/protocol_api/core/engine/instrument.py +109 -26
- opentrons/protocol_api/core/engine/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +95 -4
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +4 -18
- opentrons/protocol_api/core/engine/protocol.py +51 -2
- opentrons/protocol_api/core/engine/stringify.py +2 -0
- opentrons/protocol_api/core/engine/tasks.py +48 -0
- opentrons/protocol_api/core/engine/well.py +8 -0
- opentrons/protocol_api/core/instrument.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
- opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
- opentrons/protocol_api/core/legacy/tasks.py +19 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
- 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 +58 -2
- opentrons/protocol_api/core/protocol.py +23 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/core/well.py +4 -0
- opentrons/protocol_api/instrument_context.py +388 -2
- opentrons/protocol_api/labware.py +10 -2
- opentrons/protocol_api/module_contexts.py +170 -6
- opentrons/protocol_api/protocol_context.py +87 -21
- opentrons/protocol_api/robot_context.py +41 -25
- opentrons/protocol_api/tasks.py +48 -0
- opentrons/protocol_api/validation.py +49 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/__init__.py +6 -2
- opentrons/protocol_engine/actions/actions.py +31 -9
- opentrons/protocol_engine/clients/sync_client.py +42 -7
- opentrons/protocol_engine/commands/__init__.py +56 -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/absorbance_reader/read.py +22 -23
- opentrons/protocol_engine/commands/aspirate.py +1 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
- opentrons/protocol_engine/commands/capture_image.py +302 -0
- opentrons/protocol_engine/commands/command.py +2 -0
- opentrons/protocol_engine/commands/command_unions.py +62 -0
- opentrons/protocol_engine/commands/create_timer.py +83 -0
- opentrons/protocol_engine/commands/dispense.py +1 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
- opentrons/protocol_engine/commands/drop_tip.py +32 -8
- opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
- opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
- opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
- opentrons/protocol_engine/commands/move_labware.py +3 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
- opentrons/protocol_engine/commands/movement_common.py +31 -2
- opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
- opentrons/protocol_engine/commands/pipetting_common.py +48 -3
- opentrons/protocol_engine/commands/set_tip_state.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
- opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -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 +44 -7
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
- opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
- 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/create_protocol_engine.py +12 -0
- opentrons/protocol_engine/engine_support.py +3 -0
- opentrons/protocol_engine/errors/__init__.py +12 -0
- opentrons/protocol_engine/errors/exceptions.py +119 -0
- opentrons/protocol_engine/execution/__init__.py +4 -0
- opentrons/protocol_engine/execution/command_executor.py +62 -1
- opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
- opentrons/protocol_engine/execution/labware_movement.py +13 -15
- opentrons/protocol_engine/execution/movement.py +2 -0
- opentrons/protocol_engine/execution/pipetting.py +26 -25
- 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 +137 -36
- opentrons/protocol_engine/resources/__init__.py +4 -0
- opentrons/protocol_engine/resources/camera_provider.py +110 -0
- opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
- opentrons/protocol_engine/resources/file_provider.py +133 -58
- opentrons/protocol_engine/resources/labware_validation.py +10 -6
- opentrons/protocol_engine/slot_standardization.py +2 -0
- opentrons/protocol_engine/state/_well_math.py +60 -18
- opentrons/protocol_engine/state/addressable_areas.py +2 -0
- opentrons/protocol_engine/state/camera.py +54 -0
- opentrons/protocol_engine/state/commands.py +37 -14
- opentrons/protocol_engine/state/geometry.py +276 -379
- opentrons/protocol_engine/state/labware.py +62 -108
- opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
- opentrons/protocol_engine/state/modules.py +30 -8
- opentrons/protocol_engine/state/motion.py +60 -18
- opentrons/protocol_engine/state/preconditions.py +59 -0
- opentrons/protocol_engine/state/state.py +44 -0
- opentrons/protocol_engine/state/state_summary.py +4 -0
- opentrons/protocol_engine/state/tasks.py +139 -0
- opentrons/protocol_engine/state/tips.py +177 -258
- opentrons/protocol_engine/state/update_types.py +26 -9
- opentrons/protocol_engine/types/__init__.py +23 -4
- opentrons/protocol_engine/types/command_preconditions.py +18 -0
- 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/location.py +26 -2
- opentrons/protocol_engine/types/module.py +11 -1
- 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/protocol_runner.py +14 -1
- opentrons/protocol_runner/run_orchestrator.py +49 -2
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +2 -1
- opentrons/simulate.py +51 -15
- opentrons/system/camera.py +334 -4
- opentrons/system/ffmpeg.py +110 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/RECORD +189 -161
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,19 +2,21 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Optional, Mapping
|
|
5
|
+
from typing import Optional, Mapping, Callable
|
|
6
6
|
from typing_extensions import Final
|
|
7
7
|
|
|
8
8
|
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
9
9
|
from opentrons.drivers.heater_shaker.driver import HeaterShakerDriver
|
|
10
10
|
from opentrons.drivers.heater_shaker.abstract import AbstractHeaterShakerDriver
|
|
11
11
|
from opentrons.drivers.heater_shaker.simulator import SimulatingDriver
|
|
12
|
+
from opentrons.drivers.asyncio.communication.errors import UnhandledGcode
|
|
12
13
|
from opentrons.drivers.types import Temperature, RPM, HeaterShakerLabwareLatchStatus
|
|
13
14
|
from opentrons.hardware_control.execution_manager import ExecutionManager
|
|
14
15
|
from opentrons.hardware_control.poller import Reader, Poller
|
|
15
16
|
from opentrons.hardware_control.modules import mod_abc, update
|
|
16
17
|
from opentrons.hardware_control.modules.types import (
|
|
17
18
|
ModuleDisconnectedCallback,
|
|
19
|
+
ModuleErrorCallback,
|
|
18
20
|
ModuleType,
|
|
19
21
|
TemperatureStatus,
|
|
20
22
|
SpeedStatus,
|
|
@@ -47,12 +49,13 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
47
49
|
port: str,
|
|
48
50
|
usb_port: USBPort,
|
|
49
51
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
50
|
-
execution_manager:
|
|
52
|
+
execution_manager: ExecutionManager,
|
|
53
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
54
|
+
error_callback: ModuleErrorCallback,
|
|
51
55
|
poll_interval_seconds: Optional[float] = None,
|
|
52
56
|
simulating: bool = False,
|
|
53
57
|
sim_model: Optional[str] = None,
|
|
54
58
|
sim_serial_number: Optional[str] = None,
|
|
55
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
56
59
|
) -> "HeaterShaker":
|
|
57
60
|
"""
|
|
58
61
|
Build a HeaterShaker
|
|
@@ -67,6 +70,7 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
67
70
|
loop: Loop
|
|
68
71
|
sim_model: The model name used by simulator
|
|
69
72
|
disconnected_callback: Callback to inform the module controller that the device was disconnected
|
|
73
|
+
error_callback: Callback to inform the module controller of an asynchronous error
|
|
70
74
|
|
|
71
75
|
Returns:
|
|
72
76
|
HeaterShaker instance
|
|
@@ -91,6 +95,7 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
91
95
|
hw_control_loop=hw_control_loop,
|
|
92
96
|
execution_manager=execution_manager,
|
|
93
97
|
disconnected_callback=disconnected_callback,
|
|
98
|
+
error_callback=error_callback,
|
|
94
99
|
)
|
|
95
100
|
|
|
96
101
|
try:
|
|
@@ -109,8 +114,9 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
109
114
|
poller: Poller,
|
|
110
115
|
device_info: Mapping[str, str],
|
|
111
116
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
112
|
-
execution_manager:
|
|
113
|
-
disconnected_callback: ModuleDisconnectedCallback
|
|
117
|
+
execution_manager: ExecutionManager,
|
|
118
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
119
|
+
error_callback: ModuleErrorCallback,
|
|
114
120
|
):
|
|
115
121
|
super().__init__(
|
|
116
122
|
port=port,
|
|
@@ -118,14 +124,22 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
118
124
|
hw_control_loop=hw_control_loop,
|
|
119
125
|
execution_manager=execution_manager,
|
|
120
126
|
disconnected_callback=disconnected_callback,
|
|
127
|
+
error_callback=error_callback,
|
|
121
128
|
)
|
|
122
129
|
self._device_info = device_info
|
|
123
130
|
self._driver = driver
|
|
124
131
|
self._reader = reader
|
|
125
132
|
self._poller = poller
|
|
133
|
+
self._unsubscribe_reader = self._reader.register_error_handler(
|
|
134
|
+
self._handle_error
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _handle_error(self, error: Exception) -> None:
|
|
138
|
+
self.error_callback(error)
|
|
126
139
|
|
|
127
140
|
async def cleanup(self) -> None:
|
|
128
141
|
"""Stop the poller task"""
|
|
142
|
+
self._unsubscribe_reader()
|
|
129
143
|
await self._poller.stop()
|
|
130
144
|
await self._driver.disconnect()
|
|
131
145
|
|
|
@@ -397,11 +411,22 @@ class HeaterShakerReader(Reader):
|
|
|
397
411
|
self.labware_latch = HeaterShakerLabwareLatchStatus.IDLE_UNKNOWN
|
|
398
412
|
self.error: Optional[str] = None
|
|
399
413
|
self._driver = driver
|
|
414
|
+
self._handle_error: Callable[[Exception], None] | None = None
|
|
415
|
+
|
|
416
|
+
def register_error_handler(
|
|
417
|
+
self, handle_error: Callable[[Exception], None]
|
|
418
|
+
) -> Callable[[], None]:
|
|
419
|
+
self._handle_error = handle_error
|
|
420
|
+
return self._unsubscribe_error_handler
|
|
421
|
+
|
|
422
|
+
def _unsubscribe_error_handler(self) -> None:
|
|
423
|
+
self._handle_error = None
|
|
400
424
|
|
|
401
425
|
async def read(self) -> None:
|
|
402
426
|
await self.read_temperature()
|
|
403
427
|
await self.read_rpm()
|
|
404
428
|
await self.read_labware_latch()
|
|
429
|
+
await self._read_errors()
|
|
405
430
|
self._set_error(None)
|
|
406
431
|
|
|
407
432
|
def on_error(self, exception: Exception) -> None:
|
|
@@ -420,7 +445,19 @@ class HeaterShakerReader(Reader):
|
|
|
420
445
|
if exception is None:
|
|
421
446
|
self.error = None
|
|
422
447
|
else:
|
|
448
|
+
if self._handle_error:
|
|
449
|
+
self._handle_error(exception)
|
|
423
450
|
try:
|
|
424
451
|
self.error = str(exception.args[0])
|
|
425
452
|
except Exception:
|
|
426
453
|
self.error = repr(exception)
|
|
454
|
+
|
|
455
|
+
async def _read_errors(self) -> None:
|
|
456
|
+
try:
|
|
457
|
+
await self._driver.get_error_state()
|
|
458
|
+
except UnhandledGcode:
|
|
459
|
+
# This device's firmware cannot accept this command, because it
|
|
460
|
+
# hasn't been updated or because it's a gen1. Ignore the result.
|
|
461
|
+
pass
|
|
462
|
+
# If the error is one we should let pass, raise it so the top level
|
|
463
|
+
# error handler can take it.
|
|
@@ -49,12 +49,13 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
49
49
|
port: str,
|
|
50
50
|
usb_port: USBPort,
|
|
51
51
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
52
|
-
execution_manager:
|
|
52
|
+
execution_manager: ExecutionManager,
|
|
53
|
+
disconnected_callback: types.ModuleDisconnectedCallback,
|
|
54
|
+
error_callback: types.ModuleErrorCallback,
|
|
53
55
|
poll_interval_seconds: Optional[float] = None,
|
|
54
56
|
simulating: bool = False,
|
|
55
57
|
sim_model: Optional[str] = None,
|
|
56
58
|
sim_serial_number: Optional[str] = None,
|
|
57
|
-
disconnected_callback: types.ModuleDisconnectedCallback = None,
|
|
58
59
|
) -> "MagDeck":
|
|
59
60
|
"""Factory function."""
|
|
60
61
|
driver: AbstractMagDeckDriver
|
|
@@ -73,6 +74,7 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
73
74
|
device_info=await driver.get_device_info(),
|
|
74
75
|
driver=driver,
|
|
75
76
|
disconnected_callback=disconnected_callback,
|
|
77
|
+
error_callback=error_callback,
|
|
76
78
|
)
|
|
77
79
|
return mod
|
|
78
80
|
|
|
@@ -83,8 +85,9 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
83
85
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
84
86
|
driver: AbstractMagDeckDriver,
|
|
85
87
|
device_info: Dict[str, str],
|
|
86
|
-
execution_manager:
|
|
87
|
-
disconnected_callback: types.ModuleDisconnectedCallback
|
|
88
|
+
execution_manager: ExecutionManager,
|
|
89
|
+
disconnected_callback: types.ModuleDisconnectedCallback,
|
|
90
|
+
error_callback: types.ModuleErrorCallback,
|
|
88
91
|
) -> None:
|
|
89
92
|
"""Constructor"""
|
|
90
93
|
super().__init__(
|
|
@@ -93,6 +96,7 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
93
96
|
hw_control_loop=hw_control_loop,
|
|
94
97
|
execution_manager=execution_manager,
|
|
95
98
|
disconnected_callback=disconnected_callback,
|
|
99
|
+
error_callback=error_callback,
|
|
96
100
|
)
|
|
97
101
|
self._device_info = device_info
|
|
98
102
|
self._driver = driver
|
|
@@ -11,6 +11,7 @@ from ..execution_manager import ExecutionManager
|
|
|
11
11
|
from .types import (
|
|
12
12
|
BundledFirmware,
|
|
13
13
|
ModuleDisconnectedCallback,
|
|
14
|
+
ModuleErrorCallback,
|
|
14
15
|
UploadFunction,
|
|
15
16
|
LiveData,
|
|
16
17
|
ModuleType,
|
|
@@ -47,12 +48,13 @@ class AbstractModule(abc.ABC):
|
|
|
47
48
|
port: str,
|
|
48
49
|
usb_port: USBPort,
|
|
49
50
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
50
|
-
execution_manager:
|
|
51
|
-
|
|
51
|
+
execution_manager: ExecutionManager,
|
|
52
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
53
|
+
error_callback: ModuleErrorCallback,
|
|
54
|
+
poll_interval_seconds: float | None = None,
|
|
52
55
|
simulating: bool = False,
|
|
53
56
|
sim_model: Optional[str] = None,
|
|
54
57
|
sim_serial_number: Optional[str] = None,
|
|
55
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
56
58
|
) -> "AbstractModule":
|
|
57
59
|
"""Modules should always be created using this factory.
|
|
58
60
|
|
|
@@ -65,8 +67,9 @@ class AbstractModule(abc.ABC):
|
|
|
65
67
|
port: str,
|
|
66
68
|
usb_port: USBPort,
|
|
67
69
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
68
|
-
execution_manager:
|
|
69
|
-
disconnected_callback: ModuleDisconnectedCallback
|
|
70
|
+
execution_manager: ExecutionManager,
|
|
71
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
72
|
+
error_callback: ModuleErrorCallback,
|
|
70
73
|
) -> None:
|
|
71
74
|
self._port = port
|
|
72
75
|
self._usb_port = usb_port
|
|
@@ -75,6 +78,7 @@ class AbstractModule(abc.ABC):
|
|
|
75
78
|
self._bundled_fw: Optional[BundledFirmware] = self.get_bundled_fw()
|
|
76
79
|
self._disconnected_callback = disconnected_callback
|
|
77
80
|
self._updating = False
|
|
81
|
+
self._error_callback = error_callback
|
|
78
82
|
|
|
79
83
|
@staticmethod
|
|
80
84
|
def sort_key(inst: "AbstractModule") -> int:
|
|
@@ -101,7 +105,11 @@ class AbstractModule(abc.ABC):
|
|
|
101
105
|
def disconnected_callback(self) -> None:
|
|
102
106
|
"""Called from within the module object to signify the object is no longer connected"""
|
|
103
107
|
if self._disconnected_callback is not None:
|
|
104
|
-
self._disconnected_callback(self.port, self.serial_number)
|
|
108
|
+
self._disconnected_callback(self.model(), self.port, self.serial_number)
|
|
109
|
+
|
|
110
|
+
def error_callback(self, exc: Exception) -> None:
|
|
111
|
+
"""Called from within the module object when an asynchronous hardware error occurrs."""
|
|
112
|
+
self._error_callback(exc, self.model(), self.port, self.serial_number)
|
|
105
113
|
|
|
106
114
|
def get_bundled_fw(self) -> Optional[BundledFirmware]:
|
|
107
115
|
"""Get absolute path to bundled version of module fw if available."""
|
|
@@ -2,10 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Dict, Optional
|
|
5
|
+
from typing import Dict, Optional, Callable
|
|
6
6
|
|
|
7
7
|
from opentrons.hardware_control.modules.types import (
|
|
8
8
|
ModuleDisconnectedCallback,
|
|
9
|
+
ModuleErrorCallback,
|
|
9
10
|
TemperatureStatus,
|
|
10
11
|
)
|
|
11
12
|
from opentrons.hardware_control.poller import Reader, Poller
|
|
@@ -38,12 +39,13 @@ class TempDeck(mod_abc.AbstractModule):
|
|
|
38
39
|
port: str,
|
|
39
40
|
usb_port: USBPort,
|
|
40
41
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
41
|
-
execution_manager:
|
|
42
|
+
execution_manager: ExecutionManager,
|
|
43
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
44
|
+
error_callback: ModuleErrorCallback,
|
|
42
45
|
poll_interval_seconds: Optional[float] = None,
|
|
43
46
|
simulating: bool = False,
|
|
44
47
|
sim_model: Optional[str] = None,
|
|
45
48
|
sim_serial_number: Optional[str] = None,
|
|
46
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
47
49
|
) -> "TempDeck":
|
|
48
50
|
"""
|
|
49
51
|
Build a TempDeck
|
|
@@ -83,6 +85,7 @@ class TempDeck(mod_abc.AbstractModule):
|
|
|
83
85
|
device_info=await driver.get_device_info(),
|
|
84
86
|
hw_control_loop=hw_control_loop,
|
|
85
87
|
disconnected_callback=disconnected_callback,
|
|
88
|
+
error_callback=error_callback,
|
|
86
89
|
)
|
|
87
90
|
|
|
88
91
|
try:
|
|
@@ -101,8 +104,9 @@ class TempDeck(mod_abc.AbstractModule):
|
|
|
101
104
|
poller: Poller,
|
|
102
105
|
device_info: Dict[str, str],
|
|
103
106
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
104
|
-
execution_manager:
|
|
105
|
-
disconnected_callback: ModuleDisconnectedCallback
|
|
107
|
+
execution_manager: ExecutionManager,
|
|
108
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
109
|
+
error_callback: ModuleErrorCallback,
|
|
106
110
|
) -> None:
|
|
107
111
|
"""Constructor"""
|
|
108
112
|
super().__init__(
|
|
@@ -111,11 +115,13 @@ class TempDeck(mod_abc.AbstractModule):
|
|
|
111
115
|
hw_control_loop=hw_control_loop,
|
|
112
116
|
execution_manager=execution_manager,
|
|
113
117
|
disconnected_callback=disconnected_callback,
|
|
118
|
+
error_callback=error_callback,
|
|
114
119
|
)
|
|
115
120
|
self._device_info = device_info
|
|
116
121
|
self._driver = driver
|
|
117
122
|
self._reader = reader
|
|
118
123
|
self._poller = poller
|
|
124
|
+
self._reader.set_error_callback(self.error_callback)
|
|
119
125
|
|
|
120
126
|
async def cleanup(self) -> None:
|
|
121
127
|
"""Stop the poller task."""
|
|
@@ -293,7 +299,21 @@ class TempDeckReader(Reader):
|
|
|
293
299
|
def __init__(self, driver: AbstractTempDeckDriver) -> None:
|
|
294
300
|
self.temperature = Temperature(current=25, target=None)
|
|
295
301
|
self._driver = driver
|
|
302
|
+
self._error_callback: Optional[Callable[[Exception], None]] = None
|
|
296
303
|
|
|
297
304
|
async def read(self) -> None:
|
|
298
305
|
"""Read the module's current and target temperatures."""
|
|
299
306
|
self.temperature = await self._driver.get_temperature()
|
|
307
|
+
|
|
308
|
+
def set_error_callback(
|
|
309
|
+
self, error_callback: Callable[[Exception], None]
|
|
310
|
+
) -> Callable[[], None]:
|
|
311
|
+
self._error_callback = error_callback
|
|
312
|
+
return self._remove_error_callback
|
|
313
|
+
|
|
314
|
+
def _remove_error_callback(self) -> None:
|
|
315
|
+
self._error_callback = None
|
|
316
|
+
|
|
317
|
+
def on_error(self, exception: Exception) -> None:
|
|
318
|
+
if self._error_callback:
|
|
319
|
+
self._error_callback(exception)
|
|
@@ -9,6 +9,7 @@ from opentrons.hardware_control.modules.lid_temp_status import LidTemperatureSta
|
|
|
9
9
|
from opentrons.hardware_control.modules.plate_temp_status import PlateTemperatureStatus
|
|
10
10
|
from opentrons.hardware_control.modules.types import (
|
|
11
11
|
ModuleDisconnectedCallback,
|
|
12
|
+
ModuleErrorCallback,
|
|
12
13
|
TemperatureStatus,
|
|
13
14
|
)
|
|
14
15
|
from opentrons.hardware_control.poller import Reader, Poller
|
|
@@ -21,6 +22,7 @@ from opentrons.drivers.thermocycler import (
|
|
|
21
22
|
ThermocyclerDriverV2,
|
|
22
23
|
ThermocyclerDriverFactory,
|
|
23
24
|
)
|
|
25
|
+
from opentrons.drivers.asyncio.communication.errors import UnhandledGcode
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
log = logging.getLogger(__name__)
|
|
@@ -36,6 +38,8 @@ DFU_PID = "df11"
|
|
|
36
38
|
_TC_PLATE_LIFT_OPEN_DEGREES = 20
|
|
37
39
|
_TC_PLATE_LIFT_RETURN_DEGREES = 23
|
|
38
40
|
|
|
41
|
+
_TC_RAMP_RATE_ADDED_VERSION = (1, 0, 8) # v1.0.8
|
|
42
|
+
|
|
39
43
|
|
|
40
44
|
class ThermocyclerError(Exception):
|
|
41
45
|
pass
|
|
@@ -62,12 +66,13 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
62
66
|
port: str,
|
|
63
67
|
usb_port: USBPort,
|
|
64
68
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
65
|
-
execution_manager:
|
|
69
|
+
execution_manager: ExecutionManager,
|
|
70
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
71
|
+
error_callback: ModuleErrorCallback,
|
|
66
72
|
poll_interval_seconds: Optional[float] = None,
|
|
67
73
|
simulating: bool = False,
|
|
68
74
|
sim_model: Optional[str] = None,
|
|
69
75
|
sim_serial_number: Optional[str] = None,
|
|
70
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
71
76
|
) -> "Thermocycler":
|
|
72
77
|
"""
|
|
73
78
|
Build and connect to a Thermocycler
|
|
@@ -108,6 +113,7 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
108
113
|
hw_control_loop=hw_control_loop,
|
|
109
114
|
execution_manager=execution_manager,
|
|
110
115
|
disconnected_callback=disconnected_callback,
|
|
116
|
+
error_callback=error_callback,
|
|
111
117
|
)
|
|
112
118
|
|
|
113
119
|
try:
|
|
@@ -126,8 +132,9 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
126
132
|
poller: Poller,
|
|
127
133
|
device_info: Dict[str, str],
|
|
128
134
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
129
|
-
execution_manager:
|
|
130
|
-
disconnected_callback: ModuleDisconnectedCallback
|
|
135
|
+
execution_manager: ExecutionManager,
|
|
136
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
137
|
+
error_callback: ModuleErrorCallback,
|
|
131
138
|
) -> None:
|
|
132
139
|
"""
|
|
133
140
|
Constructor
|
|
@@ -150,6 +157,7 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
150
157
|
hw_control_loop=hw_control_loop,
|
|
151
158
|
execution_manager=execution_manager,
|
|
152
159
|
disconnected_callback=disconnected_callback,
|
|
160
|
+
error_callback=error_callback,
|
|
153
161
|
)
|
|
154
162
|
self._device_info = device_info
|
|
155
163
|
self._reader = reader
|
|
@@ -159,10 +167,13 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
159
167
|
self._total_step_count: Optional[int] = None
|
|
160
168
|
self._current_step_index: Optional[int] = None
|
|
161
169
|
self._error: Optional[str] = None
|
|
162
|
-
self._reader.register_error_handler(
|
|
170
|
+
self._unsubscribe_reader = self._reader.register_error_handler(
|
|
171
|
+
self._enter_error_state
|
|
172
|
+
)
|
|
163
173
|
|
|
164
174
|
async def cleanup(self) -> None:
|
|
165
175
|
"""Stop the poller task."""
|
|
176
|
+
self._unsubscribe_reader()
|
|
166
177
|
await self._poller.stop()
|
|
167
178
|
await self._driver.disconnect()
|
|
168
179
|
|
|
@@ -265,6 +276,19 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
265
276
|
await self.open()
|
|
266
277
|
await self._wait_for_lid_status(ThermocyclerLidStatus.OPEN)
|
|
267
278
|
|
|
279
|
+
def can_use_ramp_rate(self) -> bool:
|
|
280
|
+
version_string = self._device_info.get("version", "v")
|
|
281
|
+
if version_string.startswith("v"):
|
|
282
|
+
version_string = version_string[1:]
|
|
283
|
+
try:
|
|
284
|
+
version_tuple = tuple(int(c) for c in version_string.split("."))
|
|
285
|
+
return version_tuple >= _TC_RAMP_RATE_ADDED_VERSION
|
|
286
|
+
except (ValueError, IndexError):
|
|
287
|
+
log.error(
|
|
288
|
+
f"Invalid version from device: {self._device_info.get('version', '')}"
|
|
289
|
+
)
|
|
290
|
+
return False
|
|
291
|
+
|
|
268
292
|
async def set_temperature(
|
|
269
293
|
self,
|
|
270
294
|
temperature: float,
|
|
@@ -290,6 +314,11 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
290
314
|
|
|
291
315
|
Returns: None
|
|
292
316
|
"""
|
|
317
|
+
if ramp_rate and not self.can_use_ramp_rate():
|
|
318
|
+
raise ThermocyclerError(
|
|
319
|
+
"Ramp rate is not supported by this thermocycler's firmware version, please update."
|
|
320
|
+
)
|
|
321
|
+
|
|
293
322
|
await self.wait_for_is_running()
|
|
294
323
|
await self._set_temperature_no_pause(
|
|
295
324
|
temperature=temperature,
|
|
@@ -312,11 +341,13 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
312
341
|
total_seconds = seconds + (minutes * 60)
|
|
313
342
|
hold_time = total_seconds if total_seconds > 0 else 0
|
|
314
343
|
|
|
315
|
-
if ramp_rate
|
|
316
|
-
|
|
344
|
+
if ramp_rate and not self.can_use_ramp_rate():
|
|
345
|
+
raise ThermocyclerError(
|
|
346
|
+
"Ramp rate is not supported by this thermocycler's firmware version, please update."
|
|
347
|
+
)
|
|
317
348
|
|
|
318
349
|
await self._driver.set_plate_temperature(
|
|
319
|
-
temp=temperature, hold_time=hold_time, volume=volume
|
|
350
|
+
temp=temperature, hold_time=hold_time, volume=volume, ramp_rate=ramp_rate
|
|
320
351
|
)
|
|
321
352
|
|
|
322
353
|
task = self._loop.create_task(self._wait_for_block_target())
|
|
@@ -419,6 +450,7 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
419
450
|
celsius: float,
|
|
420
451
|
hold_time_seconds: Optional[float] = None,
|
|
421
452
|
volume: Optional[float] = None,
|
|
453
|
+
ramp_rate: Optional[float] = None,
|
|
422
454
|
) -> None:
|
|
423
455
|
"""Set the Thermocycler's target block temperature.
|
|
424
456
|
|
|
@@ -428,10 +460,17 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
428
460
|
celsius: The target block temperature, in degrees celsius.
|
|
429
461
|
"""
|
|
430
462
|
await self.wait_for_is_running()
|
|
463
|
+
|
|
464
|
+
if ramp_rate and not self.can_use_ramp_rate():
|
|
465
|
+
raise ThermocyclerError(
|
|
466
|
+
"Ramp rate is not supported by this thermocycler's firmware version, please update."
|
|
467
|
+
)
|
|
468
|
+
|
|
431
469
|
await self._driver.set_plate_temperature(
|
|
432
470
|
temp=celsius,
|
|
433
471
|
hold_time=hold_time_seconds,
|
|
434
472
|
volume=volume,
|
|
473
|
+
ramp_rate=ramp_rate,
|
|
435
474
|
)
|
|
436
475
|
await self._reader.read_block_temperature()
|
|
437
476
|
|
|
@@ -596,11 +635,12 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
596
635
|
temperature = step.get("temperature")
|
|
597
636
|
hold_time_minutes = step.get("hold_time_minutes", None)
|
|
598
637
|
hold_time_seconds = step.get("hold_time_seconds", None)
|
|
638
|
+
ramp_rate = step.get("ramp_rate", None)
|
|
599
639
|
await self._set_temperature_no_pause(
|
|
600
640
|
temperature=temperature, # type: ignore
|
|
601
641
|
hold_time_minutes=hold_time_minutes,
|
|
602
642
|
hold_time_seconds=hold_time_seconds,
|
|
603
|
-
ramp_rate=
|
|
643
|
+
ramp_rate=ramp_rate,
|
|
604
644
|
volume=volume,
|
|
605
645
|
)
|
|
606
646
|
|
|
@@ -672,7 +712,7 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
672
712
|
f"https://support.opentrons.com/en/articles/3469797-thermocycler-module"
|
|
673
713
|
f" for troubleshooting."
|
|
674
714
|
)
|
|
675
|
-
|
|
715
|
+
self.error_callback(error)
|
|
676
716
|
|
|
677
717
|
|
|
678
718
|
class ThermocyclerReader(Reader):
|
|
@@ -710,14 +750,31 @@ class ThermocyclerReader(Reader):
|
|
|
710
750
|
if self._handle_error is not None:
|
|
711
751
|
self._handle_error(exception)
|
|
712
752
|
|
|
713
|
-
def register_error_handler(
|
|
753
|
+
def register_error_handler(
|
|
754
|
+
self, handle_error: Callable[[Exception], None]
|
|
755
|
+
) -> Callable[[], None]:
|
|
714
756
|
self._handle_error = handle_error
|
|
757
|
+
return self._unsubscribe_error_handler
|
|
758
|
+
|
|
759
|
+
def _unsubscribe_error_handler(self) -> None:
|
|
760
|
+
self._handle_error = None
|
|
715
761
|
|
|
716
762
|
async def read(self) -> None:
|
|
717
763
|
"""Poll the thermocycler."""
|
|
718
764
|
await self.read_lid_status()
|
|
719
765
|
await self.read_lid_temperature()
|
|
720
766
|
await self.read_block_temperature()
|
|
767
|
+
await self._read_errors()
|
|
768
|
+
|
|
769
|
+
async def _read_errors(self) -> None:
|
|
770
|
+
try:
|
|
771
|
+
await self._driver.get_error_state()
|
|
772
|
+
except UnhandledGcode:
|
|
773
|
+
# This device's firmware cannot accept this command, because it
|
|
774
|
+
# hasn't been updated or because it's a gen1. Ignore the result.
|
|
775
|
+
pass
|
|
776
|
+
# If the error is one we should let pass, raise it so the top level
|
|
777
|
+
# error handler can take it.
|
|
721
778
|
|
|
722
779
|
async def read_lid_status(self) -> None:
|
|
723
780
|
self.lid_status = await self._driver.get_lid_status()
|
|
@@ -11,6 +11,7 @@ from typing import (
|
|
|
11
11
|
Awaitable,
|
|
12
12
|
Union,
|
|
13
13
|
Optional,
|
|
14
|
+
Protocol,
|
|
14
15
|
cast,
|
|
15
16
|
TYPE_CHECKING,
|
|
16
17
|
TypeGuard,
|
|
@@ -44,6 +45,7 @@ class ThermocyclerStepBase(TypedDict):
|
|
|
44
45
|
class ThermocyclerStep(ThermocyclerStepBase, total=False):
|
|
45
46
|
hold_time_seconds: float
|
|
46
47
|
hold_time_minutes: float
|
|
48
|
+
ramp_rate: Optional[float]
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
class ThermocyclerCycle(TypedDict):
|
|
@@ -54,7 +56,24 @@ class ThermocyclerCycle(TypedDict):
|
|
|
54
56
|
UploadFunction = Callable[[str, str, Dict[str, Any]], Awaitable[Tuple[bool, str]]]
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
ModuleDisconnectedCallback
|
|
59
|
+
class ModuleDisconnectedCallback(Protocol):
|
|
60
|
+
"""Protocol for the callback when the module should be disconnected."""
|
|
61
|
+
|
|
62
|
+
def __call__(self, model: str, port: str, serial: str | None) -> None:
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ModuleErrorCallback(Protocol):
|
|
67
|
+
"""Protocol for the callback when the module sees a hardware error."""
|
|
68
|
+
|
|
69
|
+
def __call__(
|
|
70
|
+
self,
|
|
71
|
+
exc: Exception,
|
|
72
|
+
model: str,
|
|
73
|
+
port: str,
|
|
74
|
+
serial: str | None,
|
|
75
|
+
) -> None:
|
|
76
|
+
...
|
|
58
77
|
|
|
59
78
|
|
|
60
79
|
class MagneticModuleData(TypedDict):
|
|
@@ -6,7 +6,12 @@ from opentrons.drivers.rpi_drivers.types import USBPort
|
|
|
6
6
|
|
|
7
7
|
from ..execution_manager import ExecutionManager
|
|
8
8
|
|
|
9
|
-
from .types import
|
|
9
|
+
from .types import (
|
|
10
|
+
ModuleDisconnectedCallback,
|
|
11
|
+
ModuleType,
|
|
12
|
+
SpeedStatus,
|
|
13
|
+
ModuleErrorCallback,
|
|
14
|
+
)
|
|
10
15
|
from .mod_abc import AbstractModule
|
|
11
16
|
from .tempdeck import TempDeck
|
|
12
17
|
from .magdeck import MagDeck
|
|
@@ -46,10 +51,11 @@ async def build(
|
|
|
46
51
|
simulating: bool,
|
|
47
52
|
usb_port: USBPort,
|
|
48
53
|
hw_control_loop: asyncio.AbstractEventLoop,
|
|
49
|
-
execution_manager:
|
|
54
|
+
execution_manager: ExecutionManager,
|
|
55
|
+
disconnected_callback: ModuleDisconnectedCallback,
|
|
56
|
+
error_callback: ModuleErrorCallback,
|
|
50
57
|
sim_model: Optional[str] = None,
|
|
51
58
|
sim_serial_number: Optional[str] = None,
|
|
52
|
-
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
53
59
|
) -> AbstractModule:
|
|
54
60
|
return await _MODULE_CLS_BY_TYPE[type].build(
|
|
55
61
|
port=port,
|
|
@@ -57,9 +63,10 @@ async def build(
|
|
|
57
63
|
simulating=simulating,
|
|
58
64
|
hw_control_loop=hw_control_loop,
|
|
59
65
|
execution_manager=execution_manager,
|
|
66
|
+
disconnected_callback=disconnected_callback,
|
|
67
|
+
error_callback=error_callback,
|
|
60
68
|
sim_model=sim_model,
|
|
61
69
|
sim_serial_number=sim_serial_number,
|
|
62
|
-
disconnected_callback=disconnected_callback,
|
|
63
70
|
)
|
|
64
71
|
|
|
65
72
|
|
|
@@ -195,8 +195,7 @@ def target_position_from_plunger(
|
|
|
195
195
|
def target_positions_from_plunger_tracking(
|
|
196
196
|
mount: Union[Mount, OT3Mount],
|
|
197
197
|
plunger_delta: float,
|
|
198
|
-
|
|
199
|
-
current_position: Dict[Axis, float],
|
|
198
|
+
end_position: OrderedDict[Axis, float],
|
|
200
199
|
) -> "OrderedDict[Axis, float]":
|
|
201
200
|
"""Create a target position for machine axes including plungers for dynamic liquid tracking.
|
|
202
201
|
|
|
@@ -206,10 +205,11 @@ def target_positions_from_plunger_tracking(
|
|
|
206
205
|
volume to aspirate/dispense.
|
|
207
206
|
z_delta: the distance to move the z axis- should be determined based on volume and well geometry.
|
|
208
207
|
"""
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
208
|
+
plunger_pos = OrderedDict()
|
|
209
|
+
plunger = Axis.of_main_tool_actuator(mount)
|
|
210
|
+
plunger_pos[plunger] = plunger_delta
|
|
211
|
+
end_position.update(plunger_pos)
|
|
212
|
+
return end_position
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
def deck_point_from_machine_point(
|
|
@@ -77,6 +77,8 @@ class NozzleMap:
|
|
|
77
77
|
#: A map of all of the nozzles of an instrument
|
|
78
78
|
full_instrument_rows: Dict[str, List[str]]
|
|
79
79
|
#: A map of all the rows of an instrument
|
|
80
|
+
full_instrument_columns: Dict[str, List[str]]
|
|
81
|
+
#: A map of all the columns of an instrument
|
|
80
82
|
|
|
81
83
|
@classmethod
|
|
82
84
|
def determine_nozzle_configuration(
|
|
@@ -299,6 +301,7 @@ class NozzleMap:
|
|
|
299
301
|
rows=rows,
|
|
300
302
|
full_instrument_map_store=physical_nozzles,
|
|
301
303
|
full_instrument_rows=physical_rows,
|
|
304
|
+
full_instrument_columns=physical_columns,
|
|
302
305
|
columns=columns,
|
|
303
306
|
configuration=cls.determine_nozzle_configuration(
|
|
304
307
|
physical_rows, rows, physical_columns, columns
|