opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a2__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/drivers/asyncio/communication/__init__.py +2 -0
  9. opentrons/drivers/asyncio/communication/errors.py +16 -3
  10. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  11. opentrons/drivers/command_builder.py +2 -2
  12. opentrons/drivers/flex_stacker/__init__.py +9 -0
  13. opentrons/drivers/flex_stacker/abstract.py +89 -0
  14. opentrons/drivers/flex_stacker/driver.py +260 -0
  15. opentrons/drivers/flex_stacker/simulator.py +109 -0
  16. opentrons/drivers/flex_stacker/types.py +138 -0
  17. opentrons/drivers/heater_shaker/driver.py +18 -3
  18. opentrons/drivers/temp_deck/driver.py +13 -3
  19. opentrons/drivers/thermocycler/driver.py +17 -3
  20. opentrons/execute.py +3 -1
  21. opentrons/hardware_control/__init__.py +1 -2
  22. opentrons/hardware_control/api.py +28 -20
  23. opentrons/hardware_control/backends/flex_protocol.py +4 -6
  24. opentrons/hardware_control/backends/ot3controller.py +177 -59
  25. opentrons/hardware_control/backends/ot3simulator.py +10 -8
  26. opentrons/hardware_control/backends/ot3utils.py +3 -13
  27. opentrons/hardware_control/dev_types.py +2 -0
  28. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  29. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  30. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  31. opentrons/hardware_control/emulation/settings.py +3 -4
  32. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  33. opentrons/hardware_control/instruments/ot2/pipette.py +9 -21
  34. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  35. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  36. opentrons/hardware_control/instruments/ot3/pipette.py +13 -22
  37. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  38. opentrons/hardware_control/modules/mod_abc.py +2 -2
  39. opentrons/hardware_control/motion_utilities.py +68 -0
  40. opentrons/hardware_control/nozzle_manager.py +39 -41
  41. opentrons/hardware_control/ot3_calibration.py +1 -1
  42. opentrons/hardware_control/ot3api.py +34 -22
  43. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  44. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  45. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  46. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  47. opentrons/hardware_control/robot_calibration.py +1 -1
  48. opentrons/hardware_control/types.py +61 -0
  49. opentrons/protocol_api/__init__.py +20 -1
  50. opentrons/protocol_api/_liquid.py +24 -49
  51. opentrons/protocol_api/_liquid_properties.py +754 -0
  52. opentrons/protocol_api/_types.py +24 -0
  53. opentrons/protocol_api/core/common.py +2 -0
  54. opentrons/protocol_api/core/engine/instrument.py +67 -10
  55. opentrons/protocol_api/core/engine/labware.py +29 -7
  56. opentrons/protocol_api/core/engine/protocol.py +130 -5
  57. opentrons/protocol_api/core/engine/robot.py +139 -0
  58. opentrons/protocol_api/core/engine/well.py +4 -1
  59. opentrons/protocol_api/core/instrument.py +42 -4
  60. opentrons/protocol_api/core/labware.py +13 -4
  61. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -3
  62. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  63. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  64. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +34 -3
  66. opentrons/protocol_api/core/protocol.py +34 -1
  67. opentrons/protocol_api/core/robot.py +51 -0
  68. opentrons/protocol_api/instrument_context.py +145 -43
  69. opentrons/protocol_api/labware.py +231 -7
  70. opentrons/protocol_api/module_contexts.py +21 -17
  71. opentrons/protocol_api/protocol_context.py +125 -4
  72. opentrons/protocol_api/robot_context.py +204 -32
  73. opentrons/protocol_api/validation.py +261 -3
  74. opentrons/protocol_engine/__init__.py +4 -0
  75. opentrons/protocol_engine/actions/actions.py +2 -3
  76. opentrons/protocol_engine/clients/sync_client.py +18 -0
  77. opentrons/protocol_engine/commands/__init__.py +81 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  79. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  80. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  81. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  82. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  83. opentrons/protocol_engine/commands/aspirate.py +103 -53
  84. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  85. opentrons/protocol_engine/commands/blow_out.py +44 -39
  86. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  87. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  88. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  89. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  90. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  91. opentrons/protocol_engine/commands/command.py +73 -66
  92. opentrons/protocol_engine/commands/command_unions.py +101 -1
  93. opentrons/protocol_engine/commands/comment.py +1 -1
  94. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  95. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  96. opentrons/protocol_engine/commands/custom.py +6 -12
  97. opentrons/protocol_engine/commands/dispense.py +82 -48
  98. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  99. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  100. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  101. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  102. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  103. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  104. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  111. opentrons/protocol_engine/commands/home.py +13 -4
  112. opentrons/protocol_engine/commands/liquid_probe.py +60 -25
  113. opentrons/protocol_engine/commands/load_labware.py +29 -7
  114. opentrons/protocol_engine/commands/load_lid.py +146 -0
  115. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  116. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  117. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  118. opentrons/protocol_engine/commands/load_module.py +31 -10
  119. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  120. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  121. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  122. opentrons/protocol_engine/commands/move_labware.py +19 -6
  123. opentrons/protocol_engine/commands/move_relative.py +35 -25
  124. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  125. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  126. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  127. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  128. opentrons/protocol_engine/commands/movement_common.py +338 -0
  129. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  130. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  131. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  132. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  133. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  134. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  135. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  136. opentrons/protocol_engine/commands/robot/common.py +18 -0
  137. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  139. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  140. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  141. opentrons/protocol_engine/commands/save_position.py +14 -5
  142. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  143. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  144. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  147. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  148. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  152. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  153. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  154. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  158. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  159. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  160. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  161. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  162. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  163. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  164. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  165. opentrons/protocol_engine/errors/__init__.py +8 -0
  166. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  167. opentrons/protocol_engine/errors/exceptions.py +50 -0
  168. opentrons/protocol_engine/execution/command_executor.py +1 -1
  169. opentrons/protocol_engine/execution/equipment.py +73 -5
  170. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  171. opentrons/protocol_engine/execution/movement.py +27 -0
  172. opentrons/protocol_engine/execution/pipetting.py +5 -1
  173. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  174. opentrons/protocol_engine/notes/notes.py +1 -1
  175. opentrons/protocol_engine/protocol_engine.py +7 -6
  176. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  177. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  178. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  179. opentrons/protocol_engine/resources/pipette_data_provider.py +12 -0
  180. opentrons/protocol_engine/slot_standardization.py +9 -9
  181. opentrons/protocol_engine/state/_move_types.py +9 -5
  182. opentrons/protocol_engine/state/_well_math.py +193 -0
  183. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  184. opentrons/protocol_engine/state/command_history.py +12 -0
  185. opentrons/protocol_engine/state/commands.py +17 -13
  186. opentrons/protocol_engine/state/files.py +10 -12
  187. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  188. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  189. opentrons/protocol_engine/state/geometry.py +47 -1
  190. opentrons/protocol_engine/state/labware.py +79 -25
  191. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  192. opentrons/protocol_engine/state/liquids.py +16 -4
  193. opentrons/protocol_engine/state/modules.py +52 -70
  194. opentrons/protocol_engine/state/motion.py +6 -1
  195. opentrons/protocol_engine/state/pipettes.py +135 -58
  196. opentrons/protocol_engine/state/state.py +21 -2
  197. opentrons/protocol_engine/state/state_summary.py +4 -2
  198. opentrons/protocol_engine/state/tips.py +11 -44
  199. opentrons/protocol_engine/state/update_types.py +343 -48
  200. opentrons/protocol_engine/state/wells.py +19 -11
  201. opentrons/protocol_engine/types.py +176 -28
  202. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  203. opentrons/protocol_reader/file_format_validator.py +5 -5
  204. opentrons/protocol_runner/json_file_reader.py +9 -3
  205. opentrons/protocol_runner/json_translator.py +51 -25
  206. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  207. opentrons/protocol_runner/protocol_runner.py +35 -4
  208. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  209. opentrons/protocol_runner/run_orchestrator.py +13 -3
  210. opentrons/protocols/advanced_control/common.py +38 -0
  211. opentrons/protocols/advanced_control/mix.py +1 -1
  212. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  213. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  214. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  215. opentrons/protocols/api_support/definitions.py +1 -1
  216. opentrons/protocols/api_support/instrument.py +1 -1
  217. opentrons/protocols/api_support/util.py +10 -0
  218. opentrons/protocols/labware.py +70 -8
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/entrypoint_util.py +2 -5
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/METADATA +16 -15
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/RECORD +229 -202
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/WHEEL +1 -1
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/LICENSE +0 -0
  228. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,189 @@
