opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.0__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 (238) 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 +33 -21
  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 +78 -31
  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 +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.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
@@ -104,8 +115,23 @@ class Well:
104
115
  @property
105
116
  @requires_version(2, 0)
106
117
  def has_tip(self) -> bool:
107
- """Whether this well contains a tip. Always ``False`` if the parent labware
108
- isn't a tip rack."""
118
+ """Whether this well contains an unused tip.
119
+
120
+ From API v2.2 on:
121
+
122
+ - Returns ``False`` if:
123
+
124
+ - the well has no tip present, or
125
+ - the well has a tip that's been used by the protocol previously
126
+
127
+ - Returns ``True`` if the well has an unused tip.
128
+
129
+ Before API v2.2:
130
+
131
+ - Returns ``True`` as long as the well has a tip, even if it is used.
132
+
133
+ Always ``False`` if the parent labware isn't a tip rack.
134
+ """
109
135
  return self._core.has_tip()
110
136
 
111
137
  @has_tip.setter
@@ -280,6 +306,10 @@ class Well:
280
306
 
281
307
  :param Liquid liquid: The liquid to load into the well.
282
308
  :param float volume: The volume of liquid to load, in µL.
309
+
310
+ .. TODO: flag as deprecated in 2.22 docs
311
+ In API version 2.22 and later, use :py:meth:`~Labware.load_liquid`, :py:meth:`~Labware.load_liquid_by_well`,
312
+ or :py:meth:`~Labware.load_empty` to load liquid into a well.
283
313
  """
284
314
  self._core.load_liquid(
285
315
  liquid=liquid,
@@ -529,6 +559,7 @@ class Labware:
529
559
  self,
530
560
  name: str,
531
561
  label: Optional[str] = None,
562
+ lid: Optional[str] = None,
532
563
  namespace: Optional[str] = None,
533
564
  version: Optional[int] = None,
534
565
  ) -> Labware:
@@ -558,6 +589,20 @@ class Labware:
558
589
 
559
590
  self._core_map.add(labware_core, labware)
560
591
 
592
+ if lid is not None:
593
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
594
+ raise APIVersionError(
595
+ api_element="Loading a Lid on a Labware",
596
+ until_version="2.23",
597
+ current_version=f"{self._api_version}",
598
+ )
599
+ self._protocol_core.load_lid(
600
+ load_name=lid,
601
+ location=labware_core,
602
+ namespace=namespace,
603
+ version=version,
604
+ )
605
+
561
606
  return labware
562
607
 
563
608
  @requires_version(2, 15)
@@ -582,6 +627,65 @@ class Labware:
582
627
  label=label,
583
628
  )
584
629
 
630
+ @requires_version(2, 23)
631
+ def load_lid_stack(
632
+ self,
633
+ load_name: str,
634
+ quantity: int,
635
+ namespace: Optional[str] = None,
636
+ version: Optional[int] = None,
637
+ ) -> Labware:
638
+ """
639
+ Load a stack of Lids onto a valid Deck Location or Adapter.
640
+
641
+ :param str load_name: A string to use for looking up a lid definition.
642
+ You can find the ``load_name`` for any standard lid on the Opentrons
643
+ `Labware Library <https://labware.opentrons.com>`_.
644
+ :param int quantity: The quantity of lids to be loaded in the stack.
645
+ :param str namespace: The namespace that the lid labware definition belongs to.
646
+ If unspecified, the API will automatically search two namespaces:
647
+
648
+ - ``"opentrons"``, to load standard Opentrons labware definitions.
649
+ - ``"custom_beta"``, to load custom labware definitions created with the
650
+ `Custom Labware Creator <https://labware.opentrons.com/create>`__.
651
+
652
+ You might need to specify an explicit ``namespace`` if you have a custom
653
+ definition whose ``load_name`` is the same as an Opentrons-verified
654
+ definition, and you want to explicitly choose one or the other.
655
+
656
+ :param version: The version of the labware definition. You should normally
657
+ leave this unspecified to let ``load_lid_stack()`` choose a version
658
+ automatically.
659
+
660
+ :return: The initialized and loaded labware object representing the Lid Stack.
661
+ """
662
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
663
+ raise APIVersionError(
664
+ api_element="Loading a Lid Stack",
665
+ until_version="2.23",
666
+ current_version=f"{self._api_version}",
667
+ )
668
+
669
+ load_location = self._core
670
+
671
+ load_name = validation.ensure_lowercase_name(load_name)
672
+
673
+ result = self._protocol_core.load_lid_stack(
674
+ load_name=load_name,
675
+ location=load_location,
676
+ quantity=quantity,
677
+ namespace=namespace,
678
+ version=version,
679
+ )
680
+
681
+ labware = Labware(
682
+ core=result,
683
+ api_version=self._api_version,
684
+ protocol_core=self._protocol_core,
685
+ core_map=self._core_map,
686
+ )
687
+ return labware
688
+
585
689
  def set_calibration(self, delta: Point) -> None:
586
690
  """
