opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.0a1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +28 -20
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +60 -23
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  47. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/protocol_api/__init__.py +20 -1
  51. opentrons/protocol_api/_liquid.py +24 -49
  52. opentrons/protocol_api/_liquid_properties.py +754 -0
  53. opentrons/protocol_api/_types.py +24 -0
  54. opentrons/protocol_api/core/common.py +2 -0
  55. opentrons/protocol_api/core/engine/instrument.py +82 -10
  56. opentrons/protocol_api/core/engine/labware.py +29 -7
  57. opentrons/protocol_api/core/engine/protocol.py +130 -5
  58. opentrons/protocol_api/core/engine/robot.py +139 -0
  59. opentrons/protocol_api/core/engine/well.py +4 -1
  60. opentrons/protocol_api/core/instrument.py +46 -4
  61. opentrons/protocol_api/core/labware.py +13 -4
  62. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
  63. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  65. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
  67. opentrons/protocol_api/core/protocol.py +34 -1
  68. opentrons/protocol_api/core/robot.py +51 -0
  69. opentrons/protocol_api/instrument_context.py +158 -44
  70. opentrons/protocol_api/labware.py +231 -7
  71. opentrons/protocol_api/module_contexts.py +21 -17
  72. opentrons/protocol_api/protocol_context.py +125 -4
  73. opentrons/protocol_api/robot_context.py +204 -32
  74. opentrons/protocol_api/validation.py +262 -3
  75. opentrons/protocol_engine/__init__.py +4 -0
  76. opentrons/protocol_engine/actions/actions.py +2 -3
  77. opentrons/protocol_engine/clients/sync_client.py +18 -0
  78. opentrons/protocol_engine/commands/__init__.py +81 -0
  79. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  80. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  81. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  82. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  83. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  84. opentrons/protocol_engine/commands/aspirate.py +103 -53
  85. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  86. opentrons/protocol_engine/commands/blow_out.py +44 -39
  87. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  88. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  89. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  90. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  91. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  92. opentrons/protocol_engine/commands/command.py +73 -66
  93. opentrons/protocol_engine/commands/command_unions.py +101 -1
  94. opentrons/protocol_engine/commands/comment.py +1 -1
  95. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  96. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  97. opentrons/protocol_engine/commands/custom.py +6 -12
  98. opentrons/protocol_engine/commands/dispense.py +82 -48
  99. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  100. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  101. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  102. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  103. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  104. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  112. opentrons/protocol_engine/commands/home.py +13 -4
  113. opentrons/protocol_engine/commands/liquid_probe.py +67 -24
  114. opentrons/protocol_engine/commands/load_labware.py +29 -7
  115. opentrons/protocol_engine/commands/load_lid.py +146 -0
  116. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  117. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  118. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  119. opentrons/protocol_engine/commands/load_module.py +31 -10
  120. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  121. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  122. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  123. opentrons/protocol_engine/commands/move_labware.py +19 -6
  124. opentrons/protocol_engine/commands/move_relative.py +35 -25
  125. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  126. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  127. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  128. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  129. opentrons/protocol_engine/commands/movement_common.py +338 -0
  130. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  131. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  132. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  133. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  134. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  135. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  136. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  137. opentrons/protocol_engine/commands/robot/common.py +18 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  139. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  140. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  141. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  142. opentrons/protocol_engine/commands/save_position.py +14 -5
  143. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  144. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  147. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  148. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  152. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  153. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  154. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  155. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  158. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  159. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  160. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  161. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  162. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  163. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  164. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  165. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  166. opentrons/protocol_engine/errors/__init__.py +8 -0
  167. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  168. opentrons/protocol_engine/errors/exceptions.py +50 -0
  169. opentrons/protocol_engine/execution/command_executor.py +1 -1
  170. opentrons/protocol_engine/execution/equipment.py +73 -5
  171. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  172. opentrons/protocol_engine/execution/movement.py +27 -0
  173. opentrons/protocol_engine/execution/pipetting.py +5 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  175. opentrons/protocol_engine/notes/notes.py +1 -1
  176. opentrons/protocol_engine/protocol_engine.py +7 -6
  177. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  178. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  179. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  180. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  181. opentrons/protocol_engine/slot_standardization.py +9 -9
  182. opentrons/protocol_engine/state/_move_types.py +9 -5
  183. opentrons/protocol_engine/state/_well_math.py +193 -0
  184. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  185. opentrons/protocol_engine/state/command_history.py +12 -0
  186. opentrons/protocol_engine/state/commands.py +17 -13
  187. opentrons/protocol_engine/state/files.py +10 -12
  188. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  190. opentrons/protocol_engine/state/geometry.py +47 -1
  191. opentrons/protocol_engine/state/labware.py +79 -25
  192. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  193. opentrons/protocol_engine/state/liquids.py +16 -4
  194. opentrons/protocol_engine/state/modules.py +52 -70
  195. opentrons/protocol_engine/state/motion.py +6 -1
  196. opentrons/protocol_engine/state/pipettes.py +144 -58
  197. opentrons/protocol_engine/state/state.py +21 -2
  198. opentrons/protocol_engine/state/state_summary.py +4 -2
  199. opentrons/protocol_engine/state/tips.py +11 -44
  200. opentrons/protocol_engine/state/update_types.py +343 -48
  201. opentrons/protocol_engine/state/wells.py +19 -11
  202. opentrons/protocol_engine/types.py +176 -28
  203. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  204. opentrons/protocol_reader/file_format_validator.py +5 -5
  205. opentrons/protocol_runner/json_file_reader.py +9 -3
  206. opentrons/protocol_runner/json_translator.py +51 -25
  207. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  208. opentrons/protocol_runner/protocol_runner.py +35 -4
  209. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  210. opentrons/protocol_runner/run_orchestrator.py +13 -3
  211. opentrons/protocols/advanced_control/common.py +38 -0
  212. opentrons/protocols/advanced_control/mix.py +1 -1
  213. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  214. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  215. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  216. opentrons/protocols/api_support/definitions.py +1 -1
  217. opentrons/protocols/api_support/instrument.py +1 -1
  218. opentrons/protocols/api_support/util.py +10 -0
  219. opentrons/protocols/labware.py +39 -6
  220. opentrons/protocols/models/json_protocol.py +5 -9
  221. opentrons/simulate.py +3 -1
  222. opentrons/types.py +162 -2
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  225. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
  226. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  227. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  228. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- """ opentrons.protocol_api.labware: classes and functions for labware handling
1
+ """opentrons.protocol_api.labware: classes and functions for labware handling
2
2
 