1
+ """Load lid stack command request, result, and implementation models."""
2
+ from __future__ import annotations
3
+ from pydantic import BaseModel, Field
4
+ from typing import TYPE_CHECKING, Optional, Type, List
5
+ from typing_extensions import Literal
6
+
7
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
8
+
9
+ from ..errors import LabwareIsNotAllowedInLocationError, ProtocolEngineError
10
+ from ..resources import fixture_validation, labware_validation
11
+ from ..types import (
12
+ LabwareLocation,
13
+ OnLabwareLocation,
14
+ DeckSlotLocation,
15
+ AddressableAreaLocation,
16
+ )
17
+
18
+ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
19
+ from ..errors.error_occurrence import ErrorOccurrence
20
+ from ..state.update_types import StateUpdate
21
+
22
+ if TYPE_CHECKING:
23
+ from ..state.state import StateView
24
+ from ..execution import EquipmentHandler
25
+
26
+
27
+ LoadLidStackCommandType = Literal["loadLidStack"]
28
+
29
+ _LID_STACK_PE_LABWARE = "protocol_engine_lid_stack_object"
30
+ _LID_STACK_PE_NAMESPACE = "opentrons"
31
+ _LID_STACK_PE_VERSION = 1
32
+
33
+
34
+ class LoadLidStackParams(BaseModel):
35
+ """Payload required to load a lid stack onto a location."""
36
+
37
+ location: LabwareLocation = Field(
38
+ ...,
39
+ description="Location the lid stack should be loaded into.",
40
+ )
41
+ loadName: str = Field(
42
+ ...,
43
+ description="Name used to reference a lid labware definition.",
44
+ )
45
+ namespace: str = Field(
46
+ ...,
47
+ description="The namespace the lid labware definition belongs to.",
48
+ )
49
+ version: int = Field(
50
+ ...,
51
+ description="The lid labware definition version.",
52
+ )
53
+ quantity: int = Field(
54
+ ...,
55
+ description="The quantity of lids to load.",
56
+ )
57
+
58
+
59
+ class LoadLidStackResult(BaseModel):
60
+ """Result data from the execution of a LoadLidStack command."""
61
+
62
+ stackLabwareId: str = Field(
63
+ ...,
64
+ description="An ID to reference the Protocol Engine Labware Lid Stack in subsequent commands.",
65
+ )
66
+ labwareIds: List[str] = Field(
67
+ ...,
68
+ description="A list of lid labware IDs to reference the lids in this stack by. The first ID is the bottom of the stack.",
69
+ )
70
+ definition: LabwareDefinition = Field(
71
+ ...,
72
+ description="The full definition data for this lid labware.",
73
+ )
74
+ location: LabwareLocation = Field(
75
+ ..., description="The Location that the stack of lid labware has been loaded."
76
+ )
77
+
78
+
79
+ class LoadLidStackImplementation(
80
+ AbstractCommandImpl[LoadLidStackParams, SuccessData[LoadLidStackResult]]
81
+ ):
82
+ """Load lid stack command implementation."""
83
+
84
+ def __init__(
85
+ self, equipment: EquipmentHandler, state_view: StateView, **kwargs: object
86
+ ) -> None:
87
+ self._equipment = equipment
88
+ self._state_view = state_view
89
+
90
+ async def execute(
91
+ self, params: LoadLidStackParams
92
+ ) -> SuccessData[LoadLidStackResult]:
93
+ """Load definition and calibration data necessary for a lid stack."""
94
+ if isinstance(params.location, AddressableAreaLocation):
95
+ area_name = params.location.addressableAreaName
96
+ if not (
97
+ fixture_validation.is_deck_slot(params.location.addressableAreaName)
98
+ or fixture_validation.is_abs_reader(params.location.addressableAreaName)
99
+ ):
100
+ raise LabwareIsNotAllowedInLocationError(
101
+ f"Cannot load {params.loadName} onto addressable area {area_name}"
102
+ )
103
+ self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
104
+ area_name
105
+ )
106
+ elif isinstance(params.location, DeckSlotLocation):
107
+ self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
108
+ params.location.slotName.id
109
+ )
110
+
111
+ verified_location = self._state_view.geometry.ensure_location_not_occupied(
112
+ params.location
113
+ )
114
+
115
+ lid_stack_object = await self._equipment.load_labware(
116
+ load_name=_LID_STACK_PE_LABWARE,
117
+ namespace=_LID_STACK_PE_NAMESPACE,
118
+ version=_LID_STACK_PE_VERSION,
119
+ location=verified_location,
120
+ labware_id=None,
121
+ )
122
+ if not labware_validation.validate_definition_is_system(
123
+ lid_stack_object.definition
124
+ ):
125
+ raise ProtocolEngineError(
126
+ message="Lid Stack Labware Object Labware Definition does not contain required allowed role 'system'."
127
+ )
128
+
129
+ loaded_lid_labwares = await self._equipment.load_lids(
130
+ load_name=params.loadName,
131
+ namespace=params.namespace,
132
+ version=params.version,
133
+ location=OnLabwareLocation(labwareId=lid_stack_object.labware_id),
134
+ quantity=params.quantity,
135
+ )
136
+ loaded_lid_locations_by_id = {}
137
+ load_location = OnLabwareLocation(labwareId=lid_stack_object.labware_id)
138
+ for loaded_lid in loaded_lid_labwares:
139
+ loaded_lid_locations_by_id[loaded_lid.labware_id] = load_location
140
+ load_location = OnLabwareLocation(labwareId=loaded_lid.labware_id)
141
+
142
+ state_update = StateUpdate()
143
+ state_update.set_loaded_lid_stack(
144
+ stack_id=lid_stack_object.labware_id,
145
+ stack_object_definition=lid_stack_object.definition,
146
+ stack_location=verified_location,
147
+ labware_ids=list(loaded_lid_locations_by_id.keys()),
148
+ labware_definition=loaded_lid_labwares[0].definition,
149
+ locations=loaded_lid_locations_by_id,
150
+ )
151
+
152
+ if isinstance(verified_location, OnLabwareLocation):
153
+ self._state_view.labware.raise_if_labware_cannot_be_stacked(
154
+ top_labware_definition=loaded_lid_labwares[
155
+ params.quantity - 1
156
+ ].definition,
157
+ bottom_labware_id=verified_location.labwareId,
158
+ )
159
+
160
+ return SuccessData(
161
+ public=LoadLidStackResult(
162
+ stackLabwareId=lid_stack_object.labware_id,
163
+ labwareIds=list(loaded_lid_locations_by_id.keys()),
164
+ definition=loaded_lid_labwares[0].definition,
165
+ location=params.location,
166
+ ),
167
+ state_update=state_update,
168
+ )
169
+
170
+
171
+ class LoadLidStack(
172
+ BaseCommand[LoadLidStackParams, LoadLidStackResult, ErrorOccurrence]
173
+ ):
174
+ """Load lid stack command resource model."""
175
+
176
+ commandType: LoadLidStackCommandType = "loadLidStack"
177
+ params: LoadLidStackParams
178
+ result: Optional[LoadLidStackResult]
179
+
180
+ _ImplementationCls: Type[LoadLidStackImplementation] = LoadLidStackImplementation
181
+
182
+
183
+ class LoadLidStackCreate(BaseCommandCreate[LoadLidStackParams]):
184
+ """Load lid stack command creation request."""
185
+
186
+ commandType: LoadLidStackCommandType = "loadLidStack"
187
+ params: LoadLidStackParams
188
+
189
+ _CommandCls: Type[LoadLidStack] = LoadLidStack
@@ -5,6 +5,8 @@ from typing import Optional, Type, Dict, TYPE_CHECKING
5
5
  from typing_extensions import Literal
