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
@@ -24,11 +24,17 @@ from opentrons.hardware_control.modules.types import LiveData
24
24
  from opentrons.motion_planning.adjacent_slots_getters import (
25
25
  get_east_slot,
26
26
  get_west_slot,
27
+ get_adjacent_staging_slot,
27
28
  )
29
+ from opentrons.protocol_engine.actions.get_state_update import get_state_updates
28
30
  from opentrons.protocol_engine.commands.calibration.calibrate_module import (
29
31
  CalibrateModuleResult,
30
32
  )
31
- from opentrons.types import DeckSlotName, MountType
33
+ from opentrons.protocol_engine.state import update_types
34
+ from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate import (
35
+ AbsorbanceReaderMeasureMode,
36
+ )
37
+ from opentrons.types import DeckSlotName, MountType, StagingSlotName
32
38
  from ..errors import ModuleNotConnectedError
33
39
 
34
40
  from ..types import (
@@ -45,7 +51,10 @@ from ..types import (
45
51
  HeaterShakerMovementRestrictors,
46
52
  DeckType,
47
53
  LabwareMovementOffsetData,
54
+ AddressableAreaLocation,
48
55
  )
56
+
57
+ from ..resources import DeckFixedLabware
49
58
  from .addressable_areas import AddressableAreaView
50
59
  from .. import errors
51
60
  from ..commands import (
@@ -56,8 +65,12 @@ from ..commands import (
56
65
  thermocycler,
57
66
  absorbance_reader,
58
67
  )
59
- from ..actions import Action, SucceedCommandAction, AddModuleAction
60
- from .abstract_store import HasState, HandlesActions
68
+ from ..actions import (
69
+ Action,
70
+ SucceedCommandAction,
71
+ AddModuleAction,
72
+ )
73
+ from ._abstract_store import HasState, HandlesActions
61
74
  from .module_substates import (
62
75
  MagneticModuleSubState,
63
76
  HeaterShakerModuleSubState,
@@ -174,6 +187,15 @@ class ModuleState:
174
187
  deck_type: DeckType
175
188
  """Type of deck that the modules are on."""
176
189
 
190
+ deck_fixed_labware: Sequence[DeckFixedLabware]
191
+ """Fixed labware from the deck which may be assigned to a module.
192
+
193
+ The Opentrons Plate Reader module makes use of an electronic Lid labware which moves
194
+ between the Reader and Dock positions, and is pre-loaded into the engine as to persist
195
+ even when not in use. For this reason, we inject it here when an appropriate match
196
+ is identified.
197
+ """
198
+
177
199
 
178
200
  class ModuleStore(HasState[ModuleState], HandlesActions):
179
201
  """Module state container."""
@@ -183,6 +205,7 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
183
205
  def __init__(
184
206
  self,
185
207
  config: Config,
208
+ deck_fixed_labware: Sequence[DeckFixedLabware],
186
209
  module_calibration_offsets: Optional[Dict[str, ModuleOffsetData]] = None,
187
210
  ) -> None:
188
211
  """Initialize a ModuleStore and its state."""
@@ -194,6 +217,7 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
194
217
  substate_by_module_id={},
195
218
  module_offset_by_serial=module_calibration_offsets or {},
196
219
  deck_type=config.deck_type,
220
+ deck_fixed_labware=deck_fixed_labware,
197
221
  )
198
222
  self._robot_type = config.robot_type
199
223
 
@@ -212,7 +236,13 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
212
236
  module_live_data=action.module_live_data,
213
237
  )
214
238
 
239
+ for state_update in get_state_updates(action):
240
+ self._handle_state_update(state_update)
241
+
215
242
  def _handle_command(self, command: Command) -> None:
243
+ # todo(mm, 2024-11-04): Delete this function. Port these isinstance()
244
+ # checks to the update_types.StateUpdate mechanism.
245
+
216
246
  if isinstance(command.result, LoadModuleResult):
217
247
  slot_name = command.params.location.slotName
218
248
  self._add_module_substate(
@@ -270,11 +300,38 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
270
300
  command.result,
271
301
  (
272
302
  absorbance_reader.InitializeResult,
273
- absorbance_reader.MeasureAbsorbanceResult,
303
+ absorbance_reader.ReadAbsorbanceResult,
274
304
  ),
275
305
  ):
276
306
  self._handle_absorbance_reader_commands(command)
277
307
 
308
+ 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,
333
+ )
334
+
278
335
  def _add_module_substate(
279
336
  self,
280
337
  module_id: str,
@@ -338,8 +395,11 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
338
395
  module_id=AbsorbanceReaderId(module_id),
339
396
  configured=False,
340
397
  measured=False,
398
+ is_lid_on=True,
341
399
  data=None,
342
- configured_wavelength=None,
400
+ measure_mode=None,
401
+ configured_wavelengths=None,
402
+ reference_wavelength=None,
343
403
  )
