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

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

Potentially problematic release.


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

Files changed (62) hide show
  1. opentrons/drivers/absorbance_reader/async_byonoy.py +5 -6
  2. opentrons/hardware_control/backends/ot3utils.py +0 -1
  3. opentrons/hardware_control/modules/absorbance_reader.py +0 -2
  4. opentrons/hardware_control/ot3api.py +5 -5
  5. opentrons/hardware_control/protocols/position_estimator.py +1 -3
  6. opentrons/hardware_control/types.py +0 -2
  7. opentrons/legacy_commands/helpers.py +2 -8
  8. opentrons/protocol_api/core/engine/labware.py +2 -10
  9. opentrons/protocol_api/core/engine/module_core.py +1 -38
  10. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +5 -12
  11. opentrons/protocol_api/core/engine/protocol.py +30 -5
  12. opentrons/protocol_api/core/labware.py +0 -4
  13. opentrons/protocol_api/core/legacy/legacy_labware_core.py +0 -5
  14. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +0 -1
  15. opentrons/protocol_api/core/protocol.py +0 -1
  16. opentrons/protocol_api/module_contexts.py +26 -69
  17. opentrons/protocol_api/protocol_context.py +2 -12
  18. opentrons/protocol_engine/actions/__init__.py +2 -0
  19. opentrons/protocol_engine/actions/actions.py +12 -0
  20. opentrons/protocol_engine/clients/sync_client.py +6 -0
  21. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +31 -18
  22. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +7 -19
  23. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +29 -17
  24. opentrons/protocol_engine/commands/absorbance_reader/read.py +0 -4
  25. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -3
  26. opentrons/protocol_engine/commands/command.py +1 -3
  27. opentrons/protocol_engine/commands/dispense_in_place.py +1 -1
  28. opentrons/protocol_engine/commands/drop_tip.py +1 -2
  29. opentrons/protocol_engine/commands/drop_tip_in_place.py +2 -7
  30. opentrons/protocol_engine/commands/load_labware.py +0 -9
  31. opentrons/protocol_engine/commands/load_module.py +39 -0
  32. opentrons/protocol_engine/commands/move_labware.py +4 -49
  33. opentrons/protocol_engine/commands/pick_up_tip.py +1 -1
  34. opentrons/protocol_engine/commands/pipetting_common.py +1 -8
  35. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +35 -49
  36. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -3
  37. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -5
  38. opentrons/protocol_engine/create_protocol_engine.py +1 -18
  39. opentrons/protocol_engine/errors/__init__.py +0 -2
  40. opentrons/protocol_engine/errors/error_occurrence.py +3 -8
  41. opentrons/protocol_engine/errors/exceptions.py +0 -13
  42. opentrons/protocol_engine/execution/labware_movement.py +21 -69
  43. opentrons/protocol_engine/execution/movement.py +4 -9
  44. opentrons/protocol_engine/protocol_engine.py +7 -0
  45. opentrons/protocol_engine/resources/deck_data_provider.py +39 -0
  46. opentrons/protocol_engine/resources/file_provider.py +7 -11
  47. opentrons/protocol_engine/resources/fixture_validation.py +1 -6
  48. opentrons/protocol_engine/state/geometry.py +49 -91
  49. opentrons/protocol_engine/state/labware.py +25 -102
  50. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +1 -3
  51. opentrons/protocol_engine/state/modules.py +80 -53
  52. opentrons/protocol_engine/state/motion.py +5 -17
  53. opentrons/protocol_engine/state/update_types.py +0 -16
  54. opentrons/protocol_runner/run_orchestrator.py +0 -15
  55. opentrons/protocols/parameters/csv_parameter_interface.py +1 -3
  56. opentrons/util/logging_config.py +1 -1
  57. {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +4 -4
  58. {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +62 -62
  59. {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
  60. {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
  61. {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
  62. {opentrons-8.2.0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,9 @@
1
1
  """Place labware payload, result, and implementaiton."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import TYPE_CHECKING, Optional, Type
5
- from typing_extensions import Literal
6
-
7
- from opentrons_shared_data.labware.types import LabwareUri
8
- from opentrons_shared_data.labware.labware_definition import LabwareDefinition
9
4
  from pydantic import BaseModel, Field
5
+ from typing import TYPE_CHECKING, Optional, Type, cast
6
+ from typing_extensions import Literal
10
7
 
11
8
  from opentrons.hardware_control.types import Axis, OT3Mount
12
9
  from opentrons.motion_planning.waypoints import get_gripper_labware_placement_waypoints
@@ -16,14 +13,11 @@ from opentrons.protocol_engine.errors.exceptions import (
16
13
  )
17
14
  from opentrons.types import Point
18
15
 
19
- from ...types import (
20
- DeckSlotLocation,
21
- ModuleModel,
22
- OnDeckLabwareLocation,
23
- )
16
+ from ...types import DeckSlotLocation, ModuleModel, OnDeckLabwareLocation
24
17
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
25
18
  from ...errors.error_occurrence import ErrorOccurrence
26
19
  from ...resources import ensure_ot3_hardware
20
+ from ...state.update_types import StateUpdate
27
21
 
28
22
  from opentrons.hardware_control import HardwareControlAPI, OT3HardwareControlAPI
29
23
 
@@ -38,7 +32,7 @@ UnsafePlaceLabwareCommandType = Literal["unsafe/placeLabware"]
38
32
  class UnsafePlaceLabwareParams(BaseModel):
39
33
  """Payload required for an UnsafePlaceLabware command."""
40
34
 
41
- labwareURI: str = Field(..., description="Labware URI for labware.")
35
+ labwareId: str = Field(..., description="The id of the labware to place.")
42
36
  location: OnDeckLabwareLocation = Field(
43
37
  ..., description="Where to place the labware."
44
38
  )
@@ -77,8 +71,8 @@ class UnsafePlaceLabwareImplementation(
77
71
  is pressed, get into error recovery, etc).
78
72
 
79
73
  Unlike the `moveLabware` command, where you pick a source and destination
80
- location, this command takes the labwareURI of the labware to be moved
81
- and location to move it to.
74
+ location, this command takes the labwareId to be moved and location to
75
+ move it to.
82
76
 
83
77
  """
84
78
  ot3api = ensure_ot3_hardware(self._hardware_api)
@@ -90,37 +84,23 @@ class UnsafePlaceLabwareImplementation(
90
84
  "Cannot place labware when gripper is not gripping."
91
85
  )
92
86
 
93
- location = self._state_view.geometry.ensure_valid_gripper_location(
94
- params.location,
95
- )
96
-
97
- definition = self._state_view.labware.get_definition_by_uri(
98
- # todo(mm, 2024-11-07): This is an unsafe cast from untrusted input.
99
- # We need a str -> LabwareUri parse/validate function.
100
- LabwareUri(params.labwareURI)
101
- )
102
-
103
- # todo(mm, 2024-11-06): This is only correct in the special case of an
104
- # absorbance reader lid. Its definition currently puts the offsets for *itself*
105
- # in the property that's normally meant for offsets for its *children.*
106
- final_offsets = self._state_view.labware.get_child_gripper_offsets(
107
- labware_definition=definition, slot_name=None
108
- )
109
- drop_offset = (
110
- Point(
111
- final_offsets.dropOffset.x,
112
- final_offsets.dropOffset.y,
113
- final_offsets.dropOffset.z,
114
- )
115
- if final_offsets
116
- else None
87
+ # Allow propagation of LabwareNotLoadedError.
88
+ labware_id = params.labwareId
89
+ definition_uri = self._state_view.labware.get(labware_id).definitionUri
90
+ final_offsets = self._state_view.labware.get_labware_gripper_offsets(
91
+ labware_id, None
117
92
  )
93
+ drop_offset = cast(Point, final_offsets.dropOffset) if final_offsets else None
118
94
 
119
95
  if isinstance(params.location, DeckSlotLocation):
120
96
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
121
97
  params.location.slotName.id
122
98
  )
123
99
 
100
+ location = self._state_view.geometry.ensure_valid_gripper_location(
101
+ params.location,
102
+ )
103
+
124
104
  # This is an absorbance reader, move the lid to its dock (staging area).
125
105
  if isinstance(location, DeckSlotLocation):
126
106
  module = self._state_view.modules.get_by_slot(location.slotName)
@@ -129,24 +109,30 @@ class UnsafePlaceLabwareImplementation(
129
109
  module.id
130
110
  )
131
111
 
132
- # NOTE: When the estop is pressed, the gantry loses position, lets use
133
- # the encoders to sync position.
134
- # Ideally, we'd do a full home, but this command is used when
135
- # the gripper is holding the plate reader, and a full home would
136
- # bang it into the right window.
137
- await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G])
138
- await ot3api.engage_axes([Axis.X, Axis.Y])
139
- await ot3api.update_axis_position_estimations([Axis.X, Axis.Y])
112
+ new_offset_id = self._equipment.find_applicable_labware_offset_id(
113
+ labware_definition_uri=definition_uri,
114
+ labware_location=location,
115
+ )
116
+
117
+ # NOTE: When the estop is pressed, the gantry loses position,
118
+ # so the robot needs to home x, y to sync.
119
+ await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G, Axis.X, Axis.Y])
120
+ state_update = StateUpdate()
140
121
 
141
122
  # Place the labware down
142
- await self._start_movement(ot3api, definition, location, drop_offset)
123
+ await self._start_movement(ot3api, labware_id, location, drop_offset)
143
124
 
144
- return SuccessData(public=UnsafePlaceLabwareResult())
125
+ state_update.set_labware_location(
126
+ labware_id=labware_id,
127
+ new_location=location,
128
+ new_offset_id=new_offset_id,
129
+ )
130
+ return SuccessData(public=UnsafePlaceLabwareResult(), state_update=state_update)
145
131
 
146
132
  async def _start_movement(
147
133
  self,
148
134
  ot3api: OT3HardwareControlAPI,
149
- labware_definition: LabwareDefinition,
135
+ labware_id: str,
150
136
  location: OnDeckLabwareLocation,
151
137
  drop_offset: Optional[Point],
152
138
  ) -> None:
@@ -156,7 +142,7 @@ class UnsafePlaceLabwareImplementation(
156
142
  )
157
143
 
158
144
  to_labware_center = self._state_view.geometry.get_labware_grip_point(
159
- labware_definition=labware_definition, location=location
145
+ labware_id=labware_id, location=location
160
146
  )
161
147
 
162
148
  movement_waypoints = get_gripper_labware_placement_waypoints(
@@ -1,8 +1,6 @@
1
1
  """Ungrip labware payload, result, and implementaiton."""
2
2
 
3
3
  from __future__ import annotations
4
-
5
- from opentrons.hardware_control.types import Axis
6
4
  from opentrons.protocol_engine.errors.exceptions import GripperNotAttachedError
7
5
  from pydantic import BaseModel
8
6
  from typing import Optional, Type
@@ -48,7 +46,7 @@ class UnsafeUngripLabwareImplementation(
48
46
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
49
47
  if not ot3_hardware_api.has_gripper():
50
48
  raise GripperNotAttachedError("No gripper found to perform ungrip.")
51
- await ot3_hardware_api.home([Axis.G])
49
+ await ot3_hardware_api.ungrip()
52
50
  return SuccessData(
53
51
  public=UnsafeUngripLabwareResult(),
54
52
  )
@@ -23,11 +23,7 @@ class UpdatePositionEstimatorsParams(BaseModel):
23
23
  """Payload required for an UpdatePositionEstimators command."""
24
24
 
25
25
  axes: List[MotorAxis] = Field(
26
- ...,
27
- description=(
28
- "The axes for which to update the position estimators."
29
- " Any axes that are not physically present will be ignored."
30
- ),
26
+ ..., description="The axes for which to update the position estimators."
31
27
  )
32
28
 
33
29
 
@@ -8,9 +8,6 @@ from opentrons.hardware_control.types import DoorState
8
8
  from opentrons.protocol_engine.execution.error_recovery_hardware_state_synchronizer import (
9
9
  ErrorRecoveryHardwareStateSynchronizer,
10
10
  )
11
- from opentrons.protocol_engine.resources.labware_data_provider import (
12
- LabwareDataProvider,
13
- )
14
11
  from opentrons.util.async_helpers import async_context_manager_in_thread
15
12
 
16
13
  from opentrons_shared_data.robot import load as load_robot
@@ -84,7 +81,7 @@ async def create_protocol_engine(
84
81
  module_data_provider = ModuleDataProvider()
85
82
  file_provider = file_provider or FileProvider()
86
83
 
87
- pe = ProtocolEngine(
84
+ return ProtocolEngine(
88
85
  hardware_api=hardware_api,
89
86
  state_store=state_store,
90
87
  action_dispatcher=action_dispatcher,
@@ -96,20 +93,6 @@ async def create_protocol_engine(
96
93
  file_provider=file_provider,
97
94
  )
98
95
 
99
- # todo(mm, 2024-11-08): This is a quick hack to support the absorbance reader, which
100
- # expects the engine to have this special labware definition available. It would be
101
- # cleaner for the `loadModule` command to do this I/O and insert the definition
102
- # into state. That gets easier after https://opentrons.atlassian.net/browse/EXEC-756.
103
- #
104
- # NOTE: This needs to stay in sync with LabwareView.get_absorbance_reader_lid_definition().
105
- pe.add_labware_definition(
106
- await LabwareDataProvider().get_labware_definition(
107
- "opentrons_flex_lid_absorbance_plate_reader_module", "opentrons", 1
108
- )
109
- )
110
-
111
- return pe
112
-
113
96
 
114
97
  @contextlib.contextmanager
115
98
  def create_protocol_engine_in_thread(
@@ -55,7 +55,6 @@ from .exceptions import (
55
55
  InvalidTargetTemperatureError,
56
56
  InvalidBlockVolumeError,
57
57
  InvalidHoldTimeError,
58
- InvalidWavelengthError,
59
58
  CannotPerformModuleAction,
60
59
  PauseNotAllowedError,
61
60
  ResumeFromRecoveryNotAllowedError,
@@ -138,7 +137,6 @@ __all__ = [
138
137
  "InvalidTargetSpeedError",
139
138
  "InvalidBlockVolumeError",
140
139
  "InvalidHoldTimeError",
141
- "InvalidWavelengthError",
142
140
  "CannotPerformModuleAction",
143
141
  "ResumeFromRecoveryNotAllowedError",
144
142
  "PauseNotAllowedError",
@@ -12,6 +12,8 @@ from opentrons_shared_data.errors.exceptions import EnumeratedError
12
12
  log = getLogger(__name__)
13
13
 
14
14
 
15
+ # TODO(mc, 2021-11-12): flesh this model out with structured error data
16
+ # for each error type so client may produce better error messages
15
17
  class ErrorOccurrence(BaseModel):
16
18
  """An occurrence of a specific error during protocol execution."""
17
19
 
@@ -42,15 +44,8 @@ class ErrorOccurrence(BaseModel):
42
44
  id: str = Field(..., description="Unique identifier of this error occurrence.")
43
45
  createdAt: datetime = Field(..., description="When the error occurred.")
44
46
 
45
- # Our Python should probably always set this to False--if we want it to be True,
46
- # we should probably be using a more specific subclass of ErrorOccurrence anyway.
47
- # However, we can't make this Literal[False], because we want this class to be able
48
- # to act as a catch-all for parsing defined errors that might be missing some
49
- # `errorInfo` fields because they were serialized by older software.
50
47
  isDefined: bool = Field(
51
- # default=False for database backwards compatibility, so we can parse objects
52
- # serialized before isDefined existed.
53
- default=False,
48
+ default=False, # default=False for database backwards compatibility.
54
49
  description=dedent(
55
50
  """\
56
51
  Whether this error is *defined.*
@@ -773,19 +773,6 @@ class InvalidBlockVolumeError(ProtocolEngineError):
773
773
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
774
774
 
775
775
 
776
- class InvalidWavelengthError(ProtocolEngineError):
777
- """Raised when attempting to set an invalid absorbance wavelength."""
778
-
779
- def __init__(
780
- self,
781
- message: Optional[str] = None,
782
- details: Optional[Dict[str, Any]] = None,
783
- wrapping: Optional[Sequence[EnumeratedError]] = None,
784
- ) -> None:
785
- """Build a InvalidWavelengthError."""
786
- super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
787
-
788
-
789
776
  class InvalidHoldTimeError(ProtocolEngineError):
790
777
  """An error raised when attempting to set an invalid temperature hold time."""
791
778
 
@@ -1,9 +1,7 @@
1
1
  """Labware movement command handling."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Optional, TYPE_CHECKING, overload
5
-
6
- from opentrons_shared_data.labware.labware_definition import LabwareDefinition
4
+ from typing import Optional, TYPE_CHECKING
7
5
 
8
6
  from opentrons.types import Point
9
7
 
@@ -81,64 +79,24 @@ class LabwareMovementHandler:
81
79
  )
82
80
  )
83
81
 
84
- @overload
85
82
  async def move_labware_with_gripper(
86
83
  self,
87
- *,
88
84
  labware_id: str,
89
85
  current_location: OnDeckLabwareLocation,
90
86
  new_location: OnDeckLabwareLocation,
91
87
  user_offset_data: LabwareMovementOffsetData,
92
88
  post_drop_slide_offset: Optional[Point],
93
89
  ) -> None:
94
- ...
95
-
96
- @overload
97
- async def move_labware_with_gripper(
98
- self,
99
- *,
100
- labware_definition: LabwareDefinition,
101
- current_location: OnDeckLabwareLocation,
102
- new_location: OnDeckLabwareLocation,
103
- user_offset_data: LabwareMovementOffsetData,
104
- post_drop_slide_offset: Optional[Point],
105
- ) -> None:
106
- ...
107
-
108
- async def move_labware_with_gripper( # noqa: C901
109
- self,
110
- *,
111
- labware_id: str | None = None,
112
- labware_definition: LabwareDefinition | None = None,
113
- current_location: OnDeckLabwareLocation,
114
- new_location: OnDeckLabwareLocation,
115
- user_offset_data: LabwareMovementOffsetData,
116
- post_drop_slide_offset: Optional[Point],
117
- ) -> None:
118
- """Physically move a labware from one location to another using the gripper.
119
-
120
- Generally, provide the `labware_id` of a loaded labware, and this method will
121
- automatically look up its labware definition. If you're physically moving
122
- something that has not been loaded as a labware (this is not common),
123
- provide the `labware_definition` yourself instead.
124
- """
90
+ """Move a loaded labware from one location to another using gripper."""
125
91
  use_virtual_gripper = self._state_store.config.use_virtual_gripper
126
92
 
127
- if labware_definition is None:
128
- assert labware_id is not None # From this method's @typing.overloads.
129
- labware_definition = self._state_store.labware.get_definition(labware_id)
130
-
131
93
  if use_virtual_gripper:
132
- # todo(mm, 2024-11-07): We should do this collision checking even when we
133
- # only have a `labware_definition`, not a `labware_id`. Resolve when
134
- # `check_gripper_labware_tip_collision()` can be made independent of `labware_id`.
135
- if labware_id is not None:
136
- self._state_store.geometry.check_gripper_labware_tip_collision(
137
- # During Analysis we will pass in hard coded estimates for certain positions only accessible during execution
138
- gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z,
139
- labware_id=labware_id,
140
- current_location=current_location,
141
- )
94
+ # During Analysis we will pass in hard coded estimates for certain positions only accessible during execution
95
+ self._state_store.geometry.check_gripper_labware_tip_collision(
96
+ gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z,
97
+ labware_id=labware_id,
98
+ current_location=current_location,
99
+ )
142
100
  return
143
101
 
144
102
  ot3api = ensure_ot3_hardware(
@@ -161,16 +119,14 @@ class LabwareMovementHandler:
161
119
  await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G])
162
120
  gripper_homed_position = await ot3api.gantry_position(mount=gripper_mount)
163
121
 
164
- # todo(mm, 2024-11-07): We should do this collision checking even when we
165
- # only have a `labware_definition`, not a `labware_id`. Resolve when
166
- # `check_gripper_labware_tip_collision()` can be made independent of `labware_id`.
167
- if labware_id is not None:
168
- self._state_store.geometry.check_gripper_labware_tip_collision(
169
- gripper_homed_position_z=gripper_homed_position.z,
170
- labware_id=labware_id,
171
- current_location=current_location,
172
- )
122
+ # Verify that no tip collisions will occur during the move
123
+ self._state_store.geometry.check_gripper_labware_tip_collision(
124
+ gripper_homed_position_z=gripper_homed_position.z,
125
+ labware_id=labware_id,
126
+ current_location=current_location,
127
+ )
173
128
 
129
+ current_labware = self._state_store.labware.get_definition(labware_id)
174
130
  async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement(
175
131
  labware_location=current_location
176
132
  ):
@@ -179,14 +135,14 @@ class LabwareMovementHandler:
179
135
  from_location=current_location,
180
136
  to_location=new_location,
181
137
  additional_offset_vector=user_offset_data,
182
- current_labware=labware_definition,
138
+ current_labware=current_labware,
183
139
  )
184
140
  )
185
141
  from_labware_center = self._state_store.geometry.get_labware_grip_point(
186
- labware_definition=labware_definition, location=current_location
142
+ labware_id=labware_id, location=current_location
187
143
  )
188
144
  to_labware_center = self._state_store.geometry.get_labware_grip_point(
189
- labware_definition=labware_definition, location=new_location
145
+ labware_id=labware_id, location=new_location
190
146
  )
191
147
  movement_waypoints = get_gripper_labware_movement_waypoints(
192
148
  from_labware_center=from_labware_center,
@@ -195,9 +151,7 @@ class LabwareMovementHandler:
195
151
  offset_data=final_offsets,
196
152
  post_drop_slide_offset=post_drop_slide_offset,
197
153
  )
198
- labware_grip_force = self._state_store.labware.get_grip_force(
199
- labware_definition
200
- )
154
+ labware_grip_force = self._state_store.labware.get_grip_force(labware_id)
201
155
  holding_labware = False
202
156
  for waypoint_data in movement_waypoints:
203
157
  if waypoint_data.jaw_open:
@@ -220,11 +174,9 @@ class LabwareMovementHandler:
220
174
  # should be holding labware
221
175
  if holding_labware:
222
176
  labware_bbox = self._state_store.labware.get_dimensions(
223
- labware_definition=labware_definition
224
- )
225
- well_bbox = self._state_store.labware.get_well_bbox(
226
- labware_definition=labware_definition
177
+ labware_id
227
178
  )
179
+ well_bbox = self._state_store.labware.get_well_bbox(labware_id)
228
180
  # todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError.
229
181
  # Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError,
230
182
  # depending on what waypoint we're at, to propagate a more specific error code to users.
@@ -4,10 +4,9 @@ from __future__ import annotations
4
4
  import logging
5
5
  from typing import Optional, List, Union
6
6
 
7
- from opentrons.types import Point, MountType, StagingSlotName
7
+ from opentrons.types import Point, MountType
8
8
  from opentrons.hardware_control import HardwareControlAPI
9
9
  from opentrons_shared_data.errors.exceptions import PositionUnknownError
10
- from opentrons.protocol_engine.errors import LocationIsStagingSlotError
11
10
 
12
11
  from ..types import (
13
12
  WellLocation,
@@ -94,13 +93,9 @@ class MovementHandler:
94
93
  self._state_store.modules.get_heater_shaker_movement_restrictors()
95
94
  )
96
95
 
97
- ancestor = self._state_store.geometry.get_ancestor_slot_name(labware_id)
98
- if isinstance(ancestor, StagingSlotName):
99
- raise LocationIsStagingSlotError(
100
- "Cannot move to well on labware in Staging Area Slot."
101
- )
102
-
103
- dest_slot_int = ancestor.as_int()
96
+ dest_slot_int = self._state_store.geometry.get_ancestor_slot_name(
97
+ labware_id
98
+ ).as_int()
104
99
 
105
100
  self._hs_movement_flagger.raise_if_movement_restricted(
106
101
  hs_movement_restrictors=hs_movement_restrictors,
@@ -59,6 +59,7 @@ from .actions import (
59
59
  HardwareStoppedAction,
60
60
  ResetTipsAction,
61
61
  SetPipetteMovementSpeedAction,
62
+ AddAbsorbanceReaderLidAction,
62
63
  )
63
64
 
64
65
 
@@ -576,6 +577,12 @@ class ProtocolEngine:
576
577
  AddAddressableAreaAction(addressable_area=area)
577
578
  )
578
579
 
580
+ def add_absorbance_reader_lid(self, module_id: str, lid_id: str) -> None:
581
+ """Add an absorbance reader lid to the module state."""
582
+ self._action_dispatcher.dispatch(
583
+ AddAbsorbanceReaderLidAction(module_id=module_id, lid_id=lid_id)
584
+ )
585
+
579
586
  def reset_tips(self, labware_id: str) -> None:
580
587
  """Reset the tip state of a given labware."""
581
588
  # TODO(mm, 2023-03-10): Safely raise an error if the given labware isn't a
@@ -17,9 +17,11 @@ from ..types import (
17
17
  DeckSlotLocation,
18
18
  DeckType,
19
19
  LabwareLocation,
20
+ AddressableAreaLocation,
20
21
  DeckConfigurationType,
21
22
  )
22
23
  from .labware_data_provider import LabwareDataProvider
24
+ from ..resources import deck_configuration_provider
23
25
 
24
26
 
25
27
  @final
@@ -69,6 +71,43 @@ class DeckDataProvider:
69
71
  slot = cast(Optional[str], fixture.get("slot"))
70
72
 
71
73
  if (
74
+ deck_configuration is not None
75
+ and load_name is not None
76
+ and slot is not None
77
+ and slot not in DeckSlotName._value2member_map_
78
+ ):
79
+ # The provided slot is likely to be an addressable area for Module-required labware Eg: Plate Reader Lid
80
+ for (
81
+ cutout_id,
82
+ cutout_fixture_id,
83
+ opentrons_module_serial_number,
84
+ ) in deck_configuration:
85
+ provided_addressable_areas = (
86
+ deck_configuration_provider.get_provided_addressable_area_names(
87
+ cutout_fixture_id=cutout_fixture_id,
88
+ cutout_id=cutout_id,
89
+ deck_definition=deck_definition,
90
+ )
91
+ )
92
+ if slot in provided_addressable_areas:
93
+ addressable_area_location = AddressableAreaLocation(
94
+ addressableAreaName=slot
95
+ )
96
+ definition = await self._labware_data.get_labware_definition(
97
+ load_name=load_name,
98
+ namespace="opentrons",
99
+ version=1,
100
+ )
101
+
102
+ labware.append(
103
+ DeckFixedLabware(
104
+ labware_id=labware_id,
105
+ definition=definition,
106
+ location=addressable_area_location,
107
+ )
108
+ )
109
+
110
+ elif (
72
111
  load_fixed_trash
73
112
  and load_name is not None
74
113
  and slot is not None
@@ -66,7 +66,7 @@ class PlateReaderData(BaseModel):
66
66
  row.append(str(measurement.data[f"{plate_alpharows[i]}{j+1}"]))
67
67
  rows.append(row)
68
68
  for i in range(3):
69
- rows.append([])
69
+ rows.append([""])
70
70
  rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"])
71
71
  for i in range(8):
72
72
  row = [plate_alpharows[i]]
@@ -74,7 +74,7 @@ class PlateReaderData(BaseModel):
74
74
  row.append("")
75
75
  rows.append(row)
76
76
  for i in range(3):
77
- rows.append([])
77
+ rows.append([""])
78
78
  rows.append(
79
79
  [
80
80
  "",
@@ -86,7 +86,7 @@ class PlateReaderData(BaseModel):
86
86
  ]
87
87
  )
88
88
  for i in range(3):
89
- rows.append([])
89
+ rows.append([""])
90
90
  rows.append(
91
91
  [
92
92
  "",
@@ -100,7 +100,7 @@ class PlateReaderData(BaseModel):
100
100
  )
101
101
  rows.append(["1", "Sample 1", "", "", "", "1", "", "", "", "", "", ""])
102
102
  for i in range(3):
103
- rows.append([])
103
+ rows.append([""])
104
104
 
105
105
  # end of file metadata
106
106
  rows.append(["Protocol"])
@@ -109,17 +109,13 @@ class PlateReaderData(BaseModel):
109
109
  if self.reference_wavelength is not None:
110
110
  rows.append(["Reference Wavelength (nm)", str(self.reference_wavelength)])
111
111
  rows.append(["Serial No.", self.serial_number])
112
- rows.append(
113
- ["Measurement started at", self.start_time.strftime("%m %d %H:%M:%S %Y")]
114
- )
115
- rows.append(
116
- ["Measurement finished at", self.finish_time.strftime("%m %d %H:%M:%S %Y")]
117
- )
112
+ rows.append(["Measurement started at", str(self.start_time)])
113
+ rows.append(["Measurement finished at", str(self.finish_time)])
118
114
 
119
115
  # Ensure the filename adheres to ruleset contains the wavelength for a given measurement
120
116
  if filename.endswith(".csv"):
121
117
  filename = filename[:-4]
122
- filename = filename + str(measurement.wavelength) + "nm.csv"
118
+ filename = filename + "_" + str(measurement.wavelength) + ".csv"
123
119
 
124
120
  return GenericCsvTransform.build(
125
121
  filename=filename,
@@ -29,12 +29,7 @@ def is_drop_tip_waste_chute(addressable_area_name: str) -> bool:
29
29
 
30
30
  def is_trash(addressable_area_name: str) -> bool:
31
31
  """Check if an addressable area is a trash bin."""
32
- return any(
33
- [
34
- s in addressable_area_name
35
- for s in {"movableTrash", "fixedTrash", "shortFixedTrash"}
36
- ]
37
- )
32
+ return addressable_area_name in {"movableTrash", "fixedTrash", "shortFixedTrash"}
38
33
 
39
34
 
40
35
  def is_staging_slot(addressable_area_name: str) -> bool: