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
@@ -43,7 +43,8 @@ from .module_validation_and_errors import (
43
43
  )
44
44
  from .labware import Labware
45
45
  from . import validation
46
-
46
+ from . import Task
47
+ from opentrons.drivers.thermocycler.driver import BLOCK_VOL_MIN, BLOCK_VOL_MAX
47
48
 
48
49
  _MAGNETIC_MODULE_HEIGHT_PARAM_REMOVED_IN = APIVersion(2, 14)
49
50
 
@@ -447,18 +448,28 @@ class TemperatureModuleContext(ModuleContext):
447
448
  No other protocol commands will execute while waiting for the temperature.
448
449
 
449
450
  :param celsius: A value between 4 and 95, representing the target temperature in °C.
451
+
450
452
  """
451
453
  self._core.set_target_temperature(celsius)
452
454
  self._core.wait_for_target_temperature()
453
455
 
454
456
  @publish(command=cmds.tempdeck_set_temp)
455
457
  @requires_version(2, 3)
456
- def start_set_temperature(self, celsius: float) -> None:
458
+ def start_set_temperature(self, celsius: float) -> Task:
457
459
  """Set the target temperature without waiting for the target to be hit.
458
460
 
461
+ .. versionchanged:: 2.27
462
+ Returns a task object that represents concurrent preheating.
463
+ Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for the preheat to complete.
464
+
465
+ On version 2.26 or below, this function returns ``None``.
459
466
  :param celsius: A value between 4 and 95, representing the target temperature in °C.
460
467
  """
461
- self._core.set_target_temperature(celsius)
468
+ task = self._core.set_target_temperature(celsius)
469
+ if self._api_version >= APIVersion(2, 27):
470
+ return Task(api_version=self._api_version, core=task)
471
+ else:
472
+ return cast(Task, None)
462
473
 
463
474
  @publish(command=cmds.tempdeck_await_temp)
464
475
  @requires_version(2, 3)
@@ -672,6 +683,10 @@ class ThermocyclerContext(ModuleContext):
672
683
  :param block_max_volume: The greatest volume of liquid contained in any
673
684
  individual well of the loaded labware, in µL.
674
685
  If not specified, the default is 25 µL.
686
+ After API version 2.27 it will attempt to use
687
+ the liquid tracking of the labware first and
688
+ then fall back to the 25 if there is no probed
689
+ or loaded liquid.
675
690
 
676
691
  .. note::
677
692
 
@@ -682,18 +697,59 @@ class ThermocyclerContext(ModuleContext):
682
697
  seconds = validation.ensure_hold_time_seconds(
683
698
  seconds=hold_time_seconds, minutes=hold_time_minutes
684
699
  )
700
+ if self._api_version >= APIVersion(2, 27) and block_max_volume is None:
701
+ block_max_volume = self._get_current_labware_max_vol()
685
702
  self._core.set_target_block_temperature(
686
703
  celsius=temperature,
687
704
  hold_time_seconds=seconds,
688
705
  block_max_volume=block_max_volume,
706
+ ramp_rate=ramp_rate,
689
707
  )
690
708
  self._core.wait_for_block_temperature()
691
709
 
710
+ @publish(command=cmds.thermocycler_start_set_block_temp)
711
+ @requires_version(2, 27)
712
+ def start_set_block_temperature(
713
+ self,
714
+ temperature: float,
715
+ ramp_rate: Optional[float] = None,
716
+ block_max_volume: Optional[float] = None,
717
+ ) -> Task:
718
+ """Starts to set the target temperature for the well block, in °C.
719
+
720
+ Returns a task object that represents concurrent preheating.
721
+ Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
722
+ the preheat to complete.
723
+
724
+ :param temperature: A value between 4 and 99, representing the target
725
+ temperature in °C.
726
+ :param block_max_volume: The greatest volume of liquid contained in any
727
+ individual well of the loaded labware, in µL.
728
+ If not specified, the default is 25 µL.
729
+ After API version 2.27 it will attempt to use
730
+ the liquid tracking of the labware first and
731
+ then fall back to the 25 if there is no probed
732
+ or loaded liquid.
733
+ """
734
+
735
+ if block_max_volume is None:
736
+ block_max_volume = self._get_current_labware_max_vol()
737
+ task = self._core.start_set_target_block_temperature(
738
+ celsius=temperature,
739
+ block_max_volume=block_max_volume,
740
+ ramp_rate=ramp_rate,
741
+ )
742
+ return Task(api_version=self._api_version, core=task)
743
+
692
744
  @publish(command=cmds.thermocycler_set_lid_temperature)
693
745
  @requires_version(2, 0)
694
746
  def set_lid_temperature(self, temperature: float) -> None:
695
747
  """Set the target temperature for the heated lid, in °C.