344
404
 
345
405
  def _update_additional_slots_occupied_by_thermocycler(
@@ -513,7 +573,6 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
513
573
  target_block_temperature=block_temperature,
514
574
  target_lid_temperature=None,
515
575
  )
516
- # TODO (spp, 2022-08-01): set is_lid_open to False upon lid commands' failure
517
576
  elif isinstance(command.result, thermocycler.OpenLidResult):
518
577
  self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState(
519
578
  module_id=ThermocyclerModuleId(module_id),
@@ -533,7 +592,7 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
533
592
  self,
534
593
  command: Union[
535
594
  absorbance_reader.Initialize,
536
- absorbance_reader.MeasureAbsorbance,
595
+ absorbance_reader.ReadAbsorbance,
537
596
  ],
538
597
  ) -> None:
539
598
  module_id = command.params.moduleId
@@ -544,22 +603,31 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
544
603
 
545
604
  # Get current values
546
605
  configured = absorbance_reader_substate.configured
547
- configured_wavelength = absorbance_reader_substate.configured_wavelength
606
+ measure_mode = absorbance_reader_substate.measure_mode
607
+ configured_wavelengths = absorbance_reader_substate.configured_wavelengths
608
+ reference_wavelength = absorbance_reader_substate.reference_wavelength
609
+ is_lid_on = absorbance_reader_substate.is_lid_on
548
610
 
549
611
  if isinstance(command.result, absorbance_reader.InitializeResult):
550
612
  self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
551
613
  module_id=AbsorbanceReaderId(module_id),
552
614
  configured=True,
553
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,
554
620
  data=None,
555
- configured_wavelength=command.params.sampleWavelength,
556
621
  )
557
- elif isinstance(command.result, absorbance_reader.MeasureAbsorbanceResult):
622
+ elif isinstance(command.result, absorbance_reader.ReadAbsorbanceResult):
558
623
  self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
559
624
  module_id=AbsorbanceReaderId(module_id),
560
625
  configured=configured,
561
- configured_wavelength=configured_wavelength,
562
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,
563
631
  data=command.result.data,
564
632
  )
565
633
 
@@ -711,7 +779,7 @@ class ModuleView(HasState[ModuleState]):
711
779
  return self._get_module_substate(
712
780
  module_id=module_id,
713
781
  expected_type=AbsorbanceReaderSubState,
714
- expected_name="Thermocycler Module",
782
+ expected_name="Absorbance Reader",
715
783
  )
716
784
 
717
785
  def get_location(self, module_id: str) -> DeckSlotLocation:
@@ -776,12 +844,21 @@ class ModuleView(HasState[ModuleState]):
776
844
  """Get the specified module's dimensions."""
777
845
  return self.get_definition(module_id).dimensions
778
846
 
