opentrons 8.2.0a0__py2.py3-none-any.whl → 8.2.0a2__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 (47) hide show
  1. opentrons/drivers/absorbance_reader/async_byonoy.py +3 -3
  2. opentrons/hardware_control/ot3api.py +5 -5
  3. opentrons/hardware_control/protocols/position_estimator.py +3 -1
  4. opentrons/legacy_commands/helpers.py +8 -2
  5. opentrons/protocol_api/core/engine/labware.py +10 -2
  6. opentrons/protocol_api/core/engine/module_core.py +38 -1
  7. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +12 -5
  8. opentrons/protocol_api/core/engine/protocol.py +5 -30
  9. opentrons/protocol_api/core/labware.py +4 -0
  10. opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
  11. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -0
  12. opentrons/protocol_api/core/protocol.py +1 -0
  13. opentrons/protocol_api/module_contexts.py +13 -0
  14. opentrons/protocol_api/protocol_context.py +12 -2
  15. opentrons/protocol_engine/actions/__init__.py +0 -2
  16. opentrons/protocol_engine/actions/actions.py +0 -12
  17. opentrons/protocol_engine/clients/sync_client.py +0 -6
  18. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +18 -31
  19. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -7
  20. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +17 -29
  21. opentrons/protocol_engine/commands/load_labware.py +9 -0
  22. opentrons/protocol_engine/commands/load_module.py +0 -39
  23. opentrons/protocol_engine/commands/move_labware.py +49 -4
  24. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +49 -35
  25. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -1
  26. opentrons/protocol_engine/create_protocol_engine.py +18 -1
  27. opentrons/protocol_engine/errors/__init__.py +2 -0
  28. opentrons/protocol_engine/errors/exceptions.py +13 -0
  29. opentrons/protocol_engine/execution/labware_movement.py +69 -21
  30. opentrons/protocol_engine/execution/movement.py +9 -4
  31. opentrons/protocol_engine/protocol_engine.py +0 -7
  32. opentrons/protocol_engine/resources/deck_data_provider.py +0 -39
  33. opentrons/protocol_engine/resources/file_provider.py +11 -7
  34. opentrons/protocol_engine/resources/fixture_validation.py +6 -1
  35. opentrons/protocol_engine/state/geometry.py +91 -49
  36. opentrons/protocol_engine/state/labware.py +102 -25
  37. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +3 -1
  38. opentrons/protocol_engine/state/modules.py +49 -79
  39. opentrons/protocol_engine/state/motion.py +17 -5
  40. opentrons/protocol_engine/state/update_types.py +16 -0
  41. opentrons/util/logging_config.py +1 -1
  42. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/METADATA +4 -4
  43. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/RECORD +47 -47
  44. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/LICENSE +0 -0
  45. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/WHEEL +0 -0
  46. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/entry_points.txt +0 -0
  47. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from typing import (
13
13
  NamedTuple,
14
14
  cast,
15
15
  Union,
16
+ overload,
16
17
  )
17
18
 
18
19
  from opentrons.protocol_engine.state import update_types
@@ -81,6 +82,10 @@ _RIGHT_SIDE_SLOTS = {
81
82
  }
82
83
 
83
84
 
85
+ # The max height of the labware that can fit in a plate reader
86
+ _PLATE_READER_MAX_LABWARE_Z_MM = 16
87
+
88
+
84
89
  class LabwareLoadParams(NamedTuple):
85
90
  """Parameters required to load a labware in Protocol Engine."""
86
91
 
@@ -227,10 +232,11 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
227
232
  if labware_location_update.new_location:
228
233
  new_location = labware_location_update.new_location
229
234
 
230
- if isinstance(
231
- new_location, AddressableAreaLocation
232
- ) and fixture_validation.is_gripper_waste_chute(
233
- new_location.addressableAreaName
235
+ if isinstance(new_location, AddressableAreaLocation) and (
236
+ fixture_validation.is_gripper_waste_chute(
237
+ new_location.addressableAreaName
238
+ )
239
+ or fixture_validation.is_trash(new_location.addressableAreaName)
234
240
  ):
235
241
  # If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
236
242
  new_location = OFF_DECK_LOCATION
@@ -625,10 +631,26 @@ class LabwareView(HasState[LabwareState]):
625
631
  definition = self.get_definition(labware_id)
626
632
  return definition.parameters.loadName
627
633
 
628
- def get_dimensions(self, labware_id: str) -> Dimensions:
634
+ @overload
635
+ def get_dimensions(self, *, labware_definition: LabwareDefinition) -> Dimensions:
636
+ pass
637
+
638
+ @overload
639
+ def get_dimensions(self, *, labware_id: str) -> Dimensions:
640
+ pass
641
+
642
+ def get_dimensions(
643
+ self,
644
+ *,
645
+ labware_definition: LabwareDefinition | None = None,
646
+ labware_id: str | None = None,
647
+ ) -> Dimensions:
629
648
  """Get the labware's dimensions."""
630
- definition = self.get_definition(labware_id)
631
- dims = definition.dimensions
649
+ if labware_definition is None:
650
+ assert labware_id is not None # From our @overloads.
651
+ labware_definition = self.get_definition(labware_id)
652
+
653
+ dims = labware_definition.dimensions
632
654
 
633
655
  return Dimensions(
634
656
  x=dims.xDimension,
@@ -637,10 +659,9 @@ class LabwareView(HasState[LabwareState]):
637
659
  )
638
660
 
639
661
  def get_labware_overlap_offsets(
640
- self, labware_id: str, below_labware_name: str
662
+ self, definition: LabwareDefinition, below_labware_name: str
641
663
  ) -> OverlapOffset:
642
664
  """Get the labware's overlap with requested labware's load name."""
643
- definition = self.get_definition(labware_id)
644
665
  if below_labware_name in definition.stackingOffsetWithLabware.keys():
645
666
  stacking_overlap = definition.stackingOffsetWithLabware.get(
646
667
  below_labware_name, OverlapOffset(x=0, y=0, z=0)
@@ -654,10 +675,9 @@ class LabwareView(HasState[LabwareState]):
654
675
  )
655
676
 
656
677
  def get_module_overlap_offsets(
657
- self, labware_id: str, module_model: ModuleModel
678
+ self, definition: LabwareDefinition, module_model: ModuleModel
658
679
  ) -> OverlapOffset:
659
680
  """Get the labware's overlap with requested module model."""
660
- definition = self.get_definition(labware_id)
661
681
  stacking_overlap = definition.stackingOffsetWithModule.get(
662
682
  str(module_model.value)
663
683
  )
@@ -817,6 +837,24 @@ class LabwareView(HasState[LabwareState]):
817
837
  f"Labware {labware.loadName} is already present at {location}."
818
838
  )
819
839
 
840
+ def raise_if_labware_incompatible_with_plate_reader(
841
+ self,
842
+ labware_definition: LabwareDefinition,
843
+ ) -> None:
844
+ """Raise an error if the labware is not compatible with the plate reader."""
845
+ load_name = labware_definition.parameters.loadName
846
+ number_of_wells = len(labware_definition.wells)
847
+ if number_of_wells != 96:
848
+ raise errors.LabwareMovementNotAllowedError(
849
+ f"Cannot move '{load_name}' into plate reader because the"
850
+ f" labware contains {number_of_wells} wells where 96 wells is expected."
851
+ )
852
+ elif labware_definition.dimensions.zDimension > _PLATE_READER_MAX_LABWARE_Z_MM:
853
+ raise errors.LabwareMovementNotAllowedError(
854
+ f"Cannot move '{load_name}' into plate reader because the"
855
+ f" maximum allowed labware height is {_PLATE_READER_MAX_LABWARE_Z_MM}mm."
856
+ )
857
+
820
858
  def raise_if_labware_cannot_be_stacked( # noqa: C901
821
859
  self, top_labware_definition: LabwareDefinition, bottom_labware_id: str
822
860
  ) -> None:
@@ -900,22 +938,60 @@ class LabwareView(HasState[LabwareState]):
900
938
  else None
901
939
  )
902
940
 
