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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +36 -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 +1 -1
  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 +52 -70
  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.0a4.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  """Configure for volume command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
+
3
5
  from pydantic import BaseModel, Field
4
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from .pipetting_common import PipetteIdMixin
@@ -16,6 +18,10 @@ if TYPE_CHECKING:
16
18
  ConfigureForVolumeCommandType = Literal["configureForVolume"]
17
19
 
18
20
 
21
+ def _remove_default(s: dict[str, Any]) -> None:
22
+ s.pop("default", None)
23
+
24
+
19
25
  class ConfigureForVolumeParams(PipetteIdMixin):
20
26
  """Parameters required to configure volume for a specific pipette."""
21
27
 
@@ -25,12 +31,13 @@ class ConfigureForVolumeParams(PipetteIdMixin):
25
31
  "than a pipette-specific maximum volume.",
26
32
  ge=0,
27
33
  )
28
- tipOverlapNotAfterVersion: Optional[str] = Field(
34
+ tipOverlapNotAfterVersion: str | SkipJsonSchema[None] = Field(
29
35
  None,
30
36
  description="A version of tip overlap data to not exceed. The highest-versioned "
31
37
  "tip overlap data that does not exceed this version will be used. Versions are "
32
38
  "expressed as vN where N is an integer, counting up from v0. If None, the current "
33
39
  "highest version will be used.",
40
+ json_schema_extra=_remove_default,
34
41
  )
35
42
 
36
43
 
@@ -81,7 +88,7 @@ class ConfigureForVolume(
81
88
 
82
89
  commandType: ConfigureForVolumeCommandType = "configureForVolume"
83
90
  params: ConfigureForVolumeParams
84
- result: Optional[ConfigureForVolumeResult]
91
+ result: Optional[ConfigureForVolumeResult] = None
85
92
 
86
93
  _ImplementationCls: Type[
87
94
  ConfigureForVolumeImplementation
@@ -61,9 +61,11 @@ class ConfigureNozzleLayoutImplementation(
61
61
  self, params: ConfigureNozzleLayoutParams
62
62
  ) -> SuccessData[ConfigureNozzleLayoutResult]:
63
63
  """Check that requested pipette can support the requested nozzle layout."""
64
- primary_nozzle = params.configurationParams.dict().get("primaryNozzle")
65
- front_right_nozzle = params.configurationParams.dict().get("frontRightNozzle")
66
- back_left_nozzle = params.configurationParams.dict().get("backLeftNozzle")
64
+ primary_nozzle = params.configurationParams.model_dump().get("primaryNozzle")
65
+ front_right_nozzle = params.configurationParams.model_dump().get(
66
+ "frontRightNozzle"
67
+ )
68
+ back_left_nozzle = params.configurationParams.model_dump().get("backLeftNozzle")
67
69
  nozzle_params = await self._tip_handler.available_for_nozzle_layout(
68
70
  pipette_id=params.pipetteId,
69
71
  style=params.configurationParams.style,
@@ -97,7 +99,7 @@ class ConfigureNozzleLayout(
97
99
 
98
100
  commandType: ConfigureNozzleLayoutCommandType = "configureNozzleLayout"
99
101
  params: ConfigureNozzleLayoutParams
100
- result: Optional[ConfigureNozzleLayoutResult]
102
+ result: Optional[ConfigureNozzleLayoutResult] = None
101
103
 
102
104
  _ImplementationCls: Type[
103
105
  ConfigureNozzleLayoutImplementation
@@ -10,7 +10,7 @@ data still adheres to the shapes that ProtocolEngine expects.
10
10
  If you are implementing a custom command, you should probably
11
11
  put your own disambiguation identifier in the payload.
12
12
  """
13
- from pydantic import BaseModel, Extra
13
+ from pydantic import ConfigDict, BaseModel, SerializeAsAny
14
14
  from typing import Optional, Type
15
15
  from typing_extensions import Literal
16
16
 
@@ -24,19 +24,13 @@ CustomCommandType = Literal["custom"]
24
24
  class CustomParams(BaseModel):
25
25
  """Payload used by a custom command."""
26
26
 
27
- class Config:
28
- """Allow arbitrary fields."""
29
-
30
- extra = Extra.allow
27
+ model_config = ConfigDict(extra="allow")
31
28
 
32
29
 
33
30
  class CustomResult(BaseModel):
34
31
  """Result data from a custom command."""
35
32
 