779
- def get_nominal_module_offset(
847
+ def get_nominal_offset_to_child(
780
848
  self,
781
849
  module_id: str,
850
+ # todo(mm, 2024-11-07): A method of one view taking a sibling view as an argument
851
+ # is unusual, and may be bug-prone if the order in which the views are updated
852
+ # matters. If we need to compute something that depends on module info and
853
+ # addressable area info, can we do that computation in GeometryView instead of
854
+ # here?
782
855
  addressable_areas: AddressableAreaView,
783
856
  ) -> LabwareOffsetVector:
784
- """Get the module's nominal offset vector computed with slot transform."""
857
+ """Get the nominal offset from a module's location to its child labware's location.
858
+
859
+ Includes the slot-specific transform. Does not include the child's
860
+ Labware Position Check offset.
861
+ """
785
862
  if (
786
863
  self.state.deck_type == DeckType.OT2_STANDARD
787
864
  or self.state.deck_type == DeckType.OT2_SHORT_TRASH
@@ -889,7 +966,7 @@ class ModuleView(HasState[ModuleState]):
889
966
  default_lw_offset_point = self.get_definition(module_id).labwareOffset.z
890
967
  z_difference = module_height - default_lw_offset_point
891
968
 
892
- nominal_transformed_lw_offset_z = self.get_nominal_module_offset(
969
+ nominal_transformed_lw_offset_z = self.get_nominal_offset_to_child(
893
970
  module_id=module_id, addressable_areas=addressable_areas
894
971
  ).z
895
972
  calibration_offset = self.get_module_calibration_offset(module_id)
@@ -1017,8 +1094,8 @@ class ModuleView(HasState[ModuleState]):
1017
1094
 
1018
1095
  def should_dodge_thermocycler(
1019
1096
  self,
1020
- from_slot: DeckSlotName,
1021
- to_slot: DeckSlotName,
1097
+ from_slot: Union[DeckSlotName, StagingSlotName],
1098
+ to_slot: Union[DeckSlotName, StagingSlotName],
1022
1099
  ) -> bool:
1023
1100
  """Decide if the requested path would cross the thermocycler, if installed.
1024
1101
 
@@ -1179,6 +1256,28 @@ class ModuleView(HasState[ModuleState]):
1179
1256
  else:
1180
1257
  return False
1181
1258
 
1259
+ def convert_absorbance_reader_data_points(
1260
+ self, data: List[float]
1261
+ ) -> Dict[str, float]:
1262
+ """Return the data from the Absorbance Reader module in a map of wells for each read value."""
1263
+ if len(data) == 96:
1264
+ # We have to reverse the reader values because the Opentrons Absorbance Reader is rotated 180 degrees on the deck
1265
+ data.reverse()
1266
+ well_map: Dict[str, float] = {}
1267
+ for i, value in enumerate(data):
1268
+ row = chr(ord("A") + i // 12) # Convert index to row (A-H)
1269
+ col = (i % 12) + 1 # Convert index to column (1-12)
1270
+ well_key = f"{row}{col}"
1271
+ truncated_value = float(
1272
+ "{:.5}".format(str(value))
1273
+ ) # Truncate the returned value to the third decimal place
1274
+ well_map[well_key] = truncated_value
1275
+ return well_map
1276
+ else:
1277
+ raise ValueError(
1278
+ "Only readings of 96 Well labware are supported for conversion to map of values by well."
1279
+ )
1280
+
1182
1281
  def ensure_and_convert_module_fixture_location(
1183
1282
  self,
1184
1283
  deck_slot: DeckSlotName,
@@ -1194,84 +1293,40 @@ class ModuleView(HasState[ModuleState]):
1194
1293
  f"Invalid Deck Type: {deck_type.name} - Does not support modules as fixtures."
1195
1294
  )
1196
1295
 
1296
+ assert deck_slot in DeckSlotName.ot3_slots()
1197
1297
  if model == ModuleModel.MAGNETIC_BLOCK_V1:
1198
- valid_slots = [
1199
- slot
1200
- for slot in [
1201
- "A1",
1202
- "B1",
1203
- "C1",
1204
- "D1",
1205
- "A2",
1206
- "B2",
1207
- "C2",
1208
- "D2",
1209
- "A3",
1210
- "B3",
1211
- "C3",
1212
- "D3",
1213
- ]
1214
- ]
1215
- addressable_areas = [
1216
- "magneticBlockV1A1",
1217
- "magneticBlockV1B1",
1218
- "magneticBlockV1C1",
1219
- "magneticBlockV1D1",
1220
- "magneticBlockV1A2",
1221
- "magneticBlockV1B2",
1222
- "magneticBlockV1C2",
1223
- "magneticBlockV1D2",
1224
- "magneticBlockV1A3",
1225
- "magneticBlockV1B3",
1226
- "magneticBlockV1C3",
1227
- "magneticBlockV1D3",
1228
- ]
1298
+ return f"magneticBlockV1{deck_slot.value}"
1229
1299
 
1230
1300
  elif model == ModuleModel.HEATER_SHAKER_MODULE_V1:
1231
- valid_slots = [
1232
- slot for slot in ["A1", "B1", "C1", "D1", "A3", "B3", "C3", "D3"]
1233
- ]
1234
- addressable_areas = [
1235
- "heaterShakerV1A1",
1236
- "heaterShakerV1B1",
1237
- "heaterShakerV1C1",
1238
- "heaterShakerV1D1",
1239
- "heaterShakerV1A3",
1240
- "heaterShakerV1B3",
1241
- "heaterShakerV1C3",
1242
- "heaterShakerV1D3",
1243
- ]
1301
+ # only allowed in column 1 & 3
1302
+ assert deck_slot.value[-1] in ("1", "3")
1303
+ return f"heaterShakerV1{deck_slot.value}"
1304
+
1244
1305
  elif model == ModuleModel.TEMPERATURE_MODULE_V2:
1245
- valid_slots = [
1246
- slot for slot in ["A1", "B1", "C1", "D1", "A3", "B3", "C3", "D3"]
1247
- ]
1248
- addressable_areas = [
1249
- "temperatureModuleV2A1",
1250
- "temperatureModuleV2B1",
1251
- "temperatureModuleV2C1",
1252
- "temperatureModuleV2D1",
1253
- "temperatureModuleV2A3",
1254
- "temperatureModuleV2B3",
1255
- "temperatureModuleV2C3",
1256
- "temperatureModuleV2D3",
1257
- ]
1306
+ # only allowed in column 1 & 3
1307
+ assert deck_slot.value[-1] in ("1", "3")
1308
+ return f"temperatureModuleV2{deck_slot.value}"
1309
+
1258
1310
  elif model == ModuleModel.THERMOCYCLER_MODULE_V2:
1259
1311
  return "thermocyclerModuleV2"
1312
+
1260
1313
  elif model == ModuleModel.ABSORBANCE_READER_V1:
1261
- valid_slots = ["A3", "B3", "C3", "D3"]
1262
- addressable_areas = [
1263
- "absorbanceReaderV1A3",
1264
- "absorbanceReaderV1B3",
1265
- "absorbanceReaderV1C3",
1266
- "absorbanceReaderV1D3",
1267
- ]
1268
- else:
1269
- raise ValueError(
1270
- f"Unknown module {model.name} has no addressable areas to provide."
1271
- )
1314
+ # only allowed in column 3
1315
+ assert deck_slot.value[-1] == "3"
1316
+ return f"absorbanceReaderV1{deck_slot.value}"
1272
1317
 
1273
- map_addressable_area = {
1274
- slot: addressable_area
1275
- for slot, addressable_area in zip(valid_slots, addressable_areas)
1276
- }
1277
- return map_addressable_area[deck_slot.value]
1318
+ raise ValueError(
1319
+ f"Unknown module {model.name} has no addressable areas to provide."
1320
+ )
1321
+
1322
+ def absorbance_reader_dock_location(
1323
+ self, module_id: str
1324
+ ) -> AddressableAreaLocation:
1325
+ """Get the addressable area for the absorbance reader dock."""
1326
+ reader_slot = self.get_location(module_id)
1327
+ lid_doc_slot = get_adjacent_staging_slot(reader_slot.slotName)
1328
+ assert lid_doc_slot is not None
1329
+ lid_dock_area = AddressableAreaLocation(
1330
+ addressableAreaName="absorbanceReaderV1LidDock" + lid_doc_slot.value
1331
+ )
1332
+ return lid_dock_area
@@ -1,8 +1,8 @@
1
1
  """Motion state store and getters."""
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from typing import List, Optional, Union
4
4
 
5
- from opentrons.types import MountType, Point
5
+ from opentrons.types import MountType, Point, StagingSlotName
6
6
  from opentrons.hardware_control.types import CriticalPoint
7
7
  from opentrons.motion_planning.adjacent_slots_getters import (
8
8
  get_east_west_slots,
@@ -10,11 +10,12 @@ from opentrons.motion_planning.adjacent_slots_getters import (
10
10
  )
11
11
  from opentrons import motion_planning
12
12
 
13
- from . import move_types
13
+ from . import _move_types
14
14
  from .. import errors
15
15
  from ..types import (
16
16
  MotorAxis,
17
17
  WellLocation,
18
+ LiquidHandlingWellLocation,
18
19
  CurrentWell,
19
20
  CurrentPipetteLocation,
20
21
  AddressableOffsetVector,
@@ -89,13 +90,14 @@ class MotionView:
89
90
  pipette_id: str,
90
91
  labware_id: str,
91
92
  well_name: str,
92
- well_location: Optional[WellLocation],
93
+ well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]],
93
94
  origin: Point,
94
95
  origin_cp: Optional[CriticalPoint],
95
96
  max_travel_z: float,
96
97
  current_well: Optional[CurrentWell] = None,
97
98
  force_direct: bool = False,
98
99
  minimum_z_height: Optional[float] = None,
100
+ operation_volume: Optional[float] = None,
99
101
  ) -> List[motion_planning.Waypoint]:
100
102
  """Calculate waypoints to a destination that's specified as a well."""
101
103
  location = current_well or self._pipettes.get_current_location()
@@ -107,12 +109,14 @@ class MotionView:
107
109
  destination_cp = CriticalPoint.XY_CENTER
108
110
 
109
111
  destination = self._geometry.get_well_position(
110
- labware_id,
111
- well_name,
112
- well_location,
112
+ labware_id=labware_id,
113
+ well_name=well_name,
114
+ well_location=well_location,
115
+ operation_volume=operation_volume,
116
+ pipette_id=pipette_id,
113
117
  )
114
118
 
115
- move_type = move_types.get_move_type_to_well(
119
+ move_type = _move_types.get_move_type_to_well(
116
120
  pipette_id, labware_id, well_name, location, force_direct
117
121
  )
118
122
  min_travel_z = self._geometry.get_min_travel_z(
@@ -151,6 +155,7 @@ class MotionView:
151
155
  minimum_z_height: Optional[float] = None,
152
156
  stay_at_max_travel_z: bool = False,
153
157
  ignore_tip_configuration: Optional[bool] = True,
158
+ max_travel_z_extra_margin: Optional[float] = None,
154
159
  ) -> List[motion_planning.Waypoint]:
155
160
  """Calculate waypoints to a destination that's specified as an addressable area."""
156
161
  location = self._pipettes.get_current_location()
@@ -169,7 +174,9 @@ class MotionView:
169
174
  # beneath max_travel_z. Investigate why motion_planning.get_waypoints() does not
170
175
  # let us travel at max_travel_z, and whether it's safe to make it do that.
171
176
  # Possibly related: https://github.com/Opentrons/opentrons/pull/6882#discussion_r514248062
172
- max_travel_z - motion_planning.waypoints.MINIMUM_Z_MARGIN,
177
+ max_travel_z
178
+ - motion_planning.waypoints.MINIMUM_Z_MARGIN
179
+ - (max_travel_z_extra_margin or 0.0),
173
180
  )
174
181
  destination = base_destination_at_max_z + Point(
175
182
  offset.x, offset.y, offset.z
@@ -270,9 +277,13 @@ class MotionView:
270
277
  current_location = self._pipettes.get_current_location()
271
278
  if current_location is not None:
272
279
  if isinstance(current_location, CurrentWell):
273
- pipette_deck_slot = self._geometry.get_ancestor_slot_name(
280
+ ancestor = self._geometry.get_ancestor_slot_name(
274
281
  current_location.labware_id
275
- ).as_int()
282
+ )
283
+ if isinstance(ancestor, StagingSlotName):
284
+ # Staging Area Slots cannot intersect with the h/s
285
+ return False
286
+ pipette_deck_slot = ancestor.as_int()
276
287
  else:
277
288
  pipette_deck_slot = (
278
289
  self._addressable_areas.get_addressable_area_base_slot(
@@ -292,9 +303,13 @@ class MotionView:
292
303
  current_location = self._pipettes.get_current_location()
293
304
  if current_location is not None:
294
305
  if isinstance(current_location, CurrentWell):
295
- pipette_deck_slot = self._geometry.get_ancestor_slot_name(
306
+ ancestor = self._geometry.get_ancestor_slot_name(
296
307
  current_location.labware_id
297
- ).as_int()
308
+ )
309
+ if isinstance(ancestor, StagingSlotName):
310
+ # Staging Area Slots cannot intersect with the h/s
311
+ return False
312
+ pipette_deck_slot = ancestor.as_int()
298
313
  else:
299
314
  pipette_deck_slot = (
300
315
  self._addressable_areas.get_addressable_area_base_slot(
@@ -317,6 +332,10 @@ class MotionView:
317
332
  """Get a list of touch points for a touch tip operation."""
318
333
  mount = self._pipettes.get_mount(pipette_id)
319
334
  labware_slot = self._geometry.get_ancestor_slot_name(labware_id)
335
+ if isinstance(labware_slot, StagingSlotName):
336
+ raise errors.LocationIsStagingSlotError(
337
+ "Cannot perform Touch Tip on labware in Staging Area Slot."
338
+ )
320
339
  next_to_module = self._modules.is_edge_move_unsafe(mount, labware_slot)
321
340
  edge_path_type = self._labware.get_edge_path_type(
322
341
  labware_id, well_name, mount, labware_slot, next_to_module
@@ -326,7 +345,7 @@ class MotionView:
326
345
  labware_id, well_name, radius
327
346
  )
328
347
 
329
- positions = move_types.get_edge_point_list(
348
+ positions = _move_types.get_edge_point_list(
330
349
  center_point, x_offset, y_offset, edge_path_type
331
350
  )
332
351
  critical_point: Optional[CriticalPoint] = None