opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a7__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 (189) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/cli/analyze.py +4 -1
  3. opentrons/config/__init__.py +7 -0
  4. opentrons/drivers/asyncio/communication/serial_connection.py +126 -49
  5. opentrons/drivers/heater_shaker/abstract.py +5 -0
  6. opentrons/drivers/heater_shaker/driver.py +10 -0
  7. opentrons/drivers/heater_shaker/simulator.py +4 -0
  8. opentrons/drivers/thermocycler/abstract.py +6 -0
  9. opentrons/drivers/thermocycler/driver.py +61 -10
  10. opentrons/drivers/thermocycler/simulator.py +6 -0
  11. opentrons/drivers/vacuum_module/__init__.py +5 -0
  12. opentrons/drivers/vacuum_module/abstract.py +93 -0
  13. opentrons/drivers/vacuum_module/driver.py +208 -0
  14. opentrons/drivers/vacuum_module/errors.py +39 -0
  15. opentrons/drivers/vacuum_module/simulator.py +85 -0
  16. opentrons/drivers/vacuum_module/types.py +79 -0
  17. opentrons/execute.py +3 -0
  18. opentrons/hardware_control/api.py +24 -5
  19. opentrons/hardware_control/backends/controller.py +8 -2
  20. opentrons/hardware_control/backends/flex_protocol.py +1 -0
  21. opentrons/hardware_control/backends/ot3controller.py +35 -2
  22. opentrons/hardware_control/backends/ot3simulator.py +3 -1
  23. opentrons/hardware_control/backends/ot3utils.py +37 -0
  24. opentrons/hardware_control/backends/simulator.py +2 -1
  25. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  26. opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
  27. opentrons/hardware_control/emulation/connection_handler.py +8 -5
  28. opentrons/hardware_control/emulation/heater_shaker.py +12 -3
  29. opentrons/hardware_control/emulation/settings.py +1 -1
  30. opentrons/hardware_control/emulation/thermocycler.py +67 -15
  31. opentrons/hardware_control/module_control.py +105 -10
  32. opentrons/hardware_control/modules/__init__.py +3 -0
  33. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  34. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  35. opentrons/hardware_control/modules/heater_shaker.py +42 -5
  36. opentrons/hardware_control/modules/magdeck.py +8 -4
  37. opentrons/hardware_control/modules/mod_abc.py +14 -6
  38. opentrons/hardware_control/modules/tempdeck.py +25 -5
  39. opentrons/hardware_control/modules/thermocycler.py +68 -11
  40. opentrons/hardware_control/modules/types.py +20 -1
  41. opentrons/hardware_control/modules/utils.py +11 -4
  42. opentrons/hardware_control/motion_utilities.py +6 -6
  43. opentrons/hardware_control/nozzle_manager.py +3 -0
  44. opentrons/hardware_control/ot3api.py +85 -17
  45. opentrons/hardware_control/poller.py +22 -8
  46. opentrons/hardware_control/protocols/liquid_handler.py +6 -2
  47. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  48. opentrons/hardware_control/types.py +43 -2
  49. opentrons/legacy_commands/commands.py +58 -5
  50. opentrons/legacy_commands/module_commands.py +52 -0
  51. opentrons/legacy_commands/protocol_commands.py +53 -1
  52. opentrons/legacy_commands/types.py +155 -1
  53. opentrons/motion_planning/deck_conflict.py +17 -12
  54. opentrons/motion_planning/waypoints.py +15 -29
  55. opentrons/protocol_api/__init__.py +5 -1
  56. opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
  57. opentrons/protocol_api/_types.py +8 -1
  58. opentrons/protocol_api/core/common.py +3 -1
  59. opentrons/protocol_api/core/engine/_default_labware_versions.py +33 -11
  60. opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
  61. opentrons/protocol_api/core/engine/instrument.py +109 -26
  62. opentrons/protocol_api/core/engine/labware.py +8 -1
  63. opentrons/protocol_api/core/engine/module_core.py +95 -4
  64. opentrons/protocol_api/core/engine/protocol.py +51 -2
  65. opentrons/protocol_api/core/engine/stringify.py +2 -0
  66. opentrons/protocol_api/core/engine/tasks.py +48 -0
  67. opentrons/protocol_api/core/engine/well.py +8 -0
  68. opentrons/protocol_api/core/instrument.py +19 -2
  69. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  70. opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
  71. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
  72. opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
  73. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  74. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  75. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  76. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  77. opentrons/protocol_api/core/module.py +58 -2
  78. opentrons/protocol_api/core/protocol.py +23 -2
  79. opentrons/protocol_api/core/tasks.py +31 -0
  80. opentrons/protocol_api/core/well.py +4 -0
  81. opentrons/protocol_api/instrument_context.py +388 -2
  82. opentrons/protocol_api/labware.py +10 -2
  83. opentrons/protocol_api/module_contexts.py +170 -6
  84. opentrons/protocol_api/protocol_context.py +87 -21
  85. opentrons/protocol_api/robot_context.py +41 -25
  86. opentrons/protocol_api/tasks.py +48 -0
  87. opentrons/protocol_api/validation.py +49 -3
  88. opentrons/protocol_engine/__init__.py +4 -0
  89. opentrons/protocol_engine/actions/__init__.py +6 -2
  90. opentrons/protocol_engine/actions/actions.py +31 -9
  91. opentrons/protocol_engine/clients/sync_client.py +42 -7
  92. opentrons/protocol_engine/commands/__init__.py +56 -0
  93. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  94. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  95. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  96. opentrons/protocol_engine/commands/aspirate.py +1 -0
  97. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  98. opentrons/protocol_engine/commands/capture_image.py +302 -0
  99. opentrons/protocol_engine/commands/command.py +2 -0
  100. opentrons/protocol_engine/commands/command_unions.py +62 -0
  101. opentrons/protocol_engine/commands/create_timer.py +83 -0
  102. opentrons/protocol_engine/commands/dispense.py +1 -0
  103. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  104. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  105. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  106. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  107. opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
  108. opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
  110. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
  111. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
  112. opentrons/protocol_engine/commands/move_labware.py +3 -4
  113. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  114. opentrons/protocol_engine/commands/movement_common.py +31 -2
  115. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  116. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  117. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  118. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
  119. opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
  120. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  121. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  122. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +44 -7
  123. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
  124. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
  125. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  126. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  127. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  128. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  129. opentrons/protocol_engine/engine_support.py +3 -0
  130. opentrons/protocol_engine/errors/__init__.py +12 -0
  131. opentrons/protocol_engine/errors/exceptions.py +119 -0
  132. opentrons/protocol_engine/execution/__init__.py +4 -0
  133. opentrons/protocol_engine/execution/command_executor.py +62 -1
  134. opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
  135. opentrons/protocol_engine/execution/labware_movement.py +13 -15
  136. opentrons/protocol_engine/execution/movement.py +2 -0
  137. opentrons/protocol_engine/execution/pipetting.py +19 -25
  138. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  139. opentrons/protocol_engine/execution/run_control.py +8 -0
  140. opentrons/protocol_engine/execution/task_handler.py +157 -0
  141. opentrons/protocol_engine/protocol_engine.py +137 -36
  142. opentrons/protocol_engine/resources/__init__.py +4 -0
  143. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  144. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  145. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  146. opentrons/protocol_engine/resources/file_provider.py +133 -58
  147. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  148. opentrons/protocol_engine/slot_standardization.py +2 -0
  149. opentrons/protocol_engine/state/_well_math.py +60 -18
  150. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  151. opentrons/protocol_engine/state/camera.py +54 -0
  152. opentrons/protocol_engine/state/commands.py +37 -14
  153. opentrons/protocol_engine/state/geometry.py +276 -379
  154. opentrons/protocol_engine/state/labware.py +62 -108
  155. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  156. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
  157. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  158. opentrons/protocol_engine/state/modules.py +30 -8
  159. opentrons/protocol_engine/state/motion.py +44 -0
  160. opentrons/protocol_engine/state/preconditions.py +59 -0
  161. opentrons/protocol_engine/state/state.py +44 -0
  162. opentrons/protocol_engine/state/state_summary.py +4 -0
  163. opentrons/protocol_engine/state/tasks.py +139 -0
  164. opentrons/protocol_engine/state/tips.py +177 -258
  165. opentrons/protocol_engine/state/update_types.py +26 -9
  166. opentrons/protocol_engine/types/__init__.py +23 -4
  167. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  168. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  169. opentrons/protocol_engine/types/instrument.py +8 -1
  170. opentrons/protocol_engine/types/labware.py +1 -13
  171. opentrons/protocol_engine/types/location.py +26 -2
  172. opentrons/protocol_engine/types/module.py +11 -1
  173. opentrons/protocol_engine/types/tasks.py +38 -0
  174. opentrons/protocol_engine/types/tip.py +9 -0
  175. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  176. opentrons/protocol_runner/protocol_runner.py +14 -1
  177. opentrons/protocol_runner/run_orchestrator.py +49 -2
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/types.py +2 -1
  181. opentrons/simulate.py +51 -15
  182. opentrons/system/camera.py +334 -4
  183. opentrons/system/ffmpeg.py +110 -0
  184. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/METADATA +4 -4
  185. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/RECORD +188 -160
  186. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  187. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/WHEEL +0 -0
  188. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/entry_points.txt +0 -0
  189. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -112,6 +112,38 @@ DEFAULT_LABWARE_VERSIONS: DefaultLabwareVersions = {
112
112
  "thermoscientificnunc_96_wellplate_2000ul": 3,
113
113
  "usascientific_96_wellplate_2.4ml_deep": 3,
114
114
  },