3
3
  This module provides things like :py:class:`Labware`, and :py:class:`Well`
4
4
  to encapsulate labware instances used in protocols
@@ -13,18 +13,29 @@ from __future__ import annotations
13
13
  import logging
14
14
 
15
15
  from itertools import dropwhile
16
- from typing import TYPE_CHECKING, Any, List, Dict, Optional, Union, Tuple, cast
16
+ from typing import (
17
+ TYPE_CHECKING,
18
+ Any,
19
+ List,
20
+ Dict,
21
+ Optional,
22
+ Union,
23
+ Tuple,
24
+ cast,
25
+ Sequence,
26
+ Mapping,
27
+ )
17
28
 
18
29
  from opentrons_shared_data.labware.types import LabwareDefinition, LabwareParameters
19
30
 
20
- from opentrons.types import Location, Point
31
+ from opentrons.types import Location, Point, NozzleMapInterface
21
32
  from opentrons.protocols.api_support.types import APIVersion
22
33
  from opentrons.protocols.api_support.util import (
23
34
  requires_version,
24
35
  APIVersionError,
25
36
  UnsupportedAPIError,
26
37
  )
27
- from opentrons.hardware_control.nozzle_manager import NozzleMap
38
+
28
39
 
29
40
  # TODO(mc, 2022-09-02): re-exports provided for backwards compatibility
