opentrons 8.7.0a7__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.

Potentially problematic release.


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

Files changed (109) 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 +8 -5
  5. opentrons/drivers/flex_stacker/driver.py +6 -1
  6. opentrons/drivers/vacuum_module/__init__.py +5 -0
  7. opentrons/drivers/vacuum_module/abstract.py +93 -0
  8. opentrons/drivers/vacuum_module/driver.py +208 -0
  9. opentrons/drivers/vacuum_module/errors.py +39 -0
  10. opentrons/drivers/vacuum_module/simulator.py +85 -0
  11. opentrons/drivers/vacuum_module/types.py +79 -0
  12. opentrons/execute.py +3 -0
  13. opentrons/hardware_control/backends/flex_protocol.py +2 -0
  14. opentrons/hardware_control/backends/ot3controller.py +35 -2
  15. opentrons/hardware_control/backends/ot3simulator.py +2 -0
  16. opentrons/hardware_control/backends/ot3utils.py +37 -0
  17. opentrons/hardware_control/module_control.py +23 -2
  18. opentrons/hardware_control/modules/mod_abc.py +1 -1
  19. opentrons/hardware_control/modules/types.py +1 -1
  20. opentrons/hardware_control/motion_utilities.py +6 -6
  21. opentrons/hardware_control/ot3api.py +62 -13
  22. opentrons/hardware_control/protocols/gripper_controller.py +1 -0
  23. opentrons/hardware_control/protocols/liquid_handler.py +6 -2
  24. opentrons/hardware_control/types.py +12 -0
  25. opentrons/legacy_commands/commands.py +58 -5
  26. opentrons/legacy_commands/module_commands.py +29 -0
  27. opentrons/legacy_commands/protocol_commands.py +33 -1
  28. opentrons/legacy_commands/types.py +75 -1
  29. opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
  30. opentrons/protocol_api/_types.py +2 -0
  31. opentrons/protocol_api/core/engine/_default_labware_versions.py +1 -0
  32. opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
  33. opentrons/protocol_api/core/engine/instrument.py +109 -26
  34. opentrons/protocol_api/core/engine/module_core.py +27 -3
  35. opentrons/protocol_api/core/engine/protocol.py +33 -1
  36. opentrons/protocol_api/core/engine/stringify.py +2 -0
  37. opentrons/protocol_api/core/instrument.py +19 -2
  38. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  39. opentrons/protocol_api/core/legacy/legacy_module_core.py +15 -4
  40. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +12 -0
  41. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  42. opentrons/protocol_api/core/module.py +25 -2
  43. opentrons/protocol_api/core/protocol.py +12 -0
  44. opentrons/protocol_api/instrument_context.py +388 -2
  45. opentrons/protocol_api/labware.py +5 -2
  46. opentrons/protocol_api/module_contexts.py +133 -30
  47. opentrons/protocol_api/protocol_context.py +61 -17
  48. opentrons/protocol_api/robot_context.py +3 -4
  49. opentrons/protocol_api/validation.py +43 -2
  50. opentrons/protocol_engine/__init__.py +4 -0
  51. opentrons/protocol_engine/actions/__init__.py +2 -0
  52. opentrons/protocol_engine/actions/actions.py +9 -0
  53. opentrons/protocol_engine/commands/__init__.py +14 -0
  54. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  55. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  56. opentrons/protocol_engine/commands/capture_image.py +302 -0
  57. opentrons/protocol_engine/commands/command.py +1 -0
  58. opentrons/protocol_engine/commands/command_unions.py +13 -0
  59. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  60. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  61. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  62. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +1 -1
  63. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  64. opentrons/protocol_engine/commands/move_labware.py +3 -4
  65. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  66. opentrons/protocol_engine/commands/movement_common.py +29 -2
  67. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  68. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +12 -9
  69. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +17 -12
  70. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +1 -1
  71. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  72. opentrons/protocol_engine/engine_support.py +3 -0
  73. opentrons/protocol_engine/errors/__init__.py +8 -0
  74. opentrons/protocol_engine/errors/exceptions.py +64 -0
  75. opentrons/protocol_engine/execution/__init__.py +2 -0
  76. opentrons/protocol_engine/execution/command_executor.py +54 -1
  77. opentrons/protocol_engine/execution/create_queue_worker.py +4 -1
  78. opentrons/protocol_engine/execution/labware_movement.py +13 -4
  79. opentrons/protocol_engine/execution/pipetting.py +19 -25
  80. opentrons/protocol_engine/protocol_engine.py +62 -2
  81. opentrons/protocol_engine/resources/__init__.py +2 -0
  82. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  83. opentrons/protocol_engine/resources/file_provider.py +133 -58
  84. opentrons/protocol_engine/slot_standardization.py +2 -0
  85. opentrons/protocol_engine/state/camera.py +54 -0
  86. opentrons/protocol_engine/state/commands.py +24 -4
  87. opentrons/protocol_engine/state/geometry.py +68 -10
  88. opentrons/protocol_engine/state/labware.py +10 -6
  89. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +6 -1
  90. opentrons/protocol_engine/state/modules.py +9 -0
  91. opentrons/protocol_engine/state/preconditions.py +59 -0
  92. opentrons/protocol_engine/state/state.py +30 -0
  93. opentrons/protocol_engine/state/state_summary.py +2 -0
  94. opentrons/protocol_engine/state/update_types.py +10 -0
  95. opentrons/protocol_engine/types/__init__.py +14 -1
  96. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  97. opentrons/protocol_engine/types/location.py +26 -2
  98. opentrons/protocol_engine/types/module.py +1 -1
  99. opentrons/protocol_runner/protocol_runner.py +14 -1
  100. opentrons/protocol_runner/run_orchestrator.py +31 -0
  101. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  102. opentrons/simulate.py +3 -0
  103. opentrons/system/camera.py +333 -3
  104. opentrons/system/ffmpeg.py +110 -0
  105. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/METADATA +4 -4
  106. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/RECORD +109 -97
  107. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/WHEEL +0 -0
  108. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/entry_points.txt +0 -0
  109. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -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 "
