opentrons 8.1.0__py2.py3-none-any.whl → 8.2.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. opentrons/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 +208 -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/backends/ot3utils.py +1 -0
  20. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  21. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  22. opentrons/hardware_control/module_control.py +43 -2
  23. opentrons/hardware_control/modules/__init__.py +7 -1
  24. opentrons/hardware_control/modules/absorbance_reader.py +232 -83
  25. opentrons/hardware_control/modules/errors.py +7 -0
  26. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  27. opentrons/hardware_control/modules/magdeck.py +12 -3
  28. opentrons/hardware_control/modules/mod_abc.py +27 -2
  29. opentrons/hardware_control/modules/tempdeck.py +15 -7
  30. opentrons/hardware_control/modules/thermocycler.py +69 -3
  31. opentrons/hardware_control/modules/types.py +11 -5
  32. opentrons/hardware_control/modules/update.py +11 -5
  33. opentrons/hardware_control/modules/utils.py +3 -1
  34. opentrons/hardware_control/ot3_calibration.py +6 -6
  35. opentrons/hardware_control/ot3api.py +131 -94
  36. opentrons/hardware_control/poller.py +15 -11
  37. opentrons/hardware_control/protocols/__init__.py +1 -7
  38. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  39. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  40. opentrons/hardware_control/protocols/position_estimator.py +3 -1
  41. opentrons/hardware_control/types.py +2 -0
  42. opentrons/legacy_commands/helpers.py +8 -2
  43. opentrons/motion_planning/__init__.py +2 -0
  44. opentrons/motion_planning/waypoints.py +32 -0
  45. opentrons/protocol_api/__init__.py +2 -1
  46. opentrons/protocol_api/_liquid.py +87 -1
  47. opentrons/protocol_api/_parameter_context.py +10 -1
  48. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  49. opentrons/protocol_api/core/engine/instrument.py +29 -25
  50. opentrons/protocol_api/core/engine/labware.py +20 -4
  51. opentrons/protocol_api/core/engine/module_core.py +166 -17
  52. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
  53. opentrons/protocol_api/core/engine/protocol.py +30 -2
  54. opentrons/protocol_api/core/instrument.py +2 -0
  55. opentrons/protocol_api/core/labware.py +4 -0
  56. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  57. opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
  58. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
  59. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  60. opentrons/protocol_api/core/module.py +22 -4
  61. opentrons/protocol_api/core/protocol.py +6 -2
  62. opentrons/protocol_api/instrument_context.py +52 -20
  63. opentrons/protocol_api/labware.py +13 -1
  64. opentrons/protocol_api/module_contexts.py +115 -17
  65. opentrons/protocol_api/protocol_context.py +49 -5
  66. opentrons/protocol_api/validation.py +5 -3
  67. opentrons/protocol_engine/__init__.py +10 -9
  68. opentrons/protocol_engine/actions/__init__.py +3 -0
  69. opentrons/protocol_engine/actions/actions.py +30 -25
  70. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  71. opentrons/protocol_engine/clients/sync_client.py +1 -1
  72. opentrons/protocol_engine/clients/transports.py +1 -1
  73. opentrons/protocol_engine/commands/__init__.py +0 -4
  74. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  75. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
  76. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
  77. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
  79. opentrons/protocol_engine/commands/aspirate.py +29 -16
  80. opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
  81. opentrons/protocol_engine/commands/blow_out.py +63 -14
  82. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  83. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  84. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  85. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  86. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  87. opentrons/protocol_engine/commands/command.py +31 -18
  88. opentrons/protocol_engine/commands/command_unions.py +37 -24
  89. opentrons/protocol_engine/commands/comment.py +5 -3
  90. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  91. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  92. opentrons/protocol_engine/commands/custom.py +5 -3
  93. opentrons/protocol_engine/commands/dispense.py +42 -20
  94. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  95. opentrons/protocol_engine/commands/drop_tip.py +70 -16
  96. opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
  97. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  98. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  99. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  100. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  101. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  102. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  103. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  104. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  105. opentrons/protocol_engine/commands/home.py +11 -5
  106. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  107. opentrons/protocol_engine/commands/load_labware.py +28 -5
  108. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  109. opentrons/protocol_engine/commands/load_module.py +4 -6
  110. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  111. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  112. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  113. opentrons/protocol_engine/commands/move_labware.py +155 -23
  114. opentrons/protocol_engine/commands/move_relative.py +15 -3
  115. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  116. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  117. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  118. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  119. opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
  120. opentrons/protocol_engine/commands/pipetting_common.py +47 -16
  121. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  122. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  123. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  124. opentrons/protocol_engine/commands/save_position.py +2 -3
  125. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  126. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  127. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  128. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  129. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  130. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  131. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  132. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  133. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  135. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  136. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  137. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  138. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  139. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  140. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  141. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  142. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  143. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  144. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  145. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  146. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  147. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  148. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
  149. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  150. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  151. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  152. opentrons/protocol_engine/create_protocol_engine.py +60 -10
  153. opentrons/protocol_engine/engine_support.py +2 -1
  154. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  155. opentrons/protocol_engine/errors/__init__.py +20 -0
  156. opentrons/protocol_engine/errors/error_occurrence.py +8 -3
  157. opentrons/protocol_engine/errors/exceptions.py +127 -2
  158. opentrons/protocol_engine/execution/__init__.py +2 -0
  159. opentrons/protocol_engine/execution/command_executor.py +22 -13
  160. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  161. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  162. opentrons/protocol_engine/execution/equipment.py +2 -1
  163. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  164. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  165. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  166. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  167. opentrons/protocol_engine/execution/labware_movement.py +73 -22
  168. opentrons/protocol_engine/execution/movement.py +17 -7
  169. opentrons/protocol_engine/execution/pipetting.py +7 -4
  170. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  171. opentrons/protocol_engine/execution/run_control.py +1 -1
  172. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  173. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  175. opentrons/protocol_engine/notes/__init__.py +14 -2
  176. opentrons/protocol_engine/notes/notes.py +18 -1
  177. opentrons/protocol_engine/plugins.py +1 -1
  178. opentrons/protocol_engine/protocol_engine.py +47 -31
  179. opentrons/protocol_engine/resources/__init__.py +2 -0
  180. opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
  181. opentrons/protocol_engine/resources/file_provider.py +161 -0
  182. opentrons/protocol_engine/resources/fixture_validation.py +11 -1
  183. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  184. opentrons/protocol_engine/state/__init__.py +0 -70
  185. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  186. opentrons/protocol_engine/state/command_history.py +21 -2
  187. opentrons/protocol_engine/state/commands.py +110 -31
  188. opentrons/protocol_engine/state/files.py +59 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  190. opentrons/protocol_engine/state/geometry.py +445 -59
  191. opentrons/protocol_engine/state/labware.py +264 -84
  192. opentrons/protocol_engine/state/liquids.py +1 -1
  193. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
  194. opentrons/protocol_engine/state/modules.py +145 -90
  195. opentrons/protocol_engine/state/motion.py +33 -14
  196. opentrons/protocol_engine/state/pipettes.py +157 -317
  197. opentrons/protocol_engine/state/state.py +30 -1
  198. opentrons/protocol_engine/state/state_summary.py +3 -0
  199. opentrons/protocol_engine/state/tips.py +69 -114
  200. opentrons/protocol_engine/state/update_types.py +424 -0
  201. opentrons/protocol_engine/state/wells.py +236 -0
  202. opentrons/protocol_engine/types.py +90 -0
  203. opentrons/protocol_reader/file_format_validator.py +83 -15
  204. opentrons/protocol_runner/json_translator.py +21 -5
  205. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  206. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  207. opentrons/protocol_runner/protocol_runner.py +6 -3
  208. opentrons/protocol_runner/run_orchestrator.py +41 -6
  209. opentrons/protocols/advanced_control/mix.py +3 -5
  210. opentrons/protocols/advanced_control/transfers.py +125 -56
  211. opentrons/protocols/api_support/constants.py +1 -1
  212. opentrons/protocols/api_support/definitions.py +1 -1
  213. opentrons/protocols/api_support/labware_like.py +4 -4
  214. opentrons/protocols/api_support/tip_tracker.py +2 -2
  215. opentrons/protocols/api_support/types.py +15 -2
  216. opentrons/protocols/api_support/util.py +30 -42
  217. opentrons/protocols/duration/errors.py +1 -1
  218. opentrons/protocols/duration/estimator.py +50 -29
  219. opentrons/protocols/execution/dev_types.py +2 -2
  220. opentrons/protocols/execution/execute_json_v4.py +15 -10
  221. opentrons/protocols/execution/execute_python.py +8 -3
  222. opentrons/protocols/geometry/planning.py +12 -12
  223. opentrons/protocols/labware.py +17 -33
  224. opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
  225. opentrons/simulate.py +3 -3
  226. opentrons/types.py +30 -3
  227. opentrons/util/logging_config.py +34 -0
  228. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
  229. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
  230. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  231. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  232. opentrons/protocol_runner/thread_async_queue.py +0 -174
  233. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  234. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  235. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
  236. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
  237. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,6 @@ from typing import (
13
13
  Sequence,
14
14
  Iterator,
15
15
  TypeVar,
16
- overload,
17
16
  )