115
+ APIVersion(2, 27): {
116
+ "agilent_1_reservoir_290ml": 4,
117
+ "axygen_1_reservoir_90ml": 3,
118
+ "biorad_96_wellplate_200ul_pcr": 5,
119
+ "corning_12_wellplate_6.9ml_flat": 5,
120
+ "corning_24_wellplate_3.4ml_flat": 5,
121
+ "corning_384_wellplate_112ul_flat": 5,
122
+ "corning_48_wellplate_1.6ml_flat": 6,
123
+ "corning_6_wellplate_16.8ml_flat": 5,
124
+ "corning_96_wellplate_360ul_flat": 5,
125
+ "nest_12_reservoir_15ml": 3,
126
+ "nest_1_reservoir_195ml": 4,
127
+ "nest_1_reservoir_290ml": 4,
128
+ "nest_96_wellplate_100ul_pcr_full_skirt": 5,
129
+ "nest_96_wellplate_200ul_flat": 5,
130
+ "nest_96_wellplate_2ml_deep": 5,
131
+ "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical": 3,
132
+ "opentrons_15_tuberack_falcon_15ml_conical": 3,
133
+ "opentrons_24_aluminumblock_nest_0.5ml_screwcap": 4,
134
+ "opentrons_24_aluminumblock_nest_1.5ml_screwcap": 3,
135
+ "opentrons_24_aluminumblock_nest_1.5ml_snapcap": 3,
136
+ "opentrons_24_aluminumblock_nest_2ml_screwcap": 3,
137
+ "opentrons_24_aluminumblock_nest_2ml_snapcap": 3,
138
+ "opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap": 3,
139
+ "opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap": 3,
140
+ "opentrons_24_tuberack_nest_0.5ml_screwcap": 4,
141
+ "opentrons_6_tuberack_nest_50ml_conical": 3,
142
+ "opentrons_96_aluminumblock_generic_pcr_strip_200ul": 4,
143
+ "opentrons_tough_universal_lid": 2,
144
+ "usascientific_12_reservoir_22ml": 4,
145
+ "usascientific_96_wellplate_2.4ml_deep": 4,
146
+ },
115
147
  }
