opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.0a1__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/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +19 -3
- opentrons/legacy_commands/helpers.py +15 -0
- opentrons/legacy_commands/types.py +3 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1233 -65
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
- opentrons/protocol_api/core/engine/well.py +60 -5
- opentrons/protocol_api/core/instrument.py +65 -19
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +21 -1
- opentrons/protocol_api/instrument_context.py +246 -123
- opentrons/protocol_api/labware.py +75 -11
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +156 -16
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +30 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +69 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
- opentrons/protocol_engine/commands/liquid_probe.py +21 -12
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +154 -7
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +233 -26
- opentrons/protocol_engine/execution/tip_handler.py +14 -5
- opentrons/protocol_engine/labware_offset_standardization.py +173 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +26 -10
- opentrons/protocol_engine/state/geometry.py +683 -100
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +178 -52
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +54 -8
- opentrons/protocol_engine/types/__init__.py +292 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +110 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +108 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
- opentrons/protocol_engine/types/location.py +193 -0
- opentrons/protocol_engine/types/module.py +269 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +107 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +5 -6
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +15 -6
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Dict, Optional, Mapping
|
|
6
|
+
|
|
7
|
+
from opentrons.drivers.flex_stacker.types import (
|
|
8
|
+
Direction,
|
|
9
|
+
LEDColor,
|
|
10
|
+
LEDPattern,
|
|
11
|
+
MoveParams,
|
|
12
|
+
MoveResult,
|
|
13
|
+
StackerAxis,
|
|
14
|
+
)
|
|
15
|
+
from opentrons.drivers.rpi_drivers.types import USBPort
|
|
16
|
+
from opentrons.drivers.flex_stacker.driver import (
|
|
17
|
+
STACKER_MOTION_CONFIG,
|
|
18
|
+
STALLGUARD_CONFIG,
|
|
19
|
+
FlexStackerDriver,
|
|
20
|
+
)
|
|
21
|
+
from opentrons.drivers.flex_stacker.abstract import AbstractFlexStackerDriver
|
|
22
|
+
from opentrons.drivers.flex_stacker.simulator import SimulatingDriver
|
|
23
|
+
from opentrons.hardware_control.execution_manager import ExecutionManager
|
|
24
|
+
from opentrons.hardware_control.poller import Reader, Poller
|
|
25
|
+
from opentrons.hardware_control.modules import mod_abc, update
|
|
26
|
+
from opentrons.hardware_control.modules.types import (
|
|
27
|
+
FlexStackerStatus,
|
|
28
|
+
HopperDoorState,
|
|
29
|
+
LatchState,
|
|
30
|
+
ModuleDisconnectedCallback,
|
|
31
|
+
ModuleType,
|
|
32
|
+
PlatformState,
|
|
33
|
+
StackerAxisState,
|
|
34
|
+
UploadFunction,
|
|
35
|
+
LiveData,
|
|
36
|
+
FlexStackerData,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from opentrons_shared_data.errors.exceptions import FlexStackerStallError
|
|
40
|
+
|
|
41
|
+
log = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
POLL_PERIOD = 1.0
|
|
44
|
+
SIMULATING_POLL_PERIOD = POLL_PERIOD / 20.0
|
|
45
|
+
|
|
46
|
+
DFU_PID = "df11"
|
|
47
|
+
|
|
48
|
+
# Maximum distance in mm the axis can travel.
|
|
49
|
+
MAX_TRAVEL = {
|
|
50
|
+
StackerAxis.X: 194.0,
|
|
51
|
+
StackerAxis.Z: 139.5,
|
|
52
|
+
StackerAxis.L: 22.0,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# The offset in mm to subtract from MAX_TRAVEL when moving an axis before we home.
|
|
56
|
+
# This lets us use `move_axis` to move fast, leaving the axis OFFSET mm
|
|
57
|
+
# from the limit switch. Then we can use `home_axis` to move the axis the rest
|
|
58
|
+
# of the way until we trigger the expected limit switch.
|
|
59
|
+
OFFSET_SM = 5.0
|
|
60
|
+
OFFSET_MD = 10.0
|
|
61
|
+
OFFSET_LG = 20.0
|
|
62
|
+
|
|
63
|
+
# height limit in mm of labware to use OFFSET_MD used when storing labware.
|
|
64
|
+
MEDIUM_LABWARE_Z_LIMIT = 20.0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FlexStacker(mod_abc.AbstractModule):
|
|
68
|
+
"""Hardware control interface for an attached Flex-Stacker module."""
|
|
69
|
+
|
|
70
|
+
MODULE_TYPE = ModuleType.FLEX_STACKER
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
async def build(
|
|
74
|
+
cls,
|
|
75
|
+
port: str,
|
|
76
|
+
usb_port: USBPort,
|
|
77
|
+
execution_manager: ExecutionManager,
|
|
78
|
+
hw_control_loop: asyncio.AbstractEventLoop,
|
|
79
|
+
poll_interval_seconds: Optional[float] = None,
|
|
80
|
+
simulating: bool = False,
|
|
81
|
+
sim_model: Optional[str] = None,
|
|
82
|
+
sim_serial_number: Optional[str] = None,
|
|
83
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
84
|
+
) -> "FlexStacker":
|
|
85
|
+
"""
|
|
86
|
+
Build a FlexStacker
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
port: The port to connect to
|
|
90
|
+
usb_port: USB Port
|
|
91
|
+
execution_manager: Execution manager.
|
|
92
|
+
hw_control_loop: The event loop running in the hardware control thread.
|
|
93
|
+
poll_interval_seconds: Poll interval override.
|
|
94
|
+
simulating: whether to build a simulating driver
|
|
95
|
+
loop: Loop
|
|
96
|
+
sim_model: The model name used by simulator
|
|
97
|
+
disconnected_callback: Callback to inform the module controller that the device was disconnected
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
FlexStacker instance
|
|
101
|
+
"""
|
|
102
|
+
driver: AbstractFlexStackerDriver
|
|
103
|
+
if not simulating:
|
|
104
|
+
driver = await FlexStackerDriver.create(port=port, loop=hw_control_loop)
|
|
105
|
+
poll_interval_seconds = poll_interval_seconds or POLL_PERIOD
|
|
106
|
+
else:
|
|
107
|
+
driver = SimulatingDriver(serial_number=sim_serial_number)
|
|
108
|
+
poll_interval_seconds = poll_interval_seconds or SIMULATING_POLL_PERIOD
|
|
109
|
+
|
|
110
|
+
reader = FlexStackerReader(driver=driver)
|
|
111
|
+
poller = Poller(reader=reader, interval=poll_interval_seconds)
|
|
112
|
+
module = cls(
|
|
113
|
+
port=port,
|
|
114
|
+
usb_port=usb_port,
|
|
115
|
+
driver=driver,
|
|
116
|
+
reader=reader,
|
|
117
|
+
poller=poller,
|
|
118
|
+
device_info=(await driver.get_device_info()).to_dict(),
|
|
119
|
+
hw_control_loop=hw_control_loop,
|
|
120
|
+
execution_manager=execution_manager,
|
|
121
|
+
disconnected_callback=disconnected_callback,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Enable stallguard
|
|
125
|
+
for axis, config in STALLGUARD_CONFIG.items():
|
|
126
|
+
await driver.set_stallguard_threshold(
|
|
127
|
+
axis, config.enabled, config.threshold
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
await poller.start()
|
|
132
|
+
except Exception:
|
|
133
|
+
log.exception(f"First read of Flex-Stacker on port {port} failed")
|
|
134
|
+
|
|
135
|
+
return module
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
port: str,
|
|
140
|
+
usb_port: USBPort,
|
|
141
|
+
execution_manager: ExecutionManager,
|
|
142
|
+
driver: AbstractFlexStackerDriver,
|
|
143
|
+
reader: FlexStackerReader,
|
|
144
|
+
poller: Poller,
|
|
145
|
+
device_info: Mapping[str, str],
|
|
146
|
+
hw_control_loop: asyncio.AbstractEventLoop,
|
|
147
|
+
disconnected_callback: ModuleDisconnectedCallback = None,
|
|
148
|
+
):
|
|
149
|
+
super().__init__(
|
|
150
|
+
port=port,
|
|
151
|
+
usb_port=usb_port,
|
|
152
|
+
hw_control_loop=hw_control_loop,
|
|
153
|
+
execution_manager=execution_manager,
|
|
154
|
+
disconnected_callback=disconnected_callback,
|
|
155
|
+
)
|
|
156
|
+
self._device_info = device_info
|
|
157
|
+
self._driver = driver
|
|
158
|
+
self._reader = reader
|
|
159
|
+
self._poller = poller
|
|
160
|
+
self._stacker_status = FlexStackerStatus.IDLE
|
|
161
|
+
self._stall_detected = False
|
|
162
|
+
|
|
163
|
+
async def cleanup(self) -> None:
|
|
164
|
+
"""Stop the poller task"""
|
|
165
|
+
await self._poller.stop()
|
|
166
|
+
await self._driver.disconnect()
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def name(cls) -> str:
|
|
170
|
+
"""Used for picking up serial port symlinks"""
|
|
171
|
+
return "flexstacker"
|
|
172
|
+
|
|
173
|
+
def firmware_prefix(self) -> str:
|
|
174
|
+
"""The prefix used for looking up firmware"""
|
|
175
|
+
return "flex-stacker"
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def _model_from_revision(revision: Optional[str]) -> str:
|
|
179
|
+
"""Defines the revision -> model mapping"""
|
|
180
|
+
return "flexStackerModuleV1"
|
|
181
|
+
|
|
182
|
+
def model(self) -> str:
|
|
183
|
+
return self._model_from_revision(self._device_info.get("model"))
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def latch_state(self) -> LatchState:
|
|
187
|
+
"""The state of the latch."""
|
|
188
|
+
return LatchState.from_state(self.limit_switch_status[StackerAxis.L])
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def platform_state(self) -> PlatformState:
|
|
192
|
+
"""The state of the platform."""
|
|
193
|
+
return self._reader.platform_state
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def hopper_door_state(self) -> HopperDoorState:
|
|
197
|
+
"""The status of the hopper door."""
|
|
198
|
+
return HopperDoorState.from_state(self._reader.hopper_door_closed)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def limit_switch_status(self) -> Dict[StackerAxis, StackerAxisState]:
|
|
202
|
+
"""The status of the Limit switches."""
|
|
203
|
+
return self._reader.limit_switch_status
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def device_info(self) -> Mapping[str, str]:
|
|
207
|
+
return self._device_info
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def status(self) -> FlexStackerStatus:
|
|
211
|
+
"""Module status or error state details."""
|
|
212
|
+
return self._stacker_status
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def is_simulated(self) -> bool:
|
|
216
|
+
return isinstance(self._driver, SimulatingDriver)
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def live_data(self) -> LiveData:
|
|
220
|
+
data: FlexStackerData = {
|
|
221
|
+
"latchState": self.latch_state.value,
|
|
222
|
+
"platformState": self.platform_state.value,
|
|
223
|
+
"hopperDoorState": self.hopper_door_state.value,
|
|
224
|
+
"axisStateX": self.limit_switch_status[StackerAxis.X].value,
|
|
225
|
+
"axisStateZ": self.limit_switch_status[StackerAxis.Z].value,
|
|
226
|
+
"errorDetails": self._reader.error,
|
|
227
|
+
}
|
|
228
|
+
return {"status": self.status.value, "data": data}
|
|
229
|
+
|
|
230
|
+
async def prep_for_update(self) -> str:
|
|
231
|
+
await self._poller.stop()
|
|
232
|
+
await self._driver.stop_motors()
|
|
233
|
+
await self._driver.enter_programming_mode()
|
|
234
|
+
# flex stacker has three unique "devices" over DFU
|
|
235
|
+
dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=3)
|
|
236
|
+
return dfu_info
|
|
237
|
+
|
|
238
|
+
def bootloader(self) -> UploadFunction:
|
|
239
|
+
return update.upload_via_dfu
|
|
240
|
+
|
|
241
|
+
async def deactivate(self, must_be_running: bool = True) -> None:
|
|
242
|
+
await self._driver.stop_motors()
|
|
243
|
+
|
|
244
|
+
async def reset_stall_detected(self) -> None:
|
|
245
|
+
"""Sets the statusbar to normal."""
|
|
246
|
+
if self._stall_detected:
|
|
247
|
+
await self.set_led_state(0.5, LEDColor.GREEN, LEDPattern.STATIC)
|
|
248
|
+
self._stall_detected = False
|
|
249
|
+
|
|
250
|
+
async def set_led_state(
|
|
251
|
+
self,
|
|
252
|
+
power: float,
|
|
253
|
+
color: Optional[LEDColor] = None,
|
|
254
|
+
pattern: Optional[LEDPattern] = None,
|
|
255
|
+
duration: Optional[int] = None,
|
|
256
|
+
reps: Optional[int] = None,
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Sets the statusbar state."""
|
|
259
|
+
return await self._driver.set_led(
|
|
260
|
+
power, color=color, pattern=pattern, duration=duration, reps=reps
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
async def move_axis(
|
|
264
|
+
self,
|
|
265
|
+
axis: StackerAxis,
|
|
266
|
+
direction: Direction,
|
|
267
|
+
distance: float,
|
|
268
|
+
speed: Optional[float] = None,
|
|
269
|
+
acceleration: Optional[float] = None,
|
|
270
|
+
current: Optional[float] = None,
|
|
271
|
+
) -> bool:
|
|
272
|
+
"""Move the axis in a direction by the given distance in mm."""
|
|
273
|
+
await self.reset_stall_detected()
|
|
274
|
+
default = STACKER_MOTION_CONFIG[axis]["move"]
|
|
275
|
+
await self._driver.set_run_current(
|
|
276
|
+
axis, current if current is not None else default.run_current
|
|
277
|
+
)
|
|
278
|
+
await self._driver.set_ihold_current(axis, default.hold_current)
|
|
279
|
+
motion_params = default.move_params.update(
|
|
280
|
+
max_speed=speed, acceleration=acceleration
|
|
281
|
+
)
|
|
282
|
+
distance = direction.distance(distance)
|
|
283
|
+
res = await self._driver.move_in_mm(axis, distance, params=motion_params)
|
|
284
|
+
if res == MoveResult.STALL_ERROR:
|
|
285
|
+
self._stall_detected = True
|
|
286
|
+
raise FlexStackerStallError(self.device_info["serial"], axis)
|
|
287
|
+
return res == MoveResult.NO_ERROR
|
|
288
|
+
|
|
289
|
+
async def home_axis(
|
|
290
|
+
self,
|
|
291
|
+
axis: StackerAxis,
|
|
292
|
+
direction: Direction,
|
|
293
|
+
speed: Optional[float] = None,
|
|
294
|
+
acceleration: Optional[float] = None,
|
|
295
|
+
current: Optional[float] = None,
|
|
296
|
+
) -> bool:
|
|
297
|
+
await self.reset_stall_detected()
|
|
298
|
+
default = STACKER_MOTION_CONFIG[axis]["home"]
|
|
299
|
+
await self._driver.set_run_current(
|
|
300
|
+
axis, current if current is not None else default.run_current
|
|
301
|
+
)
|
|
302
|
+
await self._driver.set_ihold_current(axis, default.hold_current)
|
|
303
|
+
motion_params = default.move_params.update(
|
|
304
|
+
max_speed=speed, acceleration=acceleration
|
|
305
|
+
)
|
|
306
|
+
success = await self._driver.move_to_limit_switch(
|
|
307
|
+
axis=axis, direction=direction, params=motion_params
|
|
308
|
+
)
|
|
309
|
+
if success == MoveResult.STALL_ERROR:
|
|
310
|
+
self._stall_detected = True
|
|
311
|
+
raise FlexStackerStallError(self.device_info["serial"], axis)
|
|
312
|
+
return success == MoveResult.NO_ERROR
|
|
313
|
+
|
|
314
|
+
async def close_latch(
|
|
315
|
+
self,
|
|
316
|
+
velocity: Optional[float] = None,
|
|
317
|
+
acceleration: Optional[float] = None,
|
|
318
|
+
) -> bool:
|
|
319
|
+
"""Close the latch, dropping any labware its holding."""
|
|
320
|
+
# Dont move the latch if its already closed.
|
|
321
|
+
if self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED:
|
|
322
|
+
return True
|
|
323
|
+
success = await self.home_axis(
|
|
324
|
+
StackerAxis.L,
|
|
325
|
+
Direction.RETRACT,
|
|
326
|
+
speed=velocity,
|
|
327
|
+
acceleration=acceleration,
|
|
328
|
+
)
|
|
329
|
+
# Check that the latch is closed.
|
|
330
|
+
await self._reader.get_limit_switch_status()
|
|
331
|
+
return (
|
|
332
|
+
success
|
|
333
|
+
and self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
async def open_latch(
|
|
337
|
+
self,
|
|
338
|
+
velocity: Optional[float] = None,
|
|
339
|
+
acceleration: Optional[float] = None,
|
|
340
|
+
) -> bool:
|
|
341
|
+
"""Open the latch."""
|
|
342
|
+
# Dont move the latch if its already opened.
|
|
343
|
+
if self.limit_switch_status[StackerAxis.L] == StackerAxisState.RETRACTED:
|
|
344
|
+
return True
|
|
345
|
+
# The latch only has one limit switch, so we have to travel a fixed distance
|
|
346
|
+
# to open the latch.
|
|
347
|
+
success = await self.move_axis(
|
|
348
|
+
StackerAxis.L,
|
|
349
|
+
Direction.EXTEND,
|
|
350
|
+
distance=MAX_TRAVEL[StackerAxis.L],
|
|
351
|
+
speed=velocity,
|
|
352
|
+
acceleration=acceleration,
|
|
353
|
+
)
|
|
354
|
+
# Check that the latch is opened.
|
|
355
|
+
await self._reader.get_limit_switch_status()
|
|
356
|
+
axis_state = self.limit_switch_status[StackerAxis.L]
|
|
357
|
+
return success and axis_state == StackerAxisState.RETRACTED
|
|
358
|
+
|
|
359
|
+
async def dispense_labware(self, labware_height: float) -> bool:
|
|
360
|
+
"""Dispenses the next labware in the stacker."""
|
|
361
|
+
await self._prepare_for_action()
|
|
362
|
+
|
|
363
|
+
# Move platform along the X then Z axis
|
|
364
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, OFFSET_SM)
|
|
365
|
+
await self._move_and_home_axis(StackerAxis.Z, Direction.EXTEND, OFFSET_SM)
|
|
366
|
+
|
|
367
|
+
# Transfer
|
|
368
|
+
await self.open_latch()
|
|
369
|
+
await self.move_axis(StackerAxis.Z, Direction.RETRACT, (labware_height / 2) + 2)
|
|
370
|
+
await self.close_latch()
|
|
371
|
+
|
|
372
|
+
# Move platform along the Z then X axis
|
|
373
|
+
offset = labware_height / 2 + OFFSET_MD
|
|
374
|
+
await self._move_and_home_axis(StackerAxis.Z, Direction.RETRACT, offset)
|
|
375
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.EXTEND, OFFSET_SM)
|
|
376
|
+
return True
|
|
377
|
+
|
|
378
|
+
async def store_labware(self, labware_height: float) -> bool:
|
|
379
|
+
"""Stores a labware in the stacker."""
|
|
380
|
+
await self._prepare_for_action()
|
|
381
|
+
|
|
382
|
+
# Move X then Z axis
|
|
383
|
+
offset = OFFSET_MD if labware_height < MEDIUM_LABWARE_Z_LIMIT else OFFSET_LG * 2
|
|
384
|
+
distance = MAX_TRAVEL[StackerAxis.Z] - (labware_height / 2) - offset
|
|
385
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, OFFSET_SM)
|
|
386
|
+
await self.move_axis(StackerAxis.Z, Direction.EXTEND, distance)
|
|
387
|
+
|
|
388
|
+
# Transfer
|
|
389
|
+
await self.open_latch()
|
|
390
|
+
z_speed = (
|
|
391
|
+
STACKER_MOTION_CONFIG[StackerAxis.Z]["move"].move_params.max_speed or 0
|
|
392
|
+
) / 2
|
|
393
|
+
await self.move_axis(
|
|
394
|
+
StackerAxis.Z, Direction.EXTEND, (labware_height / 2), z_speed
|
|
395
|
+
)
|
|
396
|
+
await self.home_axis(StackerAxis.Z, Direction.EXTEND, z_speed)
|
|
397
|
+
await self.close_latch()
|
|
398
|
+
|
|
399
|
+
# Move Z then X axis
|
|
400
|
+
await self._move_and_home_axis(StackerAxis.Z, Direction.RETRACT, OFFSET_LG)
|
|
401
|
+
await self._move_and_home_axis(StackerAxis.X, Direction.EXTEND, OFFSET_SM)
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
async def _move_and_home_axis(
|
|
405
|
+
self, axis: StackerAxis, direction: Direction, offset: float = 0
|
|
406
|
+
) -> bool:
|
|
407
|
+
distance = MAX_TRAVEL[axis] - offset
|
|
408
|
+
await self.move_axis(axis, direction, distance)
|
|
409
|
+
return await self.home_axis(axis, direction)
|
|
410
|
+
|
|
411
|
+
async def _prepare_for_action(self) -> bool:
|
|
412
|
+
"""Helper to prepare axis for dispensing or storing labware."""
|
|
413
|
+
# TODO: check if we need to home first
|
|
414
|
+
await self.home_axis(StackerAxis.X, Direction.EXTEND)
|
|
415
|
+
await self.home_axis(StackerAxis.Z, Direction.RETRACT)
|
|
416
|
+
await self.close_latch()
|
|
417
|
+
return True
|
|
418
|
+
|
|
419
|
+
async def home_all(self, ignore_latch: bool = False) -> None:
|
|
420
|
+
"""Home all axes based on current state, assuming normal operation.
|
|
421
|
+
|
|
422
|
+
If ignore_latch is True, we will not attempt to close the latch. This
|
|
423
|
+
is useful when we want the shuttle to be out of the way for error
|
|
424
|
+
recovery (e.g. when the latch is stuck open).
|
|
425
|
+
"""
|
|
426
|
+
await self._reader.read()
|
|
427
|
+
# we should always be able to home the X axis first
|
|
428
|
+
await self.home_axis(StackerAxis.X, Direction.RETRACT)
|
|
429
|
+
# If latch is open, we must first close it
|
|
430
|
+
if not ignore_latch and self.latch_state == LatchState.OPENED:
|
|
431
|
+
if self.limit_switch_status[StackerAxis.Z] != StackerAxisState.RETRACTED:
|
|
432
|
+
# it was likely in the middle of a dispense/store command
|
|
433
|
+
# z should be moved up before we can safely close the latch
|
|
434
|
+
await self.home_axis(StackerAxis.Z, Direction.EXTEND)
|
|
435
|
+
await self.close_latch()
|
|
436
|
+
await self.home_axis(StackerAxis.Z, Direction.RETRACT)
|
|
437
|
+
await self.home_axis(StackerAxis.X, Direction.EXTEND)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class FlexStackerReader(Reader):
|
|
441
|
+
error: Optional[str]
|
|
442
|
+
|
|
443
|
+
def __init__(self, driver: AbstractFlexStackerDriver) -> None:
|
|
444
|
+
self.error: Optional[str] = None
|
|
445
|
+
self._driver = driver
|
|
446
|
+
self.limit_switch_status = {
|
|
447
|
+
axis: StackerAxisState.UNKNOWN for axis in StackerAxis
|
|
448
|
+
}
|
|
449
|
+
self.platform_state = PlatformState.UNKNOWN
|
|
450
|
+
self.hopper_door_closed = False
|
|
451
|
+
self.motion_params: Dict[StackerAxis, Optional[MoveParams]] = {
|
|
452
|
+
axis: None for axis in StackerAxis
|
|
453
|
+
}
|
|
454
|
+
self.get_config = True
|
|
455
|
+
|
|
456
|
+
async def read(self) -> None:
|
|
457
|
+
await self.get_limit_switch_status()
|
|
458
|
+
await self.get_platform_sensor_state()
|
|
459
|
+
await self.get_door_closed()
|
|
460
|
+
if self.get_config:
|
|
461
|
+
await self.get_motion_parameters()
|
|
462
|
+
self.get_config = False
|
|
463
|
+
self._set_error(None)
|
|
464
|
+
|
|
465
|
+
async def get_limit_switch_status(self) -> None:
|
|
466
|
+
"""Get the limit switch status."""
|
|
467
|
+
status = await self._driver.get_limit_switches_status()
|
|
468
|
+
self.limit_switch_status = {
|
|
469
|
+
axis: StackerAxisState.from_status(status, axis) for axis in StackerAxis
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async def get_motion_parameters(self) -> None:
|
|
473
|
+
"""Get the motion parameters used by the axis motors."""
|
|
474
|
+
self.motion_params = {
|
|
475
|
+
axis: await self._driver.get_motion_params(axis) for axis in StackerAxis
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async def get_platform_sensor_state(self) -> None:
|
|
479
|
+
"""Get the platform state."""
|
|
480
|
+
status = await self._driver.get_platform_status()
|
|
481
|
+
self.platform_state = PlatformState.from_status(status)
|
|
482
|
+
|
|
483
|
+
async def get_door_closed(self) -> None:
|
|
484
|
+
"""Check if the hopper door is closed."""
|
|
485
|
+
self.hopper_door_closed = await self._driver.get_hopper_door_closed()
|
|
486
|
+
|
|
487
|
+
def on_error(self, exception: Exception) -> None:
|
|
488
|
+
self._driver.reset_serial_buffers()
|
|
489
|
+
self._set_error(exception)
|
|
490
|
+
|
|
491
|
+
def _set_error(self, exception: Optional[Exception]) -> None:
|
|
492
|
+
if exception is None:
|
|
493
|
+
self.error = None
|
|
494
|
+
else:
|
|
495
|
+
try:
|
|
496
|
+
self.error = str(exception.args[0])
|
|
497
|
+
except Exception:
|
|
498
|
+
self.error = repr(exception)
|
|
@@ -21,6 +21,7 @@ from opentrons.hardware_control.modules.types import (
|
|
|
21
21
|
HeaterShakerStatus,
|
|
22
22
|
UploadFunction,
|
|
23
23
|
LiveData,
|
|
24
|
+
HeaterShakerData,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
log = logging.getLogger(__name__)
|
|
@@ -200,18 +201,19 @@ class HeaterShaker(mod_abc.AbstractModule):
|
|
|
200
201
|
|
|
201
202
|
@property
|
|
202
203
|
def live_data(self) -> LiveData:
|
|
204
|
+
data: HeaterShakerData = {
|
|
205
|
+
"temperatureStatus": self.temperature_status,
|
|
206
|
+
"speedStatus": self.speed_status,
|
|
207
|
+
"labwareLatchStatus": self.labware_latch_status,
|
|
208
|
+
"currentTemp": self.temperature,
|
|
209
|
+
"targetTemp": self.target_temperature,
|
|
210
|
+
"currentSpeed": self.speed,
|
|
211
|
+
"targetSpeed": self.target_speed,
|
|
212
|
+
"errorDetails": self._reader.error,
|
|
213
|
+
}
|
|
203
214
|
return {
|
|
204
215
|
"status": self.status.value,
|
|
205
|
-
"data":
|
|
206
|
-
"temperatureStatus": self.temperature_status.value,
|
|
207
|
-
"speedStatus": self.speed_status.value,
|
|
208
|
-
"labwareLatchStatus": self.labware_latch_status.value,
|
|
209
|
-
"currentTemp": self.temperature,
|
|
210
|
-
"targetTemp": self.target_temperature,
|
|
211
|
-
"currentSpeed": self.speed,
|
|
212
|
-
"targetSpeed": self.target_speed,
|
|
213
|
-
"errorDetails": self._reader.error,
|
|
214
|
-
},
|
|
216
|
+
"data": data,
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
@property
|
|
@@ -196,9 +196,13 @@ class MagDeck(mod_abc.AbstractModule):
|
|
|
196
196
|
|
|
197
197
|
@property
|
|
198
198
|
def live_data(self) -> types.LiveData:
|
|
199
|
+
data: types.MagneticModuleData = {
|
|
200
|
+
"engaged": self.engaged,
|
|
201
|
+
"height": self.current_height,
|
|
202
|
+
}
|
|
199
203
|
return {
|
|
200
204
|
"status": self.status,
|
|
201
|
-
"data":
|
|
205
|
+
"data": data,
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
@property
|
|
@@ -195,9 +195,13 @@ class TempDeck(mod_abc.AbstractModule):
|
|
|
195
195
|
|
|
196
196
|
@property
|
|
197
197
|
def live_data(self) -> types.LiveData:
|
|
198
|
+
data: types.TemperatureModuleData = {
|
|
199
|
+
"currentTemp": self.temperature,
|
|
200
|
+
"targetTemp": self.target,
|
|
201
|
+
}
|
|
198
202
|
return {
|
|
199
203
|
"status": self.status,
|
|
200
|
-
"data":
|
|
204
|
+
"data": data,
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
@property
|
|
@@ -544,22 +544,23 @@ class Thermocycler(mod_abc.AbstractModule):
|
|
|
544
544
|
|
|
545
545
|
@property
|
|
546
546
|
def live_data(self) -> types.LiveData:
|
|
547
|
+
data: types.ThermocyclerData = {
|
|
548
|
+
"lid": self.lid_status,
|
|
549
|
+
"lidTarget": self.lid_target,
|
|
550
|
+
"lidTemp": self.lid_temp,
|
|
551
|
+
"lidTempStatus": self.lid_temp_status,
|
|
552
|
+
"currentTemp": self.temperature,
|
|
553
|
+
"targetTemp": self.target,
|
|
554
|
+
"holdTime": self.hold_time,
|
|
555
|
+
"rampRate": self.ramp_rate,
|
|
556
|
+
"currentCycleIndex": self.current_cycle_index,
|
|
557
|
+
"totalCycleCount": self.total_cycle_count,
|
|
558
|
+
"currentStepIndex": self.current_step_index,
|
|
559
|
+
"totalStepCount": self.total_step_count,
|
|
560
|
+
}
|
|
547
561
|
return {
|
|
548
562
|
"status": self.status,
|
|
549
|
-
"data":
|
|
550
|
-
"lid": self.lid_status,
|
|
551
|
-
"lidTarget": self.lid_target,
|
|
552
|
-
"lidTemp": self.lid_temp,
|
|
553
|
-
"lidTempStatus": self.lid_temp_status,
|
|
554
|
-
"currentTemp": self.temperature,
|
|
555
|
-
"targetTemp": self.target,
|
|
556
|
-
"holdTime": self.hold_time,
|
|
557
|
-
"rampRate": self.ramp_rate,
|
|
558
|
-
"currentCycleIndex": self.current_cycle_index,
|
|
559
|
-
"totalCycleCount": self.total_cycle_count,
|
|
560
|
-
"currentStepIndex": self.current_step_index,
|
|
561
|
-
"totalStepCount": self.total_step_count,
|
|
562
|
-
},
|
|
563
|
+
"data": data,
|
|
563
564
|
}
|
|
564
565
|
|
|
565
566
|
@property
|