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
@@ -1,5 +1,5 @@
1
1
  """Pipetting command handling."""
2
- from typing import Optional, Iterator
2
+ from typing import Optional, Iterator, Tuple
3
3
  from typing_extensions import Protocol as TypingProtocol
4
4
  from contextlib import contextmanager
5
5
 
@@ -13,8 +13,13 @@ from ..errors.exceptions import (
13
13
  InvalidAspirateVolumeError,
14
14
  InvalidPushOutVolumeError,
15
15
  InvalidDispenseVolumeError,
16
+ InvalidLiquidHeightFound,
16
17
  )
17
18
  from opentrons.protocol_engine.types import WellLocation
19
+ from opentrons.protocol_engine.types.liquid_level_detection import (
20
+ SimulatedProbeResult,
21
+ LiquidTrackingType,
22
+ )
18
23
 
19
24
  # 1e-9 µL (1 femtoliter!) is a good value because:
20
25
  # * It's large relative to rounding errors that occur in practice in protocols. For
@@ -30,6 +35,9 @@ _VOLUME_ROUNDING_ERROR_TOLERANCE = 1e-9
30
35
  class PipettingHandler(TypingProtocol):
31
36
  """Liquid handling commands."""
32
37
 
38
+ def get_state_view(self) -> StateView:
39
+ """Get the stateview associated with this handler."""
40
+
33
41
  def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
34
42
  """Get whether a pipette is ready to aspirate."""
35
43
 
@@ -42,15 +50,41 @@ class PipettingHandler(TypingProtocol):
42
50
  volume: float,
43
51
  flow_rate: float,
44
52
  command_note_adder: CommandNoteAdder,
53
+ correction_volume: float = 0.0,
45
54
  ) -> float:
46
55
  """Set flow-rate and aspirate."""
47
56
 
57
+ async def aspirate_while_tracking(
58
+ self,
59
+ pipette_id: str,
60
+ labware_id: str,
61
+ well_name: str,
62
+ volume: float,
63
+ flow_rate: float,
64
+ command_note_adder: CommandNoteAdder,
65
+ ) -> float:
66
+ """Set flow-rate and aspirate while tracking."""
67
+
68
+ async def dispense_while_tracking(
69
+ self,
70
+ pipette_id: str,
71
+ labware_id: str,
72
+ well_name: str,
73
+ volume: float,
74
+ flow_rate: float,
75
+ push_out: Optional[float],
76
+ is_full_dispense: bool = False,
77
+ ) -> float:
78
+ """Set flow-rate and dispense while tracking."""
79
+
48
80
  async def dispense_in_place(
49
81
  self,
50
82
  pipette_id: str,
51
83
  volume: float,
52
84
  flow_rate: float,
53
85
  push_out: Optional[float],
86
+ is_full_dispense: bool,
87
+ correction_volume: float = 0.0,
54
88
  ) -> float:
55
89
  """Set flow-rate and dispense."""
56
90
 
@@ -67,18 +101,25 @@ class PipettingHandler(TypingProtocol):
67
101
  labware_id: str,
68
102
  well_name: str,
69
103
  well_location: WellLocation,
70
- ) -> float:
104
+ ) -> LiquidTrackingType:
71
105
  """Detect liquid level."""
72
106
 
107
+ async def increase_evo_disp_count(self, pipette_id: str) -> None:
108
+ """Increase evo tip dispense action count."""
109
+
73
110
 
74
111
  class HardwarePipettingHandler(PipettingHandler):
75
- """Liquid handling, using the Hardware API.""" ""
112
+ """Liquid handling, using the Hardware API."""
76
113
 
77
114
  def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> None:
78
115
  """Initialize a PipettingHandler instance."""
79
116
  self._state_view = state_view
80
117
  self._hardware_api = hardware_api
81
118
 
119
+ def get_state_view(self) -> StateView:
120
+ """Get the stateview associated with this handler."""
121
+ return self._state_view
122
+
82
123
  def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
83
124
  """Get whether a pipette is ready to aspirate."""
