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.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

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
@@ -0,0 +1,331 @@
1
+ """Seal evotip resin tip command request, result, and implementation models."""
2
+
3
+ from __future__ import annotations
4
+ from pydantic import Field, BaseModel
5
+ from typing import TYPE_CHECKING, Optional, Type, Union
6
+ from opentrons.types import MountType
7
+ from opentrons.protocol_engine.types import MotorAxis
8
+ from typing_extensions import Literal
9
+
10
+ from opentrons.protocol_engine.errors import UnsupportedLabwareForActionError
11
+ from ..resources import ModelUtils, labware_validation
12
+ from ..types import PickUpTipWellLocation, FluidKind, AspiratedFluid
13
+ from .pipetting_common import (
14
+ PipetteIdMixin,
15
+ )
16
+ from .movement_common import (
17
+ DestinationPositionResult,
18
+ StallOrCollisionError,
19
+ move_to_well,
20
+ )
21
+ from .command import (
22
+ AbstractCommandImpl,
23
+ BaseCommand,
24
+ BaseCommandCreate,
25
+ DefinedErrorData,
26
+ SuccessData,
27
+ )
28
+
29
+ from opentrons.hardware_control import HardwareControlAPI
30
+ from opentrons.hardware_control.types import Axis
31
+ from ..state.update_types import StateUpdate
32
+
33
+ if TYPE_CHECKING:
34
+ from ..state.state import StateView
35
+ from ..execution import (
36
+ MovementHandler,
37
+ TipHandler,
38
+ GantryMover,
39
+ PipettingHandler,
40
+ )
41
+
42
+
43
+ EvotipSealPipetteCommandType = Literal["evotipSealPipette"]
44
+ _PREP_DISTANCE_DEFAULT = 8.25
45
+ _PRESS_DISTANCE_DEFAULT = 3.5
46
+ _EJECTOR_PUSH_MM_DEFAULT = 7.0
47
+ _SAFE_TOP_VOLUME = 400
48
+
49
+
50
+ class TipPickUpParams(BaseModel):
51
+ """Payload used to specify press-tip parameters for a seal command."""
52
+
53
+ prepDistance: float = Field(
54
+ default=0, description="The distance to move down to fit the tips on."
55
+ )
56
+ pressDistance: float = Field(
57
+ default=0, description="The distance to press on tips."
58
+ )
59
+ ejectorPushMm: float = Field(
60
+ default=0,
61
+ description="The distance to back off to ensure that the tip presence sensors are not triggered.",
62
+ )
63
+
64
+
65
+ class EvotipSealPipetteParams(PipetteIdMixin):
66
+ """Payload needed to seal resin tips to a pipette."""
67
+
68
+ labwareId: str = Field(..., description="Identifier of labware to use.")
69
+ wellName: str = Field(..., description="Name of well to use in labware.")
70
+ wellLocation: PickUpTipWellLocation = Field(
71
+ default_factory=PickUpTipWellLocation,
72
+ description="Relative well location at which to pick up the tip.",
73
+ )
74
+ tipPickUpParams: Optional[TipPickUpParams] = Field(
75
+ default=None, description="Specific parameters for "
76
+ )
77
+
78
+
79
+ class EvotipSealPipetteResult(DestinationPositionResult):
80
+ """Result data from the execution of a EvotipSealPipette."""
81
+
82
+ tipVolume: float = Field(
83
+ 0,
84
+ description="Maximum volume of liquid that the picked up tip can hold, in µL.",
85
+ ge=0,
86
+ )
87
+
88
+ tipLength: float = Field(
89
+ 0,
90
+ description="The length of the tip in mm.",
91
+ ge=0,
92
+ )
93
+
94
+ tipDiameter: float = Field(
95
+ 0,
96
+ description="The diameter of the tip in mm.",
97
+ ge=0,
98
+ )
99
+
100
+
101
+ _ExecuteReturn = Union[
102
+ SuccessData[EvotipSealPipetteResult],
103
+ DefinedErrorData[StallOrCollisionError],
104
+ ]
105
+
106
+
107
+ class EvotipSealPipetteImplementation(
108
+ AbstractCommandImpl[EvotipSealPipetteParams, _ExecuteReturn]
109
+ ):
110
+ """Evotip seal pipette command implementation."""
111
+
112
+ def __init__(
113
+ self,
114
+ state_view: StateView,
115
+ tip_handler: TipHandler,
116
+ model_utils: ModelUtils,
117
+ movement: MovementHandler,
118
+ hardware_api: HardwareControlAPI,
119
+ gantry_mover: GantryMover,
120
+ pipetting: PipettingHandler,
121
+ **kwargs: object,
122
+ ) -> None:
123
+ self._state_view = state_view
124
+ self._tip_handler = tip_handler
125
+ self._model_utils = model_utils
126
+ self._movement = movement
127
+ self._gantry_mover = gantry_mover
128
+ self._pipetting = pipetting
129
+ self._hardware_api = hardware_api
130
+
131
+ async def relative_pickup_tip(
132
+ self,
133
+ tip_pick_up_params: TipPickUpParams,
134
+ mount: MountType,
135
+ ) -> None:
136
+ """A relative press-fit pick up command using gantry moves."""
137
+ prep_distance = tip_pick_up_params.prepDistance
138
+ press_distance = tip_pick_up_params.pressDistance
139
+ retract_distance = -1 * (prep_distance + press_distance)
140
+
141
+ mount_axis = MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
142
+
143
+ # TODO chb, 2025-01-29): Factor out the movement constants and relocate this logic into the hardware controller
144
+ await self._gantry_mover.move_axes(
145
+ axis_map={mount_axis: prep_distance}, speed=10, relative_move=True
146
+ )
147
+
148
+ # Drive mount down for press-fit
149
+ await self._gantry_mover.move_axes(
150
+ axis_map={mount_axis: press_distance},
151
+ speed=10.0,
152
+ relative_move=True,
153
+ expect_stalls=True,
154
+ )
155
+ # retract cam : 11.05
156
+ await self._gantry_mover.move_axes(
157
+ axis_map={mount_axis: retract_distance}, speed=5.5, relative_move=True
158
+ )
159
+
160
+ async def cam_action_relative_pickup_tip(
161
+ self,
162
+ tip_pick_up_params: TipPickUpParams,
163
+ mount: MountType,
164
+ ) -> None:
165
+ """A cam action pick up command using gantry moves."""
166
+ prep_distance = tip_pick_up_params.prepDistance
167
+ press_distance = tip_pick_up_params.pressDistance
168
+ ejector_push_mm = tip_pick_up_params.ejectorPushMm
169
+ retract_distance = -1 * (prep_distance + press_distance)
170
+
171
+ mount_axis = MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
172
+
173
+ # TODO chb, 2025-01-29): Factor out the movement constants and relocate this logic into the hardware controller
174
+ await self._gantry_mover.move_axes(
175
+ axis_map={mount_axis: -6}, speed=10, relative_move=True
176
+ )
177
+
178
+ # Drive Q down 3mm at fast speed - look into the pick up tip fuinction to find slow and fast: 10.0
179
+ await self._gantry_mover.move_axes(
180
+ axis_map={MotorAxis.AXIS_96_CHANNEL_CAM: prep_distance},
181
+ speed=10.0,
182
+ relative_move=True,
183
+ )
184
+ # 2.8mm at slow speed - cam action pickup speed: 5.5
185
+ await self._gantry_mover.move_axes(
186
+ axis_map={MotorAxis.AXIS_96_CHANNEL_CAM: press_distance},
187
+ speed=5.5,
188
+ relative_move=True,
189
+ )
190
+ # retract cam : 11.05
191
+ await self._gantry_mover.move_axes(
192
+ axis_map={MotorAxis.AXIS_96_CHANNEL_CAM: retract_distance},
193
+ speed=5.5,
194
+ relative_move=True,
195
+ )
196
+
197
+ # Lower tip presence
198
+ await self._gantry_mover.move_axes(
199
+ axis_map={mount_axis: 2}, speed=10, relative_move=True
200
+ )
201
+ await self._gantry_mover.move_axes(
202
+ axis_map={MotorAxis.AXIS_96_CHANNEL_CAM: ejector_push_mm},
203
+ speed=5.5,
204
+ relative_move=True,
205
+ )
206
+ await self._gantry_mover.move_axes(
207
+ axis_map={MotorAxis.AXIS_96_CHANNEL_CAM: -1 * ejector_push_mm},
208
+ speed=5.5,
209
+ relative_move=True,
210
+ )
211
+
212
+ async def execute(
213
+ self, params: EvotipSealPipetteParams
214
+ ) -> Union[SuccessData[EvotipSealPipetteResult], _ExecuteReturn]:
215
+ """Move to and pick up a tip using the requested pipette."""
216
+ pipette_id = params.pipetteId
217
+ labware_id = params.labwareId
218
+ well_name = params.wellName
219
+
220
+ labware_definition = self._state_view.labware.get_definition(params.labwareId)
221
+ if not labware_validation.is_evotips(labware_definition.parameters.loadName):
222
+ raise UnsupportedLabwareForActionError(
223
+ f"Cannot use command: `EvotipSealPipette` with labware: {labware_definition.parameters.loadName}"
224
+ )
225
+
226
+ well_location = self._state_view.geometry.convert_pick_up_tip_well_location(
227
+ well_location=params.wellLocation
228
+ )
229
+ move_result = await move_to_well(
230
+ movement=self._movement,
231
+ model_utils=self._model_utils,
232
+ pipette_id=pipette_id,
233
+ labware_id=labware_id,
234
+ well_name=well_name,
235
+ well_location=well_location,
236
+ )
237
+ if isinstance(move_result, DefinedErrorData):
238
+ return move_result
239
+
240
+ # Aspirate to move plunger to a maximum volume position per pipette type
241
+ tip_geometry = self._state_view.geometry.get_nominal_tip_geometry(
242
+ pipette_id, labware_id, well_name
243
+ )
244
+ if self._state_view.pipettes.get_mount(pipette_id) == MountType.LEFT:
245
+ await self._hardware_api.home(axes=[Axis.P_L])
246
+ else:
247
+ await self._hardware_api.home(axes=[Axis.P_R])
248
+
249
+ # Begin relative pickup steps for the resin tips
250
+
251
+ channels = self._state_view.tips.get_pipette_active_channels(pipette_id)
252
+ mount = self._state_view.pipettes.get_mount(pipette_id)
253
+ tip_pick_up_params = params.tipPickUpParams
254
+ if tip_pick_up_params is None:
255
+ tip_pick_up_params = TipPickUpParams(
256
+ prepDistance=_PREP_DISTANCE_DEFAULT,
257
+ pressDistance=_PRESS_DISTANCE_DEFAULT,
258
+ ejectorPushMm=_EJECTOR_PUSH_MM_DEFAULT,
259
+ )
260
+
261
+ if channels != 96:
262
+ await self.relative_pickup_tip(
263
+ tip_pick_up_params=tip_pick_up_params,
264
+ mount=mount,
265
+ )
266
+ elif channels == 96:
267
+ await self.cam_action_relative_pickup_tip(
268
+ tip_pick_up_params=tip_pick_up_params,
269
+ mount=mount,
270
+ )
271
+ else:
272
+ tip_geometry = await self._tip_handler.pick_up_tip(
273
+ pipette_id=pipette_id,
274
+ labware_id=labware_id,
275
+ well_name=well_name,
276
+ do_not_ignore_tip_presence=True,
277
+ )
278
+
279
+ # cache_tip
280
+ if self._state_view.config.use_virtual_pipettes is False:
281
+ self._tip_handler.cache_tip(pipette_id, tip_geometry)
282
+ hw_instr = self._hardware_api.hardware_instruments[mount.to_hw_mount()]
283
+ if hw_instr is not None:
284
+ hw_instr.set_current_volume(_SAFE_TOP_VOLUME)
285
+
286
+ state_update = StateUpdate()
287
+ state_update.update_pipette_tip_state(
288
+ pipette_id=pipette_id,
289
+ tip_geometry=tip_geometry,
290
+ )
291
+
292
+ state_update.set_fluid_aspirated(
293
+ pipette_id=pipette_id,
294
+ fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=_SAFE_TOP_VOLUME),
295
+ )
296
+ return SuccessData(
297
+ public=EvotipSealPipetteResult(
298
+ tipVolume=tip_geometry.volume,
299
+ tipLength=tip_geometry.length,
300
+ tipDiameter=tip_geometry.diameter,
301
+ position=move_result.public.position,
302
+ ),
303
+ state_update=state_update,
304
+ )
305
+
306
+
307
+ class EvotipSealPipette(
308
+ BaseCommand[
309
+ EvotipSealPipetteParams,
310
+ EvotipSealPipetteResult,
311
+ StallOrCollisionError,
312
+ ]
313
+ ):
314
+ """Seal evotip resin tip command model."""
315
+
316
+ commandType: EvotipSealPipetteCommandType = "evotipSealPipette"
317
+ params: EvotipSealPipetteParams
318
+ result: Optional[EvotipSealPipetteResult] = None
319
+
320
+ _ImplementationCls: Type[
321
+ EvotipSealPipetteImplementation
322
+ ] = EvotipSealPipetteImplementation
323
+
324
+
325
+ class EvotipSealPipetteCreate(BaseCommandCreate[EvotipSealPipetteParams]):
326
+ """Seal evotip resin tip command creation request model."""
327
+
328
+ commandType: EvotipSealPipetteCommandType = "evotipSealPipette"
329
+ params: EvotipSealPipetteParams
330
+
331
+ _CommandCls: Type[EvotipSealPipette] = EvotipSealPipette
@@ -0,0 +1,160 @@
1
+ """Unseal evotip resin tip command request, result, and implementation models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import Field
6
+ from typing import TYPE_CHECKING, Optional, Type
7
+ from typing_extensions import Literal
8
+
9
+ from opentrons.protocol_engine.resources.model_utils import ModelUtils
10
+ from opentrons.protocol_engine.errors import UnsupportedLabwareForActionError
11
+ from opentrons.protocol_engine.types import MotorAxis
12
+ from opentrons.types import MountType
13
+
14
+ from ..types import DropTipWellLocation
15
+ from .pipetting_common import (
16
+ PipetteIdMixin,
17
+ )
18
+ from .movement_common import (
19
+ DestinationPositionResult,
20
+ move_to_well,
21
+ StallOrCollisionError,
22
+ )
23
+ from .command import (
24
+ AbstractCommandImpl,
25
+ BaseCommand,
26
+ BaseCommandCreate,
27
+ DefinedErrorData,
28
+ SuccessData,
29
+ )
30
+ from ..resources import labware_validation
31
+
32
+ if TYPE_CHECKING:
33
+ from ..state.state import StateView
34
+ from ..execution import MovementHandler, TipHandler, GantryMover
35
+
36
+
37
+ EvotipUnsealPipetteCommandType = Literal["evotipUnsealPipette"]
38
+
39
+
40
+ class EvotipUnsealPipetteParams(PipetteIdMixin):
41
+ """Payload required to drop a tip in a specific well."""
42
+
43
+ labwareId: str = Field(..., description="Identifier of labware to use.")
44
+ wellName: str = Field(..., description="Name of well to use in labware.")
45
+ wellLocation: DropTipWellLocation = Field(
46
+ default_factory=DropTipWellLocation,
47
+ description="Relative well location at which to drop the tip.",
48
+ )
49
+
50
+
51
+ class EvotipUnsealPipetteResult(DestinationPositionResult):
52
+ """Result data from the execution of a DropTip command."""
53
+
54
+ pass
55
+
56
+
57
+ _ExecuteReturn = (
58
+ SuccessData[EvotipUnsealPipetteResult] | DefinedErrorData[StallOrCollisionError]
59
+ )
60
+
61
+
62
+ class EvotipUnsealPipetteImplementation(
63
+ AbstractCommandImpl[EvotipUnsealPipetteParams, _ExecuteReturn]
64
+ ):
65
+ """Drop tip command implementation."""
66
+
67
+ def __init__(
68
+ self,
69
+ state_view: StateView,
70
+ tip_handler: TipHandler,
71
+ movement: MovementHandler,
72
+ model_utils: ModelUtils,
73
+ gantry_mover: GantryMover,
74
+ **kwargs: object,
75
+ ) -> None:
76
+ self._state_view = state_view
77
+ self._tip_handler = tip_handler
78
+ self._movement_handler = movement
79
+ self._model_utils = model_utils
80
+ self._gantry_mover = gantry_mover
81
+
82
+ async def execute(self, params: EvotipUnsealPipetteParams) -> _ExecuteReturn:
83
+ """Move to and drop a tip using the requested pipette."""
84
+ pipette_id = params.pipetteId
85
+ labware_id = params.labwareId
86
+ well_name = params.wellName
87
+
88
+ well_location = params.wellLocation
89
+ labware_definition = self._state_view.labware.get_definition(params.labwareId)
90
+ if not labware_validation.is_evotips(labware_definition.parameters.loadName):
91
+ raise UnsupportedLabwareForActionError(
92
+ f"Cannot use command: `EvotipUnsealPipette` with labware: {labware_definition.parameters.loadName}"
93
+ )
94
+ is_partially_configured = self._state_view.pipettes.get_is_partially_configured(
95
+ pipette_id=pipette_id
96
+ )
97
+ tip_drop_location = self._state_view.geometry.get_checked_tip_drop_location(
98
+ pipette_id=pipette_id,
99
+ labware_id=labware_id,
100
+ well_location=well_location,
101
+ partially_configured=is_partially_configured,
102
+ )
103
+
104
+ move_result = await move_to_well(
105
+ movement=self._movement_handler,
106
+ model_utils=self._model_utils,
107
+ pipette_id=pipette_id,
108
+ labware_id=labware_id,
109
+ well_name=well_name,
110
+ well_location=tip_drop_location,
111
+ )
112
+ if isinstance(move_result, DefinedErrorData):
113
+ return move_result
114
+
115
+ # Move to an appropriate position
116
+ mount = self._state_view.pipettes.get_mount(pipette_id)
117
+
118
+ mount_axis = MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
119
+ await self._gantry_mover.move_axes(
120
+ axis_map={mount_axis: -14}, speed=10, relative_move=True
121
+ )
122
+
123
+ await self._tip_handler.drop_tip(
124
+ pipette_id=pipette_id,
125
+ home_after=None,
126
+ do_not_ignore_tip_presence=False,
127
+ ignore_plunger=True,
128
+ )
129
+
130
+ return SuccessData(
131
+ public=EvotipUnsealPipetteResult(position=move_result.public.position),
132
+ state_update=move_result.state_update.set_fluid_unknown(
133
+ pipette_id=pipette_id
134
+ ).update_pipette_tip_state(pipette_id=params.pipetteId, tip_geometry=None),
135
+ )
136
+
137
+
138
+ class EvotipUnsealPipette(
139
+ BaseCommand[
140
+ EvotipUnsealPipetteParams, EvotipUnsealPipetteResult, StallOrCollisionError
141
+ ]
142
+ ):
143
+ """Evotip unseal command model."""
144
+
145
+ commandType: EvotipUnsealPipetteCommandType = "evotipUnsealPipette"
146
+ params: EvotipUnsealPipetteParams
147
+ result: Optional[EvotipUnsealPipetteResult] = None
148
+
149
+ _ImplementationCls: Type[
150
+ EvotipUnsealPipetteImplementation
151
+ ] = EvotipUnsealPipetteImplementation
152
+
153
+
154
+ class EvotipUnsealPipetteCreate(BaseCommandCreate[EvotipUnsealPipetteParams]):
155
+ """Evotip unseal command creation request model."""
156
+
157
+ commandType: EvotipUnsealPipetteCommandType = "evotipUnsealPipette"
158
+ params: EvotipUnsealPipetteParams
159
+
160
+ _CommandCls: Type[EvotipUnsealPipette] = EvotipUnsealPipette
@@ -1,24 +1,17 @@
1
1
  """Generate a JSON schema against which all create commands statically validate."""
2
+
2
3
  import json
3
- import pydantic
4
4
  import argparse
5
5
  import sys
6
- from opentrons.protocol_engine.commands.command_unions import CommandCreate
7
-
8
-
9
- class CreateCommandUnion(pydantic.BaseModel):
10
- """Model that validates a union of all CommandCreate models."""
11
-
12
- __root__: CommandCreate
6
+ from opentrons.protocol_engine.commands.command_unions import CommandCreateAdapter
13
7
 
14
8
 
15
9
  def generate_command_schema(version: str) -> str:
16
10
  """Generate a JSON Schema that all valid create commands can validate against."""
17
- raw_json_schema = CreateCommandUnion.schema_json()
18
- schema_as_dict = json.loads(raw_json_schema)
11
+ schema_as_dict = CommandCreateAdapter.json_schema(mode="validation")
19
12
  schema_as_dict["$id"] = f"opentronsCommandSchemaV{version}"
20
13
  schema_as_dict["$schema"] = "http://json-schema.org/draft-07/schema#"
21
- return json.dumps(schema_as_dict, indent=2)
14
+ return json.dumps(schema_as_dict, indent=2, sort_keys=True)
22
15
 
23
16
 
24
17
  if __name__ == "__main__":
@@ -0,0 +1,134 @@
1
+ """Get next tip command request, result, and implementation models."""
2
+
3
+ from __future__ import annotations
4
+ from pydantic import BaseModel, Field
5
+ from typing import TYPE_CHECKING, Any, Optional, Type, List, Literal, Union
6
+
7
+ from pydantic.json_schema import SkipJsonSchema
8
+
9
+ from opentrons.types import NozzleConfigurationType
10
+
11
+ from ..errors import ErrorOccurrence
12
+ from ..types import NextTipInfo, NoTipAvailable, NoTipReason
13
+ from .pipetting_common import PipetteIdMixin
14
+
15
+ from .command import (
16
+ AbstractCommandImpl,
17
+ BaseCommand,
18
+ BaseCommandCreate,
19
+ SuccessData,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from ..state.state import StateView
24
+
25
+
26
+ def _remove_default(s: dict[str, Any]) -> None:
27
+ s.pop("default", None)
28
+
29
+
30
+ GetNextTipCommandType = Literal["getNextTip"]
31
+
32
+
33
+ class GetNextTipParams(PipetteIdMixin):
34
+ """Payload needed to resolve the next available tip."""
35
+
36
+ labwareIds: List[str] = Field(
37
+ ...,
38
+ description="Labware ID(s) of tip racks to resolve next available tip(s) from"
39
+ " Labware IDs will be resolved sequentially",
40
+ )
41
+ startingTipWell: str | SkipJsonSchema[None] = Field(
42
+ None,
43
+ description="Name of starting tip rack 'well'."
44
+ " This only applies to the first tip rack in the list provided in labwareIDs",
45
+ json_schema_extra=_remove_default,
46
+ )
47
+
48
+
49
+ class GetNextTipResult(BaseModel):
50
+ """Result data from the execution of a GetNextTip."""
51
+
52
+ nextTipInfo: Union[NextTipInfo, NoTipAvailable] = Field(
53
+ ...,
54
+ description="Labware ID and well name of next available tip for a pipette,"
55
+ " or information why no tip could be resolved.",
56
+ )
57
+
58
+
59
+ class GetNextTipImplementation(
60
+ AbstractCommandImpl[GetNextTipParams, SuccessData[GetNextTipResult]]
61
+ ):
62
+ """Get next tip command implementation."""
63
+
64
+ def __init__(
65
+ self,
66
+ state_view: StateView,
67
+ **kwargs: object,
68
+ ) -> None:
69
+ self._state_view = state_view
70
+
71
+ async def execute(self, params: GetNextTipParams) -> SuccessData[GetNextTipResult]:
72
+ """Get the next available tip for the requested pipette."""
73
+ pipette_id = params.pipetteId
74
+ starting_tip_name = params.startingTipWell
75
+
76
+ num_tips = self._state_view.tips.get_pipette_active_channels(pipette_id)
77
+ nozzle_map = self._state_view.tips.get_pipette_nozzle_map(pipette_id)
78
+
79
+ if (
80
+ starting_tip_name is not None
81
+ and nozzle_map.configuration != NozzleConfigurationType.FULL
82
+ ):
83
+ # This is to match the behavior found in PAPI, but also because we don't have logic to automatically find
84
+ # the next tip with partial configuration and a starting tip. This will never work for a 96-channel due to
85
+ # x-axis overlap, but could eventually work with 8-channel if we better define starting tip USED or CLEAN
86
+ # state when starting a protocol to prevent accidental tip pick-up with starting non-full tip racks.
87
+ return SuccessData(
88
+ public=GetNextTipResult(
89
+ nextTipInfo=NoTipAvailable(
90
+ noTipReason=NoTipReason.STARTING_TIP_WITH_PARTIAL,
91
+ message="Cannot automatically resolve next tip with starting tip and partial tip configuration.",
92
+ )
93
+ )
94
+ )
95
+
96
+ next_tip: Union[NextTipInfo, NoTipAvailable]
97
+ for labware_id in params.labwareIds:
98
+ well_name = self._state_view.tips.get_next_tip(
99
+ labware_id=labware_id,
100
+ num_tips=num_tips,
101
+ starting_tip_name=starting_tip_name,
102
+ nozzle_map=nozzle_map,
103
+ )
104
+ if well_name is not None:
105
+ next_tip = NextTipInfo(labwareId=labware_id, tipStartingWell=well_name)
106
+ break
107
+ # After the first tip rack is exhausted, starting tip no longer applies
108
+ starting_tip_name = None
109
+ else:
110
+ next_tip = NoTipAvailable(
111
+ noTipReason=NoTipReason.NO_AVAILABLE_TIPS,
112
+ message="No available tips for given pipette, nozzle configuration and provided tip racks.",
113
+ )
114
+
115
+ return SuccessData(public=GetNextTipResult(nextTipInfo=next_tip))
116
+
117
+
118
+ class GetNextTip(BaseCommand[GetNextTipParams, GetNextTipResult, ErrorOccurrence]):
119
+ """Get next tip command model."""
120
+
121
+ commandType: GetNextTipCommandType = "getNextTip"
122
+ params: GetNextTipParams
123
+ result: Optional[GetNextTipResult] = None
124
+
125
+ _ImplementationCls: Type[GetNextTipImplementation] = GetNextTipImplementation
126
+
127
+
128
+ class GetNextTipCreate(BaseCommandCreate[GetNextTipParams]):
129
+ """Get next tip command creation request model."""
130
+
131
+ commandType: GetNextTipCommandType = "getNextTip"
132
+ params: GetNextTipParams
133
+
134
+ _CommandCls: Type[GetNextTip] = GetNextTip
@@ -71,7 +71,7 @@ class GetTipPresence(
71
71
 
72
72
  commandType: GetTipPresenceCommandType = "getTipPresence"
73
73
  params: GetTipPresenceParams
74
- result: Optional[GetTipPresenceResult]
74
+ result: Optional[GetTipPresenceResult] = None
75
75
 
76
76
  _ImplementationCls: Type[
77
77
  GetTipPresenceImplementation
@@ -69,7 +69,7 @@ class CloseLabwareLatch(
69
69
 
70
70
  commandType: CloseLabwareLatchCommandType = "heaterShaker/closeLabwareLatch"
71
71
  params: CloseLabwareLatchParams
72
- result: Optional[CloseLabwareLatchResult]
72
+ result: Optional[CloseLabwareLatchResult] = None
73
73
 
74
74
  _ImplementationCls: Type[CloseLabwareLatchImpl] = CloseLabwareLatchImpl
75
75
 
@@ -68,7 +68,7 @@ class DeactivateHeater(
68
68
 
69
69
  commandType: DeactivateHeaterCommandType = "heaterShaker/deactivateHeater"
70
70
  params: DeactivateHeaterParams
71
- result: Optional[DeactivateHeaterResult]
71
+ result: Optional[DeactivateHeaterResult] = None
72
72
 
73
73
  _ImplementationCls: Type[DeactivateHeaterImpl] = DeactivateHeaterImpl
74
74
 
@@ -70,7 +70,7 @@ class DeactivateShaker(
70
70
 
71
71
  commandType: DeactivateShakerCommandType = "heaterShaker/deactivateShaker"
72
72
  params: DeactivateShakerParams
73
- result: Optional[DeactivateShakerResult]
73
+ result: Optional[DeactivateShakerResult] = None
74
74
 
75
75
  _ImplementationCls: Type[DeactivateShakerImpl] = DeactivateShakerImpl
76
76