903
- def get_labware_gripper_offsets(
941
+ def get_absorbance_reader_lid_definition(self) -> LabwareDefinition:
942
+ """Return the special labware definition for the plate reader lid.
943
+
944
+ See todo comments in `create_protocol_engine().
945
+ """
946
+ # NOTE: This needs to stay in sync with create_protocol_engine().
947
+ return self._state.definitions_by_uri[
948
+ "opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1"
949
+ ]
950
+
951
+ @overload
952
+ def get_child_gripper_offsets(
904
953
  self,
905
- labware_id: str,
954
+ *,
955
+ labware_definition: LabwareDefinition,
906
956
  slot_name: Optional[DeckSlotName],
907
957
  ) -> Optional[LabwareMovementOffsetData]:
908
- """Get the labware's gripper offsets of the specified type.
958
+ pass
959
+
960
+ @overload
961
+ def get_child_gripper_offsets(
962
+ self, *, labware_id: str, slot_name: Optional[DeckSlotName]
963
+ ) -> Optional[LabwareMovementOffsetData]:
964
+ pass
965
+
966
+ def get_child_gripper_offsets(
967
+ self,
968
+ *,
969
+ labware_definition: Optional[LabwareDefinition] = None,
970
+ labware_id: Optional[str] = None,
971
+ slot_name: Optional[DeckSlotName],
972
+ ) -> Optional[LabwareMovementOffsetData]:
973
+ """Get the grip offsets that a labware says should be applied to children stacked atop it.
974
+
975
+ Params:
976
+ labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked).
977
+ slot_name: The ancestor slot that the parent labware is ultimately loaded into,
978
+ perhaps after going through a module in the middle.
909
979
 
910
980
  Returns:
911
- If `slot_name` is provided, returns the gripper offsets that the labware definition
981
+ If `slot_name` is provided, returns the gripper offsets that the parent labware definition
912
982
  specifies just for that slot, or `None` if the labware definition doesn't have an
913
983
  exact match.
914
984
 
915
- If `slot_name` is `None`, returns the gripper offsets that the labware
985
+ If `slot_name` is `None`, returns the gripper offsets that the parent labware
916
986
  definition designates as "default," or `None` if it doesn't designate any as such.
917
987
  """
918
- parsed_offsets = self.get_definition(labware_id).gripperOffsets
988
+ if labware_id is not None:
989
+ labware_definition = self.get_definition(labware_id)
990
+ else:
991
+ # Should be ensured by our @overloads.
992
+ assert labware_definition is not None
993
+
994
+ parsed_offsets = labware_definition.gripperOffsets
919
995
  offset_key = slot_name.id if slot_name else "default"
920
996
 
921
997
  if parsed_offsets is None or offset_key not in parsed_offsets:
@@ -930,20 +1006,22 @@ class LabwareView(HasState[LabwareState]):
930
1006
  ),
931
1007
  )
932
1008
 
933
- def get_grip_force(self, labware_id: str) -> float:
1009
+ def get_grip_force(self, labware_definition: LabwareDefinition) -> float:
934
1010
  """Get the recommended grip force for gripping labware using gripper."""
935
- recommended_force = self.get_definition(labware_id).gripForce
1011
+ recommended_force = labware_definition.gripForce
936
1012
  return (
937
1013
  recommended_force if recommended_force is not None else LABWARE_GRIP_FORCE
938
1014
  )
939
1015
 
940
- def get_grip_height_from_labware_bottom(self, labware_id: str) -> float:
1016
+ def get_grip_height_from_labware_bottom(
1017
+ self, labware_definition: LabwareDefinition
1018
+ ) -> float:
941
1019
  """Get the recommended grip height from labware bottom, if present."""
942
- recommended_height = self.get_definition(labware_id).gripHeightFromLabwareBottom
1020
+ recommended_height = labware_definition.gripHeightFromLabwareBottom
943
1021
  return (
944
1022
  recommended_height
945
1023
  if recommended_height is not None
946
- else self.get_dimensions(labware_id).z / 2
1024
+ else self.get_dimensions(labware_definition=labware_definition).z / 2
947
1025
  )
948
1026
 
949
1027
  @staticmethod
@@ -986,7 +1064,7 @@ class LabwareView(HasState[LabwareState]):
986
1064
  def _max_z_of_well(well_defn: WellDefinition) -> float:
987
1065
  return well_defn.z + well_defn.depth
988
1066
 
989
- def get_well_bbox(self, labware_id: str) -> Dimensions:
1067
+ def get_well_bbox(self, labware_definition: LabwareDefinition) -> Dimensions:
990
1068
  """Get the bounding box implied by the wells.