30
41
  # remove when their usage is no longer needed
@@ -280,6 +291,10 @@ class Well:
280
291
 
281
292
  :param Liquid liquid: The liquid to load into the well.
282
293
  :param float volume: The volume of liquid to load, in µL.
294
+
295
+ .. TODO: flag as deprecated in 2.22 docs
296
+ In API version 2.22 and later, use :py:meth:`~Labware.load_liquid`, :py:meth:`~Labware.load_liquid_by_well`,
297
+ or :py:meth:`~Labware.load_empty` to load liquid into a well.
283
298
  """
284
299
  self._core.load_liquid(
285
300
  liquid=liquid,
@@ -529,6 +544,7 @@ class Labware:
529
544
  self,
530
545
  name: str,
531
546
  label: Optional[str] = None,
547
+ lid: Optional[str] = None,
532
548
  namespace: Optional[str] = None,
533
549
  version: Optional[int] = None,
534
550
  ) -> Labware:
@@ -558,6 +574,20 @@ class Labware:
558
574
 
559
575
  self._core_map.add(labware_core, labware)
560
576
 
577
+ if lid is not None:
578
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
579
+ raise APIVersionError(
580
+ api_element="Loading a Lid on a Labware",
581
+ until_version="2.23",
582
+ current_version=f"{self._api_version}",
583
+ )
584
+ self._protocol_core.load_lid(
585
+ load_name=lid,
586
+ location=labware_core,
587
+ namespace=namespace,
588
+ version=version,
589
+ )
590
+
561
591
  return labware
562
592
 
563
593
  @requires_version(2, 15)
@@ -582,6 +612,65 @@ class Labware:
582
612
  label=label,
583
613
  )
584
614
 
615
+ @requires_version(2, 23)
616
+ def load_lid_stack(
617
+ self,
618
+ load_name: str,
619
+ quantity: int,
620
+ namespace: Optional[str] = None,
621
+ version: Optional[int] = None,
622
+ ) -> Labware:
623
+ """
624
+ Load a stack of Lids onto a valid Deck Location or Adapter.
625
+
626
+ :param str load_name: A string to use for looking up a lid definition.
627
+ You can find the ``load_name`` for any standard lid on the Opentrons
628
+ `Labware Library <https://labware.opentrons.com>`_.
629
+ :param int quantity: The quantity of lids to be loaded in the stack.
630
+ :param str namespace: The namespace that the lid labware definition belongs to.
631
+ If unspecified, the API will automatically search two namespaces:
632
+
633
+ - ``"opentrons"``, to load standard Opentrons labware definitions.
634
+ - ``"custom_beta"``, to load custom labware definitions created with the
635
+ `Custom Labware Creator <https://labware.opentrons.com/create>`__.
636
+
637
+ You might need to specify an explicit ``namespace`` if you have a custom
638
+ definition whose ``load_name`` is the same as an Opentrons-verified
639
+ definition, and you want to explicitly choose one or the other.
640
+
641
+ :param version: The version of the labware definition. You should normally
642
+ leave this unspecified to let ``load_lid_stack()`` choose a version
643
+ automatically.
644
+
645
+ :return: The initialized and loaded labware object representing the Lid Stack.
646
+ """
647
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
648
+ raise APIVersionError(
649
+ api_element="Loading a Lid Stack",
650
+ until_version="2.23",
651
+ current_version=f"{self._api_version}",
652
+ )
653
+
654
+ load_location = self._core
655
+
656
+ load_name = validation.ensure_lowercase_name(load_name)
657
+
658
+ result = self._protocol_core.load_lid_stack(
659
+ load_name=load_name,
660
+ location=load_location,
661
+ quantity=quantity,
662
+ namespace=namespace,
663
+ version=version,
664
+ )
665
+
666
+ labware = Labware(
667
+ core=result,
668
+ api_version=self._api_version,
669
+ protocol_core=self._protocol_core,
670
+ core_map=self._core_map,
671
+ )
672
+ return labware
673
+
585
674
  def set_calibration(self, delta: Point) -> None:
586
675
  """
