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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -35,6 +35,7 @@ from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate
35
35
  AbsorbanceReaderMeasureMode,
36
36
  )
37
37
  from opentrons.types import DeckSlotName, MountType, StagingSlotName
38
+ from .update_types import AbsorbanceReaderStateUpdate
38
39
  from ..errors import ModuleNotConnectedError
39
40
 
40
41
  from ..types import (
@@ -63,7 +64,6 @@ from ..commands import (
63
64
  heater_shaker,
64
65
  temperature_module,
65
66
  thermocycler,
66
- absorbance_reader,
67
67
  )
68
68
  from ..actions import (
69
69
  Action,
@@ -296,40 +296,10 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
296
296
  ):
297
297
  self._handle_thermocycler_module_commands(command)
298
298
 
299
- if isinstance(
300
- command.result,
301
- (
302
- absorbance_reader.InitializeResult,
303
- absorbance_reader.ReadAbsorbanceResult,
304
- ),
305
- ):
306
- self._handle_absorbance_reader_commands(command)
307
-
308
299
  def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
309
- if state_update.absorbance_reader_lid != update_types.NO_CHANGE:
310
- module_id = state_update.absorbance_reader_lid.module_id
311
- is_lid_on = state_update.absorbance_reader_lid.is_lid_on
312
-
313
- # Get current values:
314
- absorbance_reader_substate = self._state.substate_by_module_id[module_id]
315
- assert isinstance(
316
- absorbance_reader_substate, AbsorbanceReaderSubState
317
- ), f"{module_id} is not an absorbance plate reader."
318
- configured = absorbance_reader_substate.configured
319
- measure_mode = absorbance_reader_substate.measure_mode
320
- configured_wavelengths = absorbance_reader_substate.configured_wavelengths
321
- reference_wavelength = absorbance_reader_substate.reference_wavelength
322
- data = absorbance_reader_substate.data
323
-
324
- self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
325
- module_id=AbsorbanceReaderId(module_id),
326
- configured=configured,
327
- measured=True,
328
- is_lid_on=is_lid_on,
329
- measure_mode=measure_mode,
330
- configured_wavelengths=configured_wavelengths,
331
- reference_wavelength=reference_wavelength,
332
- data=data,
300
+ if state_update.absorbance_reader_state_update != update_types.NO_CHANGE:
301
+ self._handle_absorbance_reader_commands(
302
+ state_update.absorbance_reader_state_update
333
303
  )
334
304
 
335
305
  def _add_module_substate(
@@ -589,50 +559,61 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
589
559
  )
590
560
 
591
561
  def _handle_absorbance_reader_commands(
592
- self,
593
- command: Union[
594
- absorbance_reader.Initialize,
595
- absorbance_reader.ReadAbsorbance,
596
- ],
562
+ self, absorbance_reader_state_update: AbsorbanceReaderStateUpdate
597
563
  ) -> None:
598
- module_id = command.params.moduleId
564
+ # Get current values:
565
+ module_id = absorbance_reader_state_update.module_id
599
566
  absorbance_reader_substate = self._state.substate_by_module_id[module_id]
600
567
  assert isinstance(
601
568
  absorbance_reader_substate, AbsorbanceReaderSubState
602
569
  ), f"{module_id} is not an absorbance plate reader."
603
-
604
- # Get current values
570
+ is_lid_on = absorbance_reader_substate.is_lid_on
571
+ measured = True
605
572
  configured = absorbance_reader_substate.configured
606
573
  measure_mode = absorbance_reader_substate.measure_mode
607
574
  configured_wavelengths = absorbance_reader_substate.configured_wavelengths
608
575
  reference_wavelength = absorbance_reader_substate.reference_wavelength
609
- is_lid_on = absorbance_reader_substate.is_lid_on
610
-
611
- if isinstance(command.result, absorbance_reader.InitializeResult):
612
- self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
613
- module_id=AbsorbanceReaderId(module_id),
614
- configured=True,
615
- measured=False,
616
- is_lid_on=is_lid_on,
617
- measure_mode=AbsorbanceReaderMeasureMode(command.params.measureMode),
618
- configured_wavelengths=command.params.sampleWavelengths,
619
- reference_wavelength=command.params.referenceWavelength,
620
- data=None,
576
+ data = absorbance_reader_substate.data
577
+ if (
578
+ absorbance_reader_state_update.absorbance_reader_lid
579
+ != update_types.NO_CHANGE
580
+ ):
581
+ is_lid_on = absorbance_reader_state_update.absorbance_reader_lid.is_lid_on
582
+ elif (
583
+ absorbance_reader_state_update.initialize_absorbance_reader_update
584
+ != update_types.NO_CHANGE
585
+ ):
586
+ configured = True
587
+ measured = False
588
+ is_lid_on = is_lid_on
589
+ measure_mode = AbsorbanceReaderMeasureMode(
590
+ absorbance_reader_state_update.initialize_absorbance_reader_update.measure_mode
621
591
  )
622
- elif isinstance(command.result, absorbance_reader.ReadAbsorbanceResult):
623
- self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
624
- module_id=AbsorbanceReaderId(module_id),
625
- configured=configured,
626
- measured=True,
627
- is_lid_on=is_lid_on,
628
- measure_mode=measure_mode,
629
- configured_wavelengths=configured_wavelengths,
630
- reference_wavelength=reference_wavelength,
631
- data=command.result.data,
592
+ configured_wavelengths = (
593
+ absorbance_reader_state_update.initialize_absorbance_reader_update.sample_wave_lengths
594
+ )
595
+ reference_wavelength = (
596
+ absorbance_reader_state_update.initialize_absorbance_reader_update.reference_wave_length
632
597
  )
598
+ data = None
599
+ elif (
600
+ absorbance_reader_state_update.absorbance_reader_data
601
+ != update_types.NO_CHANGE
602
+ ):
603
+ data = absorbance_reader_state_update.absorbance_reader_data.read_result
604
+ self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
605
+ module_id=AbsorbanceReaderId(module_id),
606
+ configured=configured,
607
+ measured=measured,
608
+ is_lid_on=is_lid_on,
609
+ measure_mode=measure_mode,
610
+ configured_wavelengths=configured_wavelengths,
611
+ reference_wavelength=reference_wavelength,
612
+ data=data,
613
+ )
633
614
 