18
17
  import numpy
19
18
 
@@ -415,7 +414,7 @@ class PipetteHandlerProvider(Generic[MountType]):
415
414
  if instr:
416
415
  instr.reset_nozzle_configuration()
417
416
 
418
- async def add_tip(self, mount: MountType, tip_length: float) -> None:
417
+ def add_tip(self, mount: MountType, tip_length: float) -> None:
419
418
  instr = self._attached_instruments[mount]
420
419
  attached = self.attached_instruments
421
420
  instr_dict = attached[mount]
@@ -430,7 +429,15 @@ class PipetteHandlerProvider(Generic[MountType]):
430
429
  f"attach tip called while tip already attached to {instr}"
431
430
  )
432
431
 
433
- async def remove_tip(self, mount: MountType) -> None:
432
+ def cache_tip(self, mount: MountType, tip_length: float) -> None:
433
+ instrument = self.get_pipette(mount)
434
+ if instrument.has_tip:
435
+ # instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip.
436
+ instrument.remove_tip()
437
+ instrument.add_tip(tip_length=tip_length)
438
+ instrument.set_current_volume(0)
439
+
440
+ def remove_tip(self, mount: MountType) -> None:
434
441
  instr = self._attached_instruments[mount]
435
442
  attached = self.attached_instruments
