opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) 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 +39 -6
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/logging_config.py +1 -1
  223. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +228 -201
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  228. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  """Dispense-in-place command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
- from typing import TYPE_CHECKING, Optional, Type, Union
4
+ from typing import TYPE_CHECKING, Optional, Type, Union, Any
4
5
  from typing_extensions import Literal
5
6
  from pydantic import Field
6
-
7
- from opentrons_shared_data.errors.exceptions import PipetteOverpressureError
7
+ from pydantic.json_schema import SkipJsonSchema
8
8
 
9
9
  from .pipetting_common import (
10
10
  PipetteIdMixin,
@@ -12,6 +12,7 @@ from .pipetting_common import (
12
12
  FlowRateMixin,
13
13
  BaseLiquidHandlingResult,
14
14
  OverpressureError,
15
+ dispense_in_place,
15
16
  )
16
17
  from .command import (
17
18
  AbstractCommandImpl,
@@ -20,8 +21,7 @@ from .command import (
20
21
  SuccessData,
21
22
  DefinedErrorData,
22
23
  )
23
- from ..errors.error_occurrence import ErrorOccurrence
24
- from ..state.update_types import StateUpdate, CLEAR
24
+ from ..state.update_types import CLEAR
25
25
  from ..types import CurrentWell
26
26
 
27
27
  if TYPE_CHECKING:
@@ -33,12 +33,17 @@ if TYPE_CHECKING:
33
33
  DispenseInPlaceCommandType = Literal["dispenseInPlace"]
34
34
 
35
35
 
36
+ def _remove_default(s: dict[str, Any]) -> None:
37
+ s.pop("default", None)
38
+
39
+
36
40
  class DispenseInPlaceParams(PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin):
37
41
  """Payload required to dispense in place."""
38
42
 
39
- pushOut: Optional[float] = Field(
43
+ pushOut: float | SkipJsonSchema[None] = Field(
40
44
  None,
41
45
  description="push the plunger a small amount farther than necessary for accurate low-volume dispensing",
46
+ json_schema_extra=_remove_default,
42
47
  )
43
48
 
44
49
 
@@ -74,63 +79,78 @@ class DispenseInPlaceImplementation(
74
79
 
75
80
  async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn:
76
81
  """Dispense without moving the pipette."""
77
- state_update = StateUpdate()
78
82
  current_location = self._state_view.pipettes.get_current_location()
79
83
  current_position = await self._gantry_mover.get_position(params.pipetteId)
80
- try:
81
- volume = await self._pipetting.dispense_in_place(
82
- pipette_id=params.pipetteId,
83
- volume=params.volume,
84
- flow_rate=params.flowRate,
85
- push_out=params.pushOut,
86
- )
87
- except PipetteOverpressureError as e:
84
+ result = await dispense_in_place(
85
+ pipette_id=params.pipetteId,
86
+ volume=params.volume,
87
+ flow_rate=params.flowRate,
88
+ push_out=params.pushOut,
89
+ location_if_error={
90
+ "retryLocation": (
91
+ current_position.x,
92
+ current_position.y,
93
+ current_position.z,
94
+ )
95
+ },
96
+ pipetting=self._pipetting,
97
+ model_utils=self._model_utils,
98
+ )
99
+ if isinstance(result, DefinedErrorData):
88
100
  if (
89
101
  isinstance(current_location, CurrentWell)
90
102
  and current_location.pipette_id == params.pipetteId
91
103
  ):
92
- state_update.set_liquid_operated(
93
- labware_id=current_location.labware_id,
94
- well_name=current_location.well_name,
95
- volume_added=CLEAR,
96
- )
97
- return DefinedErrorData(
98
- public=OverpressureError(
99
- id=self._model_utils.generate_id(),
100
- createdAt=self._model_utils.get_timestamp(),
101
- wrappedErrors=[
102
- ErrorOccurrence.from_failed(
103
- id=self._model_utils.generate_id(),
104
- createdAt=self._model_utils.get_timestamp(),
105
- error=e,
106
- )
107
- ],
108
- errorInfo=(
109
- {
110
- "retryLocation": (
111
- current_position.x,
112
- current_position.y,
113
- current_position.z,
114
- )
115
- }
104
+ return DefinedErrorData(
105
+ public=result.public,
106
+ state_update=result.state_update.set_liquid_operated(
107
+ labware_id=current_location.labware_id,
108
+ well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
109
+ current_location.labware_id,
110
+ current_location.well_name,
111
+ params.pipetteId,
112
+ ),
113
+ volume_added=CLEAR,
116
114
  ),
117
- ),
118
- state_update=state_update,
119
- )
115
+ state_update_if_false_positive=result.state_update_if_false_positive,
116
+ )
117
+ else:
118
+ return result
120
119
  else:
121
120
  if (
122
121
  isinstance(current_location, CurrentWell)
123
122
  and current_location.pipette_id == params.pipetteId
124
123
  ):
125
- state_update.set_liquid_operated(
126
- labware_id=current_location.labware_id,
127
- well_name=current_location.well_name,
128
- volume_added=volume,
124
+ volume_added = (
125
+ self._state_view.pipettes.get_liquid_dispensed_by_ejecting_volume(
126
+ pipette_id=params.pipetteId, volume=result.public.volume
127
+ )
128
+ )
129
+ if volume_added is not None:
130
+ volume_added *= self._state_view.geometry.get_nozzles_per_well(
131
+ current_location.labware_id,
132
+ current_location.well_name,
133
+ params.pipetteId,
134
+ )
135
+ return SuccessData(
136
+ public=DispenseInPlaceResult(volume=result.public.volume),
137
+ state_update=result.state_update.set_liquid_operated(
138
+ labware_id=current_location.labware_id,
139
+ well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
140
+ current_location.labware_id,
141
+ current_location.well_name,
142
+ params.pipetteId,
143
+ ),
144
+ volume_added=volume_added
145
+ if volume_added is not None
146
+ else CLEAR,
147
+ ),
148
+ )
149
+ else:
150
+ return SuccessData(
151
+ public=DispenseInPlaceResult(volume=result.public.volume),
152
+ state_update=result.state_update,
129
153
  )
130
- return SuccessData(
131
- public=DispenseInPlaceResult(volume=volume),
132
- state_update=state_update,
133
- )
134
154
 
135
155
 
136
156
  class DispenseInPlace(
@@ -140,7 +160,7 @@ class DispenseInPlace(
140
160
 
141
161
  commandType: DispenseInPlaceCommandType = "dispenseInPlace"
142
162
  params: DispenseInPlaceParams
143
- result: Optional[DispenseInPlaceResult]
163
+ result: Optional[DispenseInPlaceResult] = None
144
164
 
145
165
  _ImplementationCls: Type[
146
166
  DispenseInPlaceImplementation
@@ -1,20 +1,27 @@
1
1
  """Drop tip command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
