opentrons 8.2.0a4__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 (229) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/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 +28 -20
  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 +60 -23
  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 +18 -0
  47. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/protocol_api/__init__.py +20 -1
  51. opentrons/protocol_api/_liquid.py +24 -49
  52. opentrons/protocol_api/_liquid_properties.py +754 -0
  53. opentrons/protocol_api/_types.py +24 -0
  54. opentrons/protocol_api/core/common.py +2 -0
  55. opentrons/protocol_api/core/engine/instrument.py +82 -10
  56. opentrons/protocol_api/core/engine/labware.py +29 -7
  57. opentrons/protocol_api/core/engine/protocol.py +130 -5
  58. opentrons/protocol_api/core/engine/robot.py +139 -0
  59. opentrons/protocol_api/core/engine/well.py +4 -1
  60. opentrons/protocol_api/core/instrument.py +46 -4
  61. opentrons/protocol_api/core/labware.py +13 -4
  62. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
  63. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  65. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
  67. opentrons/protocol_api/core/protocol.py +34 -1
  68. opentrons/protocol_api/core/robot.py +51 -0
  69. opentrons/protocol_api/instrument_context.py +158 -44
  70. opentrons/protocol_api/labware.py +231 -7
  71. opentrons/protocol_api/module_contexts.py +21 -17
  72. opentrons/protocol_api/protocol_context.py +125 -4
  73. opentrons/protocol_api/robot_context.py +204 -32
  74. opentrons/protocol_api/validation.py +262 -3
  75. opentrons/protocol_engine/__init__.py +4 -0
  76. opentrons/protocol_engine/actions/actions.py +2 -3
  77. opentrons/protocol_engine/clients/sync_client.py +18 -0
  78. opentrons/protocol_engine/commands/__init__.py +81 -0
  79. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  80. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  81. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  82. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  83. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  84. opentrons/protocol_engine/commands/aspirate.py +103 -53
  85. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  86. opentrons/protocol_engine/commands/blow_out.py +44 -39
  87. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  88. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  89. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  90. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  91. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  92. opentrons/protocol_engine/commands/command.py +73 -66
  93. opentrons/protocol_engine/commands/command_unions.py +101 -1
  94. opentrons/protocol_engine/commands/comment.py +1 -1
  95. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  96. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  97. opentrons/protocol_engine/commands/custom.py +6 -12
  98. opentrons/protocol_engine/commands/dispense.py +82 -48
  99. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  100. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  101. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  102. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  103. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  104. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  112. opentrons/protocol_engine/commands/home.py +13 -4
  113. opentrons/protocol_engine/commands/liquid_probe.py +67 -24
  114. opentrons/protocol_engine/commands/load_labware.py +29 -7
  115. opentrons/protocol_engine/commands/load_lid.py +146 -0
  116. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  117. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  118. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  119. opentrons/protocol_engine/commands/load_module.py +31 -10
  120. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  121. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  122. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  123. opentrons/protocol_engine/commands/move_labware.py +19 -6
  124. opentrons/protocol_engine/commands/move_relative.py +35 -25
  125. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  126. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  127. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  128. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  129. opentrons/protocol_engine/commands/movement_common.py +338 -0
  130. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  131. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  132. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  133. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  134. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  135. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  136. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  137. opentrons/protocol_engine/commands/robot/common.py +18 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  139. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  140. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  141. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  142. opentrons/protocol_engine/commands/save_position.py +14 -5
  143. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  144. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  147. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  148. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  152. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  153. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  154. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  155. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  158. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  159. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  160. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  161. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  162. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  163. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  164. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  165. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  166. opentrons/protocol_engine/errors/__init__.py +8 -0
  167. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  168. opentrons/protocol_engine/errors/exceptions.py +50 -0
  169. opentrons/protocol_engine/execution/command_executor.py +1 -1
  170. opentrons/protocol_engine/execution/equipment.py +73 -5
  171. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  172. opentrons/protocol_engine/execution/movement.py +27 -0
  173. opentrons/protocol_engine/execution/pipetting.py +5 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  175. opentrons/protocol_engine/notes/notes.py +1 -1
  176. opentrons/protocol_engine/protocol_engine.py +7 -6
  177. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  178. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  179. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  180. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  181. opentrons/protocol_engine/slot_standardization.py +9 -9
  182. opentrons/protocol_engine/state/_move_types.py +9 -5
  183. opentrons/protocol_engine/state/_well_math.py +193 -0
  184. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  185. opentrons/protocol_engine/state/command_history.py +12 -0
  186. opentrons/protocol_engine/state/commands.py +17 -13
  187. opentrons/protocol_engine/state/files.py +10 -12
  188. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  190. opentrons/protocol_engine/state/geometry.py +47 -1
  191. opentrons/protocol_engine/state/labware.py +79 -25
  192. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  193. opentrons/protocol_engine/state/liquids.py +16 -4
  194. opentrons/protocol_engine/state/modules.py +52 -70
  195. opentrons/protocol_engine/state/motion.py +6 -1
  196. opentrons/protocol_engine/state/pipettes.py +144 -58
  197. opentrons/protocol_engine/state/state.py +21 -2
  198. opentrons/protocol_engine/state/state_summary.py +4 -2
  199. opentrons/protocol_engine/state/tips.py +11 -44
  200. opentrons/protocol_engine/state/update_types.py +343 -48
  201. opentrons/protocol_engine/state/wells.py +19 -11
  202. opentrons/protocol_engine/types.py +176 -28
  203. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  204. opentrons/protocol_reader/file_format_validator.py +5 -5
  205. opentrons/protocol_runner/json_file_reader.py +9 -3
  206. opentrons/protocol_runner/json_translator.py +51 -25
  207. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  208. opentrons/protocol_runner/protocol_runner.py +35 -4
  209. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  210. opentrons/protocol_runner/run_orchestrator.py +13 -3
  211. opentrons/protocols/advanced_control/common.py +38 -0
  212. opentrons/protocols/advanced_control/mix.py +1 -1
  213. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  214. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  215. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  216. opentrons/protocols/api_support/definitions.py +1 -1
  217. opentrons/protocols/api_support/instrument.py +1 -1
  218. opentrons/protocols/api_support/util.py +10 -0
  219. opentrons/protocols/labware.py +39 -6
  220. opentrons/protocols/models/json_protocol.py +5 -9
  221. opentrons/simulate.py +3 -1
  222. opentrons/types.py +162 -2
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  225. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
  226. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  227. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  228. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,22 @@