696
748
 
749
+ Returns a task object that represents concurrent preheating.
750
+ Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
751
+ the preheat to complete.
752
+
697
753
  :param temperature: A value between 37 and 110, representing the target
698
754
  temperature in °C.
699
755
 
@@ -706,6 +762,27 @@ class ThermocyclerContext(ModuleContext):
706
762
  self._core.set_target_lid_temperature(celsius=temperature)
707
763
  self._core.wait_for_lid_temperature()
708
764
 
765
+ @publish(command=cmds.thermocycler_start_set_lid_temperature)
766
+ @requires_version(2, 27)
767
+ def start_set_lid_temperature(self, temperature: float) -> Task:
768
+ """Set the target temperature for the heated lid, in °C.
769
+
770
+ Returns a task object that represents concurrent preheating.
771
+ Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
772
+ the preheat to complete.
773
+
774
+ :param temperature: A value between 37 and 110, representing the target
775
+ temperature in °C.
776
+
777
+ .. note::
778
+
779
+ The Thermocycler will proceed to the next command immediately after
780
+ ``temperature`` is reached.
781
+
782
+ """
783
+ task = self._core.start_set_target_lid_temperature(celsius=temperature)
784
+ return Task(api_version=self._api_version, core=task)
785
+
709
786
  @publish(command=cmds.thermocycler_execute_profile)
710
787
  @requires_version(2, 0)
711
788
  def execute_profile(
@@ -739,6 +816,39 @@ class ThermocyclerContext(ModuleContext):
739
816
  block_max_volume=block_max_volume,
740
817
  )
741
818
 
819
+ @publish(command=cmds.thermocycler_start_execute_profile)
820
+ @requires_version(2, 27)
821
+ def start_execute_profile(
822
+ self,
823
+ steps: List[ThermocyclerStep],
824
+ repetitions: int,
825
+ block_max_volume: Optional[float] = None,
826
+ ) -> Task:
827
+ """Start a Thermocycler profile and return a :py:class:`Task` representing its execution.
828
+ Profile is defined as a cycle of ``steps``, for a given number of ``repetitions``.
829
+
830
+ Returns a task object that represents concurrent execution of the profile.
831
+ Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for the preheat to complete.
832
+
833
+ :param steps: List of steps that make up a single cycle.
834
+ Each list item should be a dictionary that maps to the parameters
835
+ of the :py:meth:`set_block_temperature` method. The dictionary's
836
+ keys must be ``temperature`` and one or both of
837
+ ``hold_time_seconds`` and ``hold_time_minutes``.
838
+ :param repetitions: The number of times to repeat the cycled steps.
839
+ :param block_max_volume: The greatest volume of liquid contained in any
840
+ individual well of the loaded labware, in µL.
841
+ If not specified, the default is 25 µL.
842
+ """
843
+ repetitions = validation.ensure_thermocycler_repetition_count(repetitions)
844
+ validated_steps = validation.ensure_thermocycler_profile_steps(steps)
845
+ task = self._core.start_execute_profile(
846
+ steps=validated_steps,
847
+ repetitions=repetitions,
848
+ block_max_volume=block_max_volume,
849
+ )
850
+ return Task(api_version=self._api_version, core=task)
851
+
742
852
  @publish(command=cmds.thermocycler_deactivate_lid)
743
853
  @requires_version(2, 0)
744
854
  def deactivate_lid(self) -> None:
@@ -860,6 +970,23 @@ class ThermocyclerContext(ModuleContext):
860
970
  """Index of the current step within the current cycle"""
861
971
  return self._core.get_current_step_index()