634
615
 
635
- class ModuleView(HasState[ModuleState]):
616
+ class ModuleView:
636
617
  """Read-only view of computed module state."""
637
618
 
638
619
  _state: ModuleState
@@ -654,7 +635,7 @@ class ModuleView(HasState[ModuleState]):
654
635
  DeckSlotLocation(slotName=slot_name) if slot_name is not None else None
655
636
  )
656
637
 
657
- return LoadedModule.construct(
638
+ return LoadedModule.model_construct(
658
639
  id=module_id,
659
640
  location=location,
660
641
  model=attached_module.definition.model,
@@ -860,8 +841,8 @@ class ModuleView(HasState[ModuleState]):
860
841
  Labware Position Check offset.
861
842
  """
862
843
  if (
863
- self.state.deck_type == DeckType.OT2_STANDARD
864
- or self.state.deck_type == DeckType.OT2_SHORT_TRASH
844
+ self._state.deck_type == DeckType.OT2_STANDARD
845
+ or self._state.deck_type == DeckType.OT2_SHORT_TRASH
865
846
  ):
866
847
  definition = self.get_definition(module_id)
867
848
  slot = self.get_location(module_id).slotName.id
@@ -908,7 +889,7 @@ class ModuleView(HasState[ModuleState]):
908
889
  "Module location invalid for nominal module offset calculation."
909
890
  )
910
891
  module_addressable_area = self.ensure_and_convert_module_fixture_location(
911
- location, self.state.deck_type, module.model
892
+ location, module.model
912
893
  )
913
894
  module_addressable_area_position = (
914
895
  addressable_areas.get_addressable_area_offsets_from_cutout(
@@ -1268,7 +1249,10 @@ class ModuleView(HasState[ModuleState]):
1268
1249
  row = chr(ord("A") + i // 12) # Convert index to row (A-H)
1269
1250
  col = (i % 12) + 1 # Convert index to column (1-12)
1270
1251
  well_key = f"{row}{col}"
1271
- well_map[well_key] = value
1252
+ truncated_value = float(
1253
+ "{:.5}".format(str(value))
1254
+ ) # Truncate the returned value to the third decimal place
1255
+ well_map[well_key] = truncated_value
1272
1256
  return well_map
1273
1257
  else:
1274
1258
  raise ValueError(
@@ -1278,13 +1262,14 @@ class ModuleView(HasState[ModuleState]):
1278
1262
  def ensure_and_convert_module_fixture_location(
1279
1263
  self,
1280
1264
  deck_slot: DeckSlotName,
1281
- deck_type: DeckType,
1282
1265
  model: ModuleModel,
1283
1266
  ) -> str:
1284
1267
  """Ensure module fixture load location is valid.
1285
1268
 
1286
1269
  Also, convert the deck slot to a valid module fixture addressable area.
1287
1270
  """
1271
+ deck_type = self._state.deck_type
1272
+
1288
1273
  if deck_type == DeckType.OT2_STANDARD or deck_type == DeckType.OT2_SHORT_TRASH:
1289
1274
  raise ValueError(
1290
1275
  f"Invalid Deck Type: {deck_type.name} - Does not support modules as fixtures."
@@ -327,6 +327,7 @@ class MotionView:
327
327
  labware_id: str,
328
328
  well_name: str,
329
329
  center_point: Point,
330
+ mm_from_edge: float = 0,
330
331
  radius: float = 1.0,
331
332
  ) -> List[motion_planning.Waypoint]:
332
333
  """Get a list of touch points for a touch tip operation."""
@@ -346,7 +347,11 @@ class MotionView:
346
347
  )
347
348
 
348
349
  positions = _move_types.get_edge_point_list(
349
- center_point, x_offset, y_offset, edge_path_type
350
+ center=center_point,
351
+ x_radius=x_offset,
352
+ y_radius=y_offset,
353
+ mm_from_edge=mm_from_edge,
354
+ edge_path_type=edge_path_type,
350
355
  )
351
356
  critical_point: Optional[CriticalPoint] = None
352
357
 
@@ -1,28 +1,33 @@
1
1
  """Basic pipette data state and store."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import dataclasses
6
+ from logging import getLogger
5
7
  from typing import (
6
8
  Dict,
7
9
  List,
8
10
  Mapping,
9
11
  Optional,
10
12
  Tuple,
11
- Union,
13
+ cast,
12
14
  )
13
15
 
16
+ from typing_extensions import assert_never
17
+
14
18
  from opentrons_shared_data.pipette import pipette_definition
19
+ from opentrons_shared_data.pipette.ul_per_mm import calculate_ul_per_mm
20
+ from opentrons_shared_data.pipette.types import UlPerMmAction
21
+
15
22
  from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE
16
23
  from opentrons.hardware_control.dev_types import PipetteDict
17
24
  from opentrons.hardware_control import CriticalPoint
18
25
  from opentrons.hardware_control.nozzle_manager import (
19
- NozzleConfigurationType,
20
26
  NozzleMap,
21
27
  )
22
- from opentrons.types import MountType, Mount as HwMount, Point
28
+ from opentrons.types import MountType, Mount as HwMount, Point, NozzleConfigurationType
23
29
 
24
- from . import update_types
25
- from .. import commands
30
+ from . import update_types, fluid_stack
26
31
  from .. import errors
27
32
  from ..types import (
28
33
  LoadedPipette,
@@ -36,13 +41,13 @@ from ..types import (
36
41
  )
37
42
  from ..actions import (
38
43
  Action,
39
- FailCommandAction,
40
44
  SetPipetteMovementSpeedAction,
41
- SucceedCommandAction,
42
45
  get_state_updates,
43
46
  )
44
47
  from ._abstract_store import HasState, HandlesActions
45
48
 
49
+ LOG = getLogger(__name__)
50
+
46
51
 
47
52
  @dataclasses.dataclass(frozen=True)
48
53
  class HardwarePipette:
@@ -98,6 +103,9 @@ class StaticPipetteConfig:
98
103
  bounding_nozzle_offsets: BoundingNozzlesOffsets
99
104
  default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove?
100
105
  lld_settings: Optional[Dict[str, Dict[str, float]]]
106
+ plunger_positions: Dict[str, float]
107
+ shaft_ul_per_mm: float
108
+ available_sensors: pipette_definition.AvailableSensorDefinition
101
109
 
102
110
 
103
111
  @dataclasses.dataclass
@@ -108,7 +116,7 @@ class PipetteState:
108
116
  # attributes are populated at the appropriate times. Refactor to a
109
117
  # single dict-of-many-things instead of many dicts-of-single-things.
110
118
  pipettes_by_id: Dict[str, LoadedPipette]
111
- aspirated_volume_by_id: Dict[str, Optional[float]]
119
+ pipette_contents_by_id: Dict[str, Optional[fluid_stack.FluidStack]]
112
120
  current_location: Optional[CurrentPipetteLocation]
113
121
  current_deck_point: CurrentDeckPoint
114
122
  attached_tip_by_id: Dict[str, Optional[TipGeometry]]
@@ -128,7 +136,7 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
128
136
  """Initialize a PipetteStore and its state."""
129
137
  self._state = PipetteState(
130
138
  pipettes_by_id={},
131
- aspirated_volume_by_id={},
139
+ pipette_contents_by_id={},
132
140
  attached_tip_by_id={},
133
141
  current_location=None,
134
142
  current_deck_point=CurrentDeckPoint(mount=None, deck_point=None),
@@ -147,11 +155,9 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
147
155
  self._update_pipette_config(state_update)
148
156
  self._update_pipette_nozzle_map(state_update)
149
157
  self._update_tip_state(state_update)
158
+ self._update_volumes(state_update)
150
159
 
151
- if isinstance(action, (SucceedCommandAction, FailCommandAction)):
152
- self._update_volumes(action)
153
-
154
- elif isinstance(action, SetPipetteMovementSpeedAction):
160
+ if isinstance(action, SetPipetteMovementSpeedAction):
155
161
  self._state.movement_speed_by_id[action.pipette_id] = action.speed
156
162
 
157
163
  def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
@@ -166,7 +172,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
166
172
  self._state.liquid_presence_detection_by_id[pipette_id] = (
167
173
  state_update.loaded_pipette.liquid_presence_detection or False
168
174
  )
169
- self._state.aspirated_volume_by_id[pipette_id] = None
170
175
  self._state.movement_speed_by_id[pipette_id] = None
171
176
  self._state.attached_tip_by_id[pipette_id] = None
172
177
 
@@ -177,7 +182,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
177
182
  attached_tip = state_update.pipette_tip_state.tip_geometry
178
183
 
179
184
  self._state.attached_tip_by_id[pipette_id] = attached_tip
180
- self._state.aspirated_volume_by_id[pipette_id] = 0
181
185
 
182
186
  static_config = self._state.static_config_by_id.get(pipette_id)
183
187
  if static_config:
@@ -204,7 +208,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
204
208
 
205
209
  else:
206
210
  pipette_id = state_update.pipette_tip_state.pipette_id
207
- self._state.aspirated_volume_by_id[pipette_id] = None
208
211
  self._state.attached_tip_by_id[pipette_id] = None
209
212
 
210
213
  static_config = self._state.static_config_by_id.get(pipette_id)
@@ -292,6 +295,9 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
292
295
  ),
293
296
  default_nozzle_map=config.nozzle_map,
294
297
  lld_settings=config.pipette_lld_settings,
298
+ plunger_positions=config.plunger_positions,
299
+ shaft_ul_per_mm=config.shaft_ul_per_mm,
300
+ available_sensors=config.available_sensors,
295
301
  )
296
302
  self._state.flow_rates_by_id[
297
303
  state_update.pipette_config.pipette_id
@@ -308,54 +314,48 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
308
314
  state_update.pipette_nozzle_map.pipette_id
309
315
  ] = state_update.pipette_nozzle_map.nozzle_map
310
316
 
311
- def _update_volumes(
312
- self, action: Union[SucceedCommandAction, FailCommandAction]
317
+ def _update_volumes(self, state_update: update_types.StateUpdate) -> None:
318
+ if state_update.pipette_aspirated_fluid == update_types.NO_CHANGE:
319
+ return
320
+ if state_update.pipette_aspirated_fluid.type == "aspirated":
321
+ self._update_aspirated(state_update.pipette_aspirated_fluid)
322
+ elif state_update.pipette_aspirated_fluid.type == "ejected":
323
+ self._update_ejected(state_update.pipette_aspirated_fluid)
324
+ elif state_update.pipette_aspirated_fluid.type == "empty":
325
+ self._update_empty(state_update.pipette_aspirated_fluid)
326
+ elif state_update.pipette_aspirated_fluid.type == "unknown":
327
+ self._update_unknown(state_update.pipette_aspirated_fluid)
328
+ else:
329
+ assert_never(state_update.pipette_aspirated_fluid.type)
330
+
331
+ def _update_aspirated(
332
+ self, update: update_types.PipetteAspiratedFluidUpdate
313
333
  ) -> None:
314
- # todo(mm, 2024-10-10): Port these isinstance checks to StateUpdate.
315
- # https://opentrons.atlassian.net/browse/EXEC-754
334
+ if self._state.pipette_contents_by_id[update.pipette_id] is None:
335
+ self._state.pipette_contents_by_id[
336
+ update.pipette_id
337
+ ] = fluid_stack.FluidStack()
316
338
 
317
- if isinstance(action, SucceedCommandAction) and isinstance(
318
- action.command.result,
319
- (commands.AspirateResult, commands.AspirateInPlaceResult),
320
- ):
321
- pipette_id = action.command.params.pipetteId
322
- previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0
323
- # PipetteHandler will have clamped action.command.result.volume for us, so
324
- # next_volume should always be in bounds.
325
- next_volume = previous_volume + action.command.result.volume
339
+ self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid)
326
340
 
327
- self._state.aspirated_volume_by_id[pipette_id] = next_volume
341
+ def _update_ejected(self, update: update_types.PipetteEjectedFluidUpdate) -> None:
342
+ self._fluid_stack_log_if_empty(update.pipette_id).remove_fluid(update.volume)
328
343
 
329
- elif isinstance(action, SucceedCommandAction) and isinstance(
330
- action.command.result,
331
- (commands.DispenseResult, commands.DispenseInPlaceResult),
332
- ):
333
- pipette_id = action.command.params.pipetteId
334
- previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0
335
- # PipetteHandler will have clamped action.command.result.volume for us, so
336
- # next_volume should always be in bounds.
337
- next_volume = previous_volume - action.command.result.volume
338
- self._state.aspirated_volume_by_id[pipette_id] = next_volume
339
-
340
- elif isinstance(action, SucceedCommandAction) and isinstance(
341
- action.command.result,
342
- (
343
- commands.BlowOutResult,
344
- commands.BlowOutInPlaceResult,
345
- commands.unsafe.UnsafeBlowOutInPlaceResult,
346
- ),
347
- ):
348
- pipette_id = action.command.params.pipetteId
349
- self._state.aspirated_volume_by_id[pipette_id] = None
344
+ def _update_empty(self, update: update_types.PipetteEmptyFluidUpdate) -> None:
345
+ self._state.pipette_contents_by_id[update.pipette_id] = fluid_stack.FluidStack()
350
346
 
351
- elif isinstance(action, SucceedCommandAction) and isinstance(
352
- action.command.result, commands.PrepareToAspirateResult
353
- ):
354
- pipette_id = action.command.params.pipetteId
355
- self._state.aspirated_volume_by_id[pipette_id] = 0
347
+ def _update_unknown(self, update: update_types.PipetteUnknownFluidUpdate) -> None:
348
+ self._state.pipette_contents_by_id[update.pipette_id] = None
356
349
 
350
+ def _fluid_stack_log_if_empty(self, pipette_id: str) -> fluid_stack.FluidStack:
351
+ stack = self._state.pipette_contents_by_id[pipette_id]
352
+ if stack is None:
353
+ LOG.error("Pipette state tried to alter an unknown-contents pipette")
354
+ return fluid_stack.FluidStack()
355
+ return stack
357
356
 
358
- class PipetteView(HasState[PipetteState]):
357
+
358
+ class PipetteView:
359
359
  """Read-only view of computed pipettes state."""
360
360
 
361
361
  _state: PipetteState
@@ -457,6 +457,10 @@ class PipetteView(HasState[PipetteState]):
457
457
  def get_aspirated_volume(self, pipette_id: str) -> Optional[float]:
458
458
  """Get the currently aspirated volume of a pipette by ID.
459
459
 
460
+ This is the volume currently displaced by the plunger relative to its bottom position,
461
+ regardless of whether that volume likely contains liquid or air. This makes it the right
462
+ function to call to know how much more volume the plunger may displace.
463
+
460
464
  Returns:
461
465
  The volume the pipette has aspirated.
462
466
  None, after blow-out and the plunger is in an unsafe position.
@@ -468,13 +472,50 @@ class PipetteView(HasState[PipetteState]):
468
472
  self.validate_tip_state(pipette_id, True)
469
473
 
470
474
  try:
471
- return self._state.aspirated_volume_by_id[pipette_id]
475
+ stack = self._state.pipette_contents_by_id[pipette_id]
476
+ if stack is None:
477
+ return None
478
+ return stack.aspirated_volume()
472
479
 
473
480
  except KeyError as e:
474
481
  raise errors.PipetteNotLoadedError(
475
482
  f"Pipette {pipette_id} not found; unable to get current volume."
476
483
  ) from e
477
484
 
485
+ def get_liquid_dispensed_by_ejecting_volume(
486
+ self, pipette_id: str, volume: float
487
+ ) -> Optional[float]:
488
+ """Get the amount of liquid (not air) that will be dispensed if the pipette ejects a specified volume.
489
+
490
+ For instance, if the pipette contains, in vertical order,
491
+ 10 ul air
492
+ 80 ul liquid
493
+ 5 ul air
494
+
495
+ then dispensing 10ul would result in 5ul of liquid; dispensing 85 ul would result in 80ul liquid; dispensing
496
+ 95ul would result in 80ul liquid.
497
+
498
+ Returns:
499
+ The volume of liquid that would be dispensed by the requested volume.
500
+ None, after blow-out or when the plunger is in an unsafe position.
501
+
502
+ Raises:
503
+ PipetteNotLoadedError: pipette ID does not exist.
504
+ TipnotAttachedError: No tip is attached to the pipette.
505
+ """
506
+ self.validate_tip_state(pipette_id, True)
507
+
508
+ try:
509
+ stack = self._state.pipette_contents_by_id[pipette_id]
510
+ if stack is None:
511
+ return None
512
+ return stack.liquid_part_of_dispense_volume(volume)
513
+
514
+ except KeyError as e:
515
+ raise errors.PipetteNotLoadedError(
516
+ f"Pipette {pipette_id} not found; unable to get current liquid volume."
517
+ ) from e
518
+
478
519
  def get_working_volume(self, pipette_id: str) -> float:
479
520
  """Get the working maximum volume of a pipette by ID.
480
521
 
@@ -641,6 +682,10 @@ class PipetteView(HasState[PipetteState]):
641
682
  nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
642
683
  return nozzle_map.starting_nozzle
643
684
 
685
+ def get_nozzle_configuration(self, pipette_id: str) -> NozzleMap:
686
+ """Get the nozzle map of the pipette."""
687
+ return self._state.nozzle_configuration_by_id[pipette_id]
688
+
644
689
  def _get_critical_point_offset_without_tip(
645
690
  self, pipette_id: str, critical_point: Optional[CriticalPoint]
646
691
  ) -> Point:
@@ -723,6 +768,13 @@ class PipetteView(HasState[PipetteState]):
723
768
  pip_front_left_bound,
724
769
  )
725
770
 
771
+ def get_pipette_supports_pressure(self, pipette_id: str) -> bool:
772
+ """Return if this pipette supports a pressure sensor."""
773
+ return (
774
+ "pressure"
775
+ in self._state.static_config_by_id[pipette_id].available_sensors.sensors
776
+ )
777
+
726
778
  def get_liquid_presence_detection(self, pipette_id: str) -> bool:
727
779
  """Determine if liquid presence detection is enabled for this pipette."""
728
780
  try:
@@ -731,3 +783,42 @@ class PipetteView(HasState[PipetteState]):
731
783
  raise errors.PipetteNotLoadedError(
732
784
  f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled."
733
785
  ) from e
786
+
787
+ def get_nozzle_configuration_supports_lld(self, pipette_id: str) -> bool:
788
+ """Determine if the current partial tip configuration supports LLD."""
789
+ nozzle_map = self.get_nozzle_configuration(pipette_id)
790
+ if (
791
+ nozzle_map.physical_nozzle_count == 96
792
+ and nozzle_map.back_left != nozzle_map.full_instrument_back_left
793
+ and nozzle_map.front_right != nozzle_map.full_instrument_front_right
794
+ ):
795
+ return False
796
+ return True
797
+
798
+ def lookup_volume_to_mm_conversion(
799
+ self, pipette_id: str, volume: float, action: str
800
+ ) -> float:
801
+ """Get the volumn to mm conversion for a pipette."""
802
+ try:
803
+ lookup_volume = self.get_working_volume(pipette_id)
804
+ except errors.TipNotAttachedError:
805
+ lookup_volume = self.get_maximum_volume(pipette_id)
806
+
807
+ pipette_config = self.get_config(pipette_id)
808
+ lookup_table_from_config = pipette_config.tip_configuration_lookup_table
809
+ try:
810
+ tip_settings = lookup_table_from_config[lookup_volume]
811
+ except KeyError:
812
+ tip_settings = list(lookup_table_from_config.values())[0]
813
+ return calculate_ul_per_mm(
814
+ volume,
815
+ cast(UlPerMmAction, action),
816
+ tip_settings,
817
+ shaft_ul_per_mm=pipette_config.shaft_ul_per_mm,
818
+ )
819
+
820
+ def lookup_plunger_position_name(
821
+ self, pipette_id: str, position_name: str
822
+ ) -> float:
823
+ """Get the plunger position provided for the given pipette id."""
824
+ return self.get_config(pipette_id).plunger_positions[position_name]
@@ -9,7 +9,7 @@ from opentrons_shared_data.deck.types import DeckDefinitionV5
9
9
  from opentrons_shared_data.robot.types import RobotDefinition
10
10
 
11
11
  from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy
12
- from opentrons.protocol_engine.types import ModuleOffsetData
12
+ from opentrons.protocol_engine.types import LiquidClassRecordWithId, ModuleOffsetData
13
13
  from opentrons.util.change_notifier import ChangeNotifier
14
14
 
15
15
  from ..resources import DeckFixedLabware
@@ -25,6 +25,7 @@ from .labware import LabwareState, LabwareStore, LabwareView
25
25
  from .pipettes import PipetteState, PipetteStore, PipetteView
26
26
  from .modules import ModuleState, ModuleStore, ModuleView
27
27
  from .liquids import LiquidState, LiquidView, LiquidStore
28
+ from .liquid_classes import LiquidClassState, LiquidClassStore, LiquidClassView
28
29
  from .tips import TipState, TipView, TipStore
29
30
  from .wells import WellState, WellView, WellStore
30
31
  from .geometry import GeometryView
@@ -49,6 +50,7 @@ class State:
49
50
  pipettes: PipetteState
50
51
  modules: ModuleState
51
52
  liquids: LiquidState
53
+ liquid_classes: LiquidClassState
52
54
  tips: TipState
53
55
  wells: WellState
54
56
  files: FileState
@@ -64,6 +66,7 @@ class StateView(HasState[State]):
64
66
  _pipettes: PipetteView
65
67
  _modules: ModuleView
66
68
  _liquid: LiquidView
69
+ _liquid_classes: LiquidClassView
67
70
  _tips: TipView
68
71
  _wells: WellView
69
72
  _geometry: GeometryView
@@ -101,6 +104,11 @@ class StateView(HasState[State]):
101
104
  """Get state view selectors for liquid state."""
102
105
  return self._liquid
103
106
 
107
+ @property
108
+ def liquid_classes(self) -> LiquidClassView:
109
+ """Get state view selectors for liquid class state."""
110
+ return self._liquid_classes
111
+
104
112
  @property
105
113
  def tips(self) -> TipView:
106
114
  """Get state view selectors for tip state."""
@@ -135,7 +143,7 @@ class StateView(HasState[State]):
135
143
  """Get protocol run data."""
136
144
  error = self._commands.get_error()
137
145
  # TODO maybe add summary here for AA
138
- return StateSummary.construct(
146
+ return StateSummary.model_construct(
139
147
  status=self._commands.get_status(),
140
148
  errors=[] if error is None else [error],
141
149
  pipettes=self._pipettes.get_all(),
@@ -148,6 +156,12 @@ class StateView(HasState[State]):
148
156
  wells=self._wells.get_all(),
149
157
  hasEverEnteredErrorRecovery=self._commands.get_has_entered_recovery_mode(),
150
158
  files=self._state.files.file_ids,
159
+ liquidClasses=[
160
+ LiquidClassRecordWithId(
161
+ liquidClassId=liquid_class_id, **dict(liquid_class_record)
162
+ )
163
+ for liquid_class_id, liquid_class_record in self._liquid_classes.get_all().items()
164
+ ],
151
165
  )
152
166
 
153
167
 
@@ -213,6 +227,7 @@ class StateStore(StateView, ActionHandler):
213
227
  module_calibration_offsets=module_calibration_offsets,
214
228
  )
215
229
  self._liquid_store = LiquidStore()
230
+ self._liquid_class_store = LiquidClassStore()
216
231
  self._tip_store = TipStore()
217
232
  self._well_store = WellStore()
218
233
  self._file_store = FileStore()
@@ -224,6 +239,7 @@ class StateStore(StateView, ActionHandler):
224
239
  self._labware_store,
225
240
  self._module_store,
226
241
  self._liquid_store,
242
+ self._liquid_class_store,
227
243
  self._tip_store,
228
244
  self._well_store,
229
245
  self._file_store,
@@ -342,6 +358,7 @@ class StateStore(StateView, ActionHandler):
342
358
  pipettes=self._pipette_store.state,
343
359
  modules=self._module_store.state,
344
360
  liquids=self._liquid_store.state,
361
+ liquid_classes=self._liquid_class_store.state,
345
362
  tips=self._tip_store.state,
346
363
  wells=self._well_store.state,
347
364
  files=self._file_store.state,
@@ -359,6 +376,7 @@ class StateStore(StateView, ActionHandler):
359
376
  self._pipettes = PipetteView(state.pipettes)
360
377
  self._modules = ModuleView(state.modules)
361
378
  self._liquid = LiquidView(state.liquids)
379
+ self._liquid_classes = LiquidClassView(state.liquid_classes)
362
380
  self._tips = TipView(state.tips)
363
381
  self._wells = WellView(state.wells)
364
382
  self._files = FileView(state.files)
@@ -391,6 +409,7 @@ class StateStore(StateView, ActionHandler):
391
409
  self._pipettes._state = next_state.pipettes
392
410
  self._modules._state = next_state.modules
393
411
  self._liquid._state = next_state.liquids
412
+ self._liquid_classes._state = next_state.liquid_classes
394
413
  self._tips._state = next_state.tips
395
414
  self._wells._state = next_state.wells
396
415
  self._change_notifier.notify()