@@ -325,9 +325,9 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
325
325
  ramp_rate: Optional[float],
326
326
  hold_time_seconds: Optional[float] = None,
327
327
  block_max_volume: Optional[float] = None,
328
- ) -> EngineTaskCore:
328
+ ) -> None:
329
329
  """Set the target temperature for the well block, in °C."""
330
- result = self._engine_client.execute_command_without_recovery(
330
+ self._engine_client.execute_command(
331
331
  cmd.thermocycler.SetTargetBlockTemperatureParams(
332
332
  moduleId=self.module_id,
333
333
  celsius=celsius,
@@ -336,6 +336,22 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
336
336
  ramp_rate=ramp_rate,
337
337
  )
338
338
  )
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
+ )
339
355
  block_temperature_task = EngineTaskCore(
340
356
  engine_client=self._engine_client, task_id=result.taskId
341
357
  )
@@ -347,8 +363,16 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
347
363
  cmd.thermocycler.WaitForBlockTemperatureParams(moduleId=self.module_id)
348
364
  )
349
365
 
350
- def set_target_lid_temperature(self, celsius: float) -> EngineTaskCore:
366
+ def set_target_lid_temperature(self, celsius: float) -> None:
351
367
  """Set the target temperature for the heated lid, in °C."""
368
+ self._engine_client.execute_command(
369
+ cmd.thermocycler.SetTargetLidTemperatureParams(
370
+ moduleId=self.module_id, celsius=celsius
371
+ )
372
+ )
373
+
374
+ def start_set_target_lid_temperature(self, celsius: float) -> EngineTaskCore:
375
+ """Start setting the target temperature for the heated lid, in °C."""
352
376
  result = self._engine_client.execute_command_without_recovery(
353
377
  cmd.thermocycler.SetTargetLidTemperatureParams(
354
378
  moduleId=self.module_id, celsius=celsius
@@ -49,6 +49,7 @@ from opentrons.protocol_engine.types import (
49
49
  OFF_DECK_LOCATION,
50
50
  SYSTEM_LOCATION,
51
51
  LoadableLabwareLocation,
52
+ WASTE_CHUTE_LOCATION,
52
53
  NonStackedLocation,
53
54
  )
54
55
  from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
@@ -1168,9 +1169,38 @@ class ProtocolCore(
1168
1169
  return self._module_cores_by_id[labware_location.moduleId]
1169
1170
  elif isinstance(labware_location, OnLabwareLocation):
1170
1171
  return self._labware_cores_by_id[labware_location.labwareId]
1171
-
1172
+ elif labware_location == WASTE_CHUTE_LOCATION:
1173
+ return OffDeckType.WASTE_CHUTE
1172
1174
  return OffDeckType.OFF_DECK
1173
1175
 
1176
+ def capture_image(
1177
+ self,
1178
+ filename: Optional[str] = None,
1179
+ resolution: Optional[Tuple[int, int]] = None,
1180
+ zoom: Optional[float] = None,
1181
+ contrast: Optional[float] = None,
1182
+ brightness: Optional[float] = None,
1183
+ saturation: Optional[float] = None,
1184
+ ) -> None:
1185
+ """Capture an image using a camera.
1186
+ Args:
1187
+ resolution: Width by height resolution in pixels for the image to be captured with.
1188
+ zoom: Multiplier to use when cropping and scaling a captured image. Scale is 1.0 to 2.0.
1189
+ contrast: The contrast to use when processing an image. Scale is 0% to 100%
1190
+ brightness: The brightness to use when processing an image. Scale is 0% to 100%.
1191
+ saturation: The saturation to use when processing an image. Scale is 0% to 100%.
1192
+ """
1193
+ self._engine_client.execute_command(
1194
+ cmd.CaptureImageParams(
1195
+ fileName=filename,
1196
+ resolution=resolution,
1197
+ zoom=zoom,
1198
+ contrast=contrast,
1199
+ brightness=brightness,
1200
+ saturation=saturation,
1201
+ )
1202
+ )
1203
+
1174
1204
  def _convert_labware_location(
1175
1205
  self,
1176
1206
  location: Union[
@@ -1205,6 +1235,8 @@ class ProtocolCore(
1205
1235
  return ModuleLocation(moduleId=location.module_id)
1206
1236
  elif location is OffDeckType.OFF_DECK:
1207
1237
  return OFF_DECK_LOCATION
1238
+ elif location is OffDeckType.WASTE_CHUTE:
1239
+ return AddressableAreaLocation(addressableAreaName="gripperWasteChute")
1208
1240
  elif isinstance(location, DeckSlotName):
1209
1241
  return DeckSlotLocation(slotName=location)
1210
1242
  elif isinstance(location, StagingSlotName):
@@ -60,6 +60,8 @@ def _labware_location_string(
60
60
  return (
61
61
  f"stored in {_module_in_location_string(location.moduleId, engine_client)}"
62
62
  )
63
+ elif location == "wasteChuteLocation":
64
+ return "in waste chute"
63
65
 
64
66
 
65
67
  def _labware_name(engine_client: SyncClient, labware_id: str) -> str:
@@ -48,7 +48,10 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
48
48
  flow_rate: float,
49
49
  in_place: bool,
50
50
  meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
51
+ end_location: Optional[types.Location] = None,
52
+ end_meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
51
53
  correction_volume: Optional[float] = None,
54
+ movement_delay: Optional[float] = None,
52
55
  ) -> None:
53
56
  """Aspirate a given volume of liquid from the specified location.
54
57
  Args:
@@ -74,7 +77,10 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
74
77
  in_place: bool,
75
78
  push_out: Optional[float],
76
79
  meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
80
+ end_location: Optional[types.Location] = None,
81
+ end_meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
77
82
  correction_volume: Optional[float] = None,
83
+ movement_delay: Optional[float] = None,
78
84
  ) -> None:
79
85
  """Dispense a given volume of liquid into the specified location.
80
86
  Args:
@@ -378,6 +384,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
378
384
  trash_location: Union[types.Location, TrashBin, WasteChute],
379
385
  return_tip: bool,
380
386
  keep_last_tip: bool,
387
+ tips: Optional[List[WellCoreType]],
381
388
  ) -> None:
382
389
  """Transfer a liquid from source to dest according to liquid class properties."""
383
390
  ...
@@ -389,12 +396,17 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
389
396
  volume: float,
390
397
  source: Tuple[types.Location, WellCoreType],
391
398
  dest: List[Tuple[types.Location, WellCoreType]],
392
- new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
399
+ new_tip: Literal[
400
+ TransferTipPolicyV2.NEVER,
401
+ TransferTipPolicyV2.ONCE,
402
+ TransferTipPolicyV2.ALWAYS,
403
+ ],
393
404
  tip_racks: List[Tuple[types.Location, LabwareCoreType]],
394
405
  starting_tip: Optional[WellCoreType],
395
406
  trash_location: Union[types.Location, TrashBin, WasteChute],
396
407
  return_tip: bool,
397
408
  keep_last_tip: bool,
409
+ tips: Optional[List[WellCoreType]],
398
410
  ) -> None:
399
411
  """
400
412
  Distribute a liquid from single source to multiple destinations
@@ -409,12 +421,17 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
409
421
  volume: float,
410
422
  source: List[Tuple[types.Location, WellCoreType]],
411
423
  dest: Union[Tuple[types.Location, WellCoreType], TrashBin, WasteChute],
412
- new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
424
+ new_tip: Literal[
425
+ TransferTipPolicyV2.NEVER,
426
+ TransferTipPolicyV2.ONCE,
427
+ TransferTipPolicyV2.ALWAYS,
428
+ ],
413
429
  tip_racks: List[Tuple[types.Location, LabwareCoreType]],
414
430
  starting_tip: Optional[WellCoreType],
415
431
  trash_location: Union[types.Location, TrashBin, WasteChute],
416
432
  return_tip: bool,
417
433
  keep_last_tip: bool,
434
+ tips: Optional[List[WellCoreType]],
418
435
  ) -> None:
419
436
  """
420
437
  Consolidate liquid from multiple sources to a single destination
@@ -93,7 +93,10 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
93
93
  flow_rate: float,
94
94
  in_place: bool,
95
95
  meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
96
+ end_location: Optional[types.Location] = None,
97
+ end_meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
96
98
  correction_volume: Optional[float] = None,
99
+ movement_delay: Optional[float] = None,
97
100
  ) -> None:
98
101
  """Aspirate a given volume of liquid from the specified location.
99
102
  Args:
@@ -139,7 +142,10 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
139
142
  in_place: bool,
140
143
  push_out: Optional[float],
141
144
  meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
145
+ end_location: Optional[types.Location] = None,
146
+ end_meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None,
142
147
  correction_volume: Optional[float] = None,
148
+ movement_delay: Optional[float] = None,
143
149
  ) -> None:
144
150
  """Dispense a given volume of liquid into the specified location.
145
151
  Args:
@@ -622,6 +628,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
622
628
  trash_location: Union[types.Location, TrashBin, WasteChute],
623
629
  return_tip: bool,
624
630
  keep_last_tip: bool,
631
+ tips: Optional[List[LegacyWellCore]],
625
632
  ) -> None:
626
633
  """This will never be called because it was added in API 2.23"""
627
634
  assert False, "transfer_liquid is not supported in legacy context"
@@ -632,12 +639,17 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
632
639
  volume: float,
633
640
  source: Tuple[types.Location, LegacyWellCore],
634
641
  dest: List[Tuple[types.Location, LegacyWellCore]],
635
- new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
642
+ new_tip: Literal[
643
+ TransferTipPolicyV2.NEVER,
644
+ TransferTipPolicyV2.ONCE,
645
+ TransferTipPolicyV2.ALWAYS,
646
+ ],
636
647
  tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
637
648
  starting_tip: Optional[LegacyWellCore],
638
649
  trash_location: Union[types.Location, TrashBin, WasteChute],
639
650
  return_tip: bool,
640
651
  keep_last_tip: bool,
652
+ tips: Optional[List[LegacyWellCore]],
641
653
  ) -> None:
642
654
  """This will never be called because it was added in API 2.23"""
643
655
  assert False, "distribute_liquid is not supported in legacy context"
@@ -648,12 +660,17 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
648
660
  volume: float,
649
661
  source: List[Tuple[types.Location, LegacyWellCore]],
650
662
  dest: Union[Tuple[types.Location, LegacyWellCore], TrashBin, WasteChute],
651
- new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
663
+ new_tip: Literal[
664
+ TransferTipPolicyV2.NEVER,
665
+ TransferTipPolicyV2.ONCE,
666
+ TransferTipPolicyV2.ALWAYS,
667
+ ],
652
668
  tip_racks: List[Tuple[types.Location, LegacyLabwareCore]],
653
669
  starting_tip: Optional[LegacyWellCore],
654
670
  trash_location: Union[types.Location, TrashBin, WasteChute],
655
671
  return_tip: bool,
656
672
  keep_last_tip: bool,
673
+ tips: Optional[List[LegacyWellCore]],
657
674
  ) -> None:
