opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a8__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 (190) 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 +92 -17
  45. opentrons/hardware_control/poller.py +22 -8
  46. opentrons/hardware_control/protocols/liquid_handler.py +12 -4
  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/pipette_movement_conflict.py +4 -18
  65. opentrons/protocol_api/core/engine/protocol.py +51 -2
  66. opentrons/protocol_api/core/engine/stringify.py +2 -0
  67. opentrons/protocol_api/core/engine/tasks.py +48 -0
  68. opentrons/protocol_api/core/engine/well.py +8 -0
  69. opentrons/protocol_api/core/instrument.py +19 -2
  70. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  71. opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
  72. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
  73. opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
  74. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  75. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  76. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  77. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  78. opentrons/protocol_api/core/module.py +58 -2
  79. opentrons/protocol_api/core/protocol.py +23 -2
  80. opentrons/protocol_api/core/tasks.py +31 -0
  81. opentrons/protocol_api/core/well.py +4 -0
  82. opentrons/protocol_api/instrument_context.py +388 -2
  83. opentrons/protocol_api/labware.py +10 -2
  84. opentrons/protocol_api/module_contexts.py +170 -6
  85. opentrons/protocol_api/protocol_context.py +87 -21
  86. opentrons/protocol_api/robot_context.py +41 -25
  87. opentrons/protocol_api/tasks.py +48 -0
  88. opentrons/protocol_api/validation.py +49 -3
  89. opentrons/protocol_engine/__init__.py +4 -0
  90. opentrons/protocol_engine/actions/__init__.py +6 -2
  91. opentrons/protocol_engine/actions/actions.py +31 -9
  92. opentrons/protocol_engine/clients/sync_client.py +42 -7
  93. opentrons/protocol_engine/commands/__init__.py +56 -0
  94. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  95. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  96. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  97. opentrons/protocol_engine/commands/aspirate.py +1 -0
  98. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  99. opentrons/protocol_engine/commands/capture_image.py +302 -0
  100. opentrons/protocol_engine/commands/command.py +2 -0
  101. opentrons/protocol_engine/commands/command_unions.py +62 -0
  102. opentrons/protocol_engine/commands/create_timer.py +83 -0
  103. opentrons/protocol_engine/commands/dispense.py +1 -0
  104. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  105. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  106. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  107. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  108. opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
  109. opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
  110. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
  111. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
  112. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
  113. opentrons/protocol_engine/commands/move_labware.py +3 -4
  114. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  115. opentrons/protocol_engine/commands/movement_common.py +31 -2
  116. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  117. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  118. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  119. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
  120. opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
  121. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  122. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  123. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +44 -7
  124. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
  125. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
  126. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  127. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  128. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  129. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  130. opentrons/protocol_engine/engine_support.py +3 -0
  131. opentrons/protocol_engine/errors/__init__.py +12 -0
  132. opentrons/protocol_engine/errors/exceptions.py +119 -0
  133. opentrons/protocol_engine/execution/__init__.py +4 -0
  134. opentrons/protocol_engine/execution/command_executor.py +62 -1
  135. opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
  136. opentrons/protocol_engine/execution/labware_movement.py +13 -15
  137. opentrons/protocol_engine/execution/movement.py +2 -0
  138. opentrons/protocol_engine/execution/pipetting.py +26 -25
  139. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  140. opentrons/protocol_engine/execution/run_control.py +8 -0
  141. opentrons/protocol_engine/execution/task_handler.py +157 -0
  142. opentrons/protocol_engine/protocol_engine.py +137 -36
  143. opentrons/protocol_engine/resources/__init__.py +4 -0
  144. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  145. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  146. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  147. opentrons/protocol_engine/resources/file_provider.py +133 -58
  148. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  149. opentrons/protocol_engine/slot_standardization.py +2 -0
  150. opentrons/protocol_engine/state/_well_math.py +60 -18
  151. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  152. opentrons/protocol_engine/state/camera.py +54 -0
  153. opentrons/protocol_engine/state/commands.py +37 -14
  154. opentrons/protocol_engine/state/geometry.py +276 -379
  155. opentrons/protocol_engine/state/labware.py +62 -108
  156. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  157. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
  158. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  159. opentrons/protocol_engine/state/modules.py +30 -8
  160. opentrons/protocol_engine/state/motion.py +60 -18
  161. opentrons/protocol_engine/state/preconditions.py +59 -0
  162. opentrons/protocol_engine/state/state.py +44 -0
  163. opentrons/protocol_engine/state/state_summary.py +4 -0
  164. opentrons/protocol_engine/state/tasks.py +139 -0
  165. opentrons/protocol_engine/state/tips.py +177 -258
  166. opentrons/protocol_engine/state/update_types.py +26 -9
  167. opentrons/protocol_engine/types/__init__.py +23 -4
  168. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  169. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  170. opentrons/protocol_engine/types/instrument.py +8 -1
  171. opentrons/protocol_engine/types/labware.py +1 -13
  172. opentrons/protocol_engine/types/location.py +26 -2
  173. opentrons/protocol_engine/types/module.py +11 -1
  174. opentrons/protocol_engine/types/tasks.py +38 -0
  175. opentrons/protocol_engine/types/tip.py +9 -0
  176. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  177. opentrons/protocol_runner/protocol_runner.py +14 -1
  178. opentrons/protocol_runner/run_orchestrator.py +49 -2
  179. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  180. opentrons/protocols/api_support/definitions.py +1 -1
  181. opentrons/protocols/api_support/types.py +2 -1
  182. opentrons/simulate.py +51 -15
  183. opentrons/system/camera.py +334 -4
  184. opentrons/system/ffmpeg.py +110 -0
  185. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/METADATA +4 -4
  186. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/RECORD +189 -161
  187. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  188. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/WHEEL +0 -0
  189. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/entry_points.txt +0 -0
  190. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/licenses/LICENSE +0 -0