36
- class Config:
37
- """Allow arbitrary fields."""
38
-
39
- extra = Extra.allow
33
+ model_config = ConfigDict(extra="allow")
40
34
 
41
35
 
42
36
  class CustomImplementation(
@@ -50,7 +44,7 @@ class CustomImplementation(
50
44
  async def execute(self, params: CustomParams) -> SuccessData[CustomResult]:
51
45
  """A custom command does nothing when executed directly."""
52
46
  return SuccessData(
53
- public=CustomResult.construct(),
47
+ public=CustomResult.model_construct(),
54
48
  )
55
49
 
56
50
 
@@ -58,8 +52,8 @@ class Custom(BaseCommand[CustomParams, CustomResult, ErrorOccurrence]):
58
52
  """Custom command model."""
59
53
 
60
54
  commandType: CustomCommandType = "custom"
61
- params: CustomParams
62
- result: Optional[CustomResult]
55
+ params: SerializeAsAny[CustomParams]
56
+ result: Optional[CustomResult] = None
63
57
 
64
58
  _ImplementationCls: Type[CustomImplementation] = CustomImplementation
65
59
 
@@ -1,22 +1,27 @@
1
1
  """Dispense 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
 
6
- from opentrons_shared_data.errors.exceptions import PipetteOverpressureError
7
7
 
8
8
  from pydantic import Field
9
+ from pydantic.json_schema import SkipJsonSchema
9
10
 
10
- from ..types import DeckPoint
11
11
  from ..state.update_types import StateUpdate, CLEAR
12
12
  from .pipetting_common import (
13
13
  PipetteIdMixin,
14
14
  DispenseVolumeMixin,
15
15
  FlowRateMixin,
16
- LiquidHandlingWellLocationMixin,
17
16
  BaseLiquidHandlingResult,
18
- DestinationPositionResult,
19
17
  OverpressureError,
18
+ dispense_in_place,
19
+ )
20
+ from .movement_common import (
21
+ LiquidHandlingWellLocationMixin,
22
+ DestinationPositionResult,
23
+ StallOrCollisionError,
24
+ move_to_well,
20
25
  )
21
26
  from .command import (
22
27
  AbstractCommandImpl,
@@ -25,7 +30,6 @@ from .command import (
25
30
  DefinedErrorData,
26
31
  SuccessData,
27
32
  )
28
- from ..errors.error_occurrence import ErrorOccurrence
29
33
 
30
34
  if TYPE_CHECKING:
31
35
  from ..execution import MovementHandler, PipettingHandler
@@ -36,14 +40,19 @@ if TYPE_CHECKING:
36
40
  DispenseCommandType = Literal["dispense"]
37
41
 
38
42
 
43
+ def _remove_default(s: dict[str, Any]) -> None:
44
+ s.pop("default", None)
45
+
46
+
39
47
  class DispenseParams(
40
48
  PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
41
49
  ):
42
50
  """Payload required to dispense to a specific well."""
43
51
 
44
- pushOut: Optional[float] = Field(
52
+ pushOut: float | SkipJsonSchema[None] = Field(
45
53
  None,
46
54
  description="push the plunger a small amount farther than necessary for accurate low-volume dispensing",
55
+ json_schema_extra=_remove_default,
47
56
  )
48
57
 
49
58
 
@@ -55,7 +64,7 @@ class DispenseResult(BaseLiquidHandlingResult, DestinationPositionResult):
55
64
 
56
65
  _ExecuteReturn = Union[
57
66
  SuccessData[DispenseResult],
58
- DefinedErrorData[OverpressureError],
67
+ DefinedErrorData[OverpressureError] | DefinedErrorData[StallOrCollisionError],
59
68
  ]
60
69
 
61
70
 
@@ -77,7 +86,6 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn]
77
86
 
78
87
  async def execute(self, params: DispenseParams) -> _ExecuteReturn:
79
88
  """Move to and dispense to the requested well."""
80
- state_update = StateUpdate()
81
89
  well_location = params.wellLocation
82
90
  labware_id = params.labwareId
83
91
  well_name = params.wellName
@@ -85,66 +93,92 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn]
85
93
 
86
94
  # TODO(pbm, 10-15-24): call self._state_view.geometry.validate_dispense_volume_into_well()
87
95
 
88
- position = await self._movement.move_to_well(
96
+ move_result = await move_to_well(
97
+ movement=self._movement,
98
+ model_utils=self._model_utils,
89
99
  pipette_id=params.pipetteId,
90
100
  labware_id=labware_id,
91
101
  well_name=well_name,
92
102
  well_location=well_location,
93
103
  )
94
- deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
95
- state_update.set_pipette_location(
104
+ if isinstance(move_result, DefinedErrorData):
105
+ return move_result
106
+ dispense_result = await dispense_in_place(
96
107
  pipette_id=params.pipetteId,
97
- new_labware_id=labware_id,
98
- new_well_name=well_name,
99
- new_deck_point=deck_point,
108
+ volume=volume,
109
+ flow_rate=params.flowRate,
110
+ push_out=params.pushOut,
111
+ location_if_error={
112
+ "retryLocation": (
113
+ move_result.public.position.x,
114
+ move_result.public.position.y,
115
+ move_result.public.position.z,
116
+ )
117
+ },
118
+ pipetting=self._pipetting,
119
+ model_utils=self._model_utils,
100
120
  )
101
121
 
102
- try:
103
- volume = await self._pipetting.dispense_in_place(
104
- pipette_id=params.pipetteId,
105
- volume=volume,
106
- flow_rate=params.flowRate,
107
- push_out=params.pushOut,
108
- )
109
- except PipetteOverpressureError as e:
110
- state_update.set_liquid_operated(
111
- labware_id=labware_id,
112
- well_name=well_name,
113
- volume_added=CLEAR,
114
- )
122
+ if isinstance(dispense_result, DefinedErrorData):
115
123
  return DefinedErrorData(
116
- public=OverpressureError(
117
- id=self._model_utils.generate_id(),
118
- createdAt=self._model_utils.get_timestamp(),
119
- wrappedErrors=[
120
- ErrorOccurrence.from_failed(
121
- id=self._model_utils.generate_id(),
122
- createdAt=self._model_utils.get_timestamp(),
123
- error=e,
124
- )
125
- ],
126
- errorInfo={"retryLocation": (position.x, position.y, position.z)},
124
+ public=dispense_result.public,
125
+ state_update=(
126
+ StateUpdate.reduce(
127
+ move_result.state_update, dispense_result.state_update
128
+ ).set_liquid_operated(
129
+ labware_id=labware_id,
130
+ well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
131
+ labware_id, well_name, params.pipetteId
132
+ ),
133
+ volume_added=CLEAR,
134
+ )
135
+ ),
136
+ state_update_if_false_positive=StateUpdate.reduce(
137
+ move_result.state_update,
138
+ dispense_result.state_update_if_false_positive,
127
139
  ),
128
- state_update=state_update,
129
140
  )
130
141
  else:
131
- state_update.set_liquid_operated(
132
- labware_id=labware_id,
133
- well_name=well_name,
134
- volume_added=volume,
142
+ volume_added = (
143
+ self._state_view.pipettes.get_liquid_dispensed_by_ejecting_volume(
144
+ pipette_id=params.pipetteId, volume=dispense_result.public.volume
145
+ )
135
146
  )
147
+ if volume_added is not None:
148
+ volume_added *= self._state_view.geometry.get_nozzles_per_well(
149
+ labware_id, well_name, params.pipetteId
150
+ )
136
151
  return SuccessData(
137
- public=DispenseResult(volume=volume, position=deck_point),
138
- state_update=state_update,
152
+ public=DispenseResult(
153
+ volume=dispense_result.public.volume,
154
+ position=move_result.public.position,
155
+ ),
156
+ state_update=(
157
+ StateUpdate.reduce(
158
+ move_result.state_update, dispense_result.state_update
159
+ ).set_liquid_operated(
160
+ labware_id=labware_id,
161
+ well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
162
+ labware_id, well_name, params.pipetteId
163
+ ),
164
+ volume_added=volume_added
165
+ if volume_added is not None
166
+ else CLEAR,
167
+ )
168
+ ),
139
169
  )
140
170
 
141
171
 
142
- class Dispense(BaseCommand[DispenseParams, DispenseResult, OverpressureError]):
172
+ class Dispense(
173
+ BaseCommand[
174
+ DispenseParams, DispenseResult, OverpressureError | StallOrCollisionError
175
+ ]
176
+ ):
143
177
  """Dispense command model."""
144
178
 
145
179
  commandType: DispenseCommandType = "dispense"
146
180
  params: DispenseParams
147
- result: Optional[DispenseResult]
181
+ result: Optional[DispenseResult] = None
148
182
 
149
183
  _ImplementationCls: Type[DispenseImplementation] = DispenseImplementation
150
184
 
@@ -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