658
675
  """This will never be called because it was added in API 2.23."""
659
676
  assert False, "consolidate_liquid is not supported in legacy context"
@@ -268,7 +268,7 @@ class LegacyThermocyclerCore(
268
268
  ramp_rate: Optional[float],
269
269
  hold_time_seconds: Optional[float] = None,
270
270
  block_max_volume: Optional[float] = None,
271
- ) -> LegacyTaskCore:
271
+ ) -> None:
272
272
  """Set the target temperature for the well block, in °C."""
273
273
  self._sync_module_hardware.set_target_block_temperature(
274
274
  celsius=celsius,
@@ -276,16 +276,27 @@ class LegacyThermocyclerCore(
276
276
  volume=block_max_volume,
277
277
  ramp_rate=ramp_rate,
278
278
  )
279
- return LegacyTaskCore()
280
279
 
281
280
  def wait_for_block_temperature(self) -> None:
282
281
  """Wait for target block temperature to be reached."""
283
282
  self._sync_module_hardware.wait_for_block_target()
284
283
 
285
- def set_target_lid_temperature(self, celsius: float) -> LegacyTaskCore:
284
+ def set_target_lid_temperature(self, celsius: float) -> None:
286
285
  """Set the target temperature for the heated lid, in °C."""
287
286
  self._sync_module_hardware.set_target_lid_temperature(celsius=celsius)
288
- return LegacyTaskCore()
287
+
288
+ def start_set_target_lid_temperature(self, celsius: float) -> LegacyTaskCore:
289
+ """Set the target temperature for the heated lid, in °C."""
290
+ assert False, "start_set_target_lid_temperature only supported on engine core"
291
+
292
+ def start_set_target_block_temperature(
293
+ self,
294
+ celsius: float,
295
+ ramp_rate: Optional[float],
296
+ block_max_volume: Optional[float] = None,
297
+ ) -> LegacyTaskCore:
298
+ """Set the target temperature for the heated block, in °C."""
299
+ assert False, "start_set_target_block_temperature only supported on engine core"
289
300
 
290
301
  def wait_for_lid_temperature(self) -> None:
291
302
  """Wait for target lid temperature to be reached."""
@@ -613,6 +613,18 @@ class LegacyProtocolCore(
613
613
  """Get labware parent location."""
614
614
  assert False, "get_labware_location only supported on engine core"
615
615
 
616
+ def capture_image(
617
+ self,
618
+ filename: Optional[str] = None,
619
+ resolution: Optional[Tuple[int, int]] = None,
620
+ zoom: Optional[float] = None,
621
+ contrast: Optional[float] = None,
622
+ brightness: Optional[float] = None,
623
+ saturation: Optional[float] = None,
624
+ ) -> None:
625
+ "Capture an image using a camera."
626
+ assert False, "capture_image only supported on engine core"
627
+
616
628
  def wait_for_tasks(self, task: Sequence[LegacyTaskCore]) -> None:
617
629
  """Wait for list of tasks to complete before executing subsequent commands."""
618
630
  assert False, "wait_for_tasks only supported on engine core"