@@ -74,6 +74,8 @@ _AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
74
74
  """The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking."""
75
75
  _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN = APIVersion(2, 25)
76
76
  """The version after which the user can supply liquid class transfers with non-assigned tip racks."""
77
+ _LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN = APIVersion(2, 27)
78
+ """The version after which the user can supply liquid class transfers with selected tips."""
77
79
 
78
80
 
79
81
  AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
@@ -199,6 +201,8 @@ class InstrumentContext(publisher.CommandPublisher):
199
201
  location: Optional[Union[types.Location, labware.Well]] = None,
200
202
  rate: float = 1.0,
201
203
  flow_rate: Optional[float] = None,
204
+ end_location: Optional[types.Location] = None,
205
+ movement_delay: Optional[float] = None,
202
206
  ) -> InstrumentContext:
203
207
  """
204
208
  Draw liquid into a pipette tip.
@@ -238,6 +242,16 @@ class InstrumentContext(publisher.CommandPublisher):
238
242
  :param flow_rate: The absolute flow rate in µL/s. If ``flow_rate`` is specified,
239
243
  ``rate`` must not be set.
240
244
  :type flow_rate: float
245
+ :param end_location: Tells the robot to move between location and end_location
246
+ while aspirating liquid. When this argument is used the location and
247
+ end_location must both be :py:class:`.Location`.
248
+ :type end_location: :py:class:`.Location`
249
+ :param movement_delay: Delay the x/y/z movement during a dynamic aspirate.
250
+ This option is only valid when using end_location. When this argument
251
+ is used, the x/y/z movement will wait movement_delay seconds after the pipette
252
+ starts to aspirate before moving. This may help when dispensing very viscous liquids
253
+ that need to build up some pressure before liquid starts to flow.
254
+ :type movement_delay: float
241
255
  :returns: This instance.
242
256
 
243
257
  .. note::
@@ -291,6 +305,50 @@ class InstrumentContext(publisher.CommandPublisher):
291
305
  move_to_location, well, meniscus_tracking = self._handle_aspirate_target(
292
306
  target=target
293
307
  )
308
+ if (
309
+ meniscus_tracking is not None
310
+ and meniscus_tracking == types.MeniscusTrackingTarget.DYNAMIC
311
+ ):
312
+ # we're using the old dynamic pipetting
313
+ if end_location is not None:
314
+ raise ValueError(
315
+ "Dynamic target is depreciated and you cannot use a dynamic target and and end location."
316
+ )
317
+ # re-work the dynamic location as a start and end location
318
+ new_start_location = types.Location(
319
+ point=move_to_location.point,
320
+ labware=move_to_location.labware,
321
+ _meniscus_tracking=types.MeniscusTrackingTarget.START,
322
+ )
323
+ target = validation.validate_location(
324
+ location=new_start_location, last_location=last_location
325
+ )
326
+ # Target already checked for this above, this is just for lint
327
+ assert not isinstance(target, validation.DisposalTarget)
328
+ move_to_location, well, meniscus_tracking = self._handle_aspirate_target(
329
+ target=target
330
+ )
331
+ end_location = types.Location(
332
+ point=move_to_location.point,
333
+ labware=move_to_location.labware,
334
+ _meniscus_tracking=types.MeniscusTrackingTarget.END,
335
+ )
336
+ end_move_to_location: Optional[types.Location] = None
337
+ end_meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None
338
+ if end_location is not None:
339
+ validation.validate_dynamic_locations(location, end_location)
340
+ end_target = validation.validate_location(
341
+ location=end_location, last_location=None
342
+ )
343
+ if isinstance(end_target, validation.DisposalTarget):
344
+ raise ValueError(
345
+ "Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands."
346
+ )
347
+ (
348
+ end_move_to_location,
349
+ end_well,
350
+ end_meniscus_tracking,
351
+ ) = self._handle_aspirate_target(target=end_target)
294
352
  if self.api_version >= APIVersion(2, 11):
295
353
  instrument.validate_takes_liquid(
296
354
  location=move_to_location,
@@ -322,6 +380,7 @@ class InstrumentContext(publisher.CommandPublisher):
322
380
  location=move_to_location,
323
381
  flow_rate=flow_rate,
324
382
  rate=rate,
383
+ end_location=end_move_to_location,
325
384
  ),
326
385
  ):
327
386
  self._core.aspirate(
@@ -330,8 +389,11 @@ class InstrumentContext(publisher.CommandPublisher):
330
389
  volume=c_vol,
331
390
  rate=rate,
332
391
  flow_rate=flow_rate,
333
- in_place=target.in_place,
392
+ in_place=target.in_place and end_move_to_location is None,
334
393
  meniscus_tracking=meniscus_tracking,
394
+ end_location=end_move_to_location,
395
+ end_meniscus_tracking=end_meniscus_tracking,
396
+ movement_delay=movement_delay,
335
397
  )
336
398
 
337
399
  return self
@@ -346,6 +408,8 @@ class InstrumentContext(publisher.CommandPublisher):
346
408
  rate: float = 1.0,
347
409
  push_out: Optional[float] = None,
348
410
  flow_rate: Optional[float] = None,
411
+ end_location: Optional[types.Location] = None,
412
+ movement_delay: Optional[float] = None,
349
413
  ) -> InstrumentContext:
350
414
  """