6
6
 
7
7
  from opentrons.protocol_engine.state.update_types import StateUpdate
8
+ from opentrons.protocol_engine.types import LiquidId
9
+ from opentrons.protocol_engine.errors import InvalidLiquidError
8
10
 
9
11
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
10
12
  from ..errors.error_occurrence import ErrorOccurrence
@@ -19,9 +21,9 @@ LoadLiquidCommandType = Literal["loadLiquid"]
19
21
  class LoadLiquidParams(BaseModel):
20
22
  """Payload required to load a liquid into a well."""
21
23
 
22
- liquidId: str = Field(
24
+ liquidId: LiquidId = Field(
23
25
  ...,
24
- description="Unique identifier of the liquid to load.",
26
+ description="Unique identifier of the liquid to load. If this is the sentinel value EMPTY, all values of volumeByWell must be 0.",
25
27
  )
26
28
  labwareId: str = Field(
27
29
  ...,
@@ -29,7 +31,7 @@ class LoadLiquidParams(BaseModel):
29
31
  )
30
32
  volumeByWell: Dict[str, float] = Field(
31
33
  ...,
32
- description="Volume of liquid, in µL, loaded into each well by name, in this labware.",
34
+ description="Volume of liquid, in µL, loaded into each well by name, in this labware. If the liquid id is the sentinel value EMPTY, all volumes must be 0.",
33
35
  )
34
36
 
35
37
 
@@ -57,6 +59,12 @@ class LoadLiquidImplementation(
57
59
  self._state_view.labware.validate_liquid_allowed_in_labware(
58
60
  labware_id=params.labwareId, wells=params.volumeByWell
59
61
  )
62
+ if params.liquidId == "EMPTY":
63
+ for well_name, volume in params.volumeByWell.items():
64
+ if volume != 0.0:
65
+ raise InvalidLiquidError(
66
+ 'loadLiquid commands that specify the special liquid "EMPTY" must set volume to be 0.0, but the volume for {well_name} is {volume}'
67
+ )
60
68
 
61
69
  state_update = StateUpdate()
62
70
  state_update.set_liquid_loaded(
@@ -73,7 +81,7 @@ class LoadLiquid(BaseCommand[LoadLiquidParams, LoadLiquidResult, ErrorOccurrence
73
81
 
74
82
  commandType: LoadLiquidCommandType = "loadLiquid"
75
83
  params: LoadLiquidParams
76
- result: Optional[LoadLiquidResult]
84
+ result: Optional[LoadLiquidResult] = None
77
85
 
78
86
  _ImplementationCls: Type[LoadLiquidImplementation] = LoadLiquidImplementation
79
87
 
@@ -0,0 +1,144 @@
1
+ """LoadLiquidClass stores the liquid class settings used for a transfer into the Protocol Engine."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Optional, Type, TYPE_CHECKING, Any
5
+
6
+ from typing_extensions import Literal
7
+ from pydantic import BaseModel, Field
8
+ from pydantic.json_schema import SkipJsonSchema
9
+
10
+ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
11
+ from ..errors import LiquidClassDoesNotExistError
12
+ from ..errors.error_occurrence import ErrorOccurrence
13
+ from ..errors.exceptions import LiquidClassRedefinitionError
14
+ from ..state.update_types import LiquidClassLoadedUpdate, StateUpdate
15
+ from ..types import LiquidClassRecord
16
+
17
+ if TYPE_CHECKING:
18
+ from ..state.state import StateView
19
+ from ..resources import ModelUtils
20
+
21
+ LoadLiquidClassCommandType = Literal["loadLiquidClass"]
22
+
23
+
24
+ def _remove_default(s: dict[str, Any]) -> None:
25
+ s.pop("default", None)
26
+
27
+
28
+ class LoadLiquidClassParams(BaseModel):
29
+ """The liquid class transfer properties to store."""
30
+
31
+ liquidClassId: str | SkipJsonSchema[None] = Field(
32
+ None,
33
+ description="Unique identifier for the liquid class to store. "
34
+ "If you do not supply a liquidClassId, we will generate one.",
35
+ json_schema_extra=_remove_default,
36
+ )
37
+ liquidClassRecord: LiquidClassRecord = Field(
38
+ ...,
39
+ description="The liquid class to store.",
40
+ )
41
+
42
+
43
+ class LoadLiquidClassResult(BaseModel):
44
+ """Result from execution of LoadLiquidClass command."""
45
+
46
+ liquidClassId: str = Field(
47
+ ...,
48
+ description="The ID for the liquid class that was loaded, either the one you "
49
+ "supplied or the one we generated.",
50
+ )
51
+
52
+
53
+ class LoadLiquidClassImplementation(
54
+ AbstractCommandImpl[LoadLiquidClassParams, SuccessData[LoadLiquidClassResult]]
55
+ ):
56
+ """Load Liquid Class command implementation."""
57
+
58
+ def __init__(
59
+ self, state_view: StateView, model_utils: ModelUtils, **kwargs: object
60
+ ) -> None:
61
+ self._state_view = state_view
62
+ self._model_utils = model_utils
63
+
64
+ async def execute(
65
+ self, params: LoadLiquidClassParams
66
+ ) -> SuccessData[LoadLiquidClassResult]:
67
+ """Store the liquid class in the Protocol Engine."""
68
+ liquid_class_id: Optional[str]
69
+ already_loaded = False
70
+
71
+ if params.liquidClassId:
72
+ liquid_class_id = params.liquidClassId
73
+ if self._liquid_class_id_already_loaded(
74
+ liquid_class_id, params.liquidClassRecord
75
+ ):
76
+ already_loaded = True
77
+ else:
78
+ liquid_class_id = (
79
+ self._state_view.liquid_classes.get_id_for_liquid_class_record(
80
+ params.liquidClassRecord
81
+ ) # if liquidClassRecord was already loaded, reuse the existing ID
82
+ )
83
+ if liquid_class_id:
84
+ already_loaded = True
85
+ else:
86
+ liquid_class_id = self._model_utils.generate_id()
87
+
88
+ if already_loaded:
89
+ state_update = StateUpdate() # liquid class already loaded, do nothing
90
+ else:
91
+ state_update = StateUpdate(
92
+ liquid_class_loaded=LiquidClassLoadedUpdate(
93
+ liquid_class_id=liquid_class_id,
94
+ liquid_class_record=params.liquidClassRecord,
95
+ )
96
+ )
97
+
98
+ return SuccessData(
99
+ public=LoadLiquidClassResult(liquidClassId=liquid_class_id),
100
+ state_update=state_update,
101
+ )
102
+
103
+ def _liquid_class_id_already_loaded(
104
+ self, liquid_class_id: str, liquid_class_record: LiquidClassRecord
105
+ ) -> bool:
106
+ """Check if the liquid_class_id has already been loaded.
107
+
108
+ If it has, make sure that liquid_class_record matches the previously loaded definition.
109
+ """
110
+ try:
111
+ existing_liquid_class_record = self._state_view.liquid_classes.get(
112
+ liquid_class_id
113
+ )
114
+ except LiquidClassDoesNotExistError:
115
+ return False
116
+
117
+ if liquid_class_record != existing_liquid_class_record:
118
+ raise LiquidClassRedefinitionError(
119
+ f"Liquid class {liquid_class_id} conflicts with previously loaded definition."
120
+ )
121
+ return True
122
+
123
+
124
+ class LoadLiquidClass(
125
+ BaseCommand[LoadLiquidClassParams, LoadLiquidClassResult, ErrorOccurrence]
126
+ ):
127
+ """Load Liquid Class command resource model."""
128
+
129
+ commandType: LoadLiquidClassCommandType = "loadLiquidClass"
130
+ params: LoadLiquidClassParams
131
+ result: Optional[LoadLiquidClassResult]
132
+
133
+ _ImplementationCls: Type[
134
+ LoadLiquidClassImplementation
135
+ ] = LoadLiquidClassImplementation
136
+
137
+
138
+ class LoadLiquidClassCreate(BaseCommandCreate[LoadLiquidClassParams]):
139
+ """Load Liquid Class command creation request."""
140
+
141
+ commandType: LoadLiquidClassCommandType = "loadLiquidClass"
142
+ params: LoadLiquidClassParams
143
+
144
+ _CommandCls: Type[LoadLiquidClass] = LoadLiquidClass
@@ -1,8 +1,11 @@
1
1
  """Implementation, request models, and response models for the load module command."""
2
2
  from __future__ import annotations
3
- from typing import TYPE_CHECKING, Optional, Type
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
4
  from typing_extensions import Literal
5
5
  from pydantic import BaseModel, Field
6
+ from pydantic.json_schema import SkipJsonSchema
7
+
8
+ from opentrons.protocol_engine.state.update_types import StateUpdate
6
9
 
7
10
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
8
11
  from ..errors.error_occurrence import ErrorOccurrence
@@ -25,6 +28,10 @@ if TYPE_CHECKING:
25
28
  LoadModuleCommandType = Literal["loadModule"]
26
29
 
27
30
 
31
+ def _remove_default(s: dict[str, Any]) -> None:
32
+ s.pop("default", None)
33
+
34
+
28
35
  class LoadModuleParams(BaseModel):
29
36
  """Payload required to load a module."""
30
37
 
@@ -57,12 +64,13 @@ class LoadModuleParams(BaseModel):
57
64
  ),
58
65
  )
59
66
 
60
- moduleId: Optional[str] = Field(
67
+ moduleId: str | SkipJsonSchema[None] = Field(
61
68
  None,
62
69
  description=(
63
70
  "An optional ID to assign to this module."
64
71
  " If None, an ID will be generated."
65
72
  ),
73
+ json_schema_extra=_remove_default,
66
74
  )
67
75
 
68
76
 
@@ -75,7 +83,7 @@ class LoadModuleResult(BaseModel):
75
83
 
76
84
  # TODO(mm, 2023-04-13): Remove this field. Jira RSS-221.
77
85
  definition: ModuleDefinition = Field(
78
- deprecated=True,
86
+ json_schema_extra={"deprecated": True},
79
87
  description=(
80
88
  "The definition of the connected module."
81
89
  " This field is an implementation detail. We might change or remove it without warning."
@@ -116,26 +124,35 @@ class LoadModuleImplementation(
116
124
 
117
125
  async def execute(self, params: LoadModuleParams) -> SuccessData[LoadModuleResult]:
118
126
  """Check that the requested module is attached and assign its identifier."""
127
+ state_update = StateUpdate()
128
+
119
129
  module_type = params.model.as_type()
120
130
  self._ensure_module_location(params.location.slotName, module_type)
121
131
 
132
+ # todo(mm, 2024-12-03): Theoretically, we should be able to deal with
133
+ # addressable areas and deck configurations the same way between OT-2 and Flex.
134
+ # Can this be simplified?
122
135
  if self._state_view.config.robot_type == "OT-2 Standard":
123
136
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
124
137
  params.location.slotName.id
125
138
  )
126
139
  else:
127
- addressable_area = self._state_view.geometry._modules.ensure_and_convert_module_fixture_location(
128
- deck_slot=params.location.slotName,
129
- deck_type=self._state_view.config.deck_type,
130
- model=params.model,
140
+ addressable_area_provided_by_module = (
141
+ self._state_view.modules.ensure_and_convert_module_fixture_location(
142
+ deck_slot=params.location.slotName,
143
+ model=params.model,
144
+ )
131
145
  )
132
146
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
133
- addressable_area
147
+ addressable_area_provided_by_module
134
148
  )
135
149
 
136
150
  verified_location = self._state_view.geometry.ensure_location_not_occupied(
137
151
  params.location
138
152
  )
153
+ state_update.set_addressable_area_used(
154
+ addressable_area_name=params.location.slotName.id
155
+ )
139
156
 
140
157
  if params.model == ModuleModel.MAGNETIC_BLOCK_V1:
141
158
  loaded_module = await self._equipment.load_magnetic_block(
@@ -157,11 +174,15 @@ class LoadModuleImplementation(
157
174
  model=loaded_module.definition.model,
158
175
  definition=loaded_module.definition,
159
176
  ),
177
+ state_update=state_update,
160
178
  )
161
179
 
162
180
  def _ensure_module_location(
163
181
  self, slot: DeckSlotName, module_type: ModuleType
164
182
  ) -> None:
183
+ # todo(mm, 2024-12-03): Theoretically, we should be able to deal with
184
+ # addressable areas and deck configurations the same way between OT-2 and Flex.
185
+ # Can this be simplified?
165
186
  if self._state_view.config.robot_type == "OT-2 Standard":
166
187
  slot_def = self._state_view.addressable_areas.get_slot_definition(slot.id)
167
188
  compatible_modules = slot_def["compatibleModuleTypes"]
@@ -173,7 +194,7 @@ class LoadModuleImplementation(
173
194
  cutout_fixture_id = ModuleType.to_module_fixture_id(module_type)
174
195
  module_fixture = deck_configuration_provider.get_cutout_fixture(
175
196
  cutout_fixture_id,
176
- self._state_view.addressable_areas.state.deck_definition,
197
+ self._state_view.labware.get_deck_definition(),
177
198
  )
178
199
  cutout_id = (
179
200
  self._state_view.addressable_areas.get_cutout_id_by_deck_slot_name(slot)
@@ -189,7 +210,7 @@ class LoadModule(BaseCommand[LoadModuleParams, LoadModuleResult, ErrorOccurrence
189
210
 
190
211
  commandType: LoadModuleCommandType = "loadModule"
191
212
  params: LoadModuleParams
192
- result: Optional[LoadModuleResult]
213
+ result: Optional[LoadModuleResult] = None
193
214
 
194
215
  _ImplementationCls: Type[LoadModuleImplementation] = LoadModuleImplementation
195
216
 
@@ -1,20 +1,23 @@
1
1
  """Load pipette command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
+
5
+ from pydantic import BaseModel, Field
6
+ from pydantic.json_schema import SkipJsonSchema
7
+ from typing_extensions import Literal
3
8
 
4
- from opentrons.protocol_engine.state.update_types import StateUpdate
5
9
  from opentrons_shared_data.pipette.pipette_load_name_conversions import (
6
10
  convert_to_pipette_name_type,
7
11
  )
8
12
  from opentrons_shared_data.pipette.types import PipetteGenerationType
9
13
  from opentrons_shared_data.robot import user_facing_robot_type
10
14
  from opentrons_shared_data.robot.types import RobotTypeEnum
11
- from pydantic import BaseModel, Field
12
- from typing import TYPE_CHECKING, Optional, Type
13
- from typing_extensions import Literal
15
+
14
16
 
15
17
  from opentrons_shared_data.pipette.types import PipetteNameType
16
18
  from opentrons.types import MountType
17
19
 
20
+ from opentrons.protocol_engine.state.update_types import StateUpdate
18
21
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
19
22
  from ..errors.error_occurrence import ErrorOccurrence
20
23
  from ..errors import InvalidSpecificationForRobotTypeError, InvalidLoadPipetteSpecsError
@@ -27,6 +30,10 @@ if TYPE_CHECKING:
27
30
  LoadPipetteCommandType = Literal["loadPipette"]
28
31
 
29
32
 
33
+ def _remove_default(s: dict[str, Any]) -> None:
34
+ s.pop("default", None)
35
+
36
+
30
37
  class LoadPipetteParams(BaseModel):
31
38
  """Payload needed to load a pipette on to a mount."""
32
39
 
@@ -38,21 +45,24 @@ class LoadPipetteParams(BaseModel):
38
45
  ...,
39
46
  description="The mount the pipette should be present on.",
40
47
  )
41
- pipetteId: Optional[str] = Field(
48
+ pipetteId: str | SkipJsonSchema[None] = Field(
42
49
  None,
43
50
  description="An optional ID to assign to this pipette. If None, an ID "
44
51
  "will be generated.",
52
+ json_schema_extra=_remove_default,
45
53
  )
46
- tipOverlapNotAfterVersion: Optional[str] = Field(
54
+ tipOverlapNotAfterVersion: str | SkipJsonSchema[None] = Field(
47
55
  None,
48
56
  description="A version of tip overlap data to not exceed. The highest-versioned "
49
57
  "tip overlap data that does not exceed this version will be used. Versions are "
50
58
  "expressed as vN where N is an integer, counting up from v0. If None, the current "
51
59
  "highest version will be used.",
60
+ json_schema_extra=_remove_default,
52
61
  )
53
- liquidPresenceDetection: Optional[bool] = Field(
62
+ liquidPresenceDetection: bool | SkipJsonSchema[None] = Field(
54
63
  None,
55
64
  description="Enable liquid presence detection for this pipette. Defaults to False.",
65
+ json_schema_extra=_remove_default,
56
66
  )
57
67
 
58
68
 
@@ -127,6 +137,7 @@ class LoadPipetteImplementation(
127
137
  serial_number=loaded_pipette.serial_number,
128
138
  config=loaded_pipette.static_config,
129
139
  )
140
+ state_update.set_fluid_unknown(pipette_id=loaded_pipette.pipette_id)
130
141
 
131
142
  return SuccessData(
132
143
  public=LoadPipetteResult(pipetteId=loaded_pipette.pipette_id),
@@ -139,7 +150,7 @@ class LoadPipette(BaseCommand[LoadPipetteParams, LoadPipetteResult, ErrorOccurre
139
150
 
140
151
  commandType: LoadPipetteCommandType = "loadPipette"
141
152
  params: LoadPipetteParams
142
- result: Optional[LoadPipetteResult]
153
+ result: Optional[LoadPipetteResult] = None
143
154
 
144
155
  _ImplementationCls: Type[LoadPipetteImplementation] = LoadPipetteImplementation
145
156
 
@@ -83,7 +83,7 @@ class Disengage(BaseCommand[DisengageParams, DisengageResult, ErrorOccurrence]):
83
83
 
84
84
  commandType: DisengageCommandType = "magneticModule/disengage"
85
85
  params: DisengageParams
86
- result: Optional[DisengageResult]
86
+ result: Optional[DisengageResult] = None
87
87
 
88
88
  _ImplementationCls: Type[DisengageImplementation] = DisengageImplementation
89
89
 
@@ -105,7 +105,7 @@ class Engage(BaseCommand[EngageParams, EngageResult, ErrorOccurrence]):
105
105
 
106
106
  commandType: EngageCommandType = "magneticModule/engage"
107
107
  params: EngageParams
108
- result: Optional[EngageResult]
108
+ result: Optional[EngageResult] = None
109
109
 
110
110
  _ImplementationCls: Type[EngageImplementation] = EngageImplementation
111
111
 
@@ -1,14 +1,17 @@
1
1
  """Models and implementation for the ``moveLabware`` command."""
2
2
 
3
3
  from __future__ import annotations
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
5
+
6
+ from pydantic.json_schema import SkipJsonSchema
7
+ from pydantic import BaseModel, Field
8
+ from typing_extensions import Literal
9
+
4
10
  from opentrons_shared_data.errors.exceptions import (
5
11
  FailedGripperPickupError,
6
12
  LabwareDroppedError,
7
13
  StallOrCollisionDetectedError,
8
14
  )
9
- from pydantic import BaseModel, Field
10
- from typing import TYPE_CHECKING, Optional, Type
11
- from typing_extensions import Literal
12
15
 
13
16
  from opentrons.protocol_engine.resources.model_utils import ModelUtils
14
17
  from opentrons.types import Point
@@ -49,6 +52,10 @@ if TYPE_CHECKING:
49
52
  MoveLabwareCommandType = Literal["moveLabware"]
50
53
 
51
54
 
55
+ def _remove_default(s: dict[str, Any]) -> None:
56
+ s.pop("default", None)
57
+
58
+
52
59
  # Extra buffer on top of minimum distance to move to the right
53
60
  _TRASH_CHUTE_DROP_BUFFER_MM = 8
54
61
 
@@ -63,15 +70,17 @@ class MoveLabwareParams(BaseModel):
63
70
  description="Whether to use the gripper to perform the labware movement"
64
71
  " or to perform a manual movement with an option to pause.",
65
72
  )
66
- pickUpOffset: Optional[LabwareOffsetVector] = Field(
73
+ pickUpOffset: LabwareOffsetVector | SkipJsonSchema[None] = Field(
67
74
  None,
68
75
  description="Offset to use when picking up labware. "
69
76
  "Experimental param, subject to change",
77
+ json_schema_extra=_remove_default,
70
78
  )
71
- dropOffset: Optional[LabwareOffsetVector] = Field(
79
+ dropOffset: LabwareOffsetVector | SkipJsonSchema[None] = Field(
72
80
  None,
73
81
  description="Offset to use when dropping off labware. "
74
82
  "Experimental param, subject to change",
83
+ json_schema_extra=_remove_default,
75
84
  )
76
85
 
77
86
 
@@ -156,6 +165,7 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
156
165
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
157
166
  area_name
158
167
  )
168
+ state_update.set_addressable_area_used(addressable_area_name=area_name)
159
169
 
160
170
  if fixture_validation.is_gripper_waste_chute(area_name):
161
171
  # When dropping off labware in the waste chute, some bigger pieces
@@ -201,6 +211,9 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
201
211
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
202
212
  params.newLocation.slotName.id
203
213
  )
214
+ state_update.set_addressable_area_used(
215
+ addressable_area_name=params.newLocation.slotName.id
216
+ )
204
217
 
205
218
  available_new_location = self._state_view.geometry.ensure_location_not_occupied(
206
219
  location=params.newLocation
@@ -355,7 +368,7 @@ class MoveLabware(
355
368
 
356
369
  commandType: MoveLabwareCommandType = "moveLabware"
357
370
  params: MoveLabwareParams
358
- result: Optional[MoveLabwareResult]
371
+ result: Optional[MoveLabwareResult] = None
359
372
 
360
373
  _ImplementationCls: Type[MoveLabwareImplementation] = MoveLabwareImplementation
361
374