84
125
  hw_pipette = self._state_view.pipettes.get_hardware_pipette(
@@ -88,6 +129,7 @@ class HardwarePipettingHandler(PipettingHandler):
88
129
  return (
89
130
  self._state_view.pipettes.get_aspirated_volume(pipette_id) is not None
90
131
  and hw_pipette.config["ready_to_aspirate"]
132
+ and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
91
133
  )
92
134
 
93
135
  async def prepare_for_aspirate(self, pipette_id: str) -> None:
@@ -99,10 +141,48 @@ class HardwarePipettingHandler(PipettingHandler):
99
141
  hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
100
142
  await self._hardware_api.prepare_for_aspirate(mount=hw_mount)
101
143
 
102
- async def aspirate_in_place(
144
+ def get_hw_aspirate_params(
145
+ self,
146
+ pipette_id: str,
147
+ volume: float,
148
+ command_note_adder: CommandNoteAdder,
149
+ ) -> Tuple[HardwarePipette, float]:
150
+ """Get params for hardware aspirate."""
151
+ _adjusted_volume = _validate_aspirate_volume(
152
+ state_view=self._state_view,
153
+ pipette_id=pipette_id,
154
+ aspirate_volume=volume,
155
+ command_note_adder=command_note_adder,
156
+ )
157
+ _hw_pipette = self._state_view.pipettes.get_hardware_pipette(
158
+ pipette_id=pipette_id,
159
+ attached_pipettes=self._hardware_api.attached_instruments,
160
+ )
161
+ return _hw_pipette, _adjusted_volume
162
+
163
+ def get_hw_dispense_params(
103
164
  self,
104
165
  pipette_id: str,
105
166
  volume: float,
167
+ ) -> Tuple[HardwarePipette, float]:
168
+ """Get params for hardware dispense."""
169
+ _adjusted_volume = _validate_dispense_volume(
170
+ state_view=self._state_view,
171
+ pipette_id=pipette_id,
172
+ dispense_volume=volume,
173
+ )
174
+ _hw_pipette = self._state_view.pipettes.get_hardware_pipette(
175
+ pipette_id=pipette_id,
176
+ attached_pipettes=self._hardware_api.attached_instruments,
177
+ )
178
+ return _hw_pipette, _adjusted_volume
179
+
180
+ async def aspirate_while_tracking(
181
+ self,
182
+ pipette_id: str,
183
+ labware_id: str,
184
+ well_name: str,
185
+ volume: float,
106
186
  flow_rate: float,
107
187
  command_note_adder: CommandNoteAdder,
108
188
  ) -> float:
@@ -112,19 +192,85 @@ class HardwarePipettingHandler(PipettingHandler):
112
192
  PipetteOverpressureError, propagated as-is from the hardware controller.
113
193
  """
114
194
  # get mount and config data from state and hardware controller
115
- adjusted_volume = _validate_aspirate_volume(
116
- state_view=self._state_view,
117
- pipette_id=pipette_id,
118
- aspirate_volume=volume,
119
- command_note_adder=command_note_adder,
195
+ hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
196
+ pipette_id, volume, command_note_adder
120
197
  )
121
- hw_pipette = self._state_view.pipettes.get_hardware_pipette(
122
- pipette_id=pipette_id,
123
- attached_pipettes=self._hardware_api.attached_instruments,
198
+ aspirate_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
199
+ labware_id=labware_id,
200
+ well_name=well_name,
201
+ operation_volume=volume * -1,
202
+ )
203
+ if isinstance(aspirate_z_distance, SimulatedProbeResult):
204
+ raise InvalidLiquidHeightFound(
205
+ "Aspirate distance must be a float in Hardware pipetting handler."
206
+ )
207
+ with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
208
+ await self._hardware_api.aspirate_while_tracking(
209
+ mount=hw_pipette.mount,
210
+ z_distance=aspirate_z_distance,
211
+ flow_rate=flow_rate,
212
+ volume=adjusted_volume,
213
+ )
214
+ return adjusted_volume
215
+
216
+ async def dispense_while_tracking(
217
+ self,
218
+ pipette_id: str,
219
+ labware_id: str,
220
+ well_name: str,
221
+ volume: float,
222
+ flow_rate: float,
223
+ push_out: Optional[float],
224
+ is_full_dispense: bool = False,
225
+ ) -> float:
226
+ """Set flow-rate and dispense.
227
+
228
+ Raises:
229
+ PipetteOverpressureError, propagated as-is from the hardware controller.
230
+ """
231
+ # get mount and config data from state and hardware controller
232
+ hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
233
+ dispense_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
234
+ labware_id=labware_id,
235
+ well_name=well_name,
236
+ operation_volume=volume,
237
+ )
238
+ if isinstance(dispense_z_distance, SimulatedProbeResult):
239
+ raise InvalidLiquidHeightFound(
240
+ "Dispense distance must be a float in Hardware pipetting handler."
241
+ )
242
+ with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
243
+ await self._hardware_api.dispense_while_tracking(
244
+ mount=hw_pipette.mount,
245
+ z_distance=dispense_z_distance,
246
+ flow_rate=flow_rate,
247
+ volume=adjusted_volume,
248
+ push_out=push_out,
249
+ )
250
+ return adjusted_volume
251
+
252
+ async def aspirate_in_place(
253
+ self,
254
+ pipette_id: str,
255
+ volume: float,
256
+ flow_rate: float,
257
+ command_note_adder: CommandNoteAdder,
258
+ correction_volume: float = 0.0,
259
+ ) -> float:
260
+ """Set flow-rate and aspirate.
261
+
262
+ Raises:
263
+ PipetteOverpressureError, propagated as-is from the hardware controller.
264
+ """
265
+ # get mount and config data from state and hardware controller
266
+ hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
267
+ pipette_id, volume, command_note_adder
124
268
  )
125
269
  with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
126
270
  await self._hardware_api.aspirate(
127
- mount=hw_pipette.mount, volume=adjusted_volume
271
+ mount=hw_pipette.mount,
272
+ volume=adjusted_volume,
273
+ correction_volume=correction_volume,
128
274
  )
129
275
 
130
276
  return adjusted_volume
@@ -135,15 +281,11 @@ class HardwarePipettingHandler(PipettingHandler):
135
281
  volume: float,
136
282
  flow_rate: float,
137
283
  push_out: Optional[float],
284
+ is_full_dispense: bool,
285
+ correction_volume: float = 0.0,
138
286
  ) -> float:
139
287
  """Dispense liquid without moving the pipette."""
140
- adjusted_volume = _validate_dispense_volume(
141
- state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
142
- )
143
- hw_pipette = self._state_view.pipettes.get_hardware_pipette(
144
- pipette_id=pipette_id,
145
- attached_pipettes=self._hardware_api.attached_instruments,
146
- )
288
+ hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
147
289
  # TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
148
290
  if push_out and push_out < 0:
149
291
  raise InvalidPushOutVolumeError(
@@ -151,7 +293,11 @@ class HardwarePipettingHandler(PipettingHandler):
151
293
  )
152
294
  with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
153
295
  await self._hardware_api.dispense(
154
- mount=hw_pipette.mount, volume=adjusted_volume, push_out=push_out
296
+ mount=hw_pipette.mount,
297
+ volume=adjusted_volume,
298
+ push_out=push_out,
299
+ correction_volume=correction_volume,
300
+ is_full_dispense=is_full_dispense,
155
301
  )
156
302
 
157
303
  return adjusted_volume
@@ -176,7 +322,7 @@ class HardwarePipettingHandler(PipettingHandler):
176
322
  labware_id: str,
177
323
  well_name: str,
178
324
  well_location: WellLocation,
179
- ) -> float:
325
+ ) -> LiquidTrackingType:
180
326
  """Detect liquid level."""
181
327
  hw_pipette = self._state_view.pipettes.get_hardware_pipette(
182
328
  pipette_id=pipette_id,
@@ -223,6 +369,14 @@ class HardwarePipettingHandler(PipettingHandler):
223
369
  blow_out=original_blow_out_rate,
224
370
  )
225
371
 
372
+ async def increase_evo_disp_count(self, pipette_id: str) -> None:
373
+ """Increase evo tip dispense action count."""
374
+ hw_pipette = self._state_view.pipettes.get_hardware_pipette(
375
+ pipette_id=pipette_id,
376
+ attached_pipettes=self._hardware_api.attached_instruments,
377
+ )
378
+ await self._hardware_api.increase_evo_disp_count(mount=hw_pipette.mount)
379
+
226
380
 
227
381
  class VirtualPipettingHandler(PipettingHandler):
228
382
  """Liquid handling, using the virtual pipettes.""" ""
@@ -236,12 +390,19 @@ class VirtualPipettingHandler(PipettingHandler):
236
390
  """Initialize a PipettingHandler instance."""
237
391
  self._state_view = state_view
238
392
 
393
+ def get_state_view(self) -> StateView:
394
+ """Get the stateview associated with this handler."""
395
+ return self._state_view
396
+
239
397
  def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
240
398
  """Get whether a pipette is ready to aspirate."""
241
- return self._state_view.pipettes.get_aspirated_volume(pipette_id) is not None
399
+ return self._state_view.pipettes.get_aspirated_volume(
400
+ pipette_id
401
+ ) is not None and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
242
402
 
243
403
  async def prepare_for_aspirate(self, pipette_id: str) -> None:
244
404
  """Virtually prepare to aspirate (no-op)."""
405
+ self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
245
406
 
246
407
  async def aspirate_in_place(
247
408
  self,
@@ -249,6 +410,7 @@ class VirtualPipettingHandler(PipettingHandler):
249
410
  volume: float,
250
411
  flow_rate: float,
251
412
  command_note_adder: CommandNoteAdder,
413
+ correction_volume: float = 0.0,
252
414
  ) -> float:
253
415
  """Virtually aspirate (no-op)."""
254
416
  self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
@@ -265,6 +427,8 @@ class VirtualPipettingHandler(PipettingHandler):
265
427
  volume: float,
266
428
  flow_rate: float,
267
429
  push_out: Optional[float],
430
+ is_full_dispense: bool,
431
+ correction_volume: float = 0.0,
268
432
  ) -> float:
269
433
  """Virtually dispense (no-op)."""
270
434
  # TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
@@ -290,10 +454,9 @@ class VirtualPipettingHandler(PipettingHandler):
290
454
  labware_id: str,
291
455
  well_name: str,
292
456
  well_location: WellLocation,
293
- ) -> float:
457
+ ) -> LiquidTrackingType:
294
458
  """Detect liquid level."""
295
- well_def = self._state_view.labware.get_well_definition(labware_id, well_name)
296
- return well_def.depth
459
+ return SimulatedProbeResult()
297
460
 
298
461
  def _validate_tip_attached(self, pipette_id: str, command_name: str) -> None:
299
462
  """Validate if there is a tip attached."""
@@ -303,6 +466,50 @@ class VirtualPipettingHandler(PipettingHandler):
303
466
  f"Cannot perform {command_name} without a tip attached"
304
467
  )
305
468
 
469
+ async def aspirate_while_tracking(
470
+ self,
471
+ pipette_id: str,
472
+ labware_id: str,
473
+ well_name: str,
474
+ volume: float,
475
+ flow_rate: float,
476
+ command_note_adder: CommandNoteAdder,
477
+ ) -> float:
478
+ """Virtually aspirate (no-op)."""
479
+ self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
480
+
481
+ return _validate_aspirate_volume(
482
+ state_view=self._state_view,
483
+ pipette_id=pipette_id,
484
+ aspirate_volume=volume,
485
+ command_note_adder=command_note_adder,
486
+ )
487
+
488
+ async def dispense_while_tracking(
489
+ self,
490
+ pipette_id: str,
491
+ labware_id: str,
492
+ well_name: str,
493
+ volume: float,
494
+ flow_rate: float,
495
+ push_out: Optional[float],
496
+ is_full_dispense: bool = False,
497
+ ) -> float:
498
+ """Virtually dispense (no-op)."""
499
+ # TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
500
+ if push_out and push_out < 0:
501
+ raise InvalidPushOutVolumeError(
502
+ "push out value cannot have a negative value."
503
+ )
504
+ self._validate_tip_attached(pipette_id=pipette_id, command_name="dispense")
505
+ return _validate_dispense_volume(
506
+ state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
507
+ )
508
+
509
+ async def increase_evo_disp_count(self, pipette_id: str) -> None:
510
+ """Increase evo tip dispense action count."""
511
+ pass
512
+
306
513
 
307
514
  def create_pipetting_handler(
308
515
  state_view: StateView, hardware_api: HardwareControlAPI
@@ -4,7 +4,11 @@ from typing import Optional, Dict
4
4
  from typing_extensions import Protocol as TypingProtocol
5
5
 
6
6
  from opentrons.hardware_control import HardwareControlAPI
7
- from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType
7
+ from opentrons.hardware_control.types import (
8
+ FailedTipStateCheck,
9
+ InstrumentProbeType,
10
+ TipScrapeType,
11
+ )
8
12
  from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError
9
13
  from opentrons.types import Mount, NozzleConfigurationType
10
14
 
@@ -82,6 +86,7 @@ class TipHandler(TypingProtocol):
82
86
  home_after: Optional[bool],
83
87
  do_not_ignore_tip_presence: bool = True,
84
88
  ignore_plunger: bool = False,
89
+ scrape_type: TipScrapeType = TipScrapeType.NONE,
85
90
  ) -> None:
86
91
  """Drop the attached tip into the current location.
87
92
 
@@ -279,19 +284,22 @@ class HardwareTipHandler(TipHandler):
279
284
  home_after: Optional[bool],
280
285
  do_not_ignore_tip_presence: bool = True,
281
286
  ignore_plunger: bool = False,
287
+ scrape_type: TipScrapeType = TipScrapeType.NONE,
282
288
  ) -> None:
283
289
  """See documentation on abstract base class."""
284
290
  hw_mount = self._get_hw_mount(pipette_id)
285
291
 
286
292
  # Let the hardware controller handle defaulting home_after since its behavior
287
293
  # differs between machines
294
+ kwargs = {}
288
295
  if home_after is not None:
289
- kwargs = {"home_after": home_after}
290
- else:
291
- kwargs = {}
296
+ kwargs["home_after"] = home_after
292
297
 
293
298
  await self._hardware_api.tip_drop_moves(
294
- mount=hw_mount, ignore_plunger=ignore_plunger, **kwargs
299
+ mount=hw_mount,
300
+ ignore_plunger=ignore_plunger,
301
+ scrape_type=scrape_type,
302
+ **kwargs,
295
303
  )
296
304
 
297
305
  if do_not_ignore_tip_presence:
@@ -445,6 +453,7 @@ class VirtualTipHandler(TipHandler):
445
453
  home_after: Optional[bool],
446
454
  do_not_ignore_tip_presence: bool = True,
447
455
  ignore_plunger: bool = False,
456
+ scrape_type: TipScrapeType = TipScrapeType.NONE,
448
457
  ) -> None:
449
458
  """Pick up a tip at the current location using a virtual pipette.
450
459
 
@@ -0,0 +1,173 @@
1
+ """Convert labware offset creation requests and stored elements between legacy and new."""
2
+
3
+ from opentrons_shared_data.robot.types import RobotType
4
+ from opentrons_shared_data.deck.types import DeckDefinitionV5
5
+ from .errors import (
6
+ OffsetLocationInvalidError,
7
+ FixtureDoesNotExistError,
8
+ )
9
+ from .types import (
10
+ LabwareOffsetCreate,
11
+ LegacyLabwareOffsetCreate,
12
+ LabwareOffsetCreateInternal,
13
+ LegacyLabwareOffsetLocation,
14
+ LabwareOffsetLocationSequence,
15
+ OnLabwareOffsetLocationSequenceComponent,
16
+ OnAddressableAreaOffsetLocationSequenceComponent,
17
+ OnModuleOffsetLocationSequenceComponent,
18
+ ModuleModel,
19
+ )
20
+ from .resources import deck_configuration_provider
21
+
22
+
23
+ def standardize_labware_offset_create(
24
+ request: LabwareOffsetCreate | LegacyLabwareOffsetCreate,
25
+ robot_type: RobotType,
26
+ deck_definition: DeckDefinitionV5,
27
+ ) -> LabwareOffsetCreateInternal:
28
+ """Turn a union of old and new labware offset create requests into a new one."""
29
+ location_sequence, legacy_location = _locations_for_create(
30
+ request, robot_type, deck_definition
31
+ )
32
+ return LabwareOffsetCreateInternal(
33
+ definitionUri=request.definitionUri,
34
+ locationSequence=location_sequence,
35
+ legacyLocation=legacy_location,
36
+ vector=request.vector,
37
+ )
38
+
39
+
40
+ def legacy_offset_location_to_offset_location_sequence(
41
+ location: LegacyLabwareOffsetLocation, deck_definition: DeckDefinitionV5
42
+ ) -> LabwareOffsetLocationSequence:
43
+ """Convert a legacy location to a new-style sequence."""
44
+ sequence: LabwareOffsetLocationSequence = []
45
+ if location.definitionUri:
46
+ sequence.append(
47
+ OnLabwareOffsetLocationSequenceComponent(labwareUri=location.definitionUri)
48
+ )
49
+ if location.moduleModel:
50
+ sequence.append(
51
+ OnModuleOffsetLocationSequenceComponent(moduleModel=location.moduleModel)
52
+ )
53
+ cutout_id = deck_configuration_provider.get_cutout_id_by_deck_slot_name(
54
+ location.slotName
55
+ )
56
+ possible_cutout_fixture_id = location.moduleModel.value
57
+ try:
58
+ addressable_area = deck_configuration_provider.get_labware_hosting_addressable_area_name_for_cutout_and_cutout_fixture(
59
+ cutout_id, possible_cutout_fixture_id, deck_definition
60
+ )
61
+ sequence.append(
62
+ OnAddressableAreaOffsetLocationSequenceComponent(
63
+ addressableAreaName=addressable_area
64
+ )
65
+ )
66
+ except FixtureDoesNotExistError:
67
+ # this is an OT-2 (or this module isn't supported in the deck definition) and we should use a
68
+ # slot addressable area name
69
+ sequence.append(
70
+ OnAddressableAreaOffsetLocationSequenceComponent(
71
+ addressableAreaName=location.slotName.value
72
+ )
73
+ )
74
+
75
+ else:
76
+ # Slight hack: we should have a more formal association here. However, since the slot
77
+ # name is already standardized, and since the addressable areas for slots are just the
78
+ # name of the slots, we can rely on this.
79
+ sequence.append(
80
+ OnAddressableAreaOffsetLocationSequenceComponent(
81
+ addressableAreaName=location.slotName.value
82
+ )
83
+ )
84
+ return sequence
85
+
86
+
87
+ def _offset_location_sequence_head_to_labware_and_module(
88
+ location_sequence: LabwareOffsetLocationSequence,
89
+ ) -> tuple[ModuleModel | None, str | None]:
90
+ labware_uri: str | None = None
91
+ module_model: ModuleModel | None = None
92
+ for location in location_sequence:
93
+ if isinstance(location, OnAddressableAreaOffsetLocationSequenceComponent):
94
+ raise OffsetLocationInvalidError(
95
+ "Addressable areas may only be the final element of an offset location."
96
+ )
97
+ elif isinstance(location, OnLabwareOffsetLocationSequenceComponent):
98
+ if labware_uri is not None:
99
+ # We only take the first location
100
+ continue
101
+ if module_model is not None:
102
+ # Labware can't be underneath modules
103
+ raise OffsetLocationInvalidError(
104
+ "Labware must not be underneath a module."
105
+ )
106
+ labware_uri = location.labwareUri
107
+ elif isinstance(location, OnModuleOffsetLocationSequenceComponent):
108
+ if module_model is not None:
109
+ # Bad, somebody put more than one module in here
110
+ raise OffsetLocationInvalidError(
111
+ "Only one module location may exist in an offset location."
112
+ )
113
+ module_model = location.moduleModel
114
+ else:
115
+ raise OffsetLocationInvalidError(
116
+ f"Invalid location component in offset location: {repr(location)}"
117
+ )
118
+ return module_model, labware_uri
119
+
120
+
121
+ def _offset_location_sequence_to_legacy_offset_location(
122
+ location_sequence: LabwareOffsetLocationSequence, deck_definition: DeckDefinitionV5
123
+ ) -> LegacyLabwareOffsetLocation:
124
+ if len(location_sequence) == 0:
125
+ raise OffsetLocationInvalidError(
126
+ "Offset locations must contain at least one component."
127
+ )
128
+ last_element = location_sequence[-1]
129
+ if not isinstance(last_element, OnAddressableAreaOffsetLocationSequenceComponent):
130
+ raise OffsetLocationInvalidError(
131
+ "Offset locations must end with an addressable area."
132
+ )
133
+ module_model, labware_uri = _offset_location_sequence_head_to_labware_and_module(
134
+ location_sequence[:-1]
135
+ )
136
+ (
137
+ cutout_id,
138
+ cutout_fixtures,
139
+ ) = deck_configuration_provider.get_potential_cutout_fixtures(
140
+ last_element.addressableAreaName, deck_definition
141
+ )
142
+ slot_name = deck_configuration_provider.get_deck_slot_for_cutout_id(cutout_id)
143
+ return LegacyLabwareOffsetLocation(
144
+ slotName=slot_name, moduleModel=module_model, definitionUri=labware_uri
145
+ )
146
+
147
+
148
+ def _locations_for_create(
149
+ request: LabwareOffsetCreate | LegacyLabwareOffsetCreate,
150
+ robot_type: RobotType,
151
+ deck_definition: DeckDefinitionV5,
152
+ ) -> tuple[LabwareOffsetLocationSequence, LegacyLabwareOffsetLocation]:
153
+ if isinstance(request, LabwareOffsetCreate):
154
+ return (
155
+ request.locationSequence,
156
+ _offset_location_sequence_to_legacy_offset_location(
157
+ request.locationSequence, deck_definition
158
+ ),
159
+ )
160
+ else:
161
+ normalized = request.location.model_copy(
162
+ update={
163
+ "slotName": request.location.slotName.to_equivalent_for_robot_type(
164
+ robot_type
165
+ )
166
+ }
167
+ )
168
+ return (
169
+ legacy_offset_location_to_offset_location_sequence(
170
+ normalized, deck_definition
171
+ ),
172
+ normalized,
173
+ )