351
415
  Dispense liquid from a pipette tip.
@@ -415,6 +479,16 @@ class InstrumentContext(publisher.CommandPublisher):
415
479
  ``rate`` must not be set.
416
480
  :type flow_rate: float
417
481
 
482
+ :param end_location: Tells the robot to move between location and end_location
483
+ while dispensing liquid held in the pipette. When this argument is used
484
+ the location and end_location must both be a :py:class:`.Location`.
485
+ :type end_location: :py:class:`.Location`
486
+ :param movement_delay: Delay the x/y/z movement during a dynamic dispense.
487
+ This option is only valid when using end_location. When this argument
488
+ is used, the x/y/z movement will wait movement_delay seconds after the pipette
489
+ starts to dispense before moving. This may help when dispensing very viscous liquids
490
+ that need to build up some pressure before liquid starts to flow.
491
+ :type movement_delay: float
418
492
  :returns: This instance.
419
493
 
420
494
  .. note::
@@ -501,12 +575,59 @@ class InstrumentContext(publisher.CommandPublisher):
501
575
  in_place=target.in_place,
502
576
  push_out=push_out,
503
577
  meniscus_tracking=None,
578
+ end_location=None,
579
+ end_meniscus_tracking=None,
580
+ movement_delay=None,
504
581
  )
505
582
  return self
506
583
 
507
584
  move_to_location, well, meniscus_tracking = self._handle_dispense_target(
508
585
  target=target
509
586
  )