1
1
  """Structures to represent changes that commands want to make to engine state."""
2
2
 
3
-
4
3
  import dataclasses
5
4
  import enum
6
5
  import typing
6
+ from typing_extensions import Self
7
7
  from datetime import datetime
8
8
 
9
9
  from opentrons.hardware_control.nozzle_manager import NozzleMap
10
10
  from opentrons.protocol_engine.resources import pipette_data_provider
11
- from opentrons.protocol_engine.types import DeckPoint, LabwareLocation, TipGeometry
11
+ from opentrons.protocol_engine.types import (
12
+ DeckPoint,
13
+ LabwareLocation,
14
+ OnLabwareLocation,
15
+ TipGeometry,
16
+ AspiratedFluid,
17
+ LiquidClassRecord,
18
+ ABSMeasureMode,
19
+ )
12
20
  from opentrons.types import MountType
13
21
  from opentrons_shared_data.labware.labware_definition import LabwareDefinition
14
22
  from opentrons_shared_data.pipette.types import PipetteNameType
@@ -92,7 +100,7 @@ class LabwareLocationUpdate:
92
100
  new_location: LabwareLocation
93
101
  """The labware's new location."""
94
102
 
95
- offset_id: typing.Optional[str]
103
+ offset_id: str | None
96
104
  """The ID of the labware's new offset, for its new location."""
97
105
 
98
106
 
@@ -106,12 +114,46 @@ class LoadedLabwareUpdate:
106
114
  new_location: LabwareLocation
107
115
  """The labware's initial location."""
108
116
 
109
- offset_id: typing.Optional[str]
117
+ offset_id: str | None
110
118
  """The ID of the labware's offset."""
111
119
 
112
- display_name: typing.Optional[str]
120
+ display_name: str | None
121
+
122
+ definition: LabwareDefinition
123
+
124
+
125
+ @dataclasses.dataclass
126
+ class LoadedLidStackUpdate:
127
+ """An update that loads a new lid stack."""
128
+
129
+ stack_id: str
130
+ """The unique ID of the Lid Stack Object."""
131
+
132
+ stack_object_definition: LabwareDefinition
133
+ "The System-only Labware Definition of the Lid Stack Object"
134
+
135
+ stack_location: LabwareLocation
136
+ "The initial location of the Lid Stack Object."
137
+
138
+ labware_ids: typing.List[str]
139
+ """The unique IDs of the new lids."""
140
+
141
+ new_locations_by_id: typing.Dict[str, OnLabwareLocation]
142
+ """Each lid's initial location keyed by Labware ID."""
113
143
 