116
148
 
117
149
 
@@ -139,17 +171,7 @@ KNOWN_EXCEPTIONS_FOR_TESTS: set[str] = {
139
171
  "schema3test_flex_tiprack_lid",
140
172
  "schema3test_tough_pcr_auto_sealing_lid",
141
173
  "schema3test_universal_flat_adapter",
142
- # These were supposed to be short-lived drafts as part of of a one-two punch of
143
- # https://github.com/Opentrons/opentrons/pull/18266 + https://github.com/Opentrons/opentrons/pull/18284,
144
- # but the second punch took a while. We should merge the second punch after v8.6.0
145
- # and remove these exceptions as part of that.
146
- "agilent_1_reservoir_290ml",
147
- "corning_384_wellplate_112ul_flat",
148
- "nest_1_reservoir_290ml",
149
- "opentrons_24_aluminumblock_nest_0.5ml_screwcap",
150
- "opentrons_24_tuberack_nest_0.5ml_screwcap",
151
- "opentrons_96_aluminumblock_generic_pcr_strip_200ul",
152
- "usascientific_12_reservoir_22ml",
174
+ "schema3test_96_wellplate_360ul_flat",
153
175
  }
154
176
 
155
177
 
@@ -26,6 +26,7 @@ from opentrons.protocol_engine import (
26
26
  OnLabwareLocation,
27
27
  AddressableAreaLocation,
28
28
  InStackerHopperLocation,
29
+ WASTE_CHUTE_LOCATION,
29
30
  OFF_DECK_LOCATION,
30
31
  SYSTEM_LOCATION,
31
32
  )
@@ -241,7 +242,6 @@ def _map_labware(
241
242
  Tuple[Union[DeckSlotName, StagingSlotName], wrapped_deck_conflict.DeckItem]
242
243
  ]:
243
244
  location_from_engine = engine_state.labware.get_location(labware_id=labware_id)
244
-
245
245
  if isinstance(location_from_engine, AddressableAreaLocation):
246
246
  # This will be guaranteed to be either deck slot name or staging slot name
247
247
  slot: Union[DeckSlotName, StagingSlotName]
@@ -299,10 +299,12 @@ def _map_labware(
299
299
  location_from_engine == OFF_DECK_LOCATION
300
300
  or location_from_engine == SYSTEM_LOCATION
301
301
  or isinstance(location_from_engine, InStackerHopperLocation)
302
+ or location_from_engine == WASTE_CHUTE_LOCATION
302
303
  ):
303
304
  # This labware is off-deck. Exclude it from conflict checking.