862
972
 
973
+ def _get_current_labware_max_vol(self) -> Optional[float]:
974
+ max_vol: Optional[float] = None
975
+ if self.labware is not None:
976
+ for well in self.labware.wells():
977
+ if well.has_tracked_liquid():
978
+ # make sure that max vol is a float first if we have liquid
979
+ max_vol = 0.0 if max_vol is None else max_vol
980
+ well_vol = well.current_liquid_volume()
981
+ # ignore simulated probe results
982
+ if isinstance(well_vol, float):
983
+ max_vol = max(max_vol, well_vol)
984
+ if max_vol > BLOCK_VOL_MAX:
985
+ max_vol = BLOCK_VOL_MAX
986
+ elif max_vol < BLOCK_VOL_MIN:
987
+ max_vol = BLOCK_VOL_MIN
988
+ return max_vol
989
+
863
990
 
864
991
  class HeaterShakerContext(ModuleContext):
865
992
  """An object representing a connected Heater-Shaker Module.
@@ -973,18 +1100,21 @@ class HeaterShakerContext(ModuleContext):
973
1100
 
974
1101
  @requires_version(2, 13)
975
1102
  @publish(command=cmds.heater_shaker_set_target_temperature)
976
- def set_target_temperature(self, celsius: float) -> None:
1103
+ def set_target_temperature(self, celsius: float) -> Task:
977
1104
  """Set target temperature and return immediately.
978
1105
 
979
1106
  Sets the Heater-Shaker's target temperature and returns immediately without
980
1107
  waiting for the target to be reached. Does not delay the protocol until
981
1108
  target temperature has reached.
982
1109
  Use :py:meth:`~.HeaterShakerContext.wait_for_temperature` to delay
983
- protocol execution.
1110
+ protocol execution for api levels below 2.27.
984
1111
 
985
1112
  .. versionchanged:: 2.25
986
1113
  Removed the minimum temperature limit of 37 °C. Note that temperatures under ambient are
987
1114
  not achievable.
1115
+ .. versionchanged:: 2.27
1116
+ Returns a task object that represents concurrent preheating.
1117
+ Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for the preheat to complete.
988
1118
 
989
1119
  :param celsius: A value under 95, representing the target temperature in °C.
990
1120
  Values are automatically truncated to two decimal places,
@@ -993,7 +1123,11 @@ class HeaterShakerContext(ModuleContext):
993
1123
  validated_temp = validate_heater_shaker_temperature(
994
1124
  celsius=celsius, api_version=self.api_version
995
1125
  )
996
- self._core.set_target_temperature(celsius=validated_temp)
1126
+ task = self._core.set_target_temperature(celsius=validated_temp)
1127
+ if self._api_version >= APIVersion(2, 27):
1128
+ return Task(api_version=self._api_version, core=task)
1129
+ else:
1130
+ return cast(Task, None)
997
1131
 
998
1132
  @requires_version(2, 13)
999
1133
  @publish(command=cmds.heater_shaker_wait_for_temperature)
@@ -1021,6 +1155,21 @@ class HeaterShakerContext(ModuleContext):
1021
1155
  validated_speed = validate_heater_shaker_speed(rpm=rpm)
1022
1156
  self._core.set_and_wait_for_shake_speed(rpm=validated_speed)
1023
1157
 
1158
+ @requires_version(2, 27)
1159
+ @publish(command=cmds.heater_shaker_set_shake_speed)
1160
+ def set_shake_speed(self, rpm: int) -> Task:
1161
+ """Set a shake speed in rpm to run in the background.
1162
+
1163
+ .. note::
1164
+
1165
+ Before shaking, this command will retract the pipettes upward if they are parked adjacent to the Heater-Shaker.
1166
+
1167
+ :param rpm: A value between 200 and 3000, representing the target shake speed in revolutions per minute.
1168
+ """
1169
+ validated_speed = validate_heater_shaker_speed(rpm=rpm)
1170
+ task = self._core.set_shake_speed(rpm=validated_speed)
1171
+ return Task(api_version=self._api_version, core=task)
1172
+
1024
1173
  @requires_version(2, 13)
1025
1174
  @publish(command=cmds.heater_shaker_open_labware_latch)