587
691
  An internal, deprecated method used for updating the labware offset.
@@ -932,7 +1036,7 @@ class Labware:
932
1036
  num_tips: int = 1,
933
1037
  starting_tip: Optional[Well] = None,
934
1038
  *,
935
- nozzle_map: Optional[NozzleMap] = None,
1039
+ nozzle_map: Optional[NozzleMapInterface] = None,
936
1040
  ) -> Optional[Well]:
937
1041
  """
938
1042
  Find the next valid well for pick-up.
@@ -1105,6 +1209,141 @@ class Labware:
1105
1209
  """
1106
1210
  self._core.reset_tips()
1107
1211
 
1212
+ @requires_version(2, 22)
1213
+ def load_liquid(
1214
+ self, wells: Sequence[Union[str, Well]], volume: float, liquid: Liquid
1215
+ ) -> None:
1216
+ """Mark several wells as containing the same amount of liquid.
1217
+
1218
+ This method should be called at the beginning of a protocol, soon after loading the labware and before
1219
+ liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware
1220
+ has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
1221
+ :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
1222
+
1223
+ For example, to load 10µL of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
1224
+ into all the wells of a labware, you could call ``labware.load_liquid(labware.wells(), 10, water)``.
1225
+
1226
+ If you want to load different volumes of liquid into different wells, use :py:meth:`~Labware.load_liquid_by_well`.
1227
+
1228
+ If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
1229
+
1230
+ :param wells: The wells to load the liquid into.
1231
+ :type wells: List of well names or list of Well objects, for instance from :py:meth:`~Labware.wells`.
1232
+
1233
+ :param volume: The volume of liquid to load into each well, in 10µL.
1234
+ :type volume: float
1235
+
1236
+ :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
1237
+ :type liquid: Liquid
1238
+ """
1239
+ well_names: List[str] = []
1240
+ for well in wells:
1241
+ if isinstance(well, str):
1242
+ if well not in self.wells_by_name():
1243
+ raise KeyError(
1244
+ f"{well} is not a well in labware {self.name}. The elements of wells should name wells in this labware."
1245
+ )
1246
+ well_names.append(well)
1247
+ elif isinstance(well, Well):
1248
+ if well.parent is not self:
1249
+ raise KeyError(
1250
+ f"{well.well_name} is not a well in labware {self.name}. The elements of wells should be wells of this labware."
1251
+ )
1252
+ well_names.append(well.well_name)
1253
+ else:
1254
+ raise TypeError(
1255
+ f"Unexpected type for element {repr(well)}. The elements of wells should be Well instances or well names."
1256
+ )
1257
+ if not isinstance(volume, (float, int)):
1258
+ raise TypeError(
1259
+ f"Unexpected type for volume {repr(volume)}. Volume should be a number in microliters."
1260
+ )
1261
+ self._core.load_liquid({well_name: volume for well_name in well_names}, liquid)
1262
+
1263
+ @requires_version(2, 22)
1264
+ def load_liquid_by_well(
1265
+ self, volumes: Mapping[Union[str, Well], float], liquid: Liquid
1266
+ ) -> None:
1267
+ """Mark several wells as containing unique volumes of liquid.
1268
+
1269
+ This method should be called at the beginning of a protocol, soon after loading the labware and before
1270
+ liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware
1271
+ has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
1272
+ :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
1273
+
1274
+ For example, to load a decreasing amount of of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
1275
+ into each successive well of a row, you could call
1276
+ ``labware.load_liquid_by_well({'A1': 1000, 'A2': 950, 'A3': 900, ..., 'A12': 600}, water)``
1277
+
1278
+ 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`.
1279
+
1280
+ If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
1281
+
1282
+ :param volumes: A dictionary of well names (or :py:class:`Well` objects, for instance from ``labware['A1']``)
1283
+ :type wells: Dict[Union[str, Well], float]
1284
+
1285
+ :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
1286
+ :type liquid: Liquid
1287
+ """
1288
+ verified_volumes: Dict[str, float] = {}
1289
+ for well, volume in volumes.items():
1290
+ if isinstance(well, str):
1291
+ if well not in self.wells_by_name():
1292
+ raise KeyError(
1293
+ f"{well} is not a well in {self.name}. The keys of volumes should name wells in this labware"
1294
+ )
1295
+ verified_volumes[well] = volume
1296
+ elif isinstance(well, Well):
1297
+ if well.parent is not self:
1298
+ raise KeyError(
1299
+ f"{well.well_name} is not a well in {self.name}. The keys of volumes should be wells of this labware"
1300
+ )
1301
+ verified_volumes[well.well_name] = volume
1302
+ else:
1303
+ raise TypeError(
1304
+ f"Unexpected type for well name {repr(well)}. The keys of volumes should be Well instances or well names."
1305
+ )
1306
+ if not isinstance(volume, (float, int)):
1307
+ raise TypeError(
1308
+ f"Unexpected type for volume {repr(volume)}. The values of volumes should be numbers in microliters."
1309
+ )
1310
+ self._core.load_liquid(verified_volumes, liquid)
1311
+
1312
+ @requires_version(2, 22)
1313
+ def load_empty(self, wells: Sequence[Union[Well, str]]) -> None:
1314
+ """Mark several wells as empty.
1315
+
1316
+ This method should be called at the beginning of a protocol, soon after loading the labware and before liquid handling
1317
+ operations begin. It is a base of information for liquid tracking functionality. If a well in a labware has not been named
1318
+ in a call to :py:meth:`Labware.load_empty`, :py:meth:`Labware.load_liquid`, or :py:meth:`Labware.load_liquid_by_well`, the
1319
+ volume it contains is unknown and the well's liquid will not be tracked.
1320
+
1321
+ For instance, to mark all wells in the labware as empty, you can call ``labware.load_empty(labware.wells())``.
1322
+
1323
+ :param wells: The list of wells to mark empty. To mark all wells as empty, pass ``labware.wells()``. You can also specify
1324
+ wells by their names (for instance, ``labware.load_empty(['A1', 'A2'])``).
1325
+ :type wells: Union[List[Well], List[str]]
1326
+ """
1327
+ well_names: List[str] = []
1328
+ for well in wells:
1329
+ if isinstance(well, str):
1330
+ if well not in self.wells_by_name():
1331
+ raise KeyError(
1332
+ f"{well} is not a well in {self.name}. The elements of wells should name wells in this labware."
1333
+ )
1334
+ well_names.append(well)
1335
+ elif isinstance(well, Well):
1336
+ if well.parent is not self:
1337
+ raise KeyError(
1338
+ f"{well.well_name} is not a well in {self.name}. The elements of wells should be wells of this labware."
1339
+ )
1340
+ well_names.append(well.well_name)
1341
+ else:
1342
+ raise TypeError(
1343
+ f"Unexpected type for well name {repr(well)}. The elements of wells should be Well instances or well names."
1344
+ )
1345
+ self._core.load_empty(well_names)
1346
+
1108
1347
 
1109
1348
  # TODO(mc, 2022-11-09): implementation detail, move to core
1110
1349
  def split_tipracks(tip_racks: List[Labware]) -> Tuple[Labware, List[Labware]]:
@@ -1121,7 +1360,7 @@ def select_tiprack_from_list(
1121
1360
  num_channels: int,
1122
1361
  starting_point: Optional[Well] = None,
1123
1362
  *,
1124
- nozzle_map: Optional[NozzleMap] = None,
1363
+ nozzle_map: Optional[NozzleMapInterface] = None,
1125
1364
  ) -> Tuple[Labware, Well]:
1126
1365
  try:
1127
1366
  first, rest = split_tipracks(tip_racks)
@@ -1159,7 +1398,7 @@ def next_available_tip(
1159
1398
  tip_racks: List[Labware],
1160
1399
  channels: int,
1161
1400
  *,
1162
- nozzle_map: Optional[NozzleMap] = None,
1401
+ nozzle_map: Optional[NozzleMapInterface] = None,
1163
1402
  ) -> Tuple[Labware, Well]:
1164
1403
  start = starting_tip
1165
1404
  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],