304
305
  # todo(mm, 2023-02-23): Move this logic into wrapped_deck_conflict.
305
306
  return None
307
+ return None
306
308
 
307
309
 
308
310
  def _map_module(
@@ -203,7 +203,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
203
203
  flow_rate: float,
204
204
  in_place: bool,
205
205
  meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
206
+ end_location: Optional[Location] = None,
207
+ end_meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
206
208
  correction_volume: Optional[float] = None,
209
+ movement_delay: Optional[float] = None,
207
210
  ) -> None:
208
211
  """Aspirate a given volume of liquid from the specified location.
209
212
  Args:
@@ -215,8 +218,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
215
218
  in_place: whether this is a in-place command.
216
219
  meniscus_tracking: Optional data about where to aspirate from.
217
220
  """
218
- if meniscus_tracking == MeniscusTrackingTarget.START:
221
+ if meniscus_tracking == MeniscusTrackingTarget.START and end_location is None:
219
222
  raise ValueError("Cannot aspirate at the starting liquid height.")
223
+ final_location = location
220
224
  if well_core is None:
221
225
  if not in_place:
222
226
  self._engine_client.execute_command(
@@ -262,16 +266,34 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
262
266
  well_location=well_location,
263
267
  )
264
268
  assert isinstance(well_location, LiquidHandlingWellLocation)
265
- if dynamic_liquid_tracking:
269
+ # the dynamic liquid tracking flag is for the prototype dynamic tracking method
270
+ if dynamic_liquid_tracking or end_location is not None:
271
+ # Keep this part when above TODO is done
272
+ if end_location is None:
273
+ end_location = location
274
+ (
275
+ end_well_location,
276
+ _,
277
+ ) = self._engine_client.state.geometry.get_relative_well_location(
278
+ labware_id=labware_id,
279
+ well_name=well_name,
280
+ absolute_point=end_location.point,
281
+ location_type=WellLocationFunction.LIQUID_HANDLING,
282
+ meniscus_tracking=end_meniscus_tracking,
283
+ )
284
+ final_location = end_location
285
+ assert isinstance(end_well_location, LiquidHandlingWellLocation)
266
286
  self._engine_client.execute_command(
267
287
  cmd.AspirateWhileTrackingParams(
268
288
  pipetteId=self._pipette_id,
269
289
  labwareId=labware_id,
270
290
  wellName=well_name,
271
- wellLocation=well_location,
291
+ trackFromLocation=well_location,
292
+ trackToLocation=end_well_location,
272
293
  volume=volume,
273
294
  flowRate=flow_rate,
274
295
  correctionVolume=correction_volume,
296
+ movement_delay=movement_delay,
275
297
  )
276
298
  )
277
299
  else:
@@ -287,7 +309,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
287
309
  )
288
310
  )
289
311
 
290
- self._protocol_core.set_last_location(location=location, mount=self.get_mount())
312
+ self._protocol_core.set_last_location(
313
+ location=final_location, mount=self.get_mount()
314
+ )
291
315
 
292
316
  def dispense(
293
317
  self,
@@ -299,7 +323,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
299
323
  in_place: bool,
300
324
  push_out: Optional[float],
301
325
  meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
326
+ end_location: Optional[Location] = None,
327
+ end_meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
302
328
  correction_volume: Optional[float] = None,
329
+ movement_delay: Optional[float] = None,
303
330
  ) -> None:
304
331
  """Dispense a given volume of liquid into the specified location.
305
332
  Args:
@@ -320,7 +347,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
320
347
  # Newer API versions raise an error if you try to dispense more than
321
348
  # you can. Let the error come from Protocol Engine's validation.
322
349
  pass
323
-
350
+ final_location = location
324
351
  if well_core is None:
325
352
  if not in_place:
326
353
  if isinstance(location, (TrashBin, WasteChute)):
@@ -375,17 +402,34 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
375
402
  well_name=well_name,
376
403
  well_location=well_location,
377
404
  )
378
- if dynamic_liquid_tracking:
405
+ # the dynamic liquid tracking flag is for the prototype dynamic tracking method
406
+ if dynamic_liquid_tracking or end_location is not None:
407
+ if end_location is None:
408
+ end_location = location
409
+ final_location = end_location
410
+ (
411
+ end_well_location,
412
+ _,
413
+ ) = self._engine_client.state.geometry.get_relative_well_location(
414
+ labware_id=labware_id,
415
+ well_name=well_name,
416
+ absolute_point=end_location.point,
417
+ location_type=WellLocationFunction.LIQUID_HANDLING,
418
+ meniscus_tracking=end_meniscus_tracking,
419
+ )
420
+ assert isinstance(end_well_location, LiquidHandlingWellLocation)
379
421
  self._engine_client.execute_command(
380
422
  cmd.DispenseWhileTrackingParams(
381
423
  pipetteId=self._pipette_id,
382
424
  labwareId=labware_id,
383
425
  wellName=well_name,
384
- wellLocation=well_location,
426
+ trackFromLocation=well_location,
427
+ trackToLocation=end_well_location,
385
428
  volume=volume,
386
429
  flowRate=flow_rate,
387
430
  pushOut=push_out,
388
431
  correctionVolume=correction_volume,
432
+ movement_delay=movement_delay,
389
433
  )
390
434
  )
391
435
  else:
@@ -402,7 +446,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
402
446
  )
403
447
  )
404
448
 
405
- self._protocol_core.set_last_location(location=location, mount=self.get_mount())
449
+ self._protocol_core.set_last_location(
450
+ location=final_location, mount=self.get_mount()
451
+ )
406
452
 
407
453
  def blow_out(
408
454
  self,
@@ -1300,6 +1346,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1300
1346
  trash_location: Union[Location, TrashBin, WasteChute],
1301
1347
  return_tip: bool,
1302
1348
  keep_last_tip: bool,
1349
+ tips: Optional[List[WellCore]],
1303
1350
  ) -> None:
1304
1351
  """Execute transfer using liquid class properties.