587
676
  An internal, deprecated method used for updating the labware offset.
@@ -932,7 +1021,7 @@ class Labware:
932
1021
  num_tips: int = 1,
933
1022
  starting_tip: Optional[Well] = None,
934
1023
  *,
935
- nozzle_map: Optional[NozzleMap] = None,
1024
+ nozzle_map: Optional[NozzleMapInterface] = None,
936
1025
  ) -> Optional[Well]:
937
1026
  """
938
1027
  Find the next valid well for pick-up.
@@ -1105,6 +1194,141 @@ class Labware:
1105
1194
  """
1106
1195
  self._core.reset_tips()
1107
1196
 
1197
+ @requires_version(2, 22)
1198
+ def load_liquid(
1199
+ self, wells: Sequence[Union[str, Well]], volume: float, liquid: Liquid
1200
+ ) -> None:
1201
+ """Mark several wells as containing the same amount of liquid.
1202
+
1203
+ This method should be called at the beginning of a protocol, soon after loading the labware and before
1204
+ liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware
1205
+ has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
1206
+ :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
1207
+
1208
+ For example, to load 10µL of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
1209
+ into all the wells of a labware, you could call ``labware.load_liquid(labware.wells(), 10, water)``.
1210
+
1211
+ If you want to load different volumes of liquid into different wells, use :py:meth:`~Labware.load_liquid_by_well`.
1212
+
1213
+ If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
1214
+
1215
+ :param wells: The wells to load the liquid into.
1216
+ :type wells: List of well names or list of Well objects, for instance from :py:meth:`~Labware.wells`.
1217
+
1218
+ :param volume: The volume of liquid to load into each well, in 10µL.
1219
+ :type volume: float
1220
+
1221
+ :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
1222
+ :type liquid: Liquid
1223
+ """
1224
+ well_names: List[str] = []
1225
+ for well in wells:
1226
+ if isinstance(well, str):
1227
+ if well not in self.wells_by_name():
1228
+ raise KeyError(
1229
+ f"{well} is not a well in labware {self.name}. The elements of wells should name wells in this labware."
1230
+ )
1231
+ well_names.append(well)
1232
+ elif isinstance(well, Well):
1233
+ if well.parent is not self:
1234
+ raise KeyError(
1235
+ f"{well.well_name} is not a well in labware {self.name}. The elements of wells should be wells of this labware."
1236
+ )
1237
+ well_names.append(well.well_name)
1238
+ else:
1239
+ raise TypeError(
1240
+ f"Unexpected type for element {repr(well)}. The elements of wells should be Well instances or well names."
1241
+ )
1242
+ if not isinstance(volume, (float, int)):
1243
+ raise TypeError(
1244
+ f"Unexpected type for volume {repr(volume)}. Volume should be a number in microliters."
1245
+ )
1246
+ self._core.load_liquid({well_name: volume for well_name in well_names}, liquid)
1247
+
1248
+ @requires_version(2, 22)
1249
+ def load_liquid_by_well(
1250
+ self, volumes: Mapping[Union[str, Well], float], liquid: Liquid
1251
+ ) -> None:
1252
+ """Mark several wells as containing unique volumes of liquid.
1253
+
1254
+ This method should be called at the beginning of a protocol, soon after loading the labware and before
1255
+ liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware
1256
+ has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
1257
+ :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
1258
+
1259
+ For example, to load a decreasing amount of of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
1260
+ into each successive well of a row, you could call
1261
+ ``labware.load_liquid_by_well({'A1': 1000, 'A2': 950, 'A3': 900, ..., 'A12': 600}, water)``
1262
+
1263
+ If you want to load the same volume of a liquid into multiple wells, it is often easier to use :py:meth:`~Labware.load_liquid`.
1264
+
1265
+ If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
1266
+
1267
+ :param volumes: A dictionary of well names (or :py:class:`Well` objects, for instance from ``labware['A1']``)
1268
+ :type wells: Dict[Union[str, Well], float]
1269
+
1270
+ :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
1271
+ :type liquid: Liquid
1272
+ """
1273
+ verified_volumes: Dict[str, float] = {}
1274
+ for well, volume in volumes.items():
1275
+ if isinstance(well, str):
1276
+ if well not in self.wells_by_name():
1277
+ raise KeyError(
1278
+ f"{well} is not a well in {self.name}. The keys of volumes should name wells in this labware"
1279
+ )
1280
+ verified_volumes[well] = volume
1281
+ elif isinstance(well, Well):
1282
+ if well.parent is not self:
1283
+ raise KeyError(
1284
+ f"{well.well_name} is not a well in {self.name}. The keys of volumes should be wells of this labware"
1285
+ )
1286
+ verified_volumes[well.well_name] = volume
1287
+ else:
1288
+ raise TypeError(
1289
+ f"Unexpected type for well name {repr(well)}. The keys of volumes should be Well instances or well names."
1290
+ )
1291
+ if not isinstance(volume, (float, int)):
1292
+ raise TypeError(
1293
+ f"Unexpected type for volume {repr(volume)}. The values of volumes should be numbers in microliters."
1294
+ )
1295
+ self._core.load_liquid(verified_volumes, liquid)
1296
+
1297
+ @requires_version(2, 22)
1298
+ def load_empty(self, wells: Sequence[Union[Well, str]]) -> None:
1299
+ """Mark several wells as empty.
1300
+
1301
+ This method should be called at the beginning of a protocol, soon after loading the labware and before liquid handling
1302
+ operations begin. It is a base of information for liquid tracking functionality. If a well in a labware has not been named
1303
+ in a call to :py:meth:`Labware.load_empty`, :py:meth:`Labware.load_liquid`, or :py:meth:`Labware.load_liquid_by_well`, the
1304
+ volume it contains is unknown and the well's liquid will not be tracked.
1305
+
1306
+ For instance, to mark all wells in the labware as empty, you can call ``labware.load_empty(labware.wells())``.
1307
+
1308
+ :param wells: The list of wells to mark empty. To mark all wells as empty, pass ``labware.wells()``. You can also specify
1309
+ wells by their names (for instance, ``labware.load_empty(['A1', 'A2'])``).
1310
+ :type wells: Union[List[Well], List[str]]
1311
+ """
1312
+ well_names: List[str] = []
1313
+ for well in wells:
1314
+ if isinstance(well, str):
1315
+ if well not in self.wells_by_name():
1316
+ raise KeyError(
1317
+ f"{well} is not a well in {self.name}. The elements of wells should name wells in this labware."
1318
+ )
1319
+ well_names.append(well)
1320
+ elif isinstance(well, Well):
1321
+ if well.parent is not self:
1322
+ raise KeyError(
1323
+ f"{well.well_name} is not a well in {self.name}. The elements of wells should be wells of this labware."
1324
+ )
1325
+ well_names.append(well.well_name)
1326
+ else:
1327
+ raise TypeError(
1328
+ f"Unexpected type for well name {repr(well)}. The elements of wells should be Well instances or well names."
1329
+ )
1330
+ self._core.load_empty(well_names)
1331
+
1108
1332
 