587
+ if (
588
+ meniscus_tracking is not None
589
+ and meniscus_tracking == types.MeniscusTrackingTarget.DYNAMIC
590
+ ):
591
+ # we're using the old dynamic pipetting
592
+ if end_location is not None:
593
+ raise ValueError(
594
+ "Dynamic target is depreciated and you cannot use a dynamic target and and end location."
595
+ )
596
+ # re-work the dynamic location as a start and end location
597
+ new_start_location = types.Location(
598
+ point=move_to_location.point,
599
+ labware=move_to_location.labware,
600
+ _meniscus_tracking=types.MeniscusTrackingTarget.START,
601
+ )
602
+ target = validation.validate_location(
603
+ location=new_start_location, last_location=last_location
604
+ )
605
+ # Target already checked for this above, this is just for lint
606
+ assert not isinstance(target, validation.DisposalTarget)
607
+ move_to_location, well, meniscus_tracking = self._handle_dispense_target(
608
+ target=target
609
+ )
610
+ end_location = types.Location(
611
+ point=move_to_location.point,
612
+ labware=move_to_location.labware,
613
+ _meniscus_tracking=types.MeniscusTrackingTarget.END,
614
+ )
615
+ end_move_to_location: Optional[types.Location] = None
616
+ end_meniscus_tracking: Optional[types.MeniscusTrackingTarget] = None
617
+ if end_location is not None:
618
+ validation.validate_dynamic_locations(location, end_location)
619
+ end_target = validation.validate_location(
620
+ location=end_location, last_location=None
621
+ )
622
+ if isinstance(end_target, validation.DisposalTarget):
623
+ raise ValueError(
624
+ "Trash Bin and Waste Chute are not acceptable location parameters for dynamic pipetting commands."
625
+ )
626
+ (
627
+ end_move_to_location,
628
+ end_well,
629
+ end_meniscus_tracking,
630
+ ) = self._handle_dispense_target(target=end_target)
510
631
 
511
632
  if self.api_version >= APIVersion(2, 11):
512
633
  instrument.validate_takes_liquid(
@@ -523,6 +644,7 @@ class InstrumentContext(publisher.CommandPublisher):
523
644
  location=move_to_location,
524
645
  rate=rate,
525
646
  flow_rate=flow_rate,
647
+ end_location=end_move_to_location,
526
648
  ),
527
649
  ):
528
650
  self._core.dispense(
@@ -531,9 +653,12 @@ class InstrumentContext(publisher.CommandPublisher):
531
653
  location=move_to_location,
532
654
  well_core=well._core if well is not None else None,
533
655
  flow_rate=flow_rate,
534
- in_place=target.in_place,
656
+ in_place=target.in_place and end_move_to_location is None,
535
657
  push_out=push_out,
536
658
  meniscus_tracking=meniscus_tracking,
659
+ end_location=end_move_to_location,
660
+ end_meniscus_tracking=end_meniscus_tracking,
661
+ movement_delay=movement_delay,
537
662
  )
538
663
 
539
664
  return self
@@ -704,6 +829,204 @@ class InstrumentContext(publisher.CommandPublisher):
704
829
  dispense_with_delay(push_out=None)
705
830
  return self
706
831
 