1305
1352
 
@@ -1322,10 +1369,12 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1322
1369
  return_tip: If `True`, return tips to the tip rack location they were picked up from,
1323
1370
  otherwise drop in `trash_location`
1324
1371
  keep_last_tip: When set to `True`, do not drop the final tip used in the transfer.
1372
+ tips: If provided, transfer will pick up the tips in the order given. If this
1373
+ is less than the amount of tips needed, an error will be raised.
1325
1374
  """
1326
- if not tip_racks:
1375
+ if not tip_racks and not tips:
1327
1376
  raise RuntimeError(
1328
- "No tipracks found for pipette in order to perform transfer"
1377
+ "No tip racks or tips found for pipette in order to perform transfer"
1329
1378
  )
1330
1379
  tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
1331
1380
  transfer_props = self._get_transfer_properties_for_tip_rack(
@@ -1367,7 +1416,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1367
1416
 
1368
1417
  if new_tip == TransferTipPolicyV2.ONCE:
1369
1418
  self._pick_up_tip_for_liquid_class(
1370
- tip_racks, starting_tip, tiprack_uri_for_transfer_props
1419
+ tip_racks, starting_tip, tiprack_uri_for_transfer_props, tips
1371
1420
  )
1372
1421
 
1373
1422
  prev_src: Optional[Tuple[Location, WellCore]] = None
@@ -1407,7 +1456,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1407
1456
  if prev_src is not None and prev_dest is not None:
1408
1457
  self._drop_tip_for_liquid_class(trash_location, return_tip)
1409
1458
  self._pick_up_tip_for_liquid_class(
1410
- tip_racks, starting_tip, tiprack_uri_for_transfer_props
1459
+ tip_racks,
1460
+ starting_tip,
1461
+ tiprack_uri_for_transfer_props,
1462
+ tips,
1411
1463
  )
1412
1464
  post_disp_tip_contents = [
1413
1465
  tx_comps_executor.LiquidAndAirGapPair(
@@ -1464,6 +1516,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1464
1516
  trash_location: Union[Location, TrashBin, WasteChute],
1465
1517
  return_tip: bool,
1466
1518
  keep_last_tip: bool,
1519
+ tips: Optional[List[WellCore]],
1467
1520
  ) -> None:
1468
1521
  """Execute a distribution using liquid class properties.
1469
1522
 
@@ -1487,6 +1540,8 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1487
1540
  return_tip: If `True`, return tips to the tip rack location they were picked up from,
1488
1541
  otherwise drop in `trash_location`
1489
1542
  keep_last_tip: When set to `True`, do not drop the final tip used in the distribute.
1543
+ tips: If provided, transfer will pick up the tips in the order given. If this
1544
+ is less than the amount of tips needed, an error will be raised.
1490
1545
 
1491
1546
  This method distributes the liquid in the source well into multiple destinations.