1026
1175
  def open_labware_latch(self) -> None:
@@ -1431,9 +1580,15 @@ class FlexStackerContext(ModuleContext):
1431
1580
  :param adapter_namespace: Applies to ``adapter`` the same way that ``namespace``
1432
1581
  applies to ``load_name``.
1433
1582
 
1583
+ .. versionchanged:: 2.26
1584
+ ``adapter_namespace`` may now be specified explicitly. When you've specified ``namespace`` for ``load_name`` but not ``adapter_namespace``, ``adapter_namespace`` now independently follows the same search rules described in ``namespace``. Formerly, it took the exact ``namespace`` value.
1585
+
1434
1586
  :param adapter_version: Applies to ``adapter`` the same way that ``version``
1435
1587
  applies to ``load_name``.
1436
1588
 
1589
+ .. versionchanged:: 2.26
1590
+ ``adapter_version`` may now be specified explictly. When unspecified, improved search rules prevent selecting a version that does not exist.
1591
+
1437
1592
  :param lid: A lid to load the on top of the main labware. Accepts the same
1438
1593
  values as the ``load_name`` parameter of :py:meth:`~.ProtocolContext.load_lid_stack`. The
1439
1594
  lid will use the same namespace as the labware, and the API will
@@ -1442,9 +1597,18 @@ class FlexStackerContext(ModuleContext):
1442
1597
  :param lid_namespace: Applies to ``lid`` the same way that ``namespace``
1443
1598
  applies to ``load_name``.
1444
1599
 
1600
+ .. versionchanged:: 2.26
1601
+ ``lid_namespace`` may now be specified explicitly.
1602
+ When you've specified ``namespace`` for ``load_name`` but not ``lid_namespace``,
1603
+ ``lid_namespace`` now independently follows the same search rules
1604
+ described in ``namespace``. Formerly, it took the exact ``namespace`` value.
1605
+
1445
1606
  :param lid_version: Applies to ``lid`` the same way that ``version``
1446
1607
  applies to ``load_name``.
1447
1608
 
1609
+ .. versionchanged:: 2.26
1610
+ ``lid_version`` may now be specified explicitly. When unspecified, improved search rules prevent selecting a version that does not exist.
1611
+
1448
1612
  :param count: The number of labware that the Flex Stacker should store. If not specified, this will be the maximum amount of this kind of
1449
1613
  labware that the Flex Stacker is capable of storing.
1450
1614
 
@@ -11,6 +11,7 @@ from typing import (
11
11
  Union,
12
12
  Mapping,
13
13
  cast,
14
+ Tuple,
14
15
  )
15
16
 
16
17
  from opentrons_shared_data.labware.types import LabwareDefinition
@@ -89,6 +90,7 @@ from .module_contexts import (
89
90
  FlexStackerContext,
90
91
  ModuleContext,
91
92
  )
93
+ from .tasks import Task
92
94
  from ._parameters import Parameters
93
95
 
94
96
 
@@ -234,10 +236,7 @@ class ProtocolContext(CommandPublisher):
234
236
  @property
235
237
  @requires_version(2, 22)
236
238
  def robot(self) -> RobotContext:
237
- """The :py:class:`.RobotContext` for the protocol.
238
-
239
- :meta private:
240
- """
239
+ """The :py:class:`.RobotContext` for the protocol."""
241
240
  if self._core.robot_type != "OT-3 Standard" or not self._robot:
242
241
  raise RobotTypeError("The RobotContext is only available on Flex robots.")
243
242
  return self._robot
@@ -470,17 +469,15 @@ class ProtocolContext(CommandPublisher):
470
469
 
471
470
  .. versionchanged:: 2.26
472
471
  ``adapter_namespace`` may now be specified explicitly.
473
- Also, when you've specified ``namespace`` but not ``adapter_namespace``,
474
- ``adapter_namespace`` will now independently follow the same search rules
475
- described in ``namespace``. Formerly, it took ``namespace``'s exact value.
472
+ When you've specified ``namespace`` for ``load_name`` but not ``adapter_namespace``,
473
+ ``adapter_namespace`` now independently follows the same search rules
474
+ described in ``namespace``. Formerly, it took the exact ``namespace`` value.
476
475
 