3
5
 
4
6
  from pydantic import Field
5
- from typing import TYPE_CHECKING, Optional, Type
7
+ from pydantic.json_schema import SkipJsonSchema
8
+
6
9
  from typing_extensions import Literal
7
10
 
8
11
  from opentrons.protocol_engine.errors.exceptions import TipAttachedError
9
12
  from opentrons.protocol_engine.resources.model_utils import ModelUtils
10
13
 
11
- from ..state import update_types
12
- from ..types import DropTipWellLocation, DeckPoint
14
+ from ..state.update_types import StateUpdate
15
+ from ..types import DropTipWellLocation
13
16
  from .pipetting_common import (
14
17
  PipetteIdMixin,
15
- DestinationPositionResult,
16
18
  TipPhysicallyAttachedError,
17
19
  )
20
+ from .movement_common import (
21
+ DestinationPositionResult,
22
+ move_to_well,
23
+ StallOrCollisionError,
24
+ )
18
25
  from .command import (
19
26
  AbstractCommandImpl,
20
27
  BaseCommand,
@@ -32,6 +39,10 @@ if TYPE_CHECKING:
32
39
  DropTipCommandType = Literal["dropTip"]
33
40
 
34
41
 
42
+ def _remove_default(s: dict[str, Any]) -> None:
43
+ s.pop("default", None)
44
+
45
+
35
46
  class DropTipParams(PipetteIdMixin):
36
47
  """Payload required to drop a tip in a specific well."""
37
48
 
@@ -41,15 +52,16 @@ class DropTipParams(PipetteIdMixin):
41
52
  default_factory=DropTipWellLocation,
42
53
  description="Relative well location at which to drop the tip.",
43
54
  )
