opentrons 8.3.2__py2.py3-none-any.whl → 8.4.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (196) 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 +102 -5
  39. opentrons/legacy_commands/helpers.py +74 -1
  40. opentrons/legacy_commands/types.py +33 -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 +1356 -107
  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/pipette_movement_conflict.py +6 -14
  52. opentrons/protocol_api/core/engine/protocol.py +253 -11
  53. opentrons/protocol_api/core/engine/stringify.py +19 -8
  54. opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
  55. opentrons/protocol_api/core/engine/well.py +73 -5
  56. opentrons/protocol_api/core/instrument.py +71 -21
  57. opentrons/protocol_api/core/labware.py +6 -2
  58. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  59. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
  60. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  61. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  62. opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
  63. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  64. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  65. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
  67. opentrons/protocol_api/core/module.py +43 -0
  68. opentrons/protocol_api/core/protocol.py +33 -0
  69. opentrons/protocol_api/core/well.py +23 -2
  70. opentrons/protocol_api/instrument_context.py +454 -150
  71. opentrons/protocol_api/labware.py +98 -50
  72. opentrons/protocol_api/module_contexts.py +140 -0
  73. opentrons/protocol_api/protocol_context.py +163 -19
  74. opentrons/protocol_api/validation.py +51 -41
  75. opentrons/protocol_engine/__init__.py +21 -2
  76. opentrons/protocol_engine/actions/actions.py +5 -5
  77. opentrons/protocol_engine/clients/sync_client.py +6 -0
  78. opentrons/protocol_engine/commands/__init__.py +66 -36
  79. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  80. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  81. opentrons/protocol_engine/commands/aspirate.py +6 -2
  82. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  83. opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
  84. opentrons/protocol_engine/commands/blow_out.py +2 -0
  85. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  86. opentrons/protocol_engine/commands/command_unions.py +102 -33
  87. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  88. opentrons/protocol_engine/commands/dispense.py +3 -1
  89. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  90. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  91. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  92. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  93. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  94. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  95. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  96. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  97. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  98. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  99. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  100. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  101. opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
  102. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  103. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  104. opentrons/protocol_engine/commands/liquid_probe.py +27 -13
  105. opentrons/protocol_engine/commands/load_labware.py +42 -39
  106. opentrons/protocol_engine/commands/load_lid.py +21 -13
  107. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  108. opentrons/protocol_engine/commands/load_module.py +18 -17
  109. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  110. opentrons/protocol_engine/commands/move_labware.py +139 -20
  111. opentrons/protocol_engine/commands/move_to_well.py +5 -11
  112. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  113. opentrons/protocol_engine/commands/pipetting_common.py +159 -8
  114. opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
  115. opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
  118. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  119. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  120. opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
  121. opentrons/protocol_engine/errors/__init__.py +10 -0
  122. opentrons/protocol_engine/errors/exceptions.py +62 -0
  123. opentrons/protocol_engine/execution/equipment.py +123 -106
  124. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  125. opentrons/protocol_engine/execution/pipetting.py +235 -25
  126. opentrons/protocol_engine/execution/tip_handler.py +82 -32
  127. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  128. opentrons/protocol_engine/protocol_engine.py +22 -13
  129. opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
  130. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  131. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  132. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  133. opentrons/protocol_engine/slot_standardization.py +11 -23
  134. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  135. opentrons/protocol_engine/state/frustum_helpers.py +36 -14
  136. opentrons/protocol_engine/state/geometry.py +892 -227
  137. opentrons/protocol_engine/state/labware.py +252 -55
  138. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  139. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  140. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  141. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  142. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  143. opentrons/protocol_engine/state/modules.py +210 -67
  144. opentrons/protocol_engine/state/pipettes.py +54 -0
  145. opentrons/protocol_engine/state/state.py +1 -1
  146. opentrons/protocol_engine/state/tips.py +14 -0
  147. opentrons/protocol_engine/state/update_types.py +180 -25
  148. opentrons/protocol_engine/state/wells.py +55 -9
  149. opentrons/protocol_engine/types/__init__.py +300 -0
  150. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  151. opentrons/protocol_engine/types/command_annotations.py +53 -0
  152. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  153. opentrons/protocol_engine/types/execution.py +96 -0
  154. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  155. opentrons/protocol_engine/types/instrument.py +47 -0
  156. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  157. opentrons/protocol_engine/types/labware.py +111 -0
  158. opentrons/protocol_engine/types/labware_movement.py +22 -0
  159. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  160. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  161. opentrons/protocol_engine/types/liquid.py +40 -0
  162. opentrons/protocol_engine/types/liquid_class.py +59 -0
  163. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  164. opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
  165. opentrons/protocol_engine/types/location.py +194 -0
  166. opentrons/protocol_engine/types/module.py +301 -0
  167. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  168. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  169. opentrons/protocol_engine/types/tip.py +18 -0
  170. opentrons/protocol_engine/types/util.py +21 -0
  171. opentrons/protocol_engine/types/well_position.py +124 -0
  172. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  173. opentrons/protocol_reader/file_format_validator.py +5 -3
  174. opentrons/protocol_runner/json_translator.py +4 -2
  175. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  176. opentrons/protocol_runner/run_orchestrator.py +4 -1
  177. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/instrument.py +16 -3
  181. opentrons/protocols/labware.py +27 -23
  182. opentrons/protocols/models/__init__.py +0 -21
  183. opentrons/simulate.py +4 -2
  184. opentrons/types.py +20 -7
  185. opentrons/util/logging_config.py +94 -25
  186. opentrons/util/logging_queue_handler.py +61 -0
  187. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
  188. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
  189. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  190. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  191. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  192. opentrons/protocol_engine/types.py +0 -1311
  193. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
  194. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
  195. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
  196. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