1109
1333
  # TODO(mc, 2022-11-09): implementation detail, move to core
1110
1334
  def split_tipracks(tip_racks: List[Labware]) -> Tuple[Labware, List[Labware]]:
@@ -1121,7 +1345,7 @@ def select_tiprack_from_list(
1121
1345
  num_channels: int,
1122
1346
  starting_point: Optional[Well] = None,
1123
1347
  *,
1124
- nozzle_map: Optional[NozzleMap] = None,
1348
+ nozzle_map: Optional[NozzleMapInterface] = None,
1125
1349
  ) -> Tuple[Labware, Well]:
1126
1350
  try:
1127
1351
  first, rest = split_tipracks(tip_racks)
@@ -1159,7 +1383,7 @@ def next_available_tip(
1159
1383
  tip_racks: List[Labware],
1160
1384
  channels: int,
1161
1385
  *,
1162
- nozzle_map: Optional[NozzleMap] = None,
1386
+ nozzle_map: Optional[NozzleMapInterface] = None,
1163
1387
  ) -> Tuple[Labware, Well]:
1164
1388
  start = starting_tip
1165
1389
  if start is None:
@@ -125,6 +125,7 @@ class ModuleContext(CommandPublisher):
125
125
  namespace: Optional[str] = None,
126
126
  version: Optional[int] = None,