114
144
  definition: LabwareDefinition
145
+ "The Labware Definition of the Lid Labware(s) loaded."
146
+
147
+
148
+ @dataclasses.dataclass
149
+ class LabwareLidUpdate:
150
+ """An update that identifies a lid on a given parent labware."""
151
+
152
+ parent_labware_id: str
153
+ """The unique ID of the parent labware."""
154
+
155
+ lid_id: str
156
+ """The unique IDs of the new lids."""
115
157
 
116
158
 
117
159
  @dataclasses.dataclass
@@ -127,7 +169,7 @@ class LoadPipetteUpdate:
127
169
 
128
170
  pipette_name: PipetteNameType
129
171
  mount: MountType
130
- liquid_presence_detection: typing.Optional[bool]
172
+ liquid_presence_detection: bool | None
131
173
 
132
174
 
133
175
  @dataclasses.dataclass
@@ -156,7 +198,7 @@ class PipetteTipStateUpdate:
156
198
  """Update pipette tip state."""
157
199
 
158
200
  pipette_id: str
159
- tip_geometry: typing.Optional[TipGeometry]
201
+ tip_geometry: TipGeometry | None
160
202
 
161
203
 
162
204
  @dataclasses.dataclass
@@ -201,18 +243,101 @@ class LiquidOperatedUpdate:
201
243
  """An update from operating a liquid."""
202
244
 
203
245
  labware_id: str
204
- well_name: str
246
+ well_names: list[str]
205
247
  volume_added: float | ClearType
206
248
 
207
249
 
250
+ @dataclasses.dataclass
251
+ class PipetteAspiratedFluidUpdate:
252
+ """Represents the pipette aspirating something. Might be air or liquid from a well."""
253
+
254
+ pipette_id: str
255
+ fluid: AspiratedFluid
256
+ type: typing.Literal["aspirated"] = "aspirated"
257
+
258
+
259
+ @dataclasses.dataclass
260
+ class PipetteEjectedFluidUpdate:
261
+ """Represents the pipette pushing something out. Might be air or liquid."""
262
+
263
+ pipette_id: str
264
+ volume: float
265
+ type: typing.Literal["ejected"] = "ejected"
266
+
267
+
268
+ @dataclasses.dataclass
269
+ class PipetteUnknownFluidUpdate:
270
+ """Represents the amount of fluid in the pipette becoming unknown."""
271
+
272
+ pipette_id: str
273
+ type: typing.Literal["unknown"] = "unknown"
274
+
275
+
276
+ @dataclasses.dataclass
277
+ class PipetteEmptyFluidUpdate:
278
+ """Sets the pipette to be valid and empty."""
279
+
280
+ pipette_id: str
281
+ type: typing.Literal["empty"] = "empty"
282
+
283
+
208
284
  @dataclasses.dataclass
209
285
  class AbsorbanceReaderLidUpdate:
210
286
  """An update to an absorbance reader's lid location."""
211
287
 
212
- module_id: str
213
288
  is_lid_on: bool
214
289
 
215
290
 
291
+ @dataclasses.dataclass
292
+ class AbsorbanceReaderDataUpdate:
293
+ """An update to an absorbance reader's lid location."""
294
+
295
+ read_result: typing.Dict[int, typing.Dict[str, float]]
296
+
297
+
298
+ @dataclasses.dataclass(frozen=True)
299
+ class AbsorbanceReaderInitializeUpdate:
300
+ """An update to an absorbance reader's initialization."""
301
+
302
+ measure_mode: ABSMeasureMode
303
+ sample_wave_lengths: typing.List[int]
304
+ reference_wave_length: typing.Optional[int]
305
+
306
+
307
+ @dataclasses.dataclass
308
+ class AbsorbanceReaderStateUpdate:
309
+ """An update to the absorbance reader module state."""
310
+
311
+ module_id: str
312
+ absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE
313
+ absorbance_reader_data: AbsorbanceReaderDataUpdate | NoChangeType = NO_CHANGE
314
+ initialize_absorbance_reader_update: AbsorbanceReaderInitializeUpdate | NoChangeType = (
315
+ NO_CHANGE
316
+ )
317
+
318
+
319
+ @dataclasses.dataclass
320
+ class LiquidClassLoadedUpdate:
321
+ """The state update from loading a liquid class."""
322
+
323
+ liquid_class_id: str
324
+ liquid_class_record: LiquidClassRecord
325
+
326
+
327
+ @dataclasses.dataclass
328
+ class FilesAddedUpdate:
329
+ """An update that adds a new data file."""
330
+
331
+ file_ids: list[str]
332
+
333
+
334
+ @dataclasses.dataclass
335
+ class AddressableAreaUsedUpdate:
336
+ """An update that says an addressable area has been used."""
337
+
338
+ addressable_area_name: str
339
+
340
+
216
341
  @dataclasses.dataclass