832
+ @requires_version(2, 27)
833
+ def dynamic_mix( # noqa: C901
834
+ self,
835
+ aspirate_start_location: types.Location,
836
+ dispense_start_location: types.Location,
837
+ repetitions: int = 1,
838
+ volume: Optional[float] = None,
839
+ aspirate_end_location: Optional[types.Location] = None,
840
+ dispense_end_location: Optional[types.Location] = None,
841
+ rate: float = 1.0,
842
+ aspirate_flow_rate: Optional[float] = None,
843
+ dispense_flow_rate: Optional[float] = None,
844
+ aspirate_delay: Optional[float] = None,
845
+ dispense_delay: Optional[float] = None,
846
+ final_push_out: Optional[float] = None,
847
+ movement_delay: Optional[float] = None,
848
+ ) -> InstrumentContext:
849
+ """
850
+ Mix a volume of liquid by repeatedly aspirating and dispensing it in a multiple locations.
851
+
852
+ See :ref:`dynamic_mix` for examples.
853
+
854
+ :param repetitions: Number of times to mix (default is 1).
855
+ :param volume: The volume to mix, measured in µL. If unspecified, defaults
856
+ to the maximum volume for the pipette and its attached tip.
857
+
858
+ If ``mix`` is called with a volume of precisely 0, its behavior
859
+ depends on the API level of the protocol. On API levels below 2.16,
860
+ it will behave the same as a volume of ``None``/unspecified: mix
861
+ the full working volume of the pipette. On API levels at or above 2.16,
862
+ no liquid will be mixed.
863
+ :param aspirate_start_location: The :py:class:`~.types.Location` where the pipette will aspirate from.
864
+ :param aspirate_end_location: The :py:class:`~.types.Location` If this argument is supplied
865
+ the pipette will move between aspirate_start_location and aspirate_end_location
866
+ while performing the aspirate.
867
+ :param dispense_start_location: The :py:class:`~.types.Location` where the pipette will dispense to.
868
+ :param dispense_end_location: The :py:class:`~.types.Location` If this argument is supplied
869
+ the pipette will move between dispense_start_location and dispense_end_location
870
+ while performing the dispense.
871
+ :param rate: How quickly the pipette aspirates and dispenses liquid while
872
+ mixing. The aspiration flow rate is calculated as ``rate``
873
+ multiplied by :py:attr:`flow_rate.aspirate <flow_rate>`. The
874
+ dispensing flow rate is calculated as ``rate`` multiplied by
875
+ :py:attr:`flow_rate.dispense <flow_rate>`. See
876
+ :ref:`new-plunger-flow-rates`.
877
+ :param aspirate_flow_rate: The absolute flow rate for each aspirate in the mix, in µL/s.
878
+ If this is specified, ``rate`` must not be set.
879
+ :param dispense_flow_rate: The absolute flow rate for each dispense in the mix, in µL/s.
880
+ If this is specified, ``rate`` must not be set.
881
+ :param aspirate_delay: How long to wait after each aspirate in the mix, in seconds.
882
+ :param dispense_delay: How long to wait after each dispense in the mix, in seconds.
883
+ :param final_push_out: How much volume to push out after the final mix repetition. The
884
+ pipette will not push out after earlier repetitions. If
885
+ not specified or ``None``, the pipette will push out the
886
+ default non-zero amount. See :ref:`push-out-dispense`.
887
+ :param movement_delay: Delay the x/y/z movement during a dynamic mix.
888
+ This option is only valid when using aspirate_end_location or dispense_end_location.
889
+ When this argument is used, the x/y/z movement will wait movement_delay seconds
890
+ after the pipette starts to aspirate/dispense before moving. This may help when mixing
891
+ very viscous liquids that need to build up some pressure before liquid starts to flow.
892
+ :type movement_delay: float
893
+ :raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
894
+ :returns: This instance.
895
+
896
+ .. note::
897
+
898
+ All the arguments of ``mix`` are optional. However, if you omit one of them,
899
+ all subsequent arguments must be passed as keyword arguments. For instance,
900
+ ``pipette.mix(1, location=wellplate['A1'])`` is a valid call, but
901
+ ``pipette.mix(1, wellplate['A1'])`` is not.
902
+
903
+ """
904
+ _log.debug(
905
+ "mixing {}uL with {} repetitions in {} and {} at rate={}".format(
906
+ volume,
907
+ repetitions,
908
+ aspirate_start_location,
909
+ dispense_start_location,
910
+ rate,
911
+ )
912
+ )
913
+ # Verify all locations are within the same well
914
+ if not aspirate_start_location.labware.is_well:
915
+ raise ValueError(
916
+ "Dynamic mix aspirate start location must be within a well"
917
+ )
918
+ if not dispense_start_location.labware.is_well:
919
+ raise ValueError(
920
+ "Dynamic mix dispense start location must be within a well"
921
+ )
922
+
923
+ if (
924
+ aspirate_end_location is not None
925
+ and not aspirate_end_location.labware.is_well
926
+ ):
927
+ raise ValueError("Dynamic mix aspirate end location must be within a well")
928
+ if (
929
+ dispense_end_location is not None
930
+ and not dispense_end_location.labware.is_well
931
+ ):
932
+ raise ValueError("Dynamic mix dispense end location must be within a well")
933
+
934
+ (
935
+ _,
936
+ asp_start_well,
937
+ ) = aspirate_start_location.labware.get_parent_labware_and_well()
938
+ (
939
+ _,
940
+ disp_start_well,
941
+ ) = dispense_start_location.labware.get_parent_labware_and_well()
942
+
943
+ if asp_start_well != disp_start_well:
944
+ raise ValueError(
945
+ "Aspirate and Dispense locations must be within the same well"
946
+ )
947
+
948
+ if not self._core.has_tip():
949
+ raise UnexpectedTipRemovalError("mix", self.name, self.mount)
950
+
951
+ c_vol = self._core.get_available_volume() if volume is None else volume
952
+
953
+ if aspirate_flow_rate:
954
+ if rate != 1.0:
955
+ raise ValueError(
956
+ "rate must not be set if aspirate_flow_rate is specified"
957
+ )
958
+ if dispense_flow_rate:
959
+ if rate != 1.0:
960
+ raise ValueError(
961
+ "rate must not be set if dispense_flow_rate is specified"
962
+ )
963
+
964
+ def delay_with_publish(seconds: float) -> None:
965
+ # We don't have access to ProtocolContext.delay() which would automatically
966
+ # publish a message to the broker, so we have to do it manually:
967
+ with publisher.publish_context(
968
+ broker=self.broker,
969
+ command=protocol_cmds.delay(seconds=seconds, minutes=0, msg=None),
970
+ ):
971
+ self._protocol_core.delay(seconds=seconds, msg=None)
972
+
973
+ def aspirate_with_delay() -> None:
974
+ self.aspirate(
975
+ volume,
976
+ aspirate_start_location,
977
+ rate,
978
+ flow_rate=aspirate_flow_rate,
979
+ end_location=aspirate_end_location,
980
+ movement_delay=movement_delay,
981
+ )
982
+ if aspirate_delay:
983
+ delay_with_publish(aspirate_delay)
984
+
985
+ def dispense_with_delay(push_out: Optional[float]) -> None:
986
+ self.dispense(
987
+ volume=volume,
988
+ location=dispense_start_location,
989
+ rate=rate,
990
+ flow_rate=dispense_flow_rate,
991
+ push_out=push_out,
992
+ end_location=dispense_end_location,
993
+ movement_delay=movement_delay,
994
+ )
995
+ if dispense_delay:
996
+ delay_with_publish(dispense_delay)
997
+
998
+ with publisher.publish_context(
999
+ broker=self.broker,
1000
+ command=cmds.dynamic_mix(
1001
+ instrument=self,
1002
+ repetitions=repetitions,
1003
+ volume=c_vol,
1004
+ aspirate_start_location=aspirate_start_location,
1005
+ aspirate_end_location=aspirate_end_location,
1006
+ dispense_start_location=dispense_start_location,
1007
+ dispense_end_location=dispense_end_location,
1008
+ movement_delay=movement_delay or 0.0,
1009
+ ),
1010
+ ):
1011
+ aspirate_with_delay()
1012
+ with AutoProbeDisable(self):
1013
+ while repetitions - 1 > 0:
1014
+ # starting in 2.16, we disable push_out on all but the last
1015
+ # dispense() to prevent the tip from jumping out of the liquid
1016
+ # during the mix (PR #14004):
1017
+ self.move_to(dispense_start_location, force_direct=True)
1018
+ dispense_with_delay(push_out=0.0)
1019
+ self.move_to(aspirate_start_location, force_direct=True)
1020
+ aspirate_with_delay()
1021
+ repetitions -= 1
1022
+
1023
+ self.move_to(dispense_start_location, force_direct=True)
1024
+ if final_push_out is not None:
1025
+ dispense_with_delay(push_out=final_push_out)
1026
+ else:
1027
+ dispense_with_delay(push_out=None)
1028
+ return self
1029
+
707
1030
  @requires_version(2, 0)