477
476
  :param adapter_version: The version of the adapter being loaded.
478
477
  Applies to ``adapter`` the same way that ``version`` applies to ``load_name``.
479
478
 
480
479
  .. versionchanged:: 2.26
481
- ``adapter_version`` may now be specified explicitly. Also, when it's unspecified,
482
- the algorithm to select a version automatically has improved to avoid
483
- selecting versions that do not exist.
480
+ ``adapter_version`` may now be specified explicitly. When unspecified, the API uses the newest version available for your protocol's API level.
484
481
 
485
482
  :param lid: A lid to load on the top of the main labware. Accepts the same
486
483
  values as the ``load_name`` parameter of :py:meth:`.load_lid_stack`. The
@@ -494,17 +491,15 @@ class ProtocolContext(CommandPublisher):
494
491
 
495
492
  .. versionchanged:: 2.26
496
493
  ``lid_namespace`` may now be specified explicitly.
497
- Also, when you've specified ``namespace`` but not ``lid_namespace``,
498
- ``lid_namespace`` will now independently follow the same search rules
499
- described in ``namespace``. Formerly, it took ``namespace``'s exact value.
494
+ When you've specified ``namespace`` for ``load_name`` but not ``lid_namespace``,
495
+ ``lid_namespace`` now independently follows the same search rules
496
+ described in ``namespace``. Formerly, it took the exact ``namespace`` value.
500
497
 
501
498
  :param lid_version: The version of the adapter being loaded.
502
499
  Applies to ``lid`` the same way that ``version`` applies to ``load_name``.
503
500
 
504
501
  .. versionchanged:: 2.26