217
342
  class StateUpdate:
218
343
  """Represents an update to perform on engine state."""
@@ -227,10 +352,22 @@ class StateUpdate:
227
352
 
228
353
  pipette_tip_state: PipetteTipStateUpdate | NoChangeType = NO_CHANGE
229
354
 
355
+ pipette_aspirated_fluid: (
356
+ PipetteAspiratedFluidUpdate
357
+ | PipetteEjectedFluidUpdate
358
+ | PipetteUnknownFluidUpdate
359
+ | PipetteEmptyFluidUpdate
360
+ | NoChangeType
361
+ ) = NO_CHANGE
362
+
230
363
  labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE
231
364
 
232
365
  loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE
233
366
 
367
+ loaded_lid_stack: LoadedLidStackUpdate | NoChangeType = NO_CHANGE
368
+
369
+ labware_lid: LabwareLidUpdate | NoChangeType = NO_CHANGE
370
+
234
371
  tips_used: TipsUsedUpdate | NoChangeType = NO_CHANGE
235
372
 
236
373
  liquid_loaded: LiquidLoadedUpdate | NoChangeType = NO_CHANGE
@@ -239,42 +376,81 @@ class StateUpdate:
239
376
 
240
377
  liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE
241
378
 
242
- absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE
379
+ absorbance_reader_state_update: AbsorbanceReaderStateUpdate | NoChangeType = (
380
+ NO_CHANGE
381
+ )
382
+
383
+ liquid_class_loaded: LiquidClassLoadedUpdate | NoChangeType = NO_CHANGE
384
+
385
+ files_added: FilesAddedUpdate | NoChangeType = NO_CHANGE
386
+
387
+ addressable_area_used: AddressableAreaUsedUpdate | NoChangeType = NO_CHANGE
388
+
389
+ def append(self, other: Self) -> Self:
390
+ """Apply another `StateUpdate` "on top of" this one.
391
+
392
+ This object is mutated in-place, taking values from `other`.
393
+ If an attribute in `other` is `NO_CHANGE`, the value in this object is kept.
394
+ """
395
+ fields = dataclasses.fields(other)
396
+ for field in fields:
397
+ other_value = other.__dict__[field.name]
398
+ if other_value != NO_CHANGE:
399
+ self.__dict__[field.name] = other_value
400
+ return self
401
+
402
+ @classmethod
403
+ def reduce(cls: typing.Type[Self], *args: Self) -> Self:
404
+ """Fuse multiple state updates into a single one.
405
+
406
+ State updates that are later in the parameter list are preferred to those that are earlier;
407
+ NO_CHANGE is ignored.
408
+ """
409
+ accumulator = cls()
410
+ for arg in args:
411
+ accumulator.append(arg)
412
+ return accumulator
243
413
 
244
414
  # These convenience functions let the caller avoid the boilerplate of constructing a
245
- # complicated dataclass tree.
415
+ # complicated dataclass tree, and allow chaining.
246
416
 
247
417
  @typing.overload
248
418
  def set_pipette_location(
249
- self,
419
+ self: Self, *, pipette_id: str, new_deck_point: DeckPoint
420
+ ) -> Self:
421
+ """Schedule a pipette's coordinates to be changed while preserving its logical location."""
422
+
423
+ @typing.overload
424
+ def set_pipette_location(
425
+ self: Self,
250
426
  *,
251
427
  pipette_id: str,
252
428
  new_labware_id: str,
253
429
  new_well_name: str,
254
430
  new_deck_point: DeckPoint,
255
- ) -> None:
431
+ ) -> Self:
256
432
  """Schedule a pipette's location to be set to a well."""
257
433
 
258
434
  @typing.overload
259
435
  def set_pipette_location(
260
- self,
436
+ self: Self,
261
437
  *,
262
438
  pipette_id: str,
263
439
  new_addressable_area_name: str,
264
440
  new_deck_point: DeckPoint,
265
- ) -> None:
441
+ ) -> Self:
266
442
  """Schedule a pipette's location to be set to an addressable area."""