@@ -13,10 +13,16 @@ from typing import (
13
13
  Optional,
14
14
  cast,
15
15
  TYPE_CHECKING,
16
+ TypeGuard,
16
17
  )
17
18
  from typing_extensions import TypedDict
18
19
  from pathlib import Path
19
20
 
21
+ from opentrons.drivers.flex_stacker.types import (
22
+ LimitSwitchStatus,
23
+ PlatformStatus,
24
+ StackerAxis,
25
+ )
20
26
  from opentrons.drivers.rpi_drivers.types import USBPort
21
27
 
22
28
  if TYPE_CHECKING:
@@ -27,6 +33,7 @@ if TYPE_CHECKING:
27
33
  HeaterShakerModuleType,
28
34
  MagneticBlockType,
29
35
  AbsorbanceReaderType,
36
+ FlexStackerModuleType,
30
37
  )
31
38
 
32
39
 
@@ -50,9 +57,113 @@ UploadFunction = Callable[[str, str, Dict[str, Any]], Awaitable[Tuple[bool, str]
50
57
  ModuleDisconnectedCallback = Optional[Callable[[str, str | None], None]]
51
58
 
52
59
 
60
+ class MagneticModuleData(TypedDict):
61
+ engaged: bool
62
+ height: float
63
+
64
+
65
+ class TemperatureModuleData(TypedDict):
66
+ currentTemp: float
67
+ targetTemp: float | None
68
+
69
+
70
+ class HeaterShakerData(TypedDict):
71
+ temperatureStatus: str
72
+ speedStatus: str
73
+ labwareLatchStatus: str
74
+ currentTemp: float
75
+ targetTemp: float | None
76
+ currentSpeed: int
77
+ targetSpeed: int | None
78
+ errorDetails: str | None
79
+
80
+
81
+ class ThermocyclerData(TypedDict):
82
+ lid: str
83
+ lidTarget: float | None
84
+ lidTemp: float
85
+ lidTempStatus: str
86
+ currentTemp: float | None
87
+ targetTemp: float | None
88
+ holdTime: float | None
89
+ rampRate: float | None
90
+ currentCycleIndex: int | None
91
+ totalCycleCount: int | None
92
+ currentStepIndex: int | None
93
+ totalStepCount: int | None
94
+
95
+
96
+ class AbsorbanceReaderData(TypedDict):
97
+ uptime: int
98
+ deviceStatus: str
99
+ lidStatus: str
100
+ platePresence: str
101
+ measureMode: str
102
+ sampleWavelengths: List[int]
103
+ referenceWavelength: int
104
+
105
+
106
+ class FlexStackerData(TypedDict):
107
+ latchState: str
108
+ platformState: str
109
+ hopperDoorState: str
110
+ axisStateX: str
111
+ axisStateZ: str
112
+ errorDetails: str | None
113
+
114
+
115
+ ModuleData = Union[
116
+ Dict[Any, Any], # This allows an empty dict as module data
117
+ MagneticModuleData,
118
+ TemperatureModuleData,
119
+ HeaterShakerData,
120
+ ThermocyclerData,
121
+ AbsorbanceReaderData,
122
+ FlexStackerData,
123
+ ]
124
+
125
+
126
+ class ModuleDataValidator:
127
+ @classmethod
128
+ def is_magnetic_module_data(
129
+ cls, data: ModuleData | None
130
+ ) -> TypeGuard[MagneticModuleData]:
131
+ return data is not None and "engaged" in data.keys()
132
+
133
+ @classmethod
134
+ def is_temperature_module_data(
135
+ cls, data: ModuleData | None
136
+ ) -> TypeGuard[TemperatureModuleData]:
137
+ return data is not None and "targetTemp" in data.keys()
138
+
139
+ @classmethod
140
+ def is_heater_shaker_data(
141
+ cls, data: ModuleData | None
142
+ ) -> TypeGuard[HeaterShakerData]:
143
+ return data is not None and "labwareLatchStatus" in data.keys()
144
+
145
+ @classmethod
146
+ def is_thermocycler_data(
147
+ cls, data: ModuleData | None
148
+ ) -> TypeGuard[ThermocyclerData]:
149
+ return data is not None and "lid" in data.keys()
150
+
151
+ @classmethod
152
+ def is_absorbance_reader_data(
153
+ cls, data: ModuleData | None
154
+ ) -> TypeGuard[AbsorbanceReaderData]:
155
+ return data is not None and "uptime" in data.keys()
156
+
157
+ @classmethod
158
+ def is_flex_stacker_data(
159
+ cls, data: ModuleData | None
160
+ ) -> TypeGuard[FlexStackerData]:
161
+ return data is not None and "platformState" in data.keys()
162
+
163
+
53
164
  class LiveData(TypedDict):
54
165
  status: str
55
- data: Dict[str, Union[float, str, bool, List[int], None]]
166
+ data: ModuleData | None
56
167
 
57
168
 
58
169
  class ModuleType(str, Enum):
@@ -62,6 +173,7 @@ class ModuleType(str, Enum):
62
173
  HEATER_SHAKER: HeaterShakerModuleType = "heaterShakerModuleType"
63
174
  MAGNETIC_BLOCK: MagneticBlockType = "magneticBlockType"
64
175
  ABSORBANCE_READER: AbsorbanceReaderType = "absorbanceReaderType"
176
+ FLEX_STACKER: FlexStackerModuleType = "flexStackerModuleType"
65
177
 
66
178
  @classmethod
67
179
  def from_model(cls, model: ModuleModel) -> ModuleType:
@@ -77,6 +189,8 @@ class ModuleType(str, Enum):
77
189
  return cls.MAGNETIC_BLOCK
78
190
  if isinstance(model, AbsorbanceReaderModel):
79
191
  return cls.ABSORBANCE_READER
192
+ if isinstance(model, FlexStackerModuleModel):
193
+ return cls.FLEX_STACKER
80
194
 
81
195
  @classmethod
82
196
  def to_module_fixture_id(cls, module_type: ModuleType) -> str:
@@ -91,6 +205,8 @@ class ModuleType(str, Enum):
91
205
  return "magneticBlockV1"
92
206
  if module_type == ModuleType.ABSORBANCE_READER:
93
207
  return "absorbanceReaderV1"
208
+ if module_type == ModuleType.FLEX_STACKER:
209
+ return "flexStackerModuleV1"
94
210
  else:
95
211
  raise ValueError(
96
212
  f"Module Type {module_type} does not have a related fixture ID."
@@ -124,6 +240,10 @@ class AbsorbanceReaderModel(str, Enum):
124
240
  ABSORBANCE_READER_V1: str = "absorbanceReaderV1"
125
241
 
126
242
 
243
+ class FlexStackerModuleModel(str, Enum):
244
+ FLEX_STACKER_V1: str = "flexStackerModuleV1"
245
+
246
+
127
247
  def module_model_from_string(model_string: str) -> ModuleModel:
128
248
  for model_enum in {
129
249
  MagneticModuleModel,
@@ -132,6 +252,7 @@ def module_model_from_string(model_string: str) -> ModuleModel:
132
252
  HeaterShakerModuleModel,
133
253
  MagneticBlockModel,
134
254
  AbsorbanceReaderModel,
255
+ FlexStackerModuleModel,
135
256
  }:
136
257
  try:
137
258
  return cast(ModuleModel, model_enum(model_string))
@@ -184,6 +305,7 @@ ModuleModel = Union[
184
305
  HeaterShakerModuleModel,
185
306
  MagneticBlockModel,
186
307
  AbsorbanceReaderModel,
308
+ FlexStackerModuleModel,
187
309
  ]
188
310
 
189
311
 
@@ -225,3 +347,71 @@ class LidStatus(str, Enum):
225
347
  OFF = "off"
226
348
  UNKNOWN = "unknown"
227
349
  ERROR = "error"
350
+
351
+
352
+ class FlexStackerStatus(str, Enum):
353
+ IDLE = "idle"
354
+ DISPENSING = "dispensing"
355
+ STORING = "storing"
356
+ ERROR = "error"
357
+
358
+
359
+ class PlatformState(str, Enum):
360
+ UNKNOWN = "unknown"
361
+ EXTENDED = "extended"
362
+ RETRACTED = "retracted"
363
+
364
+ @classmethod
365
+ def from_status(cls, status: PlatformStatus) -> "PlatformState":
366
+ """Get the state from the platform status."""
367
+ if status.E and not status.R:
368
+ return cls.EXTENDED
369
+ if status.R and not status.E:
370
+ return cls.RETRACTED
371
+ return cls.UNKNOWN
372
+
373
+
374
+ class StackerAxisState(str, Enum):
375
+ UNKNOWN = "unknown"
376
+ EXTENDED = "extended"
377
+ RETRACTED = "retracted"
378
+
379
+ @classmethod
380
+ def from_status(
381
+ cls, status: LimitSwitchStatus, axis: StackerAxis
382
+ ) -> "StackerAxisState":
383
+ """Get the axis state from the limit switch status."""
384
+ match axis:
385
+ case StackerAxis.X:
386
+ if status.XE and not status.XR:
387
+ return cls.EXTENDED
388
+ if status.XR and not status.XE:
389
+ return cls.RETRACTED
390
+ case StackerAxis.Z:
391
+ if status.ZE and not status.ZR:
392
+ return cls.EXTENDED
393
+ if status.ZR and not status.ZE:
394
+ return cls.RETRACTED
395
+ case StackerAxis.L:
396
+ return cls.EXTENDED if status.LR else cls.RETRACTED
397
+ return cls.UNKNOWN
398
+
399
+
400
+ class LatchState(str, Enum):
401
+ CLOSED = "closed"
402
+ OPENED = "opened"
403
+
404
+ @classmethod
405
+ def from_state(cls, state: StackerAxisState) -> "LatchState":
406
+ """Get the latch state from the axis state."""
407
+ return cls.CLOSED if state == StackerAxisState.EXTENDED else cls.OPENED
408
+
409
+
410
+ class HopperDoorState(str, Enum):
411
+ CLOSED = "closed"
412
+ OPENED = "opened"
413
+
414
+ @classmethod
415
+ def from_state(cls, state: bool) -> "HopperDoorState":
416
+ """Get the hopper door state from the door state boolean."""
417
+ return cls.CLOSED if state else cls.OPENED
@@ -13,6 +13,7 @@ from .magdeck import MagDeck
13
13
  from .thermocycler import Thermocycler
14
14
  from .heater_shaker import HeaterShaker
15
15
  from .absorbance_reader import AbsorbanceReader
16
+ from .flex_stacker import FlexStacker
16
17
 
17
18
 
18
19
  log = logging.getLogger(__name__)
@@ -26,6 +27,7 @@ MODULE_TYPE_BY_NAME = {
26
27
  Thermocycler.name(): Thermocycler.MODULE_TYPE,
27
28
  HeaterShaker.name(): HeaterShaker.MODULE_TYPE,
28
29
  AbsorbanceReader.name(): AbsorbanceReader.MODULE_TYPE,
30
+ FlexStacker.name(): FlexStacker.MODULE_TYPE,
29
31
  }
30
32
 
31
33
  _MODULE_CLS_BY_TYPE: Dict[ModuleType, Type[AbstractModule]] = {
@@ -34,6 +36,7 @@ _MODULE_CLS_BY_TYPE: Dict[ModuleType, Type[AbstractModule]] = {
34
36
  Thermocycler.MODULE_TYPE: Thermocycler,
35
37
  HeaterShaker.MODULE_TYPE: HeaterShaker,
36
38
  AbsorbanceReader.MODULE_TYPE: AbsorbanceReader,
39
+ FlexStacker.MODULE_TYPE: FlexStacker,
37
40
  }
38
41
 
39
42
 
@@ -192,6 +192,26 @@ def target_position_from_plunger(
192
192
  return all_axes_pos
193
193
 
194
194
 
195
+ def target_positions_from_plunger_tracking(
196
+ mount: Union[Mount, OT3Mount],
197
+ plunger_delta: float,
198
+ z_delta: float,
199
+ current_position: Dict[Axis, float],
200
+ ) -> "OrderedDict[Axis, float]":
201
+ """Create a target position for machine axes including plungers for dynamic liquid tracking.
202
+
203
+ The x/y axes remain constant but the plunger and Z move to create a tracking action.
204
+
205
+ plunger_delta: the distance the plunger should move- should be determined based on desired
206
+ volume to aspirate/dispense.
207
+ z_delta: the distance to move the z axis- should be determined based on volume and well geometry.
208
+ """
209
+ all_axes_pos = target_position_from_plunger(mount, plunger_delta, current_position)
210
+ z_ax = Axis.by_mount(mount)
211
+ all_axes_pos[z_ax] = current_position[z_ax] + z_delta
212
+ return all_axes_pos
213
+
214
+
195
215
  def deck_point_from_machine_point(
196
216
  machine_point: Point, attitude: AttitudeMatrix, offset: Point
197
217
  ) -> Point:
@@ -99,6 +99,7 @@ from .types import (
99
99
  HardwareFeatureFlags,
100
100
  FailedTipStateCheck,
101
101
  PipetteSensorResponseQueue,
102
+ TipScrapeType,
102
103
  )
103
104
  from .errors import (
104
105
  UpdateOngoingError,
@@ -126,6 +127,7 @@ from .motion_utilities import (
126
127
  target_position_from_absolute,
127
128
  target_position_from_relative,
128
129
  target_position_from_plunger,
130
+ target_positions_from_plunger_tracking,
129
131
  offset_for_mount,
130
132
  deck_from_machine,
131
133
  machine_from_deck,
@@ -450,9 +452,11 @@ class OT3API(
450
452
  checked_config = config
451
453
 
452
454
  backend = await OT3Simulator.build(
453
- {OT3Mount.from_mount(k): v for k, v in attached_instruments.items()}
454
- if attached_instruments
455
- else {},
455
+ (
456
+ {OT3Mount.from_mount(k): v for k, v in attached_instruments.items()}
457
+ if attached_instruments
458
+ else {}
459
+ ),
456
460
  checked_modules,
457
461
  checked_config,
458
462
  checked_loop,
@@ -2051,12 +2055,16 @@ class OT3API(
2051
2055
  mount: Union[top_types.Mount, OT3Mount],
2052
2056
  volume: Optional[float] = None,
2053
2057
  rate: float = 1.0,
2058
+ correction_volume: float = 0.0,
2054
2059
  ) -> None:
2055
2060
  """
2056
2061
  Aspirate a volume of liquid (in microliters/uL) using this pipette."""
2057
2062
  realmount = OT3Mount.from_mount(mount)
2058
2063
  aspirate_spec = self._pipette_handler.plan_check_aspirate(
2059
- realmount, volume, rate
2064
+ mount=realmount,
2065
+ volume=volume,
2066
+ rate=rate,
2067
+ correction_volume=correction_volume,
2060
2068
  )
2061
2069
  if not aspirate_spec:
2062
2070
  return
@@ -2093,12 +2101,19 @@ class OT3API(
2093
2101
  volume: Optional[float] = None,
2094
2102
  rate: float = 1.0,
2095
2103
  push_out: Optional[float] = None,
2104
+ correction_volume: float = 0.0,
2105
+ is_full_dispense: bool = False,
2096
2106
  ) -> None:
2097
2107
  """
2098
2108
  Dispense a volume of liquid in microliters(uL) using this pipette."""
2099
2109
  realmount = OT3Mount.from_mount(mount)
2100
2110
  dispense_spec = self._pipette_handler.plan_check_dispense(
2101
- realmount, volume, rate, push_out
2111
+ mount=realmount,
2112
+ volume=volume,
2113
+ rate=rate,
2114
+ push_out=push_out,
2115
+ is_full_dispense=is_full_dispense,
2116
+ correction_volume=correction_volume,
2102
2117
  )
2103
2118
  if not dispense_spec:
2104
2119
  return
@@ -2329,6 +2344,7 @@ class OT3API(
2329
2344
  mount: Union[top_types.Mount, OT3Mount],
2330
2345
  home_after: bool = False,
2331
2346
  ignore_plunger: bool = False,
2347
+ scrape_type: TipScrapeType = TipScrapeType.NONE,
2332
2348
  ) -> None:
2333
2349
  realmount = OT3Mount.from_mount(mount)
2334
2350
  if ignore_plunger is False:
@@ -2340,18 +2356,27 @@ class OT3API(
2340
2356
  spec = self._pipette_handler.plan_ht_drop_tip()
2341
2357
  await self._tip_motor_action(realmount, spec.tip_action_moves)
2342
2358
  else:
2343
- spec = self._pipette_handler.plan_lt_drop_tip(realmount)
2359
+ spec = self._pipette_handler.plan_lt_drop_tip(realmount, scrape_type)
2344
2360
  for move in spec.tip_action_moves:
2345
2361
  async with self._backend.motor_current(move.currents):
2346
- target_pos = target_position_from_plunger(
2347
- realmount, move.distance, self._current_position
2348
- )
2349
- await self._move(
2350
- target_pos,
2351
- speed=move.speed,
2352
- home_flagged_axes=False,
2353
- )
2354
-
2362
+ if not move.scrape_axis:
2363
+ target_pos = target_position_from_plunger(
2364
+ realmount, move.distance, self._current_position
2365
+ )
2366
+ await self._move(
2367
+ target_pos,
2368
+ speed=move.speed,
2369
+ home_flagged_axes=False,
2370
+ )
2371
+ else:
2372
+ target_pos = OrderedDict(self._current_position)
2373
+ target_pos[move.scrape_axis] += move.distance
2374
+ self._log.info(f"Moving to target Pos: {target_pos}")
2375
+ await self._move(
2376
+ target_pos,
2377
+ speed=move.speed,
2378
+ home_flagged_axes=False,
2379
+ )
2355
2380
  for shake in spec.shake_off_moves:
2356
2381
  await self.move_rel(mount, shake[0], speed=shake[1])
2357
2382
 
@@ -2974,6 +2999,103 @@ class OT3API(
2974
2999
 
2975
3000
  AMKey = TypeVar("AMKey")
2976
3001
 
3002
+ async def aspirate_while_tracking(
3003
+ self,
3004
+ mount: Union[top_types.Mount, OT3Mount],
3005
+ z_distance: float,
3006
+ volume: float,
3007
+ flow_rate: float = 1.0,
3008
+ ) -> None:
3009
+ """
3010
+ Aspirate a volume of liquid (in microliters/uL) while moving the z axis synchronously.
3011
+
3012
+ :param mount: A robot mount that the instrument is on.
3013
+ :param z_distance: The distance the z axis will move during apsiration.
3014
+ :param volume: The volume of liquid to be aspirated.
3015
+ :param flow_rate: The flow rate to aspirate with.
3016
+ """
3017
+ realmount = OT3Mount.from_mount(mount)
3018
+ aspirate_spec = self._pipette_handler.plan_check_aspirate(
3019
+ realmount, volume, flow_rate
3020
+ )
3021
+ if not aspirate_spec:
3022
+ return
3023
+ target_pos = target_positions_from_plunger_tracking(
3024
+ realmount,
3025
+ aspirate_spec.plunger_distance,
3026
+ z_distance,
3027
+ self._current_position,
3028
+ )
3029
+ try:
3030
+ await self._backend.set_active_current(
3031
+ {aspirate_spec.axis: aspirate_spec.current}
3032
+ )
3033
+ async with self.restore_system_constrants():
3034
+ await self.set_system_constraints_for_plunger_acceleration(
3035
+ realmount, aspirate_spec.acceleration
3036
+ )
3037
+ await self._move(
3038
+ target_pos,
3039
+ speed=aspirate_spec.speed,
3040
+ home_flagged_axes=False,
3041
+ )
3042
+ except Exception:
3043
+ self._log.exception("Aspirate failed")
3044
+ aspirate_spec.instr.set_current_volume(0)
3045
+ raise
3046
+ else:
3047
+ aspirate_spec.instr.add_current_volume(aspirate_spec.volume)
3048
+
3049
+ async def dispense_while_tracking(
3050
+ self,
3051
+ mount: Union[top_types.Mount, OT3Mount],
3052
+ z_distance: float,
3053
+ volume: float,
3054
+ push_out: Optional[float],
3055
+ flow_rate: float = 1.0,
3056
+ is_full_dispense: bool = False,
3057
+ ) -> None:
3058
+ """
3059
+ Dispense a volume of liquid (in microliters/uL) while moving the z axis synchronously.
3060
+
3061
+ :param mount: A robot mount that the instrument is on.
3062
+ :param z_distance: The distance the z axis will move during dispensing.
3063
+ :param volume: The volume of liquid to be dispensed.
3064
+ :param flow_rate: The flow rate to dispense with.
3065
+ """
3066
+ realmount = OT3Mount.from_mount(mount)
3067
+ dispense_spec = self._pipette_handler.plan_check_dispense(
3068
+ realmount, volume, flow_rate, push_out, is_full_dispense
3069
+ )
3070
+ if not dispense_spec:
3071
+ return
3072
+ target_pos = target_positions_from_plunger_tracking(
3073
+ realmount,
3074
+ dispense_spec.plunger_distance,
3075
+ z_distance,
3076
+ self._current_position,
3077
+ )
3078
+
3079
+ try:
3080
+ await self._backend.set_active_current(
3081
+ {dispense_spec.axis: dispense_spec.current}
3082
+ )
3083
+ async with self.restore_system_constrants():
3084
+ await self.set_system_constraints_for_plunger_acceleration(
3085
+ realmount, dispense_spec.acceleration
3086
+ )
3087
+ await self._move(
3088
+ target_pos,
3089
+ speed=dispense_spec.speed,
3090
+ home_flagged_axes=False,
3091
+ )
3092
+ except Exception:
3093
+ self._log.exception("dispense failed")
3094
+ dispense_spec.instr.set_current_volume(0)
3095
+ raise
3096
+ else:
3097
+ dispense_spec.instr.remove_current_volume(dispense_spec.volume)
3098
+
2977
3099
  @property
2978
3100
  def attached_subsystems(self) -> Dict[SubSystem, SubSystemState]:
2979
3101
  """Get a view of the state of the currently-attached subsystems."""
@@ -3010,3 +3132,11 @@ class OT3API(
3010
3132
 
3011
3133
  async def get_hepa_uv_state(self) -> Optional[HepaUVState]:
3012
3134
  return await self._backend.get_hepa_uv_state()
3135
+
3136
+ async def increase_evo_disp_count(
3137
+ self,
3138
+ mount: Union[top_types.Mount, OT3Mount],
3139
+ ) -> None:
3140
+ """Tell a pipette to increase its evo-tip-dispense-count in eeprom."""
3141
+ realmount = OT3Mount.from_mount(mount)
3142
+ await self._backend.increase_evo_disp_count(realmount)
@@ -2,7 +2,7 @@ from typing import Optional
2
2
  from typing_extensions import Protocol
3
3
 
4
4
  from opentrons.types import Point
5
- from opentrons.hardware_control.types import CriticalPoint
5
+ from opentrons.hardware_control.types import CriticalPoint, TipScrapeType
6
6
  from .types import MountArgType, CalibrationType, ConfigType
7
7
 
8
8
  from .instrument_configurer import InstrumentConfigurer
@@ -98,6 +98,7 @@ class LiquidHandler(
98
98
  mount: MountArgType,
99
99
  volume: Optional[float] = None,
100
100
  rate: float = 1.0,
101
+ correction_volume: float = 0.0,
101
102
  ) -> None:
102
103
  """
103
104
  Aspirate a volume of liquid (in microliters/uL) using this pipette
@@ -117,6 +118,24 @@ class LiquidHandler(
117
118
  volume : [float] The number of microliters to aspirate
118
119
  rate : [float] Set plunger speed for this aspirate, where
119
120
  speed = rate * aspirate_speed
121
+ correction_volume : Correction volume in uL for the specified aspirate volume
122
+ """
123
+ ...
124
+
125
+ async def aspirate_while_tracking(
126
+ self,
127
+ mount: MountArgType,
128
+ z_distance: float,
129
+ volume: float,
130
+ flow_rate: float = 1.0,
131
+ ) -> None:
132
+ """
133
+ Aspirate a volume of liquid (in microliters/uL) while moving the z axis synchronously.
134
+
135
+ :param mount: A robot mount that the instrument is on.
136
+ :param z_distance: The distance the z axis will move during apsiration.
137
+ :param volume: The volume of liquid to be aspirated.
138
+ :param flow_rate: The flow rate to aspirate with.
120
139
  """
121
140
  ...
122
141
 
@@ -126,6 +145,8 @@ class LiquidHandler(
126
145
  volume: Optional[float] = None,
127
146
  rate: float = 1.0,
128
147
  push_out: Optional[float] = None,
148
+ correction_volume: float = 0.0,
149
+ is_full_dispense: bool = False,
129
150
  ) -> None:
130
151
  """
131
152
  Dispense a volume of liquid in microliters(uL) using this pipette
@@ -136,6 +157,26 @@ class LiquidHandler(
136
157
  volume : [float] The number of microliters to dispense
137
158
  rate : [float] Set plunger speed for this dispense, where
138
159
  speed = rate * dispense_speed
160
+ correction_volume : Correction volume in uL for the specified dispense volume
161
+ """
162
+ ...
163
+
164
+ async def dispense_while_tracking(
165
+ self,
166
+ mount: MountArgType,
167
+ z_distance: float,
168
+ volume: float,
169
+ push_out: Optional[float],
170
+ flow_rate: float = 1.0,
171
+ is_full_dispense: bool = False,
172
+ ) -> None:
173
+ """
174
+ Dispense a volume of liquid (in microliters/uL) while moving the z axis synchronously.
175
+
176
+ :param mount: A robot mount that the instrument is on.
177
+ :param z_distance: The distance the z axis will move during dispensing.
178
+ :param volume: The volume of liquid to be dispensed.
179
+ :param flow_rate: The flow rate to dispense with.
139
180
  """
140
181
  ...
141
182
 
@@ -187,6 +228,7 @@ class LiquidHandler(
187
228
  mount: MountArgType,
188
229
  home_after: bool = True,
189
230
  ignore_plunger: bool = False,
231
+ scrape_type: TipScrapeType = TipScrapeType.NONE,
190
232
  ) -> None:
191
233
  ...
192
234
 
@@ -218,3 +260,7 @@ class LiquidHandler(
218
260
  max_z_dist : maximum depth to probe for liquid
219
261
  """
220
262
  ...
263
+
264
+ async def increase_evo_disp_count(self, mount: MountArgType) -> None:
265
+ """Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
266
+ ...
@@ -636,6 +636,12 @@ class InstrumentProbeType(enum.Enum):
636
636
  BOTH = enum.auto()
637
637
 
638
638
 
639
+ class TipScrapeType(enum.Enum):
640
+ NONE = enum.auto()
641
+ RIGHT_ONE_COL = enum.auto()
642
+ LEFT_ONE_COL = enum.auto()
643
+
644
+
639
645
  class GripperProbe(enum.Enum):
640
646
  FRONT = enum.auto()
641
647
  REAR = enum.auto()