436
443
  instr_dict = attached[mount]
@@ -495,25 +502,12 @@ class PipetteHandlerProvider(Generic[MountType]):
495
502
  ul_per_s = mm_per_s * instr.ul_per_mm(instr.liquid_class.max_volume, action)
496
503
  return round(ul_per_s, 6)
497
504
 
498
- @overload
499
505
  def plan_check_aspirate(
500
- self, mount: top_types.Mount, volume: Optional[float], rate: float
501
- ) -> Optional[LiquidActionSpec]:
502
- ...
503
-
504
- @overload
505
- def plan_check_aspirate(
506
- self, mount: OT3Mount, volume: Optional[float], rate: float
507
- ) -> Optional[LiquidActionSpec]:
508
- ...
509
-
510
- # note on this type ignore: see motion_utilities
511
- def plan_check_aspirate( # type: ignore[no-untyped-def]
512
506
  self,
513
- mount,
514
- volume,
515
- rate,
516
- ):
507
+ mount: MountType,
508
+ volume: Optional[float],
509
+ rate: float,
510
+ ) -> Optional[LiquidActionSpec]:
517
511
  """Check preconditions for aspirate, parse args, and calculate positions.
518
512
 
519
513
  While the mechanics of issuing an aspirate move itself are left to child
@@ -572,28 +566,12 @@ class PipetteHandlerProvider(Generic[MountType]):
572
566
  current=instrument.plunger_motor_current.run,
573
567
  )
574
568
 
575
- @overload
576
569
  def plan_check_dispense(
577
570
  self,
578
- mount: top_types.Mount,
579
- volume: Optional[float],
580
- rate: float,
581
- push_out: Optional[float],
582
- ) -> Optional[LiquidActionSpec]:
583
- ...
584
-
585
- @overload
586
- def plan_check_dispense(
587
- self,
588
- mount: OT3Mount,
571
+ mount: MountType,
589
572
  volume: Optional[float],
590
573
  rate: float,
591
574
  push_out: Optional[float],
592
- ) -> Optional[LiquidActionSpec]:
593
- ...
594
-
595
- def plan_check_dispense( # type: ignore[no-untyped-def]
596
- self, mount, volume, rate, push_out
597
575
  ) -> Optional[LiquidActionSpec]:
598
576
  """Check preconditions for dispense, parse args, and calculate positions.
599
577
 
@@ -687,15 +665,7 @@ class PipetteHandlerProvider(Generic[MountType]):
687
665
  current=instrument.plunger_motor_current.run,
688
666
  )
689
667
 
690
- @overload
691
- def plan_check_blow_out(self, mount: top_types.Mount) -> LiquidActionSpec:
692
- ...
693
-
694
- @overload
695
- def plan_check_blow_out(self, mount: OT3Mount) -> LiquidActionSpec:
696
- ...
697
-
698
- def plan_check_blow_out(self, mount): # type: ignore[no-untyped-def]
668
+ def plan_check_blow_out(self, mount: MountType) -> LiquidActionSpec:
699
669
  """Check preconditions and calculate values for blowout."""