267
443
  pass
268
444
 
269
445
  def set_pipette_location( # noqa: D102
270
- self,
446
+ self: Self,
271
447
  *,
272
448
  pipette_id: str,
273
449
  new_labware_id: str | NoChangeType = NO_CHANGE,
274
450
  new_well_name: str | NoChangeType = NO_CHANGE,
275
451
  new_addressable_area_name: str | NoChangeType = NO_CHANGE,
276
452
  new_deck_point: DeckPoint,
277
- ) -> None:
453
+ ) -> Self:
278
454
  if new_addressable_area_name != NO_CHANGE:
279
455
  self.pipette_location = PipetteLocationUpdate(
280
456
  pipette_id=pipette_id,
@@ -283,43 +459,48 @@ class StateUpdate:
283
459
  ),
284
460
  new_deck_point=new_deck_point,
285
461
  )
462
+ elif new_labware_id == NO_CHANGE or new_well_name == NO_CHANGE:
463
+ self.pipette_location = PipetteLocationUpdate(
464
+ pipette_id=pipette_id,
465
+ new_location=NO_CHANGE,
466
+ new_deck_point=new_deck_point,
467
+ )
286
468
  else:
287
- # These asserts should always pass because of the overloads.
288
- assert new_labware_id != NO_CHANGE
289
- assert new_well_name != NO_CHANGE
290
-
291
469
  self.pipette_location = PipetteLocationUpdate(
292
470
  pipette_id=pipette_id,
293
471
  new_location=Well(labware_id=new_labware_id, well_name=new_well_name),
294
472
  new_deck_point=new_deck_point,
295
473
  )
474
+ return self
296
475
 
297
- def clear_all_pipette_locations(self) -> None:
476
+ def clear_all_pipette_locations(self) -> Self:
298
477
  """Mark all pipettes as having an unknown location."""
299
478
  self.pipette_location = CLEAR
479
+ return self
300
480
 
301
481
  def set_labware_location(
302
- self,
482
+ self: Self,
303
483
  *,
304
484
  labware_id: str,
305
485
  new_location: LabwareLocation,
306
486
  new_offset_id: str | None,
307
- ) -> None:
487
+ ) -> Self:
308
488
  """Set a labware's location. See `LabwareLocationUpdate`."""
309
489
  self.labware_location = LabwareLocationUpdate(
310
490
  labware_id=labware_id,
311
491
  new_location=new_location,
312
492
  offset_id=new_offset_id,
313
493
  )
494
+ return self
314
495
 
315
496
  def set_loaded_labware(
316
- self,
497
+ self: Self,
317
498
  definition: LabwareDefinition,
318
499
  labware_id: str,
319
- offset_id: typing.Optional[str],
320
- display_name: typing.Optional[str],
500
+ offset_id: str | None,
501
+ display_name: str | None,
321
502
  location: LabwareLocation,
322
- ) -> None:
503
+ ) -> Self:
323
504
  """Add a new labware to state. See `LoadedLabwareUpdate`."""
324
505
  self.loaded_labware = LoadedLabwareUpdate(
325
506
  definition=definition,
@@ -328,14 +509,47 @@ class StateUpdate:
328
509
  new_location=location,
329
510
  display_name=display_name,
330
511
  )
512
+ return self
513
+
514
+ def set_loaded_lid_stack(
515
+ self: Self,
516
+ stack_id: str,
517
+ stack_object_definition: LabwareDefinition,
518
+ stack_location: LabwareLocation,
519
+ labware_definition: LabwareDefinition,
520
+ labware_ids: typing.List[str],
521
+ locations: typing.Dict[str, OnLabwareLocation],
522
+ ) -> Self:
523
+ """Add a new lid stack to state. See `LoadedLidStackUpdate`."""
524
+ self.loaded_lid_stack = LoadedLidStackUpdate(
525
+ stack_id=stack_id,
526
+ stack_object_definition=stack_object_definition,
527
+ stack_location=stack_location,
528
+ definition=labware_definition,
529
+ labware_ids=labware_ids,
530
+ new_locations_by_id=locations,
531
+ )
532
+ return self
533
+
534
+ def set_lid(
535
+ self: Self,
536
+ parent_labware_id: str,
537
+ lid_id: str,
538
+ ) -> Self:
539
+ """Update the labware parent of a loaded or moved lid. See `LabwareLidUpdate`."""
540
+ self.labware_lid = LabwareLidUpdate(
541
+ parent_labware_id=parent_labware_id,
542
+ lid_id=lid_id,
543
+ )
544
+ return self
331
545
 