991
1069
 
992
1070
  The bounding box of the labware that is implied by the wells is that required
@@ -997,14 +1075,13 @@ class LabwareView(HasState[LabwareState]):
997
1075
  This is used for the specific purpose of finding the reasonable uncertainty bounds of
998
1076
  where and how a gripper will interact with a labware.
999
1077
  """
1000
- defn = self.get_definition(labware_id)
1001
1078
  max_x: Optional[float] = None
1002
1079
  min_x: Optional[float] = None
1003
1080
  max_y: Optional[float] = None
1004
1081
  min_y: Optional[float] = None
1005
1082
  max_z: Optional[float] = None
1006
1083
 
1007
- for well in defn.wells.values():
1084
+ for well in labware_definition.wells.values():
1008
1085
  well_max_x = self._max_x_of_well(well)
1009
1086
  well_min_x = self._min_x_of_well(well)
1010
1087
  well_max_y = self._max_y_of_well(well)
@@ -9,6 +9,9 @@ AbsorbanceReaderLidId = NewType("AbsorbanceReaderLidId", str)
9
9
  AbsorbanceReaderMeasureMode = NewType("AbsorbanceReaderMeasureMode", str)
10
10
 
11
11
 
12
+ # todo(mm, 2024-11-08): frozen=True is getting pretty painful because ModuleStore has
13
+ # no type-safe way to modify just a single attribute. Consider unfreezing this
14
+ # (taking care to ensure that consumers of ModuleView still only get a read-only view).
12
15
  @dataclass(frozen=True)
13
16
  class AbsorbanceReaderSubState:
14
17
  """Absorbance-Plate-Reader-specific state."""
@@ -21,7 +24,6 @@ class AbsorbanceReaderSubState:
21
24
  configured_wavelengths: Optional[List[int]]
22
25
  measure_mode: Optional[AbsorbanceReaderMeasureMode]
23
26
  reference_wavelength: Optional[int]
24
- lid_id: Optional[str]
25
27
 
26
28
  def raise_if_lid_status_not_expected(self, lid_on_expected: bool) -> None:
27
29
  """Raise if the lid status is not correct."""
@@ -26,13 +26,15 @@ from opentrons.motion_planning.adjacent_slots_getters import (
26
26
  get_west_slot,
27
27
  get_adjacent_staging_slot,
28
28
  )
29
+ from opentrons.protocol_engine.actions.get_state_update import get_state_updates
29
30
  from opentrons.protocol_engine.commands.calibration.calibrate_module import (
30
31
  CalibrateModuleResult,
31
32
  )
33
+ from opentrons.protocol_engine.state import update_types
32
34
  from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate import (
33
35
  AbsorbanceReaderMeasureMode,
34
36
  )
35
- from opentrons.types import DeckSlotName, MountType
37
+ from opentrons.types import DeckSlotName, MountType, StagingSlotName
36
38
  from ..errors import ModuleNotConnectedError
37
39
 
38
40
  from ..types import (
@@ -67,7 +69,6 @@ from ..actions import (
67
69
  Action,
68
70
  SucceedCommandAction,
69
71
  AddModuleAction,
70
- AddAbsorbanceReaderLidAction,
71
72
  )
72
73
  from ._abstract_store import HasState, HandlesActions
73
74
  from .module_substates import (
@@ -234,13 +235,14 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
234
235
  requested_model=None,
235
236
  module_live_data=action.module_live_data,
236
237
  )
237
- elif isinstance(action, AddAbsorbanceReaderLidAction):
238
- self._update_absorbance_reader_lid_id(
239
- module_id=action.module_id,
240
- lid_id=action.lid_id,
241
- )
238
+
239
+ for state_update in get_state_updates(action):
240
+ self._handle_state_update(state_update)
242
241
 
243
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
+
244
246
  if isinstance(command.result, LoadModuleResult):
245
247
  slot_name = command.params.location.slotName
246
248
  self._add_module_substate(
@@ -297,38 +299,40 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
297
299
  if isinstance(
298
300
  command.result,
299
301
  (
300
- absorbance_reader.CloseLidResult,
301
- absorbance_reader.OpenLidResult,
302
302
  absorbance_reader.InitializeResult,
303
303
  absorbance_reader.ReadAbsorbanceResult,
304
304
  ),
305
305
  ):
306
306
  self._handle_absorbance_reader_commands(command)
307
307
 
308
- def _update_absorbance_reader_lid_id(
309
- self,
310
- module_id: str,
311
- lid_id: str,
312
- ) -> None:
313
- abs_substate = self._state.substate_by_module_id.get(module_id)
314
- assert isinstance(
315
- abs_substate, AbsorbanceReaderSubState
316
- ), f"{module_id} is not an absorbance plate reader."
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
317
323
 
318
- prev_state: AbsorbanceReaderSubState = abs_substate
319
- self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
320
- module_id=AbsorbanceReaderId(module_id),
321
- configured=prev_state.configured,
322
- measured=prev_state.measured,
323
- is_lid_on=prev_state.is_lid_on,
324
- data=prev_state.data,
325
- measure_mode=prev_state.measure_mode,
326
- configured_wavelengths=prev_state.configured_wavelengths,
327
- reference_wavelength=prev_state.reference_wavelength,
328
- lid_id=lid_id,
329
- )
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
+ )
330
334
 
331
- def _add_module_substate( # noqa: C901
335
+ def _add_module_substate(
332
336
  self,
333
337
  module_id: str,
334
338
  serial_number: Optional[str],
@@ -387,16 +391,6 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
387
391
  module_id=MagneticBlockId(module_id)
388
392
  )
389
393
  elif ModuleModel.is_absorbance_reader(actual_model):
390
- lid_labware_id = None
391
- slot = self._state.slot_by_module_id[module_id]
392
- if slot is not None:
393
- reader_addressable_area = f"absorbanceReaderV1{slot.value}"
394
- for labware in self._state.deck_fixed_labware:
395
- if labware.location == AddressableAreaLocation(
396
- addressableAreaName=reader_addressable_area
397
- ):
398
- lid_labware_id = labware.labware_id
399
- break
400
394
  self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
401
395
  module_id=AbsorbanceReaderId(module_id),
402
396
  configured=False,
@@ -406,7 +400,6 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
406
400
  measure_mode=None,
407
401
  configured_wavelengths=None,
408
402
  reference_wavelength=None,
409
- lid_id=lid_labware_id,
410
403
  )
411
404
 
412
405
  def _update_additional_slots_occupied_by_thermocycler(
@@ -600,8 +593,6 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
600
593
  command: Union[
601
594
  absorbance_reader.Initialize,
602
595
  absorbance_reader.ReadAbsorbance,
603
- absorbance_reader.CloseLid,
604
- absorbance_reader.OpenLid,
605
596
  ],
606
597
  ) -> None:
607
598
  module_id = command.params.moduleId
@@ -616,8 +607,6 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
616
607
  configured_wavelengths = absorbance_reader_substate.configured_wavelengths
617
608
  reference_wavelength = absorbance_reader_substate.reference_wavelength
618
609
  is_lid_on = absorbance_reader_substate.is_lid_on
619
- lid_id = absorbance_reader_substate.lid_id
620
- data = absorbance_reader_substate.data
621
610
 
622
611
  if isinstance(command.result, absorbance_reader.InitializeResult):
623
612
  self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
@@ -625,7 +614,6 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
625
614
  configured=True,
626
615
  measured=False,
627
616
  is_lid_on=is_lid_on,
628
- lid_id=lid_id,
629
617
  measure_mode=AbsorbanceReaderMeasureMode(command.params.measureMode),
630
618
  configured_wavelengths=command.params.sampleWavelengths,
631
619
  reference_wavelength=command.params.referenceWavelength,
@@ -637,39 +625,12 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
637
625
  configured=configured,
638
626
  measured=True,
639
627
  is_lid_on=is_lid_on,
640
- lid_id=lid_id,
641
628
  measure_mode=measure_mode,
642
629
  configured_wavelengths=configured_wavelengths,
643
630
  reference_wavelength=reference_wavelength,
644
631
  data=command.result.data,
645
632
  )
646
633
 
647
- elif isinstance(command.result, absorbance_reader.OpenLidResult):
648
- self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
649
- module_id=AbsorbanceReaderId(module_id),
650
- configured=configured,
651
- measured=True,
652
- is_lid_on=False,
653
- lid_id=lid_id,
654
- measure_mode=measure_mode,
655
- configured_wavelengths=configured_wavelengths,
656
- reference_wavelength=reference_wavelength,
657
- data=data,
658
- )
659
-
660
- elif isinstance(command.result, absorbance_reader.CloseLidResult):
661
- self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
662
- module_id=AbsorbanceReaderId(module_id),
663
- configured=configured,
664
- measured=True,
665
- is_lid_on=True,
666
- lid_id=lid_id,
667
- measure_mode=measure_mode,
668
- configured_wavelengths=configured_wavelengths,
669
- reference_wavelength=reference_wavelength,
670
- data=data,
671
- )
672
-
673
634
 
674
635
  class ModuleView(HasState[ModuleState]):
675
636
  """Read-only view of computed module state."""
@@ -883,12 +844,21 @@ class ModuleView(HasState[ModuleState]):
883
844
  """Get the specified module's dimensions."""