708
1031
  def blow_out(
709
1032
  self,
@@ -1799,6 +2122,9 @@ class InstrumentContext(publisher.CommandPublisher):
1799
2122
  group_wells: bool = True,
1800
2123
  keep_last_tip: Optional[bool] = None,
1801
2124
  tip_racks: Optional[List[labware.Labware]] = None,
2125
+ tips: Optional[
2126
+ Union[Sequence[labware.Well], Sequence[Sequence[labware.Well]]]
2127
+ ] = None,
1802
2128
  ) -> InstrumentContext:
1803
2129
  """Move a particular type of liquid from one well or group of wells to another.
1804
2130
 
@@ -1838,9 +2164,14 @@ class InstrumentContext(publisher.CommandPublisher):
1838
2164
  :param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
1839
2165
  up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
1840
2166
  value of ``InstrumentContext.tip_racks``.
2167
+ :param tips: An ordered list of tips to use for the transfer. If the list contains fewer tips than needed to
2168
+ complete the transfer, the API will raise an error. The pipette will use only these tips even if
2169
+ ``InstrumentContext.tip_racks`` or the ``tip_racks`` parameter of this method is set.
1841
2170
 
1842
2171
  .. versionchanged:: 2.25
1843
2172
  Added the ``tip_racks`` parameter.
2173
+ .. versionchanged:: 2.27
2174
+ Added the ``tips`` parameter.
1844
2175
 
1845
2176
  """
1846
2177
  if volume == 0.0:
@@ -1859,6 +2190,15 @@ class InstrumentContext(publisher.CommandPublisher):
1859
2190
  until_version=f"{_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN}",
1860
2191
  current_version=f"{self.api_version}",
1861
2192
  )