127
127
  adapter: Optional[str] = None,
128
+ lid: Optional[str] = None,
128
129
  ) -> Labware:
129
130
  """Load a labware onto the module using its load parameters.
130
131
 
@@ -180,6 +181,19 @@ class ModuleContext(CommandPublisher):
180
181
  version=version,
181
182
  location=load_location,
182
183
  )
184
+ if lid is not None:
185
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
186
+ raise APIVersionError(
187
+ api_element="Loading a lid on a Labware",
188
+ until_version="2.23",
189
+ current_version=f"{self._api_version}",
190
+ )
191
+ self._protocol_core.load_lid(
192
+ load_name=lid,
193
+ location=labware_core,
194
+ namespace=namespace,
195
+ version=version,
196
+ )
183
197
 
184
198
  if isinstance(self._core, LegacyModuleCore):
185
199
  labware = self._core.add_labware_core(cast(LegacyLabwareCore, labware_core))
@@ -608,7 +622,7 @@ class ThermocyclerContext(ModuleContext):
608
622
  .. note::
609
623
 
610
624
  The Thermocycler will proceed to the next command immediately after
611
- ``temperature`` has been reached.
625
+ ``temperature`` is reached.
612
626
 
613
627
  """
614
628
  self._core.set_target_lid_temperature(celsius=temperature)
@@ -625,28 +639,18 @@ class ThermocyclerContext(ModuleContext):
625
639
  """Execute a Thermocycler profile, defined as a cycle of
626
640
  ``steps``, for a given number of ``repetitions``.
627
641
 
628
- :param steps: List of unique steps that make up a single cycle.
629
- Each list item should be a dictionary that maps to
630
- the parameters of the :py:meth:`set_block_temperature`
631
- method with a ``temperature`` key, and either or both of
642
+ :param steps: List of steps that make up a single cycle.
643
+ Each list item should be a dictionary that maps to the parameters
644
+ of the :py:meth:`set_block_temperature` method. The dictionary's
645
+ keys must be ``temperature`` and one or both of
632
646
  ``hold_time_seconds`` and ``hold_time_minutes``.
633
647
  :param repetitions: The number of times to repeat the cycled steps.
634
648
  :param block_max_volume: The greatest volume of liquid contained in any
635
649
  individual well of the loaded labware, in µL.
636
650
  If not specified, the default is 25 µL.
637
651
 
638
- .. note::
639
-
640
- Unlike with :py:meth:`set_block_temperature`, either or both of
641
- ``hold_time_minutes`` and ``hold_time_seconds`` must be defined
642
- and for each step.
643
-
644
- .. note::
645
-
646
- Before API Version 2.21, Thermocycler profiles run with this command
647
- would be listed in the app as having a number of repetitions equal to
648
- their step count. At or above API Version 2.21, the structure of the
649
- Thermocycler cycles is preserved.
652
+ .. versionchanged:: 2.21
653
+ Fixed run log listing number of steps instead of number of repetitions.
650
654
 
651
655
  """
652
656
  repetitions = validation.ensure_thermocycler_repetition_count(repetitions)
@@ -186,7 +186,14 @@ class ProtocolContext(CommandPublisher):
186
186
  self._commands: List[str] = []
187
187
  self._params: Parameters = Parameters()
