opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__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 (230) hide show
  1. opentrons/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  20. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  21. opentrons/hardware_control/module_control.py +43 -2
  22. opentrons/hardware_control/modules/__init__.py +7 -1
  23. opentrons/hardware_control/modules/absorbance_reader.py +230 -83
  24. opentrons/hardware_control/modules/errors.py +7 -0
  25. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  26. opentrons/hardware_control/modules/magdeck.py +12 -3
  27. opentrons/hardware_control/modules/mod_abc.py +27 -2
  28. opentrons/hardware_control/modules/tempdeck.py +15 -7
  29. opentrons/hardware_control/modules/thermocycler.py +69 -3
  30. opentrons/hardware_control/modules/types.py +11 -5
  31. opentrons/hardware_control/modules/update.py +11 -5
  32. opentrons/hardware_control/modules/utils.py +3 -1
  33. opentrons/hardware_control/ot3_calibration.py +6 -6
  34. opentrons/hardware_control/ot3api.py +126 -89
  35. opentrons/hardware_control/poller.py +15 -11
  36. opentrons/hardware_control/protocols/__init__.py +1 -7
  37. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  38. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  39. opentrons/motion_planning/__init__.py +2 -0
  40. opentrons/motion_planning/waypoints.py +32 -0
  41. opentrons/protocol_api/__init__.py +2 -1
  42. opentrons/protocol_api/_liquid.py +87 -1
  43. opentrons/protocol_api/_parameter_context.py +10 -1
  44. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  45. opentrons/protocol_api/core/engine/instrument.py +29 -25
  46. opentrons/protocol_api/core/engine/labware.py +10 -2
  47. opentrons/protocol_api/core/engine/module_core.py +129 -17
  48. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
  49. opentrons/protocol_api/core/engine/protocol.py +55 -2
  50. opentrons/protocol_api/core/instrument.py +2 -0
  51. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  52. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
  53. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  54. opentrons/protocol_api/core/module.py +22 -4
  55. opentrons/protocol_api/core/protocol.py +5 -2
  56. opentrons/protocol_api/instrument_context.py +52 -20
  57. opentrons/protocol_api/labware.py +13 -1
  58. opentrons/protocol_api/module_contexts.py +68 -13
  59. opentrons/protocol_api/protocol_context.py +38 -4
  60. opentrons/protocol_api/validation.py +5 -3
  61. opentrons/protocol_engine/__init__.py +10 -9
  62. opentrons/protocol_engine/actions/__init__.py +5 -0
  63. opentrons/protocol_engine/actions/actions.py +42 -25
  64. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  65. opentrons/protocol_engine/clients/sync_client.py +7 -1
  66. opentrons/protocol_engine/clients/transports.py +1 -1
  67. opentrons/protocol_engine/commands/__init__.py +0 -4
  68. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  69. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
  70. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
  71. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
  72. opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
  73. opentrons/protocol_engine/commands/aspirate.py +29 -16
  74. opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
  75. opentrons/protocol_engine/commands/blow_out.py +63 -14
  76. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  77. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  78. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  79. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  80. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  81. opentrons/protocol_engine/commands/command.py +28 -17
  82. opentrons/protocol_engine/commands/command_unions.py +37 -24
  83. opentrons/protocol_engine/commands/comment.py +5 -3
  84. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  85. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  86. opentrons/protocol_engine/commands/custom.py +5 -3
  87. opentrons/protocol_engine/commands/dispense.py +42 -20
  88. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  89. opentrons/protocol_engine/commands/drop_tip.py +68 -15
  90. opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
  91. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  92. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  93. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  94. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  95. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  96. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  97. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  98. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  99. opentrons/protocol_engine/commands/home.py +11 -5
  100. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  101. opentrons/protocol_engine/commands/load_labware.py +19 -5
  102. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  103. opentrons/protocol_engine/commands/load_module.py +43 -6
  104. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  105. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  106. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  107. opentrons/protocol_engine/commands/move_labware.py +106 -19
  108. opentrons/protocol_engine/commands/move_relative.py +15 -3
  109. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  110. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  111. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  112. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  113. opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
  114. opentrons/protocol_engine/commands/pipetting_common.py +39 -15
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  116. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  117. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  118. opentrons/protocol_engine/commands/save_position.py +2 -3
  119. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  120. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  121. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  122. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  123. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  124. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  125. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  126. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  127. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  128. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  129. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  130. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  131. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  132. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  133. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  135. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  136. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  137. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  138. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  139. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  140. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
  141. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
  142. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
  143. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  144. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  145. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  146. opentrons/protocol_engine/create_protocol_engine.py +41 -8
  147. opentrons/protocol_engine/engine_support.py +2 -1
  148. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  149. opentrons/protocol_engine/errors/__init__.py +18 -0
  150. opentrons/protocol_engine/errors/exceptions.py +114 -2
  151. opentrons/protocol_engine/execution/__init__.py +2 -0
  152. opentrons/protocol_engine/execution/command_executor.py +22 -13
  153. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  154. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  155. opentrons/protocol_engine/execution/equipment.py +2 -1
  156. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  157. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  158. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  159. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  160. opentrons/protocol_engine/execution/labware_movement.py +6 -3
  161. opentrons/protocol_engine/execution/movement.py +8 -3
  162. opentrons/protocol_engine/execution/pipetting.py +7 -4
  163. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  164. opentrons/protocol_engine/execution/run_control.py +1 -1
  165. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  166. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  167. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  168. opentrons/protocol_engine/notes/__init__.py +14 -2
  169. opentrons/protocol_engine/notes/notes.py +18 -1
  170. opentrons/protocol_engine/plugins.py +1 -1
  171. opentrons/protocol_engine/protocol_engine.py +54 -31
  172. opentrons/protocol_engine/resources/__init__.py +2 -0
  173. opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
  174. opentrons/protocol_engine/resources/file_provider.py +157 -0
  175. opentrons/protocol_engine/resources/fixture_validation.py +5 -0
  176. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  177. opentrons/protocol_engine/state/__init__.py +0 -70
  178. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  179. opentrons/protocol_engine/state/command_history.py +21 -2
  180. opentrons/protocol_engine/state/commands.py +110 -31
  181. opentrons/protocol_engine/state/files.py +59 -0
  182. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  183. opentrons/protocol_engine/state/geometry.py +359 -15
  184. opentrons/protocol_engine/state/labware.py +166 -63
  185. opentrons/protocol_engine/state/liquids.py +1 -1
  186. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
  187. opentrons/protocol_engine/state/modules.py +167 -85
  188. opentrons/protocol_engine/state/motion.py +16 -9
  189. opentrons/protocol_engine/state/pipettes.py +157 -317
  190. opentrons/protocol_engine/state/state.py +30 -1
  191. opentrons/protocol_engine/state/state_summary.py +3 -0
  192. opentrons/protocol_engine/state/tips.py +69 -114
  193. opentrons/protocol_engine/state/update_types.py +408 -0
  194. opentrons/protocol_engine/state/wells.py +236 -0
  195. opentrons/protocol_engine/types.py +90 -0
  196. opentrons/protocol_reader/file_format_validator.py +83 -15
  197. opentrons/protocol_runner/json_translator.py +21 -5
  198. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  199. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  200. opentrons/protocol_runner/protocol_runner.py +6 -3
  201. opentrons/protocol_runner/run_orchestrator.py +26 -6
  202. opentrons/protocols/advanced_control/mix.py +3 -5
  203. opentrons/protocols/advanced_control/transfers.py +125 -56
  204. opentrons/protocols/api_support/constants.py +1 -1
  205. opentrons/protocols/api_support/definitions.py +1 -1
  206. opentrons/protocols/api_support/labware_like.py +4 -4
  207. opentrons/protocols/api_support/tip_tracker.py +2 -2
  208. opentrons/protocols/api_support/types.py +15 -2
  209. opentrons/protocols/api_support/util.py +30 -42
  210. opentrons/protocols/duration/errors.py +1 -1
  211. opentrons/protocols/duration/estimator.py +50 -29
  212. opentrons/protocols/execution/dev_types.py +2 -2
  213. opentrons/protocols/execution/execute_json_v4.py +15 -10
  214. opentrons/protocols/execution/execute_python.py +8 -3
  215. opentrons/protocols/geometry/planning.py +12 -12
  216. opentrons/protocols/labware.py +17 -33
  217. opentrons/simulate.py +3 -3
  218. opentrons/types.py +30 -3
  219. opentrons/util/logging_config.py +34 -0
  220. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
  221. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
  222. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  223. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  224. opentrons/protocol_runner/thread_async_queue.py +0 -174
  225. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  226. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  227. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
  230. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,15 @@