700
670
  instrument = self.get_pipette(mount)
701
671
  speed = self.plunger_speed(
@@ -743,33 +713,13 @@ class PipetteHandlerProvider(Generic[MountType]):
743
713
  else:
744
714
  return []
745
715
 
746
- @overload
747
716
  def plan_check_pick_up_tip(
748
717
  self,
749
- mount: top_types.Mount,
750
- presses: Optional[int],
751
- increment: Optional[float],
752
- tip_length: float = 0,
753
- ) -> Tuple[PickUpTipSpec, Callable[[], None]]:
754
- ...
755
-
756
- @overload
757
- def plan_check_pick_up_tip(
758
- self,
759
- mount: OT3Mount,
718
+ mount: MountType,
760
719
  presses: Optional[int],
761
720
  increment: Optional[float],
762
721
  tip_length: float = 0,
763
722
  ) -> Tuple[PickUpTipSpec, Callable[[], None]]:
764
- ...
765
-
766
- def plan_check_pick_up_tip( # type: ignore[no-untyped-def]
767
- self,
768
- mount,
769
- presses,
770
- increment,
771
- tip_length=0,
772
- ):
773
723
  # Prechecks: ready for pickup tip and press/increment are valid
774
724
  instrument = self.get_pipette(mount)
775
725
  if instrument.has_tip:
@@ -917,23 +867,13 @@ class PipetteHandlerProvider(Generic[MountType]):
917
867
 
918
868
  return build
919
869
 
920
- @overload
921
- def plan_check_drop_tip(
922
- self, mount: top_types.Mount, home_after: bool
923
- ) -> Tuple[DropTipSpec, Callable[[], None]]:
924
- ...
925
-
926
- @overload
870
+ # todo(mm, 2024-10-17): The returned _remove_tips() callable is not used by anything
871
+ # anymore. Delete it.
927
872
  def plan_check_drop_tip(
928
- self, mount: OT3Mount, home_after: bool
929
- ) -> Tuple[DropTipSpec, Callable[[], None]]:
930
- ...
931
-
932
- def plan_check_drop_tip( # type: ignore[no-untyped-def]
933
873
  self,
934
- mount,
935
- home_after,
936
- ):
874
+ mount: MountType,
875
+ home_after: bool,
876
+ ) -> Tuple[DropTipSpec, Callable[[], None]]:
937
877
  instrument = self.get_pipette(mount)
938
878
 
939
879
  if not instrument.drop_configurations.plunger_eject:
@@ -425,7 +425,7 @@ class OT3PipetteHandler:
425
425
  if instr:
426
426
  instr.reset_nozzle_configuration()
427
427
 
428
- async def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
428
+ def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
429
429
  instr = self._attached_instruments[mount]
430
430
  attached = self.attached_instruments
431
431
  instr_dict = attached[mount]
@@ -440,7 +440,15 @@ class OT3PipetteHandler:
440
440
  "attach tip called while tip already attached to {instr}"
441
441
  )
442
442
 
443
- async def remove_tip(self, mount: OT3Mount) -> None:
443
+ def cache_tip(self, mount: OT3Mount, tip_length: float) -> None:
444
+ instrument = self.get_pipette(mount)
445
+ if instrument.has_tip:
446
+ # instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip.
447
+ instrument.remove_tip()
448
+ instrument.add_tip(tip_length=tip_length)
449
+ instrument.set_current_volume(0)
450
+
451
+ def remove_tip(self, mount: OT3Mount) -> None:
444
452
  instr = self._attached_instruments[mount]
445
453
  attached = self.attached_instruments
446
454
  instr_dict = attached[mount]
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ import asyncio
2
3
  import logging
3
4
  import re
4
5
  from typing import TYPE_CHECKING, List, Optional, Union
@@ -9,12 +10,13 @@ from opentrons.drivers.rpi_drivers import types, interfaces, usb, usb_simulator
9
10
  from opentrons.hardware_control.emulation.module_server.helpers import (
10
11
  listen_module_connection,
11
12
  )
13
+ from opentrons.hardware_control.modules.absorbance_reader import AbsorbanceReader
12
14
  from opentrons.hardware_control.modules.module_calibration import (
13
15
  ModuleCalibrationOffset,
14
16
  load_module_calibration_offset,
15
17
  save_module_calibration_offset,
16
18
  )