884
845
  return self.get_definition(module_id).dimensions
885
846
 
886
- def get_nominal_module_offset(
847
+ def get_nominal_offset_to_child(
887
848
  self,
888
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?
889
855
  addressable_areas: AddressableAreaView,
890
856
  ) -> LabwareOffsetVector:
891
- """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
+ """
892
862
  if (
893
863
  self.state.deck_type == DeckType.OT2_STANDARD
894
864
  or self.state.deck_type == DeckType.OT2_SHORT_TRASH
@@ -996,7 +966,7 @@ class ModuleView(HasState[ModuleState]):
996
966
  default_lw_offset_point = self.get_definition(module_id).labwareOffset.z
997
967
  z_difference = module_height - default_lw_offset_point
998
968
 
999
- nominal_transformed_lw_offset_z = self.get_nominal_module_offset(
969
+ nominal_transformed_lw_offset_z = self.get_nominal_offset_to_child(
1000
970
  module_id=module_id, addressable_areas=addressable_areas
1001
971
  ).z
1002
972
  calibration_offset = self.get_module_calibration_offset(module_id)
@@ -1124,8 +1094,8 @@ class ModuleView(HasState[ModuleState]):
1124
1094
 
1125
1095
  def should_dodge_thermocycler(
1126
1096
  self,
1127
- from_slot: DeckSlotName,
1128
- to_slot: DeckSlotName,
1097
+ from_slot: Union[DeckSlotName, StagingSlotName],
1098
+ to_slot: Union[DeckSlotName, StagingSlotName],
1129
1099
  ) -> bool:
1130
1100
  """Decide if the requested path would cross the thermocycler, if installed.
1131
1101
 
@@ -2,7 +2,7 @@
2
2
  from dataclasses import dataclass
3
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,
@@ -277,9 +277,13 @@ class MotionView:
277
277
  current_location = self._pipettes.get_current_location()
278
278
  if current_location is not None:
279
279
  if isinstance(current_location, CurrentWell):
280
- pipette_deck_slot = self._geometry.get_ancestor_slot_name(
280
+ ancestor = self._geometry.get_ancestor_slot_name(
281
281
  current_location.labware_id
282
- ).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()
283
287
  else:
284
288
  pipette_deck_slot = (
285
289
  self._addressable_areas.get_addressable_area_base_slot(
@@ -299,9 +303,13 @@ class MotionView:
299
303
  current_location = self._pipettes.get_current_location()
300
304
  if current_location is not None:
301
305
  if isinstance(current_location, CurrentWell):
302
- pipette_deck_slot = self._geometry.get_ancestor_slot_name(
306
+ ancestor = self._geometry.get_ancestor_slot_name(
303
307
  current_location.labware_id
304
- ).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()
305
313
  else:
306
314
  pipette_deck_slot = (
307
315
  self._addressable_areas.get_addressable_area_base_slot(
@@ -324,6 +332,10 @@ class MotionView:
324
332
  """Get a list of touch points for a touch tip operation."""
325
333
  mount = self._pipettes.get_mount(pipette_id)
326
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
+ )
327
339
  next_to_module = self._modules.is_edge_move_unsafe(mount, labware_slot)
328
340
  edge_path_type = self._labware.get_edge_path_type(
329
341
  labware_id, well_name, mount, labware_slot, next_to_module
@@ -205,6 +205,14 @@ class LiquidOperatedUpdate:
205
205
  volume_added: float | ClearType
206
206
 
207
207
 
208
+ @dataclasses.dataclass
209
+ class AbsorbanceReaderLidUpdate:
210
+ """An update to an absorbance reader's lid location."""
211
+
212
+ module_id: str
213
+ is_lid_on: bool
214
+
215
+
208
216
  @dataclasses.dataclass
209
217
  class StateUpdate:
210
218
  """Represents an update to perform on engine state."""
@@ -231,6 +239,8 @@ class StateUpdate:
231
239
 
232
240
  liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE
233
241
 
242
+ absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE
243
+
234
244
  # These convenience functions let the caller avoid the boilerplate of constructing a
235
245
  # complicated dataclass tree.
236
246
 
@@ -406,3 +416,9 @@ class StateUpdate:
406
416
  well_name=well_name,
407
417
  volume_added=volume_added,
408
418
  )
419
+
420
+ def set_absorbance_reader_lid(self, module_id: str, is_lid_on: bool) -> None:
421
+ """Update an absorbance reader's lid location. See `AbsorbanceReaderLidUpdate`."""
422
+ self.absorbance_reader_lid = AbsorbanceReaderLidUpdate(
423
+ module_id=module_id, is_lid_on=is_lid_on
424
+ )
@@ -5,7 +5,7 @@ from typing import Any, Dict
5
5
 
6
6
  from opentrons.config import CONFIG, ARCHITECTURE, SystemArchitecture
7
7
 
8
- if ARCHITECTURE is SystemArchitecture.BUILDROOT:
8
+ if ARCHITECTURE is SystemArchitecture.YOCTO:
9
9
  from opentrons_hardware.sensors import SENSOR_LOG_NAME
10
10
  else:
11
11
  # we don't use the sensor log on ot2 or host
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opentrons
3
- Version: 8.2.0a0
3
+ Version: 8.2.0a2
4
4
  Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
5
5
  Author: Opentrons
6
6
  Author-email: engineering@opentrons.com
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Topic :: Scientific/Engineering
22
22
  Requires-Python: >=3.10
23
23
  License-File: ../LICENSE
24
- Requires-Dist: opentrons-shared-data ==8.2.0a0
24
+ Requires-Dist: opentrons-shared-data ==8.2.0a2
25
25
  Requires-Dist: aionotify ==0.3.1
26
26
  Requires-Dist: anyio <4.0.0,>=3.6.1
27
27
  Requires-Dist: jsonschema <4.18.0,>=3.0.1
@@ -34,9 +34,9 @@ Requires-Dist: pyusb ==1.2.1
34
34
  Requires-Dist: packaging >=21.0
35
35
  Requires-Dist: importlib-metadata >=1.0 ; python_version < "3.8"
36
36
  Provides-Extra: flex-hardware
37
- Requires-Dist: opentrons-hardware[flex] ==8.2.0a0 ; extra == 'flex-hardware'
37
+ Requires-Dist: opentrons-hardware[flex] ==8.2.0a2 ; extra == 'flex-hardware'
38
38
  Provides-Extra: ot2-hardware
39
- Requires-Dist: opentrons-hardware ==8.2.0a0 ; extra == 'ot2-hardware'
39
+ Requires-Dist: opentrons-hardware ==8.2.0a2 ; extra == 'ot2-hardware'
40
40
 
41
41
  .. _Full API Documentation: http://docs.opentrons.com
42
42