44
- homeAfter: Optional[bool] = Field(
55
+ homeAfter: bool | SkipJsonSchema[None] = Field(
45
56
  None,
46
57
  description=(
47
58
  "Whether to home this pipette's plunger after dropping the tip."
48
59
  " You should normally leave this unspecified to let the robot choose"
49
60
  " a safe default depending on its hardware."
50
61
  ),
62
+ json_schema_extra=_remove_default,
51
63
  )
52
- alternateDropLocation: Optional[bool] = Field(
64
+ alternateDropLocation: bool | SkipJsonSchema[None] = Field(
53
65
  False,
54
66
  description=(
55
67
  "Whether to alternate location where tip is dropped within the labware."
@@ -58,6 +70,7 @@ class DropTipParams(PipetteIdMixin):
58
70
  " labware well."
59
71
  " If False, the tip will be dropped at the top center of the well."
60
72
  ),
73
+ json_schema_extra=_remove_default,
61
74
  )
62
75
 
63
76
 
@@ -68,7 +81,9 @@ class DropTipResult(DestinationPositionResult):
68
81
 
69
82
 
70
83
  _ExecuteReturn = (
71
- SuccessData[DropTipResult] | DefinedErrorData[TipPhysicallyAttachedError]
84
+ SuccessData[DropTipResult]
85
+ | DefinedErrorData[TipPhysicallyAttachedError]
86
+ | DefinedErrorData[StallOrCollisionError]
72
87
  )
73
88
 
74
89
 
@@ -95,8 +110,6 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
95
110
  well_name = params.wellName
96
111
  home_after = params.homeAfter
97
112
 
98
- state_update = update_types.StateUpdate()
99
-
100
113
  if params.alternateDropLocation:
101
114
  well_location = self._state_view.geometry.get_next_tip_drop_location(
102
115
  labware_id=labware_id,
@@ -116,19 +129,16 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
116
129
  partially_configured=is_partially_configured,
117
130
  )
118
131
 
119
- position = await self._movement_handler.move_to_well(
132
+ move_result = await move_to_well(
133
+ movement=self._movement_handler,
134
+ model_utils=self._model_utils,
120
135
  pipette_id=pipette_id,
121
136
  labware_id=labware_id,
122
137
  well_name=well_name,
123
138
  well_location=tip_drop_location,
124
139
  )
125
- deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
126
- state_update.set_pipette_location(
127
- pipette_id=pipette_id,
128
- new_labware_id=labware_id,
129
- new_well_name=well_name,
130
- new_deck_point=deck_point,
131
- )
140
+ if isinstance(move_result, DefinedErrorData):
141
+ return move_result
132
142
 
133
143
  try:
134
144
  await self._tip_handler.drop_tip(
@@ -145,33 +155,44 @@ class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]):
145
155
  error=exception,
146
156
  )
147
157
  ],
148
- errorInfo={"retryLocation": position},
149
- )
150
- state_update_if_false_positive = update_types.StateUpdate()
151
- state_update_if_false_positive.update_pipette_tip_state(
152
- pipette_id=params.pipetteId, tip_geometry=None
158
+ errorInfo={
159
+ "retryLocation": (
160
+ move_result.public.position.x,
161
+ move_result.public.position.y,
162
+ move_result.public.position.z,
163
+ )
164
+ },
153
165
  )
154
166
  return DefinedErrorData(
155
167
  public=error,
156
- state_update=state_update,
157
- state_update_if_false_positive=state_update_if_false_positive,
168
+ state_update=StateUpdate.reduce(
169
+ StateUpdate(), move_result.state_update
170
+ ).set_fluid_unknown(pipette_id=pipette_id),
171
+ state_update_if_false_positive=move_result.state_update.update_pipette_tip_state(
172
+ pipette_id=params.pipetteId, tip_geometry=None
173
+ ),
158
174
  )
159
175
  else:
160
- state_update.update_pipette_tip_state(
161
- pipette_id=params.pipetteId, tip_geometry=None
162
- )
163
176
  return SuccessData(
164
- public=DropTipResult(position=deck_point),
165
- state_update=state_update,
177
+ public=DropTipResult(position=move_result.public.position),
178
+ state_update=move_result.state_update.set_fluid_unknown(
179
+ pipette_id=pipette_id
180
+ ).update_pipette_tip_state(
181
+ pipette_id=params.pipetteId, tip_geometry=None
182
+ ),
166
183
  )
167
184
 
168
185
 
169
- class DropTip(BaseCommand[DropTipParams, DropTipResult, TipPhysicallyAttachedError]):
186
+ class DropTip(
187
+ BaseCommand[
188
+ DropTipParams, DropTipResult, TipPhysicallyAttachedError | StallOrCollisionError
189
+ ]
190
+ ):
170
191
  """Drop tip command model."""
171
192
 
172
193
  commandType: DropTipCommandType = "dropTip"