2193
+ if (
2194
+ tips is not None
2195
+ and self.api_version < _LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN
2196
+ ):
2197
+ raise APIVersionError(
2198
+ api_element="tips",
2199
+ until_version=f"{_LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN}",
2200
+ current_version=f"{self.api_version}",
2201
+ )
1862
2202
 
1863
2203
  transfer_args = verify_and_normalize_transfer_args(
1864
2204
  source=source,
@@ -1872,6 +2212,7 @@ class InstrumentContext(publisher.CommandPublisher):
1872
2212
  trash_location=(
1873
2213
  trash_location if trash_location is not None else self.trash_container
1874
2214
  ),
2215
+ tips=tips,
1875
2216
  )
1876
2217
  verified_keep_last_tip = resolve_keep_last_tip(
1877
2218
  keep_last_tip, transfer_args.tip_policy
@@ -1926,6 +2267,9 @@ class InstrumentContext(publisher.CommandPublisher):
1926
2267
  trash_location=transfer_args.trash_location,
1927
2268
  return_tip=return_tip,
1928
2269
  keep_last_tip=verified_keep_last_tip,
2270
+ tips=[tip._core for tip in transfer_args.tips]
2271
+ if transfer_args.tips is not None
2272
+ else None,
1929
2273
  )
1930
2274
 
1931
2275
  return self
@@ -1947,6 +2291,9 @@ class InstrumentContext(publisher.CommandPublisher):
1947
2291
  group_wells: bool = True,
1948
2292
  keep_last_tip: Optional[bool] = None,
1949
2293
  tip_racks: Optional[List[labware.Labware]] = None,
2294
+ tips: Optional[
2295
+ Union[Sequence[labware.Well], Sequence[Sequence[labware.Well]]]
2296
+ ] = None,
1950
2297
  ) -> InstrumentContext:
1951
2298
  """
1952
2299
  Distribute a particular type of liquid from one well to a group of wells.
@@ -1983,9 +2330,14 @@ class InstrumentContext(publisher.CommandPublisher):
1983
2330
  :param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
1984
2331
  up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
1985
2332
  value of ``InstrumentContext.tip_racks``.
2333
+ :param tips: An ordered list of tips to use for the transfer. If the list contains fewer tips than needed to
2334
+ complete the transfer, the API will raise an error. The pipette will use only these tips even if
2335
+ ``InstrumentContext.tip_racks`` or the ``tip_racks`` parameter of this method is set.
1986
2336
 
1987
2337
  .. versionchanged:: 2.25
1988
2338
  Added the ``tip_racks`` parameter.
2339
+ .. versionchanged:: 2.27
2340
+ Added the ``tips`` parameter.
1989
2341
 
1990
2342
  """
1991
2343
  if volume == 0.0:
@@ -2004,6 +2356,15 @@ class InstrumentContext(publisher.CommandPublisher):
2004
2356
  until_version=f"{_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN}",
2005
2357
  current_version=f"{self.api_version}",
2006
2358
  )
2359
+ if (
2360
+ tips is not None
2361
+ and self.api_version < _LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN
2362
+ ):
2363
+ raise APIVersionError(
2364
+ api_element="tips",
2365
+ until_version=f"{_LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN}",
2366
+ current_version=f"{self.api_version}",
2367
+ )
2007
2368
 
2008
2369
  transfer_args = verify_and_normalize_transfer_args(
2009
2370
  source=source,
@@ -2017,6 +2378,7 @@ class InstrumentContext(publisher.CommandPublisher):
2017
2378
  trash_location=(
2018
2379
  trash_location if trash_location is not None else self.trash_container
2019
2380
  ),
2381
+ tips=tips,
2020
2382
  )
2021
2383
  verified_keep_last_tip = resolve_keep_last_tip(
2022
2384
  keep_last_tip, transfer_args.tip_policy
@@ -2079,6 +2441,9 @@ class InstrumentContext(publisher.CommandPublisher):
2079
2441
  trash_location=transfer_args.trash_location,
2080
2442
  return_tip=return_tip,
2081
2443
  keep_last_tip=verified_keep_last_tip,
2444
+ tips=[tip._core for tip in transfer_args.tips]
2445
+ if transfer_args.tips is not None
2446
+ else None,
2082
2447
  )
2083
2448
 
2084
2449
  return self
@@ -2100,6 +2465,9 @@ class InstrumentContext(publisher.CommandPublisher):
2100
2465
  group_wells: bool = True,
2101
2466
  keep_last_tip: Optional[bool] = None,