1492
1547
  It can accomplish this by either doing a multi-dispense (aspirate once and then
@@ -1498,7 +1553,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1498
1553
  """
1499
1554
  if not tip_racks:
1500
1555
  raise RuntimeError(
1501
- "No tipracks found for pipette in order to perform transfer"
1556
+ "No tip racks found for pipette in order to perform transfer"
1502
1557
  )
1503
1558
  assert new_tip in [
1504
1559
  TransferTipPolicyV2.NEVER,
@@ -1546,6 +1601,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1546
1601
  trash_location=trash_location,
1547
1602
  return_tip=return_tip,
1548
1603
  keep_last_tip=keep_last_tip,
1604
+ tips=tips,
1549
1605
  )
1550
1606
 
1551
1607
  # TODO: use the ID returned by load_liquid_class in command annotations
@@ -1576,7 +1632,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1576
1632
 
1577
1633
  if new_tip != TransferTipPolicyV2.NEVER:
1578
1634
  self._pick_up_tip_for_liquid_class(
1579
- tip_racks, starting_tip, tiprack_uri_for_transfer_props
1635
+ tip_racks,
1636
+ starting_tip,
1637
+ tiprack_uri_for_transfer_props,
1638
+ tips,
1580
1639
  )
1581
1640
 
1582
1641
  tip_contents = [
@@ -1644,7 +1703,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1644
1703
  if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1645
1704
  self._drop_tip_for_liquid_class(trash_location, return_tip)
1646
1705
  self._pick_up_tip_for_liquid_class(
1647
- tip_racks, starting_tip, tiprack_uri_for_transfer_props
1706
+ tip_racks,
1707
+ starting_tip,
1708
+ tiprack_uri_for_transfer_props,
1709
+ tips,
1648
1710
  )
1649
1711
  tip_contents = [
1650
1712
  tx_comps_executor.LiquidAndAirGapPair(
@@ -1750,6 +1812,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1750
1812
  trash_location: Union[Location, TrashBin, WasteChute],
1751
1813
  return_tip: bool,
1752
1814
  keep_last_tip: bool,
1815
+ tips: Optional[List[WellCore]],
1753
1816
  ) -> None:
1754
1817
  """Execute consolidate using liquid class properties.
1755
1818
 
@@ -1774,10 +1837,12 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1774
1837
  return_tip: If `True`, return tips to the tip rack location they were picked up from,
1775
1838
  otherwise drop in `trash_location`
1776
1839
  keep_last_tip: When set to `True`, do not drop the final tip used in the consolidate.
1840
+ tips: If provided, transfer will pick up the tips in the order given. If this
1841
+ is less than the amount of tips needed, an error will be raised.
1777
1842
  """
1778
1843
  if not tip_racks:
1779
1844
  raise RuntimeError(
1780
- "No tipracks found for pipette in order to perform transfer"
1845
+ "No tip racks found for pipette in order to perform transfer"
1781
1846
  )
1782
1847
  # NOTE: Tip option of "always" in consolidate is equivalent to "after every dispense",
1783
1848
  # or more specifically, "before the next chunk of aspirates".
@@ -1828,7 +1893,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1828
1893
 
1829
1894
  if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
1830
1895
  self._pick_up_tip_for_liquid_class(
1831
- tip_racks, starting_tip, tiprack_uri_for_transfer_props
1896
+ tip_racks,
1897
+ starting_tip,
1898
+ tiprack_uri_for_transfer_props,
1899
+ tips,
1832
1900
  )
1833
1901
 
1834
1902
  aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
@@ -1861,7 +1929,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1861
1929
  if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1862
1930
  self._drop_tip_for_liquid_class(trash_location, return_tip)
1863
1931
  self._pick_up_tip_for_liquid_class(
1864
- tip_racks, starting_tip, tiprack_uri_for_transfer_props
1932
+ tip_racks,
1933
+ starting_tip,
1934
+ tiprack_uri_for_transfer_props,
1935
+ tips,
1865
1936
  )
1866
1937
  tip_contents = [
1867
1938
  tx_comps_executor.LiquidAndAirGapPair(
@@ -1957,22 +2028,34 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1957
2028
  tip_racks: List[Tuple[Location, LabwareCore]],
1958
2029
  starting_tip: Optional[WellCore],
1959
2030
  tiprack_uri_for_transfer_props: str,
2031
+ selected_tips: Optional[List[WellCore]],
1960
2032
  ) -> None:
1961
2033
  """Resolve next tip and pick it up, for use in liquid class transfer code."""
1962
- next_tip = self.get_next_tip(
1963
- tip_racks=[core for loc, core in tip_racks],
1964
- starting_well=starting_tip,
1965
- )
1966
- if next_tip is None:
1967
- raise RuntimeError(
1968
- f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
1969
- f" {[f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
2034
+ next_tip: Optional[NextTipInfo]
2035
+ if selected_tips is not None:
2036
+ try:
2037
+ tip_core = selected_tips.pop(0)
2038
+ except IndexError:
2039
+ raise RuntimeError("No more selected tips available for liquid class.")
2040
+ next_tip = NextTipInfo(
2041
+ labwareId=tip_core.labware_id, tipStartingWell=tip_core.get_name()
2042
+ )
2043
+ else:
2044
+ next_tip = self.get_next_tip(
2045
+ tip_racks=[core for loc, core in tip_racks],
2046
+ starting_well=starting_tip,
1970
2047
  )
2048
+ if next_tip is None:
2049
+ raise RuntimeError(
2050
+ f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
2051
+ f" {[f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
2052
+ )
1971
2053
  (
1972
2054
  tiprack_loc,
1973
2055
  tiprack_uri,
1974
2056
  tip_well,
1975
2057
  ) = self._get_location_and_well_core_from_next_tip_info(next_tip, tip_racks)
2058
+
1976
2059
  if tiprack_uri != tiprack_uri_for_transfer_props:
1977
2060
  raise RuntimeError(
1978
2061
  f"Tiprack {tiprack_uri} does not match the tiprack designated "
@@ -23,6 +23,7 @@ from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
23
23
  from opentrons.protocol_engine.types import (
24
24
  LabwareOffsetCreate,
25
25
  LabwareOffsetVector,
26
+ TipRackWellState,
26
27
  )
27
28
  from opentrons.types import DeckSlotName, NozzleMapInterface, Point, StagingSlotName
28
29
 
@@ -165,7 +166,13 @@ class LabwareCore(AbstractLabware[WellCore]):
165
166
 
166
167
  def reset_tips(self) -> None:
167
168
  if self.is_tip_rack():
168
- self._engine_client.reset_tips(labware_id=self.labware_id)
169
+ self._engine_client.execute_command(
170
+ cmd.SetTipStateParams(
171
+ labwareId=self._labware_id,
172
+ wellNames=list(self._definition.wells),
173
+ tipWellState=TipRackWellState.CLEAN,
174
+ )
175
+ )
169
176
  else:
170
177
  raise TypeError(f"{self.get_display_name()} is not a tip rack.")
171
178
 
@@ -51,6 +51,7 @@ from ..module import (
51
51
  from .exceptions import InvalidMagnetEngageHeightError
52
52
 
53
53
  from .labware import LabwareCore
54
+ from .tasks import EngineTaskCore
54
55
  from . import load_labware_params
55
56
 
56
57
  if TYPE_CHECKING:
@@ -175,13 +176,17 @@ class TemperatureModuleCore(ModuleCore, AbstractTemperatureModuleCore[LabwareCor
175
176
 
176
177
  _sync_module_hardware: SynchronousAdapter[hw_modules.TempDeck]
177
178
 
178
- def set_target_temperature(self, celsius: float) -> None:
179
+ def set_target_temperature(self, celsius: float) -> EngineTaskCore:
179
180
  """Set the Temperature Module's target temperature in °C."""
180
- self._engine_client.execute_command(
181
+ result = self._engine_client.execute_command_without_recovery(
181
182
  cmd.temperature_module.SetTargetTemperatureParams(
182
183
  moduleId=self.module_id, celsius=celsius
183
184
  )
184
185
  )
186
+ temperature_task = EngineTaskCore(
187
+ engine_client=self._engine_client, task_id=result.taskId
188
+ )
189
+ return temperature_task
185
190
 
186
191
  def wait_for_target_temperature(self, celsius: Optional[float] = None) -> None:
187
192
  """Wait until the module's target temperature is reached.
@@ -317,6 +322,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
317
322
  def set_target_block_temperature(
318
323
  self,
319
324
  celsius: float,
325
+ ramp_rate: Optional[float],
320
326
  hold_time_seconds: Optional[float] = None,
321
327
  block_max_volume: Optional[float] = None,
322
328
  ) -> None:
@@ -327,9 +333,30 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
327
333
  celsius=celsius,
328
334
  blockMaxVolumeUl=block_max_volume,
329
335
  holdTimeSeconds=hold_time_seconds,
336
+ ramp_rate=ramp_rate,
330
337
  )
331
338
  )
332
339
 
340
+ def start_set_target_block_temperature(
341
+ self,
342
+ celsius: float,
343
+ ramp_rate: Optional[float],
344
+ block_max_volume: Optional[float] = None,
345
+ ) -> EngineTaskCore:
346
+ """Start setting the target temperature for the well block, in °C."""
347
+ result = self._engine_client.execute_command_without_recovery(
348
+ cmd.thermocycler.SetTargetBlockTemperatureParams(
349
+ moduleId=self.module_id,
350
+ celsius=celsius,
351
+ blockMaxVolumeUl=block_max_volume,
352
+ ramp_rate=ramp_rate,
353
+ )
354
+ )
355
+ block_temperature_task = EngineTaskCore(
356
+ engine_client=self._engine_client, task_id=result.taskId
357
+ )
358
+ return block_temperature_task
359
+
333
360
  def wait_for_block_temperature(self) -> None:
334
361
  """Wait for target block temperature to be reached."""
335
362
  self._engine_client.execute_command(
@@ -344,6 +371,18 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
344
371
  )
345
372
  )
346
373
 
374
+ def start_set_target_lid_temperature(self, celsius: float) -> EngineTaskCore:
375
+ """Start setting the target temperature for the heated lid, in °C."""
376
+ result = self._engine_client.execute_command_without_recovery(
377
+ cmd.thermocycler.SetTargetLidTemperatureParams(
378
+ moduleId=self.module_id, celsius=celsius
379
+ )
380
+ )
381
+ lid_temperature_task = EngineTaskCore(
382
+ engine_client=self._engine_client, task_id=result.taskId
383
+ )
384
+ return lid_temperature_task
385
+
347
386
  def wait_for_lid_temperature(self) -> None:
348
387
  """Wait for target lid temperature to be reached."""
349
388
  self._engine_client.execute_command(
@@ -361,6 +400,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
361
400
  cmd.thermocycler.RunProfileStepParams(
362
401
  celsius=step["temperature"],
363
402
  holdSeconds=step["hold_time_seconds"],
403
+ rampRate=step["ramp_rate"],
364
404
  )
365
405
  for step in steps
366
406
  ]
@@ -389,6 +429,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
389
429
  cmd.thermocycler.ProfileStep(
390
430
  celsius=step["temperature"],
391
431
  holdSeconds=step["hold_time_seconds"],
432
+ rampRate=step["ramp_rate"],
392
433
  )
393
434
  for step in steps
394
435
  ],
@@ -416,6 +457,42 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
416
457
  else:
417
458
  return self._execute_profile_pre_221(steps, repetitions, block_max_volume)
418
459
 
460
+ def start_execute_profile(
461
+ self,
462
+ steps: List[ThermocyclerStep],
463
+ repetitions: int,
464
+ block_max_volume: Optional[float] = None,
465
+ ) -> EngineTaskCore:
466
+ """Start the execution of a hermocycler profile and return a task."""
467
+ self._repetitions = repetitions
468
+ self._step_count = len(steps)
469
+ engine_steps: List[
470
+ Union[cmd.thermocycler.ProfileStep, cmd.thermocycler.ProfileCycle]
471
+ ] = [
472
+ cmd.thermocycler.ProfileCycle(
473
+ repetitions=repetitions,
474
+ steps=[
475
+ cmd.thermocycler.ProfileStep(
476
+ celsius=step["temperature"],
477
+ holdSeconds=step["hold_time_seconds"],
478
+ rampRate=step["ramp_rate"],
479
+ )
480
+ for step in steps
481
+ ],
482
+ )
483
+ ]
484
+ result = self._engine_client.execute_command_without_recovery(
485
+ cmd.thermocycler.StartRunExtendedProfileParams(
486
+ moduleId=self.module_id,
487
+ profileElements=engine_steps,
488
+ blockMaxVolumeUl=block_max_volume,
489
+ )
490
+ )
491
+ start_execute_profile_result = EngineTaskCore(
492
+ engine_client=self._engine_client, task_id=result.taskId
493
+ )
494
+ return start_execute_profile_result
495
+
419
496
  def deactivate_lid(self) -> None:
420
497
  """Turn off the heated lid."""
421
498
  self._engine_client.execute_command(
@@ -507,13 +584,17 @@ class HeaterShakerModuleCore(ModuleCore, AbstractHeaterShakerCore[LabwareCore]):
507
584
 
508
585
  _sync_module_hardware: SynchronousAdapter[hw_modules.HeaterShaker]
509
586
 
510
- def set_target_temperature(self, celsius: float) -> None:
587
+ def set_target_temperature(self, celsius: float) -> EngineTaskCore:
511
588
  """Set the labware plate's target temperature in °C."""
512
- self._engine_client.execute_command(
589
+ result = self._engine_client.execute_command_without_recovery(
513
590
  cmd.heater_shaker.SetTargetTemperatureParams(
514
591
  moduleId=self.module_id, celsius=celsius
515
592
  )
516
593
  )
594
+ temperature_task = EngineTaskCore(
595
+ engine_client=self._engine_client, task_id=result.taskId
596
+ )
597
+ return temperature_task
517
598
 
518
599
  def wait_for_target_temperature(self) -> None:
519
600
  """Wait for the labware plate's target temperature to be reached."""
@@ -529,6 +610,16 @@ class HeaterShakerModuleCore(ModuleCore, AbstractHeaterShakerCore[LabwareCore]):
529
610
  )
530
611
  )
531
612
 
613
+ def set_shake_speed(self, rpm: int) -> EngineTaskCore:
614
+ """Set the shaker's target shake speed and wait for it to spin up."""
615
+ result = self._engine_client.execute_command_without_recovery(
616
+ cmd.heater_shaker.SetShakeSpeedParams(moduleId=self.module_id, rpm=rpm)
617
+ )
618
+ shake_task = EngineTaskCore(
619
+ engine_client=self._engine_client, task_id=result.taskId
620
+ )
621
+ return shake_task
622
+
532
623
  def open_labware_latch(self) -> None:
533
624
  """Open the labware latch."""
534
625
  self._engine_client.execute_command(