1
1
  """Basic pipette data state and store."""
2
2
  from __future__ import annotations
3
- from dataclasses import dataclass
4
- from typing import Dict, List, Mapping, Optional, Tuple, Union
5
- from typing_extensions import assert_type
3
+
4
+ import dataclasses
5
+ from typing import (
6
+ Dict,
7
+ List,
8
+ Mapping,
9
+ Optional,
10
+ Tuple,
11
+ Union,
12
+ )
6
13
 
7
14
  from opentrons_shared_data.pipette import pipette_definition
8
15
  from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE
@@ -12,15 +19,9 @@ from opentrons.hardware_control.nozzle_manager import (
12
19
  NozzleConfigurationType,
13
20
  NozzleMap,
14
21
  )
15
- from opentrons.protocol_engine.actions.actions import FailCommandAction
16
- from opentrons.protocol_engine.commands.command import DefinedErrorData
17
- from opentrons.protocol_engine.commands.pipetting_common import (
18
- LiquidNotFoundError,
19
- OverpressureError,
20
- OverpressureErrorInternalData,
21
- )
22
22
  from opentrons.types import MountType, Mount as HwMount, Point
23
23
 
24
+ from . import update_types
24
25
  from .. import commands
25
26
  from .. import errors
26
27
  from ..types import (
@@ -33,19 +34,17 @@ from ..types import (
33
34
  CurrentPipetteLocation,
34
35
  TipGeometry,
35
36
  )
36
- from ..commands.configuring_common import (
37
- PipetteConfigUpdateResultMixin,
38
- PipetteNozzleLayoutResultMixin,
39
- )
40
37
  from ..actions import (
41
38
  Action,
39
+ FailCommandAction,
42
40
  SetPipetteMovementSpeedAction,
43
41
  SucceedCommandAction,
42
+ get_state_updates,
44
43
  )
45
- from .abstract_store import HasState, HandlesActions
44
+ from ._abstract_store import HasState, HandlesActions
46
45
 
47
46
 
48
- @dataclass(frozen=True)
47
+ @dataclasses.dataclass(frozen=True)
49
48
  class HardwarePipette:
50
49
  """Hardware pipette data."""
51
50
 
@@ -53,7 +52,7 @@ class HardwarePipette:
53
52
  config: PipetteDict
54
53
 
55
54
 
56
- @dataclass(frozen=True)
55
+ @dataclasses.dataclass(frozen=True)
57
56
  class CurrentDeckPoint:
58
57
  """The latest deck point and mount the robot has accessed."""
59
58
 
@@ -61,7 +60,7 @@ class CurrentDeckPoint:
61
60
  deck_point: Optional[DeckPoint]
62
61
 
63
62
 
64
- @dataclass(frozen=True)
63
+ @dataclasses.dataclass(frozen=True)
65
64
  class BoundingNozzlesOffsets:
66
65
  """Offsets of the bounding nozzles of the pipette."""
67
66
 
@@ -69,7 +68,7 @@ class BoundingNozzlesOffsets:
69
68
  front_right_offset: Point
70
69
 
71
70
 
72
- @dataclass(frozen=True)
71
+ @dataclasses.dataclass(frozen=True)
73
72
  class PipetteBoundingBoxOffsets:
74
73
  """Offsets of the corners of the pipette's bounding box."""
75
74
 
@@ -79,7 +78,7 @@ class PipetteBoundingBoxOffsets:
79
78
  front_left_corner: Point
80
79
 
81
80
 
82
- @dataclass(frozen=True)
81
+ @dataclasses.dataclass(frozen=True)
83
82
  class StaticPipetteConfig:
84
83
  """Static config for a pipette."""
85
84
 
@@ -97,14 +96,17 @@ class StaticPipetteConfig:
97
96
  nozzle_offset_z: float
98
97
  pipette_bounding_box_offsets: PipetteBoundingBoxOffsets
99
98
  bounding_nozzle_offsets: BoundingNozzlesOffsets
100
- default_nozzle_map: NozzleMap
99
+ default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove?
101
100
  lld_settings: Optional[Dict[str, Dict[str, float]]]
102
101
 
103
102
 
104
- @dataclass
103
+ @dataclasses.dataclass
105
104
  class PipetteState:
106
105
  """Basic pipette data state and getter methods."""
107
106
 
107
+ # todo(mm, 2024-10-14): It's getting difficult to ensure that all of these
108
+ # attributes are populated at the appropriate times. Refactor to a
109
+ # single dict-of-many-things instead of many dicts-of-single-things.
108
110
  pipettes_by_id: Dict[str, LoadedPipette]
109
111
  aspirated_volume_by_id: Dict[str, Optional[float]]
110
112
  current_location: Optional[CurrentPipetteLocation]
@@ -113,7 +115,7 @@ class PipetteState:
113
115
  movement_speed_by_id: Dict[str, Optional[float]]
114
116
  static_config_by_id: Dict[str, StaticPipetteConfig]
115
117
  flow_rates_by_id: Dict[str, FlowRates]
116
- nozzle_configuration_by_id: Dict[str, Optional[NozzleMap]]
118
+ nozzle_configuration_by_id: Dict[str, NozzleMap]
117
119
  liquid_presence_detection_by_id: Dict[str, bool]
118
120
 
119
121
 
@@ -139,29 +141,128 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
139
141
 
140
142
  def handle_action(self, action: Action) -> None:
141
143
  """Modify state in reaction to an action."""
144
+ for state_update in get_state_updates(action):
145
+ self._set_load_pipette(state_update)
146
+ self._update_current_location(state_update)
147
+ self._update_pipette_config(state_update)
148
+ self._update_pipette_nozzle_map(state_update)
149
+ self._update_tip_state(state_update)
150
+
142
151
  if isinstance(action, (SucceedCommandAction, FailCommandAction)):
143
- self._handle_command(action)
152
+ self._update_volumes(action)
153
+
144
154
  elif isinstance(action, SetPipetteMovementSpeedAction):
145
155
  self._state.movement_speed_by_id[action.pipette_id] = action.speed
146
156
 
147
- def _handle_command( # noqa: C901
148
- self, action: Union[SucceedCommandAction, FailCommandAction]
149
- ) -> None:
150
- self._update_current_location(action)
151
- self._update_deck_point(action)
152
- self._update_volumes(action)
157
+ def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
158
+ if state_update.loaded_pipette != update_types.NO_CHANGE:
159
+ pipette_id = state_update.loaded_pipette.pipette_id
153
160
 
154
- if not isinstance(action, SucceedCommandAction):
155
- return
161
+ self._state.pipettes_by_id[pipette_id] = LoadedPipette(
162
+ id=pipette_id,
163
+ pipetteName=state_update.loaded_pipette.pipette_name,
164
+ mount=state_update.loaded_pipette.mount,
165
+ )
166
+ self._state.liquid_presence_detection_by_id[pipette_id] = (
167
+ state_update.loaded_pipette.liquid_presence_detection or False
168
+ )
169
+ self._state.aspirated_volume_by_id[pipette_id] = None
170
+ self._state.movement_speed_by_id[pipette_id] = None
171
+ self._state.attached_tip_by_id[pipette_id] = None
156
172
 
157
- command, private_result = action.command, action.private_result
173
+ def _update_tip_state(self, state_update: update_types.StateUpdate) -> None:
174
+ if state_update.pipette_tip_state != update_types.NO_CHANGE:
175
+ pipette_id = state_update.pipette_tip_state.pipette_id
176
+ if state_update.pipette_tip_state.tip_geometry:
177
+ attached_tip = state_update.pipette_tip_state.tip_geometry
178
+
179
+ self._state.attached_tip_by_id[pipette_id] = attached_tip
180
+ self._state.aspirated_volume_by_id[pipette_id] = 0
181
+
182
+ static_config = self._state.static_config_by_id.get(pipette_id)
183
+ if static_config:
184
+ try:
185
+ tip_configuration = (
186
+ static_config.tip_configuration_lookup_table[
187
+ attached_tip.volume
188
+ ]
189
+ )
190
+ except KeyError:
191
+ # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume.
192
+ # we used to look up a default tip config via the pipette max volume, but if that isn't
193
+ # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using
194
+ # the first entry in the table is ok I guess but we really need to generally rethink how
195
+ # we identify tip classes - looking things up by volume is not enough.
196
+ tip_configuration = list(
197
+ static_config.tip_configuration_lookup_table.values()
198
+ )[0]
199
+ self._state.flow_rates_by_id[pipette_id] = FlowRates(
200
+ default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
201
+ default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
202
+ default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
203
+ )
158
204
 
159
- if isinstance(private_result, PipetteConfigUpdateResultMixin):
160
- config = private_result.config
205
+ else:
206
+ pipette_id = state_update.pipette_tip_state.pipette_id
207
+ self._state.aspirated_volume_by_id[pipette_id] = None
208
+ self._state.attached_tip_by_id[pipette_id] = None
209
+
210
+ static_config = self._state.static_config_by_id.get(pipette_id)
211
+ if static_config:
212
+ # TODO(seth,9/11/2023): bad way to do defaulting, see above.
213
+ tip_configuration = list(
214
+ static_config.tip_configuration_lookup_table.values()
215
+ )[0]
216
+ self._state.flow_rates_by_id[pipette_id] = FlowRates(
217
+ default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
218
+ default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
219
+ default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
220
+ )
221
+
222
+ def _update_current_location(self, state_update: update_types.StateUpdate) -> None:
223
+ location_update = state_update.pipette_location
224
+
225
+ if location_update is update_types.NO_CHANGE:
226
+ pass
227
+ elif location_update is update_types.CLEAR:
228
+ self._state.current_location = None
229
+ self._state.current_deck_point = CurrentDeckPoint(
230
+ mount=None, deck_point=None
231
+ )
232
+ else:
233
+ new_logical_location = location_update.new_location
234
+ new_deck_point = location_update.new_deck_point
235
+ match new_logical_location:
236
+ case update_types.Well(labware_id=labware_id, well_name=well_name):
237
+ self._state.current_location = CurrentWell(
238
+ pipette_id=location_update.pipette_id,
239
+ labware_id=labware_id,
240
+ well_name=well_name,
241
+ )
242
+ case update_types.AddressableArea(
243
+ addressable_area_name=addressable_area_name
244
+ ):
245
+ self._state.current_location = CurrentAddressableArea(
246
+ pipette_id=location_update.pipette_id,
247
+ addressable_area_name=addressable_area_name,
248
+ )
249
+ case None:
250
+ self._state.current_location = None
251
+ case update_types.NO_CHANGE:
252
+ pass
253
+ if new_deck_point is not update_types.NO_CHANGE:
254
+ loaded_pipette = self._state.pipettes_by_id[location_update.pipette_id]
255
+ self._state.current_deck_point = CurrentDeckPoint(
256
+ mount=loaded_pipette.mount, deck_point=new_deck_point
257
+ )
258
+
259
+ def _update_pipette_config(self, state_update: update_types.StateUpdate) -> None:
260
+ if state_update.pipette_config != update_types.NO_CHANGE:
261
+ config = state_update.pipette_config.config
161
262
  self._state.static_config_by_id[
162
- private_result.pipette_id
263
+ state_update.pipette_config.pipette_id
163
264
  ] = StaticPipetteConfig(
164
- serial_number=private_result.serial_number,
265
+ serial_number=state_update.pipette_config.serial_number,
165
266
  model=config.model,
166
267
  display_name=config.display_name,
167
268
  min_volume=config.min_volume,
@@ -192,276 +293,27 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
192
293
  default_nozzle_map=config.nozzle_map,
193
294
  lld_settings=config.pipette_lld_settings,
194
295
  )
195
- self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates
296
+ self._state.flow_rates_by_id[
297
+ state_update.pipette_config.pipette_id
298
+ ] = config.flow_rates
196
299
  self._state.nozzle_configuration_by_id[
197
- private_result.pipette_id
300
+ state_update.pipette_config.pipette_id
198
301
  ] = config.nozzle_map
199
- elif isinstance(private_result, PipetteNozzleLayoutResultMixin):
200
- self._state.nozzle_configuration_by_id[
201
- private_result.pipette_id
202
- ] = private_result.nozzle_map
203
302
 
204
- if isinstance(command.result, commands.LoadPipetteResult):
205
- pipette_id = command.result.pipetteId
206
-
207
- self._state.pipettes_by_id[pipette_id] = LoadedPipette(
208
- id=pipette_id,
209
- pipetteName=command.params.pipetteName,
210
- mount=command.params.mount,
211
- )
212
- self._state.liquid_presence_detection_by_id[pipette_id] = (
213
- command.params.liquidPresenceDetection or False
214
- )
215
- self._state.aspirated_volume_by_id[pipette_id] = None
216
- self._state.movement_speed_by_id[pipette_id] = None
217
- self._state.attached_tip_by_id[pipette_id] = None
218
- static_config = self._state.static_config_by_id.get(pipette_id)
219
- if static_config:
220
- self._state.nozzle_configuration_by_id[
221
- pipette_id
222
- ] = static_config.default_nozzle_map
223
-
224
- elif isinstance(command.result, commands.PickUpTipResult):
225
- pipette_id = command.params.pipetteId
226
- attached_tip = TipGeometry(
227
- length=command.result.tipLength,
228
- volume=command.result.tipVolume,
229
- diameter=command.result.tipDiameter,
230
- )
231
-
232
- self._state.attached_tip_by_id[pipette_id] = attached_tip
233
- self._state.aspirated_volume_by_id[pipette_id] = 0
234
-
235
- static_config = self._state.static_config_by_id.get(pipette_id)
236
- if static_config:
237
- try:
238
- tip_configuration = static_config.tip_configuration_lookup_table[
239
- attached_tip.volume
240
- ]
241
- except KeyError:
242
- # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume.
243
- # we used to look up a default tip config via the pipette max volume, but if that isn't
244
- # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using
245
- # the first entry in the table is ok I guess but we really need to generally rethink how
246
- # we identify tip classes - looking things up by volume is not enough.
247
- tip_configuration = list(
248
- static_config.tip_configuration_lookup_table.values()
249
- )[0]
250
- self._state.flow_rates_by_id[pipette_id] = FlowRates(
251
- default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
252
- default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
253
- default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
254
- )
255
-
256
- elif isinstance(
257
- command.result,
258
- (
259
- commands.DropTipResult,
260
- commands.DropTipInPlaceResult,
261
- commands.unsafe.UnsafeDropTipInPlaceResult,
262
- ),
263
- ):
264
- pipette_id = command.params.pipetteId
265
- self._state.aspirated_volume_by_id[pipette_id] = None
266
- self._state.attached_tip_by_id[pipette_id] = None
267
-
268
- static_config = self._state.static_config_by_id.get(pipette_id)
269
- if static_config:
270
- # TODO(seth,9/11/2023): bad way to do defaulting, see above.
271
- tip_configuration = list(
272
- static_config.tip_configuration_lookup_table.values()
273
- )[0]
274
- self._state.flow_rates_by_id[pipette_id] = FlowRates(
275
- default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
276
- default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
277
- default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
278
- )
279
-
280
- def _update_current_location( # noqa: C901
281
- self, action: Union[SucceedCommandAction, FailCommandAction]
303
+ def _update_pipette_nozzle_map(
304
+ self, state_update: update_types.StateUpdate
282
305
  ) -> None:
283
- # These commands leave the pipette in a new location.
284
- # Update current_location to reflect that.
285
- if isinstance(action, SucceedCommandAction) and isinstance(
286
- action.command.result,
287
- (
288
- commands.MoveToWellResult,
289
- commands.PickUpTipResult,
290
- commands.DropTipResult,
291
- commands.AspirateResult,
292
- commands.DispenseResult,
293
- commands.BlowOutResult,
294
- commands.TouchTipResult,
295
- commands.LiquidProbeResult,
296
- commands.TryLiquidProbeResult,
297
- ),
298
- ):
299
- self._state.current_location = CurrentWell(
300
- pipette_id=action.command.params.pipetteId,
301
- labware_id=action.command.params.labwareId,
302
- well_name=action.command.params.wellName,
303
- )
304
- elif isinstance(action, FailCommandAction) and (
305
- isinstance(action.error, DefinedErrorData)
306
- and (
307
- (
308
- isinstance(
309
- action.running_command, (commands.Aspirate, commands.Dispense)
310
- )
311
- and isinstance(action.error.public, OverpressureError)
312
- )
313
- or (
314
- isinstance(action.running_command, commands.LiquidProbe)
315
- and isinstance(action.error.public, LiquidNotFoundError)
316
- )
317
- )
318
- ):
319
- self._state.current_location = CurrentWell(
320
- pipette_id=action.running_command.params.pipetteId,
321
- labware_id=action.running_command.params.labwareId,
322
- well_name=action.running_command.params.wellName,
323
- )
324
- elif isinstance(action, SucceedCommandAction) and isinstance(
325
- action.command.result,
326
- (
327
- commands.MoveToAddressableAreaResult,
328
- commands.MoveToAddressableAreaForDropTipResult,
329
- ),
330
- ):
331
- self._state.current_location = CurrentAddressableArea(
332
- pipette_id=action.command.params.pipetteId,
333
- addressable_area_name=action.command.params.addressableAreaName,
334
- )
335
-
336
- # These commands leave the pipette in a place that we can't logically associate
337
- # with a well. Clear current_location to reflect the fact that it's now unknown.
338
- #
339
- # TODO(mc, 2021-11-12): Wipe out current_location on movement failures, too.
340
- # TODO(jbl 2023-02-14): Need to investigate whether move relative should clear current location
341
- elif isinstance(action, SucceedCommandAction) and isinstance(
342
- action.command.result,
343
- (
344
- commands.HomeResult,
345
- commands.RetractAxisResult,
346
- commands.MoveToCoordinatesResult,
347
- commands.thermocycler.OpenLidResult,
348
- commands.thermocycler.CloseLidResult,
349
- ),
350
- ):
351
- self._state.current_location = None
352
-
353
- # Heater-Shaker commands may have left the pipette in a place that we can't
354
- # associate with a logical location, depending on their result.
355
- elif isinstance(action, SucceedCommandAction) and isinstance(
356
- action.command.result,
357
- (
358
- commands.heater_shaker.SetAndWaitForShakeSpeedResult,
359
- commands.heater_shaker.OpenLabwareLatchResult,
360
- ),
361
- ):
362
- if action.command.result.pipetteRetracted:
363
- self._state.current_location = None
364
-
365
- # A moveLabware command may have moved the labware that contains the current
366
- # well out from under the pipette. Clear the current location to reflect the
367
- # fact that the pipette is no longer over any labware.
368
- #
369
- # This is necessary for safe motion planning in case the next movement
370
- # goes to the same labware (now in a new place).
371
- elif isinstance(action, SucceedCommandAction) and isinstance(
372
- action.command.result, commands.MoveLabwareResult
373
- ):
374
- moved_labware_id = action.command.params.labwareId
375
- if action.command.params.strategy == "usingGripper":
376
- # All mounts will have been retracted.
377
- self._state.current_location = None
378
- elif (
379
- isinstance(self._state.current_location, CurrentWell)
380
- and self._state.current_location.labware_id == moved_labware_id
381
- ):
382
- self._state.current_location = None
383
-
384
- def _update_deck_point(
385
- self, action: Union[SucceedCommandAction, FailCommandAction]
386
- ) -> None:
387
- # This function mostly mirrors self._update_current_location().
388
- # See there for explanations.
389
-
390
- if isinstance(action, SucceedCommandAction) and isinstance(
391
- action.command.result,
392
- (
393
- commands.MoveToWellResult,
394
- commands.MoveToCoordinatesResult,
395
- commands.MoveRelativeResult,
396
- commands.MoveToAddressableAreaResult,
397
- commands.MoveToAddressableAreaForDropTipResult,
398
- commands.PickUpTipResult,
399
- commands.DropTipResult,
400
- commands.AspirateResult,
401
- commands.DispenseResult,
402
- commands.BlowOutResult,
403
- commands.TouchTipResult,
404
- ),
405
- ):
406
- pipette_id = action.command.params.pipetteId
407
- deck_point = action.command.result.position
408
- loaded_pipette = self._state.pipettes_by_id[pipette_id]
409
- self._state.current_deck_point = CurrentDeckPoint(
410
- mount=loaded_pipette.mount, deck_point=deck_point
411
- )
412
- elif (
413
- isinstance(action, FailCommandAction)
414
- and isinstance(
415
- action.running_command,
416
- (
417
- commands.Aspirate,
418
- commands.Dispense,
419
- commands.AspirateInPlace,
420
- commands.DispenseInPlace,
421
- ),
422
- )
423
- and isinstance(action.error, DefinedErrorData)
424
- and isinstance(action.error.public, OverpressureError)
425
- ):
426
- assert_type(action.error.private, OverpressureErrorInternalData)
427
- pipette_id = action.running_command.params.pipetteId
428
- deck_point = action.error.private.position
429
- loaded_pipette = self._state.pipettes_by_id[pipette_id]
430
- self._state.current_deck_point = CurrentDeckPoint(
431
- mount=loaded_pipette.mount, deck_point=deck_point
432
- )
433
-
434
- elif isinstance(action, SucceedCommandAction) and isinstance(
435
- action.command.result,
436
- (
437
- commands.HomeResult,
438
- commands.RetractAxisResult,
439
- commands.thermocycler.OpenLidResult,
440
- commands.thermocycler.CloseLidResult,
441
- ),
442
- ):
443
- self._clear_deck_point()
444
-
445
- elif isinstance(action, SucceedCommandAction) and isinstance(
446
- action.command.result,
447
- (
448
- commands.heater_shaker.SetAndWaitForShakeSpeedResult,
449
- commands.heater_shaker.OpenLabwareLatchResult,
450
- ),
451
- ):
452
- if action.command.result.pipetteRetracted:
453
- self._clear_deck_point()
454
-
455
- elif isinstance(action, SucceedCommandAction) and isinstance(
456
- action.command.result, commands.MoveLabwareResult
457
- ):
458
- if action.command.params.strategy == "usingGripper":
459
- # All mounts will have been retracted.
460
- self._clear_deck_point()
306
+ if state_update.pipette_nozzle_map != update_types.NO_CHANGE:
307
+ self._state.nozzle_configuration_by_id[
308
+ state_update.pipette_nozzle_map.pipette_id
309
+ ] = state_update.pipette_nozzle_map.nozzle_map
461
310
 
462
311
  def _update_volumes(
463
312
  self, action: Union[SucceedCommandAction, FailCommandAction]
464
313
  ) -> None:
314
+ # todo(mm, 2024-10-10): Port these isinstance checks to StateUpdate.
315
+ # https://opentrons.atlassian.net/browse/EXEC-754
316
+
465
317
  if isinstance(action, SucceedCommandAction) and isinstance(
466
318
  action.command.result,
467
319
  (commands.AspirateResult, commands.AspirateInPlaceResult),
@@ -502,10 +354,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
502
354
  pipette_id = action.command.params.pipetteId
503
355
  self._state.aspirated_volume_by_id[pipette_id] = 0
504
356
 
505
- def _clear_deck_point(self) -> None:
506
- """Reset last deck point to default None value for mount and point."""
507
- self._state.current_deck_point = CurrentDeckPoint(mount=None, deck_point=None)
508
-
509
357
 
510
358
  class PipetteView(HasState[PipetteState]):
511
359
  """Read-only view of computed pipettes state."""
@@ -781,31 +629,23 @@ class PipetteView(HasState[PipetteState]):
781
629
 
782
630
  def get_nozzle_layout_type(self, pipette_id: str) -> NozzleConfigurationType:
783
631
  """Get the current set nozzle layout configuration."""
784
- nozzle_map_for_pipette = self._state.nozzle_configuration_by_id.get(pipette_id)
785
- if nozzle_map_for_pipette:
786
- return nozzle_map_for_pipette.configuration
787
- else:
788
- return NozzleConfigurationType.FULL
632
+ nozzle_map_for_pipette = self._state.nozzle_configuration_by_id[pipette_id]
633
+ return nozzle_map_for_pipette.configuration
789
634
 
790
635
  def get_is_partially_configured(self, pipette_id: str) -> bool:
791
636
  """Determine if the provided pipette is partially configured."""
792
637
  return self.get_nozzle_layout_type(pipette_id) != NozzleConfigurationType.FULL
793
638
 
794
- def get_primary_nozzle(self, pipette_id: str) -> Optional[str]:
639
+ def get_primary_nozzle(self, pipette_id: str) -> str:
795
640
  """Get the primary nozzle, if any, related to the given pipette's nozzle configuration."""
796
- nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id)
797
- return nozzle_map.starting_nozzle if nozzle_map else None
641
+ nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
642
+ return nozzle_map.starting_nozzle
798
643
 
799
644
  def _get_critical_point_offset_without_tip(
800
645
  self, pipette_id: str, critical_point: Optional[CriticalPoint]
801
646
  ) -> Point:
802
647
  """Get the offset of the specified critical point from pipette's mount position."""
803
- nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id)
804
- # Nozzle map is unavailable only when there's no pipette loaded
805
- # so this is merely for satisfying the type checker
806
- assert (
807
- nozzle_map is not None
808
- ), "Error getting critical point offset. Nozzle map not found."
648
+ nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
809
649
  match critical_point:
810
650
  case CriticalPoint.INSTRUMENT_XY_CENTER:
811
651
  return nozzle_map.instrument_xy_center_offset