188
188
  self._unsubscribe_commands: Optional[Callable[[], None]] = None
189
- self._robot = RobotContext(self._core)
189
+ try:
190
+ self._robot: Optional[RobotContext] = RobotContext(
191
+ core=self._core.load_robot(),
192
+ protocol_core=self._core,
193
+ api_version=self._api_version,
194
+ )
195
+ except APIVersionError:
196
+ self._robot = None
190
197
  self.clear_commands()
191
198
 
192
199
  @property
@@ -212,12 +219,14 @@ class ProtocolContext(CommandPublisher):
212
219
  return self._api_version
213
220
 
214
221
  @property
215
- @requires_version(2, 21)
222
+ @requires_version(2, 22)
216
223
  def robot(self) -> RobotContext:
217
224
  """The :py:class:`.RobotContext` for the protocol.
218
225
 
219
226
  :meta private:
220
227
  """
228
+ if self._core.robot_type != "OT-3 Standard" or not self._robot:
229
+ raise RobotTypeError("The RobotContext is only available on Flex robots.")
221
230
  return self._robot
222
231
 
223
232
  @property
@@ -229,7 +238,9 @@ class ProtocolContext(CommandPublisher):
229
238
  "This function will be deprecated in later versions."
230
239
  "Please use with caution."
231
240
  )
232
- return self._robot.hardware
241
+ if self._robot:
242
+ return self._robot.hardware
243
+ return HardwareManager(hardware=self._core.get_hardware())
233
244
 
234
245
  @property
235
246
  @requires_version(2, 0)
@@ -388,6 +399,7 @@ class ProtocolContext(CommandPublisher):
388
399
  namespace: Optional[str] = None,
389
400
  version: Optional[int] = None,
390
401
  adapter: Optional[str] = None,
402
+ lid: Optional[str] = None,
391
403
  ) -> Labware:
392
404
  """Load a labware onto a location.
393
405
 
@@ -432,6 +444,10 @@ class ProtocolContext(CommandPublisher):
432
444
  values as the ``load_name`` parameter of :py:meth:`.load_adapter`. The
433
445
  adapter will use the same namespace as the labware, and the API will
434
446
  choose the adapter's version automatically.
447
+ :param lid: A lid to load the on top of the main labware. Accepts the same
448
+ values as the ``load_name`` parameter of :py:meth:`.load_lid_stack`. The
449
+ lid will use the same namespace as the labware, and the API will
450
+ choose the lid's version automatically.
435
451
 
436
452
  .. versionadded:: 2.15
437
453
  """
@@ -472,6 +488,20 @@ class ProtocolContext(CommandPublisher):
472
488
  version=version,
473
489
  )
474
490
 
491
+ if lid is not None:
492
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
493
+ raise APIVersionError(
494
+ api_element="Loading a Lid on a Labware",
495
+ until_version="2.23",
496
+ current_version=f"{self._api_version}",
497
+ )
498
+ self._core.load_lid(
499
+ load_name=lid,
500
+ location=labware_core,
501
+ namespace=namespace,
502
+ version=version,
503
+ )
504
+
475
505
  labware = Labware(
476
506
  core=labware_core,
477
507
  api_version=self._api_version,
@@ -956,7 +986,10 @@ class ProtocolContext(CommandPublisher):
956
986
  mount, checked_instrument_name
957
987
  )
958
988
 
959
- is_96_channel = checked_instrument_name == PipetteNameType.P1000_96
989
+ is_96_channel = checked_instrument_name in [
990
+ PipetteNameType.P1000_96,
991
+ PipetteNameType.P200_96,
992
+ ]
960
993
 
961
994
  tip_racks = tip_racks or []
962
995
 
@@ -1320,6 +1353,94 @@ class ProtocolContext(CommandPublisher):
1320
1353
  """Returns ``True`` if the front door of the robot is closed."""
1321
1354
  return self._core.door_closed()
1322
1355
 