2102
2467
  tip_racks: Optional[List[labware.Labware]] = None,
2468
+ tips: Optional[
2469
+ Union[Sequence[labware.Well], Sequence[Sequence[labware.Well]]]
2470
+ ] = None,
2103
2471
  ) -> InstrumentContext:
2104
2472
  """
2105
2473
  Consolidate a particular type of liquid from a group of wells to one well.
@@ -2137,9 +2505,14 @@ class InstrumentContext(publisher.CommandPublisher):
2137
2505
  :param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
2138
2506
  up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
2139
2507
  value of ``InstrumentContext.tip_racks``.
2508
+ :param tips: An ordered list of tips to use for the transfer. If the list contains fewer tips than needed to
2509
+ complete the transfer, the API will raise an error. The pipette will use only these tips even if
2510
+ ``InstrumentContext.tip_racks`` or the ``tip_racks`` parameter of this method is set.
2140
2511
 
2141
2512
  .. versionchanged:: 2.25
2142
2513
  Added the ``tip_racks`` parameter.
2514
+ .. versionchanged:: 2.27
2515
+ Added the ``tips`` parameter.
2143
2516
 
2144
2517
  """
2145
2518
  if volume == 0.0:
@@ -2158,6 +2531,15 @@ class InstrumentContext(publisher.CommandPublisher):
2158
2531
  until_version=f"{_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN}",
2159
2532
  current_version=f"{self.api_version}",
2160
2533
  )
2534
+ if (
2535
+ tips is not None
2536
+ and self.api_version < _LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN
2537
+ ):
2538
+ raise APIVersionError(
2539
+ api_element="tips",
2540
+ until_version=f"{_LIQUID_CLASS_TRANSFER_TIPS_ARG_ADDED_IN}",
2541
+ current_version=f"{self.api_version}",
2542
+ )
2161
2543
 
2162
2544
  transfer_args = verify_and_normalize_transfer_args(
2163
2545
  source=source,
@@ -2171,6 +2553,7 @@ class InstrumentContext(publisher.CommandPublisher):
2171
2553
  trash_location=(
2172
2554
  trash_location if trash_location is not None else self.trash_container
2173
2555
  ),
2556
+ tips=tips,
2174
2557
  )
2175
2558
  verified_keep_last_tip = resolve_keep_last_tip(
2176
2559
  keep_last_tip, transfer_args.tip_policy
@@ -2232,6 +2615,9 @@ class InstrumentContext(publisher.CommandPublisher):
2232
2615
  trash_location=transfer_args.trash_location,
2233
2616
  return_tip=return_tip,
2234
2617
  keep_last_tip=verified_keep_last_tip,
2618
+ tips=[tip._core for tip in transfer_args.tips]
2619
+ if transfer_args.tips is not None
2620
+ else None,
2235
2621
  )
2236
2622
 
2237
2623
  return self
@@ -347,6 +347,11 @@ class Well:
347
347
  """Get the current liquid volume in a well."""
348
348
  return self._core.get_liquid_volume()
349
349
 
350
+ @requires_version(2, 27)
351
+ def has_tracked_liquid(self) -> bool:
352
+ """Get the current liquid volume in a well."""
353
+ return self._core.has_tracked_liquid()
354
+
350
355
  @requires_version(2, 24)
351
356
  def volume_from_height(self, height: LiquidTrackingType) -> LiquidTrackingType:
352
357
  """Return the volume contained in a well at any height."""
@@ -817,8 +822,8 @@ class Labware:
817
822
  def set_offset(self, x: float, y: float, z: float) -> None:
818
823
  """Set the labware's position offset.
819
824
 
820
- The offset is an x, y, z vector in deck coordinates
821
- (see :ref:`protocol-api-deck-coords`).
825
+ An offset of `(x=0, y=0, z=0)` means the labware's uncalibrated position before
826
+ any offset from Labware Position Check is applied.
822
827
 
823
828
  How the motion system applies the offset depends on the API level of the protocol.
824
829
 
@@ -877,6 +882,9 @@ class Labware:
877
882
  def calibrated_offset(self) -> Point:
878
883
  """The front-left-bottom corner of the labware, including its labware offset.
879
884
 
885
+ The offset is an x, y, z vector in deck coordinates
886
+ (see :ref:`protocol-api-deck-coords`).
887
+
880
888
  When running a protocol in the Opentrons App or on the touchscreen, Labware
881
889
  Position Check sets the labware offset.
882
890
  """