332
546
  def set_load_pipette(
333
- self,
547
+ self: Self,
334
548
  pipette_id: str,
335
549
  pipette_name: PipetteNameType,
336
550
  mount: MountType,
337
- liquid_presence_detection: typing.Optional[bool],
338
- ) -> None:
551
+ liquid_presence_detection: bool | None,
552
+ ) -> Self:
339
553
  """Add a new pipette to state. See `LoadPipetteUpdate`."""
340
554
  self.loaded_pipette = LoadPipetteUpdate(
341
555
  pipette_id=pipette_id,
@@ -343,61 +557,69 @@ class StateUpdate:
343
557
  mount=mount,
344
558
  liquid_presence_detection=liquid_presence_detection,
345
559
  )
560
+ return self
346
561
 
347
562
  def update_pipette_config(
348
- self,
563
+ self: Self,
349
564
  pipette_id: str,
350
565
  config: pipette_data_provider.LoadedStaticPipetteData,
351
566
  serial_number: str,
352
- ) -> None:
567
+ ) -> Self:
353
568
  """Update a pipette's config. See `PipetteConfigUpdate`."""
354
569
  self.pipette_config = PipetteConfigUpdate(
355
570
  pipette_id=pipette_id, config=config, serial_number=serial_number
356
571
  )
572
+ return self
357
573
 
358
- def update_pipette_nozzle(self, pipette_id: str, nozzle_map: NozzleMap) -> None:
574
+ def update_pipette_nozzle(
575
+ self: Self, pipette_id: str, nozzle_map: NozzleMap
576
+ ) -> Self:
359
577
  """Update a pipette's nozzle map. See `PipetteNozzleMapUpdate`."""
360
578
  self.pipette_nozzle_map = PipetteNozzleMapUpdate(
361
579
  pipette_id=pipette_id, nozzle_map=nozzle_map
362
580
  )
581
+ return self
363
582
 
364
583
  def update_pipette_tip_state(
365
- self, pipette_id: str, tip_geometry: typing.Optional[TipGeometry]
366
- ) -> None:
584
+ self: Self, pipette_id: str, tip_geometry: TipGeometry | None
585
+ ) -> Self:
367
586
  """Update a pipette's tip state. See `PipetteTipStateUpdate`."""
368
587
  self.pipette_tip_state = PipetteTipStateUpdate(
369
588
  pipette_id=pipette_id, tip_geometry=tip_geometry
370
589
  )
590
+ return self
371
591
 
372
592
  def mark_tips_as_used(
373
- self, pipette_id: str, labware_id: str, well_name: str
374
- ) -> None:
593
+ self: Self, pipette_id: str, labware_id: str, well_name: str
594
+ ) -> Self:
375
595
  """Mark tips in a tip rack as used. See `TipsUsedUpdate`."""
376
596
  self.tips_used = TipsUsedUpdate(
377
597
  pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
378
598
  )
599
+ return self
379
600
 
380
601
  def set_liquid_loaded(
381
- self,
602
+ self: Self,
382
603
  labware_id: str,
383
604
  volumes: typing.Dict[str, float],
384
605
  last_loaded: datetime,
385
- ) -> None:
606
+ ) -> Self:
386
607
  """Add liquid volumes to well state. See `LoadLiquidUpdate`."""
387
608
  self.liquid_loaded = LiquidLoadedUpdate(
388
609
  labware_id=labware_id,
389
610
  volumes=volumes,
390
611
  last_loaded=last_loaded,
391
612
  )
613
+ return self
392
614
 
393
615
  def set_liquid_probed(
394
- self,
616
+ self: Self,
395
617
  labware_id: str,
396
618
  well_name: str,
397
619
  last_probed: datetime,
398
620
  height: float | ClearType,
399
621
  volume: float | ClearType,
400
- ) -> None:
622
+ ) -> Self:
401
623
  """Add a liquid height and volume to well state. See `ProbeLiquidUpdate`."""
402
624
  self.liquid_probed = LiquidProbedUpdate(
403
625
  labware_id=labware_id,
@@ -406,19 +628,92 @@ class StateUpdate:
406
628
  volume=volume,
407
629
  last_probed=last_probed,
408
630
  )
631
+ return self
409
632
 