1356
+ @requires_version(2, 23)
1357
+ def load_lid_stack(
1358
+ self,
1359
+ load_name: str,
1360
+ location: Union[DeckLocation, Labware],
1361
+ quantity: int,
1362
+ adapter: Optional[str] = None,
1363
+ namespace: Optional[str] = None,
1364
+ version: Optional[int] = None,
1365
+ ) -> Labware:
1366
+ """
1367
+ Load a stack of Lids onto a valid Deck Location or Adapter.
1368
+
1369
+ :param str load_name: A string to use for looking up a lid definition.
1370
+ You can find the ``load_name`` for any standard lid on the Opentrons
1371
+ `Labware Library <https://labware.opentrons.com>`_.
1372
+ :param location: Either a :ref:`deck slot <deck-slots>`,
1373
+ like ``1``, ``"1"``, or ``"D1"``, or the a valid Opentrons Adapter.
1374
+ :param int quantity: The quantity of lids to be loaded in the stack.
1375
+ :param adapter: An adapter to load the lid stack on top of. Accepts the same
1376
+ values as the ``load_name`` parameter of :py:meth:`.load_adapter`. The
1377
+ adapter will use the same namespace as the lid labware, and the API will
1378
+ choose the adapter's version automatically.
1379
+ :param str namespace: The namespace that the lid labware definition belongs to.
1380
+ If unspecified, the API will automatically search two namespaces:
1381
+
1382
+ - ``"opentrons"``, to load standard Opentrons labware definitions.
1383
+ - ``"custom_beta"``, to load custom labware definitions created with the
1384
+ `Custom Labware Creator <https://labware.opentrons.com/create>`__.
1385
+
1386
+ You might need to specify an explicit ``namespace`` if you have a custom
1387
+ definition whose ``load_name`` is the same as an Opentrons-verified
1388
+ definition, and you want to explicitly choose one or the other.
1389
+
1390
+ :param version: The version of the labware definition. You should normally
1391
+ leave this unspecified to let ``load_lid_stack()`` choose a version
1392
+ automatically.
1393
+
1394
+ :return: The initialized and loaded labware object representing the Lid Stack.
1395
+ """
1396
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
1397
+ raise APIVersionError(
1398
+ api_element="Loading a Lid Stack",
1399
+ until_version="2.23",
1400
+ current_version=f"{self._api_version}",
1401
+ )
1402
+
1403
+ load_location: Union[DeckSlotName, StagingSlotName, LabwareCore]
1404
+ if isinstance(location, Labware):
1405
+ load_location = location._core
1406
+ else:
1407
+ load_location = validation.ensure_and_convert_deck_slot(
1408
+ location, self._api_version, self._core.robot_type
1409
+ )
1410
+
1411
+ if adapter is not None:
1412
+ if isinstance(load_location, DeckSlotName) or isinstance(
1413
+ load_location, StagingSlotName
1414
+ ):
1415
+ loaded_adapter = self.load_adapter(
1416
+ load_name=adapter,
1417
+ location=load_location.value,
1418
+ namespace=namespace,
1419
+ )
1420
+ load_location = loaded_adapter._core
1421
+ else:
1422
+ raise ValueError(
1423
+ "Location cannot be a Labware or Adapter when the 'adapter' field is not None."
1424
+ )
1425
+
1426
+ load_name = validation.ensure_lowercase_name(load_name)
1427
+
1428
+ result = self._core.load_lid_stack(
1429
+ load_name=load_name,
1430
+ location=load_location,
1431
+ quantity=quantity,
1432
+ namespace=namespace,
1433
+ version=version,
1434
+ )
1435
+
1436
+ labware = Labware(
1437
+ core=result,
1438
+ api_version=self._api_version,
1439
+ protocol_core=self._core,
1440
+ core_map=self._core_map,
1441
+ )
1442
+ return labware
1443
+
1323
1444
 
1324
1445
  def _create_module_context(
1325
1446
  module_core: Union[ModuleCore, NonConnectedModuleCore],