505
- ``lid_version`` may now be specified explicitly. Also, when it's unspecified,
506
- the algorithm to select a version automatically has improved to avoid
507
- selecting versions that do not exist.
502
+ ``lid_version`` may now be specified explicitly. When unspecified, the API uses the newest version available for your protocol's API level.
508
503
  """
509
504
 
510
505
  if isinstance(location, OffDeckType) and self._api_version < APIVersion(2, 15):
@@ -1289,6 +1284,30 @@ class ProtocolContext(CommandPublisher):
1289
1284
  delay_time = seconds + minutes * 60
1290
1285
  self._core.delay(seconds=delay_time, msg=msg)
1291
1286
 
1287
+ @publish(command=cmds.wait_for_tasks)
1288
+ @requires_version(2, 27)
1289
+ def wait_for_tasks(self, tasks: list[Task]) -> None:
1290
+ """Wait for a list of tasks to complete before executing subsequent commands.
1291
+
1292
+ :param list Task: tasks: A list of Task objects to wait for.
1293
+
1294
+ Task objects can be commands that are allowed to run concurrently.
1295
+ """
1296
+ task_cores = [task._core for task in tasks]
1297
+ self._core.wait_for_tasks(task_cores)
1298
+
1299
+ @publish(command=cmds.create_timer)
1300
+ @requires_version(2, 27)
1301
+ def create_timer(self, seconds: float) -> Task:
1302
+ """Create a timer task that runs in the background.
1303
+
1304
+ :param float seconds: The time to delay in seconds.
1305
+
1306
+ This timer will continue to run until it is complete and will not block subsequent commands.
1307
+ """
1308
+ task_core = self._core.create_timer(seconds=seconds)
1309
+ return Task(core=task_core, api_version=self._api_version)
1310
+
1292
1311
  @requires_version(2, 0)
1293
1312
  def home(self) -> None:
1294
1313
  """Home the movement system of the robot."""
@@ -1483,8 +1502,8 @@ class ProtocolContext(CommandPublisher):
1483
1502
  - ``"water"``: an Opentrons-verified liquid class based on deionized water.
1484
1503
  - ``"glycerol_50"``: an Opentrons-verified liquid class for viscous liquid. Based on 50% glycerol.
1485
1504
  - ``"ethanol_80"``: an Opentrons-verified liquid class for volatile liquid. Based on 80% ethanol.
1486
- :param version: The version of the liquid class to retrieve. If left unspecified, the latest definition for the
1487
- protocol's API version will be loaded.
1505
+ :param version: Version of the liquid class to retrieve. If left unspecified, defaults to the latest version for the
1506
+ protocol's API level.
1488
1507
 
1489
1508
  :raises: ``LiquidClassDefinitionDoesNotExist``: if the specified liquid class does not exist.
1490
1509
 
@@ -1601,9 +1620,9 @@ class ProtocolContext(CommandPublisher):
1601
1620
 
1602
1621
  .. versionchanged:: 2.26
1603
1622
  ``adapter_namespace`` may now be specified explicitly.
1604
- Also, when you've specified ``namespace`` but not ``adapter_namespace``,
1605
- ``adapter_namespace`` will now independently follow the same search rules
1606
- described in ``namespace``. Formerly, it took ``namespace``'s exact value.
1623
+ When you've specified ``namespace`` for ``load_name`` but not ``adapter_namespace``,
1624
+ ``adapter_namespace`` now independently follows the same search rules
1625
+ described in ``namespace``. Formerly, it took the exact ``namespace`` value.
1607
1626
 
1608
1627
  :param adapter_version: The version of the adapter being loaded.
1609
1628
  Applies to ``adapter`` the same way that ``version`` applies to ``load_name``.
@@ -1803,6 +1822,53 @@ class ProtocolContext(CommandPublisher):
1803
1822
  )
1804
1823
  return None
1805
1824
 
1825
+ @requires_version(2, 27)
1826
+ def capture_image(
1827
+ self,
1828
+ home_before: Optional[bool] = False,
1829
+ filename: Optional[str] = None,
1830
+ resolution: Optional[Tuple[int, int]] = None,
1831
+ zoom: Optional[float] = None,
1832
+ contrast: Optional[float] = None,
1833
+ brightness: Optional[float] = None,
1834
+ saturation: Optional[float] = None,
1835
+ ) -> None:
1836
+ """Capture an image using the camera. Captured images get saved as a result of the protocol run.
1837
+
1838
+ :param home_before: Boolean to home the pipette before capturing an image.
1839
+ :param filename: Filename to use when saving the captured image as a file.
1840
+ :param resolution: Width/height tuple to determine the resolution to use when capturing an image.
1841
+ :param zoom: Optional zoom level, with minimum/default of 1x zoom and maximum of 2x zoom.
1842
+ :param contrast: Contrast level to be applied to an image, range is 0% to 100%.
1843
+ :param brightness: Brightness level to be applied to an image, range is 0% to 100%.
1844
+ :param saturation: Saturation level to be applied to an image, range is 0% to 100%.
1845
+
1846
+ .. versionadded:: 2.27
1847
+
1848
+ """
1849
+ if home_before is True:
1850
+ self._core.home()
1851
+
1852
+ with publish_context(
1853
+ broker=self.broker,
1854
+ command=cmds.capture_image(
1855
+ resolution=resolution,
1856
+ zoom=zoom,
1857
+ contrast=contrast,
1858
+ brightness=brightness,
1859
+ saturation=saturation,
1860
+ ),
1861
+ ):
1862
+ self._core.capture_image(
1863
+ filename=filename,
1864
+ resolution=resolution,
1865
+ zoom=zoom,
1866
+ contrast=contrast,
1867
+ brightness=brightness,
1868
+ saturation=saturation,
1869
+ )
1870
+ return None
1871
+
1806
1872
 
1807
1873
  def _create_module_context(
1808
1874
  module_core: Union[ModuleCore, NonConnectedModuleCore],
@@ -15,7 +15,6 @@ from opentrons.legacy_commands import publisher
15
15
  from opentrons.hardware_control import SyncHardwareAPI
16
16
  from opentrons.protocols.api_support.util import requires_version
17
17
  from opentrons.protocols.api_support.types import APIVersion
18
- from opentrons_shared_data.pipette.types import PipetteNameType
19
18
 
20
19
  from . import validation
21
20
  from .core.common import ProtocolCore, RobotCore
@@ -46,7 +45,7 @@ class RobotContext(publisher.CommandPublisher):
46
45
  Objects in this class should not be instantiated directly. Instead, instances are
47
46
  returned by :py:meth:`ProtocolContext.robot`.
48
47
 
49
- .. versionadded:: 2.20
48
+ .. versionadded:: 2.22
50
49
 
51
50
  """