410
633
  def set_liquid_operated(
411
- self, labware_id: str, well_name: str, volume_added: float | ClearType
412
- ) -> None:
634
+ self: Self,
635
+ labware_id: str,
636
+ well_names: list[str],
637
+ volume_added: float | ClearType,
638
+ ) -> Self:
413
639
  """Update liquid volumes in well state. See `OperateLiquidUpdate`."""
414
640
  self.liquid_operated = LiquidOperatedUpdate(
415
641
  labware_id=labware_id,
416
- well_name=well_name,
642
+ well_names=well_names,
417
643
  volume_added=volume_added,
418
644
  )
645
+ return self
419
646
 
420
- def set_absorbance_reader_lid(self, module_id: str, is_lid_on: bool) -> None:
647
+ def set_fluid_aspirated(self: Self, pipette_id: str, fluid: AspiratedFluid) -> Self:
648
+ """Update record of fluid held inside a pipette. See `PipetteAspiratedFluidUpdate`."""
649
+ self.pipette_aspirated_fluid = PipetteAspiratedFluidUpdate(
650
+ type="aspirated", pipette_id=pipette_id, fluid=fluid
651
+ )
652
+ return self
653
+
654
+ def set_fluid_ejected(self: Self, pipette_id: str, volume: float) -> Self:
655
+ """Update record of fluid held inside a pipette. See `PipetteEjectedFluidUpdate`."""
656
+ self.pipette_aspirated_fluid = PipetteEjectedFluidUpdate(
657
+ type="ejected", pipette_id=pipette_id, volume=volume
658
+ )
659
+ return self
660
+
661
+ def set_fluid_unknown(self: Self, pipette_id: str) -> Self:
662
+ """Update record of fluid held inside a pipette. See `PipetteUnknownFluidUpdate`."""
663
+ self.pipette_aspirated_fluid = PipetteUnknownFluidUpdate(
664
+ type="unknown", pipette_id=pipette_id
665
+ )
666
+ return self
667
+
668
+ def set_fluid_empty(self: Self, pipette_id: str) -> Self:
669
+ """Update record fo fluid held inside a pipette. See `PipetteEmptyFluidUpdate`."""
670
+ self.pipette_aspirated_fluid = PipetteEmptyFluidUpdate(
671
+ type="empty", pipette_id=pipette_id
672
+ )
673
+ return self
674
+
675
+ def set_absorbance_reader_lid(self: Self, module_id: str, is_lid_on: bool) -> Self:
421
676
  """Update an absorbance reader's lid location. See `AbsorbanceReaderLidUpdate`."""
