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.
Files changed (192) hide show
  1. opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
  2. opentrons/calibration_storage/ot2/tip_length.py +6 -6
  3. opentrons/config/advanced_settings.py +9 -11
  4. opentrons/config/feature_flags.py +0 -4
  5. opentrons/config/reset.py +7 -2
  6. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  7. opentrons/drivers/asyncio/communication/async_serial.py +4 -0
  8. opentrons/drivers/asyncio/communication/errors.py +41 -8
  9. opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
  10. opentrons/drivers/flex_stacker/__init__.py +9 -3
  11. opentrons/drivers/flex_stacker/abstract.py +140 -15
  12. opentrons/drivers/flex_stacker/driver.py +593 -47
  13. opentrons/drivers/flex_stacker/errors.py +64 -0
  14. opentrons/drivers/flex_stacker/simulator.py +222 -24
  15. opentrons/drivers/flex_stacker/types.py +211 -15
  16. opentrons/drivers/flex_stacker/utils.py +19 -0
  17. opentrons/execute.py +4 -2
  18. opentrons/hardware_control/api.py +5 -0
  19. opentrons/hardware_control/backends/flex_protocol.py +4 -0
  20. opentrons/hardware_control/backends/ot3controller.py +12 -1
  21. opentrons/hardware_control/backends/ot3simulator.py +3 -0
  22. opentrons/hardware_control/backends/subsystem_manager.py +8 -4
  23. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
  24. opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
  25. opentrons/hardware_control/modules/__init__.py +12 -1
  26. opentrons/hardware_control/modules/absorbance_reader.py +11 -9
  27. opentrons/hardware_control/modules/flex_stacker.py +498 -0
  28. opentrons/hardware_control/modules/heater_shaker.py +12 -10
  29. opentrons/hardware_control/modules/magdeck.py +5 -1
  30. opentrons/hardware_control/modules/tempdeck.py +5 -1
  31. opentrons/hardware_control/modules/thermocycler.py +15 -14
  32. opentrons/hardware_control/modules/types.py +191 -1
  33. opentrons/hardware_control/modules/utils.py +3 -0
  34. opentrons/hardware_control/motion_utilities.py +20 -0
  35. opentrons/hardware_control/ot3api.py +145 -15
  36. opentrons/hardware_control/protocols/liquid_handler.py +47 -1
  37. opentrons/hardware_control/types.py +6 -0
  38. opentrons/legacy_commands/commands.py +19 -3
  39. opentrons/legacy_commands/helpers.py +15 -0
  40. opentrons/legacy_commands/types.py +3 -2
  41. opentrons/protocol_api/__init__.py +2 -0
  42. opentrons/protocol_api/_liquid.py +39 -8
  43. opentrons/protocol_api/_liquid_properties.py +20 -19
  44. opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
  45. opentrons/protocol_api/core/common.py +3 -1
  46. opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
  47. opentrons/protocol_api/core/engine/instrument.py +1233 -65
  48. opentrons/protocol_api/core/engine/labware.py +8 -4
  49. opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
  50. opentrons/protocol_api/core/engine/module_core.py +118 -2
  51. opentrons/protocol_api/core/engine/protocol.py +253 -11
  52. opentrons/protocol_api/core/engine/stringify.py +19 -8
  53. opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
  54. opentrons/protocol_api/core/engine/well.py +60 -5
  55. opentrons/protocol_api/core/instrument.py +65 -19
  56. opentrons/protocol_api/core/labware.py +6 -2
  57. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  58. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
  59. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  60. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  61. opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
  62. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  63. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  64. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
  66. opentrons/protocol_api/core/module.py +43 -0
  67. opentrons/protocol_api/core/protocol.py +33 -0
  68. opentrons/protocol_api/core/well.py +21 -1
  69. opentrons/protocol_api/instrument_context.py +246 -123
  70. opentrons/protocol_api/labware.py +75 -11
  71. opentrons/protocol_api/module_contexts.py +140 -0
  72. opentrons/protocol_api/protocol_context.py +156 -16
  73. opentrons/protocol_api/validation.py +51 -41
  74. opentrons/protocol_engine/__init__.py +21 -2
  75. opentrons/protocol_engine/actions/actions.py +5 -5
  76. opentrons/protocol_engine/clients/sync_client.py +6 -0
  77. opentrons/protocol_engine/commands/__init__.py +30 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  79. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  80. opentrons/protocol_engine/commands/aspirate.py +6 -2
  81. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  82. opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
  83. opentrons/protocol_engine/commands/blow_out.py +2 -0
  84. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  85. opentrons/protocol_engine/commands/command_unions.py +69 -0
  86. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  87. opentrons/protocol_engine/commands/dispense.py +3 -1
  88. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  89. opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
  90. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  91. opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
  92. opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
  93. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
  94. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  95. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  96. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  97. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  98. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  99. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  100. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  101. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  102. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  103. opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
  104. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  105. opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
  106. opentrons/protocol_engine/commands/liquid_probe.py +21 -12
  107. opentrons/protocol_engine/commands/load_labware.py +42 -39
  108. opentrons/protocol_engine/commands/load_lid.py +21 -13
  109. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  110. opentrons/protocol_engine/commands/load_module.py +18 -17
  111. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  112. opentrons/protocol_engine/commands/move_labware.py +139 -20
  113. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  114. opentrons/protocol_engine/commands/pipetting_common.py +154 -7
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  118. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  119. opentrons/protocol_engine/errors/__init__.py +8 -0
  120. opentrons/protocol_engine/errors/exceptions.py +50 -0
  121. opentrons/protocol_engine/execution/equipment.py +123 -106
  122. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  123. opentrons/protocol_engine/execution/pipetting.py +233 -26
  124. opentrons/protocol_engine/execution/tip_handler.py +14 -5
  125. opentrons/protocol_engine/labware_offset_standardization.py +173 -0
  126. opentrons/protocol_engine/protocol_engine.py +22 -13
  127. opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
  128. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  129. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  130. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  131. opentrons/protocol_engine/slot_standardization.py +11 -23
  132. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  133. opentrons/protocol_engine/state/frustum_helpers.py +26 -10
  134. opentrons/protocol_engine/state/geometry.py +683 -100
  135. opentrons/protocol_engine/state/labware.py +252 -55
  136. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  137. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  138. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  139. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  140. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  141. opentrons/protocol_engine/state/modules.py +178 -52
  142. opentrons/protocol_engine/state/pipettes.py +54 -0
  143. opentrons/protocol_engine/state/state.py +1 -1
  144. opentrons/protocol_engine/state/tips.py +14 -0
  145. opentrons/protocol_engine/state/update_types.py +180 -25
  146. opentrons/protocol_engine/state/wells.py +54 -8
  147. opentrons/protocol_engine/types/__init__.py +292 -0
  148. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  149. opentrons/protocol_engine/types/command_annotations.py +53 -0
  150. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  151. opentrons/protocol_engine/types/execution.py +96 -0
  152. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  153. opentrons/protocol_engine/types/instrument.py +47 -0
  154. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  155. opentrons/protocol_engine/types/labware.py +110 -0
  156. opentrons/protocol_engine/types/labware_movement.py +22 -0
  157. opentrons/protocol_engine/types/labware_offset_location.py +108 -0
  158. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  159. opentrons/protocol_engine/types/liquid.py +40 -0
  160. opentrons/protocol_engine/types/liquid_class.py +59 -0
  161. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  162. opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
  163. opentrons/protocol_engine/types/location.py +193 -0
  164. opentrons/protocol_engine/types/module.py +269 -0
  165. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  166. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  167. opentrons/protocol_engine/types/tip.py +18 -0
  168. opentrons/protocol_engine/types/util.py +21 -0
  169. opentrons/protocol_engine/types/well_position.py +107 -0
  170. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  171. opentrons/protocol_reader/file_format_validator.py +5 -3
  172. opentrons/protocol_runner/json_translator.py +4 -2
  173. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  174. opentrons/protocol_runner/run_orchestrator.py +4 -1
  175. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  176. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  177. opentrons/protocols/api_support/definitions.py +1 -1
  178. opentrons/protocols/api_support/instrument.py +16 -3
  179. opentrons/protocols/labware.py +5 -6
  180. opentrons/protocols/models/__init__.py +0 -21
  181. opentrons/simulate.py +4 -2
  182. opentrons/types.py +15 -6
  183. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
  184. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
  185. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  186. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  187. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  188. opentrons/protocol_engine/types.py +0 -1311
  189. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
  190. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
  191. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
  192. {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": {"engaged": self.engaged, "height": self.current_height},
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": {"currentTemp": self.temperature, "targetTemp": self.target},
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