173
194
  params: DropTipParams
174
- result: Optional[DropTipResult]
195
+ result: Optional[DropTipResult] = None
175
196
 
176
197
  _ImplementationCls: Type[DropTipImplementation] = DropTipImplementation
177
198
 
@@ -1,7 +1,10 @@
1
1
  """Drop tip in place command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
5
+
3
6
  from pydantic import Field, BaseModel
4
- from typing import TYPE_CHECKING, Optional, Type
7
+ from pydantic.json_schema import SkipJsonSchema
5
8
  from typing_extensions import Literal
6
9
 
7
10
  from .command import (
@@ -24,16 +27,21 @@ if TYPE_CHECKING:
24
27
  DropTipInPlaceCommandType = Literal["dropTipInPlace"]
25
28
 
26
29
 
30
+ def _remove_default(s: dict[str, Any]) -> None:
31
+ s.pop("default", None)
32
+
33
+
27
34
  class DropTipInPlaceParams(PipetteIdMixin):
28
35
  """Payload required to drop a tip in place."""
29
36
 
30
- homeAfter: Optional[bool] = Field(
37
+ homeAfter: bool | SkipJsonSchema[None] = Field(
31
38
  None,
32
39
  description=(
33
40
  "Whether to home this pipette's plunger after dropping the tip."
34
41
  " You should normally leave this unspecified to let the robot choose"
35
42
  " a safe default depending on its hardware."
36
43
  ),
44
+ json_schema_extra=_remove_default,
37
45
  )
38
46
 
39
47
 
@@ -79,6 +87,7 @@ class DropTipInPlaceImplementation(
79
87
  state_update_if_false_positive.update_pipette_tip_state(
80
88
  pipette_id=params.pipetteId, tip_geometry=None
81
89
  )
90
+ state_update.set_fluid_unknown(pipette_id=params.pipetteId)
82
91
  error = TipPhysicallyAttachedError(
83
92
  id=self._model_utils.generate_id(),
84
93
  createdAt=self._model_utils.get_timestamp(),
@@ -97,6 +106,7 @@ class DropTipInPlaceImplementation(
97
106
  state_update_if_false_positive=state_update_if_false_positive,
98
107
  )
99
108
  else:
109
+ state_update.set_fluid_unknown(pipette_id=params.pipetteId)
100
110
  state_update.update_pipette_tip_state(
101
111
  pipette_id=params.pipetteId, tip_geometry=None
102
112
  )
@@ -110,7 +120,7 @@ class DropTipInPlace(
110
120
 
111
121
  commandType: DropTipInPlaceCommandType = "dropTipInPlace"
112
122
  params: DropTipInPlaceParams
113
- result: Optional[DropTipInPlaceResult]
123
+ result: Optional[DropTipInPlaceResult] = None
114
124
 
115
125
  _ImplementationCls: Type[
116
126
  DropTipInPlaceImplementation
@@ -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]
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
 
@@ -96,7 +96,7 @@ class OpenLabwareLatch(
96
96
 
97
97
  commandType: OpenLabwareLatchCommandType = "heaterShaker/openLabwareLatch"
98
98
  params: OpenLabwareLatchParams
99
- result: Optional[OpenLabwareLatchResult]
99
+ result: Optional[OpenLabwareLatchResult] = None
100
100
 
101
101
  _ImplementationCls: Type[OpenLabwareLatchImpl] = OpenLabwareLatchImpl
102
102
 
@@ -109,7 +109,7 @@ class SetAndWaitForShakeSpeed(
109
109
  "heaterShaker/setAndWaitForShakeSpeed"
110
110
  )
111
111
  params: SetAndWaitForShakeSpeedParams
112
- result: Optional[SetAndWaitForShakeSpeedResult]
112
+ result: Optional[SetAndWaitForShakeSpeedResult] = None
113
113
 
114
114
  _ImplementationCls: Type[SetAndWaitForShakeSpeedImpl] = SetAndWaitForShakeSpeedImpl
115
115
 
@@ -76,7 +76,7 @@ class SetTargetTemperature(
76
76
 
77
77
  commandType: SetTargetTemperatureCommandType = "heaterShaker/setTargetTemperature"
78
78
  params: SetTargetTemperatureParams
79
- result: Optional[SetTargetTemperatureResult]
79
+ result: Optional[SetTargetTemperatureResult] = None
80
80
 
81
81
  _ImplementationCls: Type[SetTargetTemperatureImpl] = SetTargetTemperatureImpl
82
82