422
- self.absorbance_reader_lid = AbsorbanceReaderLidUpdate(
423
- module_id=module_id, is_lid_on=is_lid_on
677
+ assert self.absorbance_reader_state_update == NO_CHANGE
678
+ self.absorbance_reader_state_update = AbsorbanceReaderStateUpdate(
679
+ module_id=module_id,
680
+ absorbance_reader_lid=AbsorbanceReaderLidUpdate(is_lid_on=is_lid_on),
681
+ )
682
+ return self
683
+
684
+ def set_absorbance_reader_data(
685
+ self, module_id: str, read_result: typing.Dict[int, typing.Dict[str, float]]
686
+ ) -> Self:
687
+ """Update an absorbance reader's read data. See `AbsorbanceReaderReadDataUpdate`."""
688
+ assert self.absorbance_reader_state_update == NO_CHANGE
689
+ self.absorbance_reader_state_update = AbsorbanceReaderStateUpdate(
690
+ module_id=module_id,
691
+ absorbance_reader_data=AbsorbanceReaderDataUpdate(read_result=read_result),
692
+ )
693
+ return self
694
+
695
+ def initialize_absorbance_reader(
696
+ self,
697
+ module_id: str,
698
+ measure_mode: ABSMeasureMode,
699
+ sample_wave_lengths: typing.List[int],
700
+ reference_wave_length: typing.Optional[int],
701
+ ) -> Self:
702
+ """Initialize absorbance reader."""
703
+ assert self.absorbance_reader_state_update == NO_CHANGE
704
+ self.absorbance_reader_state_update = AbsorbanceReaderStateUpdate(
705
+ module_id=module_id,
706
+ initialize_absorbance_reader_update=AbsorbanceReaderInitializeUpdate(
707
+ measure_mode=measure_mode,
708
+ sample_wave_lengths=sample_wave_lengths,
709
+ reference_wave_length=reference_wave_length,
710
+ ),
711
+ )
712
+ return self
713
+
714
+ def set_addressable_area_used(self: Self, addressable_area_name: str) -> Self:
715
+ """Mark that an addressable area has been used. See `AddressableAreaUsedUpdate`."""
716
+ self.addressable_area_used = AddressableAreaUsedUpdate(
717
+ addressable_area_name=addressable_area_name
424
718
  )
719
+ return self
@@ -1,4 +1,5 @@
1
1
  """Basic well data state and store."""
2
+
2
3
  from dataclasses import dataclass
3
4
  from typing import Dict, List, Union, Iterator, Optional, Tuple, overload, TypeVar
4
5
 
@@ -54,7 +55,7 @@ class WellStore(HasState[WellState], HandlesActions):
54
55
  labware_id = state_update.labware_id
55
56
  if labware_id not in self._state.loaded_volumes:
56
57
  self._state.loaded_volumes[labware_id] = {}
57
- for (well, volume) in state_update.volumes.items():
58
+ for well, volume in state_update.volumes.items():
58
59
  self._state.loaded_volumes[labware_id][well] = LoadedVolumeInfo(
59
60
  volume=_none_from_clear(volume),
60
61
  last_loaded=state_update.last_loaded,
@@ -83,19 +84,28 @@ class WellStore(HasState[WellState], HandlesActions):
83
84
  def _handle_liquid_operated_update(
84
85
  self, state_update: update_types.LiquidOperatedUpdate
85
86
  ) -> None:
86
- labware_id = state_update.labware_id
87
- well_name = state_update.well_name
87
+ for well_name in state_update.well_names:
88
+ self._handle_well_operated(
89
+ state_update.labware_id, well_name, state_update.volume_added
90
+ )
91
+
92
+ def _handle_well_operated(
93
+ self,
94
+ labware_id: str,
95
+ well_name: str,
96
+ volume_added: float | update_types.ClearType,
97
+ ) -> None:
88
98
  if (
89
99
  labware_id in self._state.loaded_volumes
90
100
  and well_name in self._state.loaded_volumes[labware_id]
91
101
  ):
92
- if state_update.volume_added is update_types.CLEAR:
102
+ if volume_added is update_types.CLEAR:
93
103
  del self._state.loaded_volumes[labware_id][well_name]
94
104
  else:
95
105
  prev_loaded_vol_info = self._state.loaded_volumes[labware_id][well_name]
96
106
  assert prev_loaded_vol_info.volume is not None
97
107
  self._state.loaded_volumes[labware_id][well_name] = LoadedVolumeInfo(
98
- volume=prev_loaded_vol_info.volume + state_update.volume_added,
108
+ volume=prev_loaded_vol_info.volume + volume_added,
99
109
  last_loaded=prev_loaded_vol_info.last_loaded,
100
110
  operations_since_load=prev_loaded_vol_info.operations_since_load
101
111
  + 1,
@@ -109,16 +119,14 @@ class WellStore(HasState[WellState], HandlesActions):
109
119
  labware_id in self._state.probed_volumes
110
120
  and well_name in self._state.probed_volumes[labware_id]
111
121
  ):
112
- if state_update.volume_added is update_types.CLEAR:
122
+ if volume_added is update_types.CLEAR:
113
123
  del self._state.probed_volumes[labware_id][well_name]
114
124
  else:
115
125
  prev_probed_vol_info = self._state.probed_volumes[labware_id][well_name]
116
126
  if prev_probed_vol_info.volume is None:
117
127
  new_vol_info: float | None = None
118
128
  else:
119
- new_vol_info = (
120
- prev_probed_vol_info.volume + state_update.volume_added
121
- )
129
+ new_vol_info = prev_probed_vol_info.volume + volume_added
122
130
  self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo(
123
131
  volume=new_vol_info,
124
132
  last_probed=prev_probed_vol_info.last_probed,
@@ -127,7 +135,7 @@ class WellStore(HasState[WellState], HandlesActions):
127
135
  )
128
136
 
129
137
 
130
- class WellView(HasState[WellState]):
138
+ class WellView:
131
139
  """Read-only well state view."""
132
140
 
133
141
  _state: WellState
@@ -214,7 +222,7 @@ def _volume_from_info(info: Optional[LoadedVolumeInfo]) -> Optional[float]:
214
222
 
215
223
 
216
224
  def _volume_from_info(
217
- info: Union[ProbedVolumeInfo, LoadedVolumeInfo, None]
225
+ info: Union[ProbedVolumeInfo, LoadedVolumeInfo, None],
218
226
  ) -> Optional[float]:
219
227
  if info is None:
220
228
  return None