52
51
 
@@ -83,15 +82,19 @@ class RobotContext(publisher.CommandPublisher):
83
82
  speed: Optional[float] = None,
84
83
  ) -> None:
85
84
  """
86
- Move a specified mount to a destination location on the deck.
85
+ Move a specified mount to a location on the deck.
87
86
 
88
87
  :param mount: The mount of the instrument you wish to move.
89
88
  This can either be an instance of :py:class:`.types.Mount` or one
90
89
  of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note
91
90
  that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``.
92
91
  :type mount: types.Mount or str
93
- :param Location destination:
94
- :param speed:
92
+ :param destination: Any location on the deck, specified as:
93
+
94
+ - a slot, like ``"A1"``
95
+ - a defined location, like labware in a deck slot
96
+ - an absolute location, like a point {x=10 , y=10, z=10} or a deck location and point ("A1" + point {x=10 , y=10, z=10})
97
+ :param speed: The absolute speed in mm/s.
95
98
  """
96
99
  mount = validation.ensure_instrument_mount(mount)
97
100
  with publisher.publish_context(
@@ -116,13 +119,12 @@ class RobotContext(publisher.CommandPublisher):
116
119
  Move a set of axes to an absolute position on the deck.
117
120
 
118
121
  :param axis_map: A dictionary mapping axes to an absolute position on the deck in mm.
119
- :param critical_point: The critical point to move the axes with. It should only
120
- specify the gantry axes (i.e. `x`, `y`, `z`).
121
- :param float speed: The maximum speed with which you want to move all the axes
122
- in the axis map.
122
+ :param critical_point: The critical point, or specific point on the object being moved, to move the axes with. It should only specify the gantry axes (i.e. `x`, `y`, `z`). When you specify a critical point, you're specifying the object on the gantry to be moved. If not specified, the critical point defaults to the center of the carriage attached to the gantry.
123
+ :param float speed: The maximum speed with which to move all axes in mm/s.
124
+
123
125
  """
124
126
  instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
125
- is_96_channel = instrument_on_left == PipetteNameType.P1000_96
127
+ is_96_channel = validation.is_pipette_96_channel(instrument_on_left)
126
128
  axis_map = validation.ensure_axis_map_type(
127
129
  axis_map, self._protocol_core.robot_type, is_96_channel
128
130
  )
@@ -154,14 +156,12 @@ class RobotContext(publisher.CommandPublisher):
154
156
  """
155
157
  Move a set of axes to a relative position on the deck.
156
158
 
157
- :param axis_map: A dictionary mapping axes to relative movements in mm.
158
- :type mount: types.Mount or str
159
+ :param axis_map: A dictionary mapping axes to relative movements from the current position in mm.
159
160
 
160
- :param float speed: The maximum speed with which you want to move all the axes
161
- in the axis map.
161
+ :param float speed: The maximum speed with which to move all axes in mm/s.
162
162
  """
163
163
  instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
164
- is_96_channel = instrument_on_left == PipetteNameType.P1000_96
164
+ is_96_channel = validation.is_pipette_96_channel(instrument_on_left)
165
165
 
166
166
  axis_map = validation.ensure_axis_map_type(
167
167
  axis_map, self._protocol_core.robot_type, is_96_channel
@@ -177,7 +177,10 @@ class RobotContext(publisher.CommandPublisher):
177
177
  self._core.move_axes_relative(axis_map, speed)
178
178
 
179
179
  def close_gripper_jaw(self, force: Optional[float] = None) -> None:
180
- """Command the gripper closed with some force."""
180
+ """Closes the Flex Gripper jaws with a specified force.
181
+
182
+ :param force: Force with which to close the gripper jaws in newtons.
183
+ """
181
184
  with publisher.publish_context(
182
185
  broker=self.broker,
183
186
  command=cmds.close_gripper(
@@ -187,7 +190,10 @@ class RobotContext(publisher.CommandPublisher):
187
190
  self._core.close_gripper(force)
188
191
 
189
192
  def open_gripper_jaw(self) -> None:
190
- """Command the gripper open."""
193
+ """Opens the Flex Gripper jaws with a specified force.
194
+
195
+ :param force: Force with which to open the gripper jaws in newtons.
196
+ """
191
197
  with publisher.publish_context(
192
198
  broker=self.broker,
193
199
  command=cmds.open_gripper(),
@@ -200,9 +206,9 @@ class RobotContext(publisher.CommandPublisher):
200
206
  location: Union[Location, ModuleContext, DeckLocation],
201
207
  ) -> AxisMapType:
202
208
  """
203
- Build a :py:class:`.types.AxisMapType` from a location to be compatible with
209
+ Build an axis map from a location to provide to
204
210
  either :py:meth:`.RobotContext.move_axes_to` or :py:meth:`.RobotContext.move_axes_relative`.
205
- You must provide only one of `location`, `slot`, or `module` to build
211
+ You must provide only one of either a `location`, `slot`, or `module` to build
206
212
  the axis map.
207
213
 
208
214
  :param mount: The mount of the instrument you wish create an axis map for.
@@ -210,7 +216,10 @@ class RobotContext(publisher.CommandPublisher):
210
216
  of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note
211
217
  that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``.
212
218
  :type mount: types.Mount or str
213
- :param location: The location to format an axis map for.
219
+ :param location: Any location on the deck, specified as:
220
+
221
+ - a deck location, like slot ``"A1"``.
222
+ - a defined location, like a module on the deck.
214
223
  :type location: `Well`, `ModuleContext`, `DeckLocation` or `OffDeckType`
215
224
  """
216
225
  mount = validation.ensure_instrument_mount(mount)
@@ -248,7 +257,11 @@ class RobotContext(publisher.CommandPublisher):
248
257
  self, mount: Union[Mount, str], volume: float, action: PipetteActionTypes
249
258
  ) -> AxisMapType:
250
259
  """
251
- Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from volume.
260
+ Build an axis map to move a pipette plunger motor to complete liquid handling actions.
261
+
262
+ :mount: The left or right instrument mount the pipette is attached to.
263
+ :param volume: A volume to convert to an axis map for linear plunger displacement.
264
+ :param action: Choose to ``aspirate`` or ``dispense``.
252
265
 
253
266
  """
254
267
  pipette_name = self._core.get_pipette_type_from_engine(mount)
@@ -268,7 +281,9 @@ class RobotContext(publisher.CommandPublisher):
268
281
  self, mount: Union[Mount, str], position_name: PlungerPositionTypes
269
282
  ) -> AxisMapType:
270
283
  """
271
- Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from position_name.
284
+ Build an axis map to move a pipette plunger motor to a named position.
285
+
286
+ :param position_name: A named position to move the pipette plunger to. Choose from ``top``, ``bottom``, ``blowout``, or ``drop`` plunger positions.
272
287
 
273
288
  """
274
289
  pipette_name = self._core.get_pipette_type_from_engine(mount)
@@ -284,8 +299,9 @@ class RobotContext(publisher.CommandPublisher):
284
299
  return {pipette_axis: pipette_position}
285
300
 
286
301
  def build_axis_map(self, axis_map: StringAxisMap) -> AxisMapType:
287
- """Take in a :py:class:`.types.StringAxisMap` and output a :py:class:`.types.AxisMapType`.
288
- A :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings:
302
+ """Take in a :py:class:`.types.StringAxisMap` and output an axis map.
303
+
304
+ The :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings:
289
305
  ``"x"``, ``"y"``, "``z_l"``, "``z_r"``, "``z_g"``, ``"q"``.
290
306
 
291
307
  An example of a valid axis map could be:
@@ -296,7 +312,7 @@ class RobotContext(publisher.CommandPublisher):
296
312
 
297
313
  """
298
314
  instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
299
- is_96_channel = instrument_on_left == PipetteNameType.P1000_96
315
+ is_96_channel = validation.is_pipette_96_channel(instrument_on_left)
300
316
 
301
317
  return validation.ensure_axis_map_type(
302
318
  axis_map, self._protocol_core.robot_type, is_96_channel