17
- from opentrons.hardware_control.modules.types import ModuleType
19
+ from opentrons.hardware_control.modules.types import ModuleAtPort, ModuleType
18
20
  from opentrons.hardware_control.modules import SimulatingModuleAtPort
19
21
 
20
22
  from opentrons.types import Point
@@ -99,6 +101,15 @@ class AttachedModulesControl:
99
101
  execution_manager=self._api._execution_manager,
100
102
  sim_model=sim_model,
101
103
  sim_serial_number=sim_serial_number,
104
+ disconnected_callback=self._disconnected_callback,
105
+ )
106
+
107
+ def _disconnected_callback(self, port: str, serial: Optional[str]) -> None:
108
+ """Used by the module to indicate that it was disconnected and should be deleted."""
109
+ mod = ModuleAtPort(port=port, serial=serial, name="")
110
+ asyncio.run_coroutine_threadsafe(
111
+ self.unregister_modules([mod]),
112
+ self._api.loop,
102
113
  )
103
114
 
104
115
  async def unregister_modules(
@@ -115,8 +126,12 @@ class AttachedModulesControl:
115
126
  removed_modules = []
116
127
  for mod in mods_at_ports:
117
128
  for attached_mod in self.available_modules:
118
- if attached_mod.port == mod.port:
129
+ if (
130
+ attached_mod.serial_number == mod.serial
131
+ or attached_mod.port == mod.port
132
+ ):
119
133
  removed_modules.append(attached_mod)
134
+
120
135
  for removed_mod in removed_modules:
121
136
  try:
122
137
  self._available_modules.remove(removed_mod)
@@ -231,9 +246,35 @@ class AttachedModulesControl:
231
246
  removed_modules = None
232
247
  if maybe_module_at_port is not None:
233
248
  if hasattr(event.flags, "DELETE") or hasattr(event.flags, "MOVED_FROM"):
249
+ # NOTE: The absorbance reader is a hidraw device, when we first
250
+ # plug it into the Flex, udev rules create a
251
+ # /dev/ot_module_absorbancereader(n) symlink which aionotify
252
+ # detects as a CREATE event and registers an absorbance module.
253
+ # When we create this Absorbance module and connect to it,
254
+ # the Byonoy library opens the device via hidapi which removes
255
+ # the symlink and triggers a DELETE action from aionoify.
256
+ # When the device is deleted, we disconnect from it causing
257
+ # the /dev/ot_module_absorbance(n) symlink to get created, repeating
258
+ # the cycle.
259
+
260
+ # This DELETE action would normally delete the device, but in this case
261
+ # Lets ignore these events for the absorbance reader and handle
262
+ # cleanup when the poller attempts to read data and fails.
263
+ if maybe_module_at_port.name == "absorbancereader":
264
+ return
234
265
  removed_modules = [maybe_module_at_port]
235
266
  log.info(f"Module Removed: {maybe_module_at_port}")
236
267
  elif hasattr(event.flags, "CREATE") or hasattr(event.flags, "MOVED_TO"):
268
+ # NOTE: Absorbance reader gets disonnected when updating, so
269
+ # if the device is updating, dont create a new instance.
270
+ for module in self._available_modules:
271
+ if (
272
+ maybe_module_at_port.name == "absorbancereader"
273
+ and isinstance(module, AbsorbanceReader)
274
+ and module.updating
275
+ ):
276
+ return
277
+
237
278
  new_modules = [maybe_module_at_port]
238
279
  log.info(f"Module Added: {maybe_module_at_port}")
239
280
  try:
@@ -10,7 +10,6 @@ from .types import (
10
10
  ThermocyclerStep,
11
11
  UploadFunction,
12
12
  BundledFirmware,
13
- UpdateError,
14
13
  ModuleAtPort,
15
14
  SimulatingModuleAtPort,
16
15
  SimulatingModule,
@@ -23,6 +22,11 @@ from .types import (
23
22
  SpeedStatus,
24
23
  LiveData,
25
24
  )
25
+ from .errors import (
26
+ UpdateError,
27
+ AbsorbanceReaderDisconnectedError,
28
+ )
29
+
26
30
 
27
31
  __all__ = [
28
32
  "MODULE_TYPE_BY_NAME",
@@ -49,4 +53,6 @@ __all__ = [
49
53
  "LiveData",
50
54
  "AbsorbanceReader",
51
55
  "AbsorbanceReaderStatus",
56
+ "AbsorbanceReaderDisconnectedError",
57
+ "ModuleDisconnectedCallback",
52
58
  ]