opentrons 8.2.0a0__py2.py3-none-any.whl → 8.2.0a1__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 (41) hide show
  1. opentrons/drivers/absorbance_reader/async_byonoy.py +3 -3
  2. opentrons/legacy_commands/helpers.py +8 -2
  3. opentrons/protocol_api/core/engine/labware.py +10 -2
  4. opentrons/protocol_api/core/engine/module_core.py +38 -1
  5. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +12 -5
  6. opentrons/protocol_api/core/engine/protocol.py +5 -30
  7. opentrons/protocol_api/core/labware.py +4 -0
  8. opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
  9. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -0
  10. opentrons/protocol_api/core/protocol.py +1 -0
  11. opentrons/protocol_api/module_contexts.py +13 -0
  12. opentrons/protocol_api/protocol_context.py +12 -2
  13. opentrons/protocol_engine/actions/__init__.py +0 -2
  14. opentrons/protocol_engine/actions/actions.py +0 -12
  15. opentrons/protocol_engine/clients/sync_client.py +0 -6
  16. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +18 -31
  17. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +17 -29
  18. opentrons/protocol_engine/commands/load_labware.py +9 -0
  19. opentrons/protocol_engine/commands/load_module.py +0 -39
  20. opentrons/protocol_engine/commands/move_labware.py +49 -4
  21. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +41 -32
  22. opentrons/protocol_engine/create_protocol_engine.py +18 -1
  23. opentrons/protocol_engine/execution/labware_movement.py +69 -21
  24. opentrons/protocol_engine/execution/movement.py +9 -4
  25. opentrons/protocol_engine/protocol_engine.py +0 -7
  26. opentrons/protocol_engine/resources/deck_data_provider.py +0 -39
  27. opentrons/protocol_engine/resources/file_provider.py +11 -7
  28. opentrons/protocol_engine/resources/fixture_validation.py +6 -1
  29. opentrons/protocol_engine/state/geometry.py +91 -49
  30. opentrons/protocol_engine/state/labware.py +102 -25
  31. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +3 -1
  32. opentrons/protocol_engine/state/modules.py +49 -79
  33. opentrons/protocol_engine/state/motion.py +17 -5
  34. opentrons/protocol_engine/state/update_types.py +16 -0
  35. opentrons/util/logging_config.py +1 -1
  36. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/METADATA +4 -4
  37. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/RECORD +41 -41
  38. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/LICENSE +0 -0
  39. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/WHEEL +0 -0
  40. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/entry_points.txt +0 -0
  41. {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/top_level.txt +0 -0
@@ -5,7 +5,6 @@ from typing_extensions import Literal
5
5
  from pydantic import BaseModel, Field
6
6
 
7
7
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
8
- from ..errors import ModuleNotLoadedError
9
8
  from ..errors.error_occurrence import ErrorOccurrence
10
9
  from ..types import (
11
10
  DeckSlotLocation,
@@ -17,7 +16,6 @@ from opentrons.types import DeckSlotName
17
16
 
18
17
  from opentrons.protocol_engine.resources import deck_configuration_provider
19
18
 
20
- from opentrons.drivers.types import AbsorbanceReaderLidStatus
21
19
 
22
20
  if TYPE_CHECKING:
23
21
  from ..state.state import StateView
@@ -152,43 +150,6 @@ class LoadModuleImplementation(
152
150
  module_id=params.moduleId,
153
151
  )
154
152
 
155
- # Handle lid position update for loaded Plate Reader module on deck
156
- if (
157
- not self._state_view.config.use_virtual_modules
158
- and params.model == ModuleModel.ABSORBANCE_READER_V1
159
- and params.moduleId is not None
160
- ):
161
- try:
162
- abs_reader = self._equipment.get_module_hardware_api(
163
- self._state_view.modules.get_absorbance_reader_substate(
164
- params.moduleId
165
- ).module_id
166
- )
167
- except ModuleNotLoadedError:
168
- abs_reader = None
169
-
170
- if abs_reader is not None:
171
- result = await abs_reader.get_current_lid_status()
172
- if (
173
- isinstance(result, AbsorbanceReaderLidStatus)
174
- and result is not AbsorbanceReaderLidStatus.ON
175
- ):
176
- reader_area = self._state_view.modules.ensure_and_convert_module_fixture_location(
177
- params.location.slotName,
178
- self._state_view.config.deck_type,
179
- params.model,
180
- )
181
- lid_labware = self._state_view.labware.get_by_addressable_area(
182
- reader_area
183
- )
184
-
185
- if lid_labware is not None:
186
- self._state_view.labware._state.labware_by_id[
187
- lid_labware.id
188
- ].location = self._state_view.modules.absorbance_reader_dock_location(
189
- params.moduleId
190
- )
191
-
192
153
  return SuccessData(
193
154
  public=LoadModuleResult(
194
155
  moduleId=loaded_module.module_id,
@@ -13,16 +13,22 @@ from typing_extensions import Literal
13
13
  from opentrons.protocol_engine.resources.model_utils import ModelUtils
14
14
  from opentrons.types import Point
15
15
  from ..types import (
16
+ ModuleModel,
16
17
  CurrentWell,
17
18
  LabwareLocation,
18
19
  DeckSlotLocation,
20
+ ModuleLocation,
19
21
  OnLabwareLocation,
20
22
  AddressableAreaLocation,
21
23
  LabwareMovementStrategy,
22
24
  LabwareOffsetVector,
23
25
  LabwareMovementOffsetData,
24
26
  )
25
- from ..errors import LabwareMovementNotAllowedError, NotSupportedOnRobotType
27
+ from ..errors import (
28
+ LabwareMovementNotAllowedError,
29
+ NotSupportedOnRobotType,
30
+ LabwareOffsetDoesNotExistError,
31
+ )
26
32
  from ..resources import labware_validation, fixture_validation
27
33
  from .command import (
28
34
  AbstractCommandImpl,
@@ -130,6 +136,7 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
130
136
  )
131
137
  definition_uri = current_labware.definitionUri
132
138
  post_drop_slide_offset: Optional[Point] = None
139
+ trash_lid_drop_offset: Optional[LabwareOffsetVector] = None
133
140
 
134
141
  if self._state_view.labware.is_fixed_trash(params.labwareId):
135
142
  raise LabwareMovementNotAllowedError(
@@ -138,9 +145,11 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
138
145
 
139
146
  if isinstance(params.newLocation, AddressableAreaLocation):
140
147
  area_name = params.newLocation.addressableAreaName
141
- if not fixture_validation.is_gripper_waste_chute(
142
- area_name
143
- ) and not fixture_validation.is_deck_slot(area_name):
148
+ if (
149
+ not fixture_validation.is_gripper_waste_chute(area_name)
150
+ and not fixture_validation.is_deck_slot(area_name)
151
+ and not fixture_validation.is_trash(area_name)
152
+ ):
144
153
  raise LabwareMovementNotAllowedError(
145
154
  f"Cannot move {current_labware.loadName} to addressable area {area_name}"
146
155
  )
@@ -162,6 +171,32 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
162
171
  y=0,
163
172
  z=0,
164
173
  )
174
+ elif fixture_validation.is_trash(area_name):
175
+ # When dropping labware in the trash bins we want to ensure they are lids
176
+ # and enforce a y-axis drop offset to ensure they fall within the trash bin
177
+ if labware_validation.validate_definition_is_lid(
178
+ self._state_view.labware.get_definition(params.labwareId)
179
+ ):
180
+ lid_disposable_offfets = (
181
+ current_labware_definition.gripperOffsets.get(
182
+ "lidDisposalOffsets"
183
+ )
184
+ )
185
+ if lid_disposable_offfets is not None:
186
+ trash_lid_drop_offset = LabwareOffsetVector(
187
+ x=lid_disposable_offfets.dropOffset.x,
188
+ y=lid_disposable_offfets.dropOffset.y,
189
+ z=lid_disposable_offfets.dropOffset.z,
190
+ )
191
+ else:
192
+ raise LabwareOffsetDoesNotExistError(
193
+ f"Labware Definition {current_labware.loadName} does not contain required field 'lidDisposalOffsets' of 'gripperOffsets'."
194
+ )
195
+ else:
196
+ raise LabwareMovementNotAllowedError(
197
+ "Can only move labware with allowed role 'Lid' to a Trash Bin."
198
+ )
199
+
165
200
  elif isinstance(params.newLocation, DeckSlotLocation):
166
201
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
167
202
  params.newLocation.slotName.id
@@ -188,6 +223,13 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
188
223
  raise LabwareMovementNotAllowedError(
189
224
  "Cannot move a labware onto itself."
190
225
  )
226
+ # Validate labware for the absorbance reader
227
+ elif isinstance(available_new_location, ModuleLocation):
228
+ module = self._state_view.modules.get(available_new_location.moduleId)
229
+ if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1:
230
+ self._state_view.labware.raise_if_labware_incompatible_with_plate_reader(
231
+ current_labware_definition
232
+ )
191
233
 
192
234
  # Allow propagation of ModuleNotLoadedError.
193
235
  new_offset_id = self._equipment.find_applicable_labware_offset_id(
@@ -232,6 +274,9 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
232
274
  dropOffset=params.dropOffset or LabwareOffsetVector(x=0, y=0, z=0),
233
275
  )
234
276
 
277
+ if trash_lid_drop_offset:
278
+ user_offset_data.dropOffset += trash_lid_drop_offset
279
+
235
280
  try:
236
281
  # Skips gripper moves when using virtual gripper
237
282
  await self._labware_movement.move_labware_with_gripper(
@@ -1,10 +1,13 @@
1
1
  """Place labware payload, result, and implementaiton."""
2
2
 
3
3
  from __future__ import annotations
4
- from pydantic import BaseModel, Field
5
- from typing import TYPE_CHECKING, Optional, Type, cast
4
+ from typing import TYPE_CHECKING, Optional, Type
6
5
  from typing_extensions import Literal
7
6
 
7
+ from opentrons_shared_data.labware.types import LabwareUri
8
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
9
+ from pydantic import BaseModel, Field
10
+
8
11
  from opentrons.hardware_control.types import Axis, OT3Mount
9
12
  from opentrons.motion_planning.waypoints import get_gripper_labware_placement_waypoints
10
13
  from opentrons.protocol_engine.errors.exceptions import (
@@ -13,11 +16,14 @@ from opentrons.protocol_engine.errors.exceptions import (
13
16
  )
14
17
  from opentrons.types import Point
15
18
 
16
- from ...types import DeckSlotLocation, ModuleModel, OnDeckLabwareLocation
19
+ from ...types import (
20
+ DeckSlotLocation,
21
+ ModuleModel,
22
+ OnDeckLabwareLocation,
23
+ )
17
24
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
18
25
  from ...errors.error_occurrence import ErrorOccurrence
19
26
  from ...resources import ensure_ot3_hardware
20
- from ...state.update_types import StateUpdate
21
27
 
22
28
  from opentrons.hardware_control import HardwareControlAPI, OT3HardwareControlAPI
23
29
 
@@ -32,7 +38,7 @@ UnsafePlaceLabwareCommandType = Literal["unsafe/placeLabware"]
32
38
  class UnsafePlaceLabwareParams(BaseModel):
33
39
  """Payload required for an UnsafePlaceLabware command."""
34
40
 
35
- labwareId: str = Field(..., description="The id of the labware to place.")
41
+ labwareURI: str = Field(..., description="Labware URI for labware.")
36
42
  location: OnDeckLabwareLocation = Field(
37
43
  ..., description="Where to place the labware."
38
44
  )
@@ -71,8 +77,8 @@ class UnsafePlaceLabwareImplementation(
71
77
  is pressed, get into error recovery, etc).
72
78
 
73
79
  Unlike the `moveLabware` command, where you pick a source and destination
74
- location, this command takes the labwareId to be moved and location to
75
- move it to.
80
+ location, this command takes the labwareURI of the labware to be moved
81
+ and location to move it to.
76
82
 
77
83
  """
78
84
  ot3api = ensure_ot3_hardware(self._hardware_api)
@@ -84,23 +90,37 @@ class UnsafePlaceLabwareImplementation(
84
90
  "Cannot place labware when gripper is not gripping."
85
91
  )
86
92
 
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
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
92
117
  )
93
- drop_offset = cast(Point, final_offsets.dropOffset) if final_offsets else None
94
118
 
95
119
  if isinstance(params.location, DeckSlotLocation):
96
120
  self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
97
121
  params.location.slotName.id
98
122
  )
99
123
 
100
- location = self._state_view.geometry.ensure_valid_gripper_location(
101
- params.location,
102
- )
103
-
104
124
  # This is an absorbance reader, move the lid to its dock (staging area).
105
125
  if isinstance(location, DeckSlotLocation):
106
126
  module = self._state_view.modules.get_by_slot(location.slotName)
@@ -109,30 +129,19 @@ class UnsafePlaceLabwareImplementation(
109
129
  module.id
110
130
  )
111
131
 
112
- new_offset_id = self._equipment.find_applicable_labware_offset_id(
113
- labware_definition_uri=definition_uri,
114
- labware_location=location,
115
- )
116
-
117
132
  # NOTE: When the estop is pressed, the gantry loses position,
118
133
  # so the robot needs to home x, y to sync.
119
134
  await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G, Axis.X, Axis.Y])
120
- state_update = StateUpdate()
121
135
 
122
136
  # Place the labware down
123
- await self._start_movement(ot3api, labware_id, location, drop_offset)
137
+ await self._start_movement(ot3api, definition, location, drop_offset)
124
138
 
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)
139
+ return SuccessData(public=UnsafePlaceLabwareResult())
131
140
 
132
141
  async def _start_movement(
133
142
  self,
134
143
  ot3api: OT3HardwareControlAPI,
135
- labware_id: str,
144
+ labware_definition: LabwareDefinition,
136
145
  location: OnDeckLabwareLocation,
137
146
  drop_offset: Optional[Point],
138
147
  ) -> None:
@@ -142,7 +151,7 @@ class UnsafePlaceLabwareImplementation(
142
151
  )
143
152
 
144
153
  to_labware_center = self._state_view.geometry.get_labware_grip_point(
145
- labware_id=labware_id, location=location
154
+ labware_definition=labware_definition, location=location
146
155
  )
147
156
 
148
157
  movement_waypoints = get_gripper_labware_placement_waypoints(
@@ -8,6 +8,9 @@ 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
+ )
11
14
  from opentrons.util.async_helpers import async_context_manager_in_thread
12
15
 
13
16
  from opentrons_shared_data.robot import load as load_robot
@@ -81,7 +84,7 @@ async def create_protocol_engine(
81
84
  module_data_provider = ModuleDataProvider()
82
85
  file_provider = file_provider or FileProvider()
83
86
 
84
- return ProtocolEngine(
87
+ pe = ProtocolEngine(
85
88
  hardware_api=hardware_api,
86
89
  state_store=state_store,
87
90
  action_dispatcher=action_dispatcher,
@@ -93,6 +96,20 @@ async def create_protocol_engine(
93
96
  file_provider=file_provider,
94
97
  )
95
98
 
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
+
96
113
 
97
114
  @contextlib.contextmanager
98
115
  def create_protocol_engine_in_thread(
@@ -1,7 +1,9 @@
1
1
  """Labware movement command handling."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Optional, TYPE_CHECKING
4
+ from typing import Optional, TYPE_CHECKING, overload
5
+
6
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
5
7
 
6
8
  from opentrons.types import Point
7
9
 
@@ -79,24 +81,64 @@ class LabwareMovementHandler:
79
81
  )
80
82
  )
81
83
 
84
+ @overload
82
85
  async def move_labware_with_gripper(
83
86
  self,
87
+ *,
84
88
  labware_id: str,
85
89
  current_location: OnDeckLabwareLocation,
86
90
  new_location: OnDeckLabwareLocation,
87
91
  user_offset_data: LabwareMovementOffsetData,
88
92
  post_drop_slide_offset: Optional[Point],
89
93
  ) -> None:
90
- """Move a loaded labware from one location to another using gripper."""
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
+ """
91
125
  use_virtual_gripper = self._state_store.config.use_virtual_gripper
92
126
 
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
+
93
131
  if use_virtual_gripper:
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
- )
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
+ )
100
142
  return
101
143
 
102
144
  ot3api = ensure_ot3_hardware(
@@ -119,14 +161,16 @@ class LabwareMovementHandler:
119
161
  await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G])
120
162
  gripper_homed_position = await ot3api.gantry_position(mount=gripper_mount)
121
163
 
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
- )
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
+ )
128
173
 
129
- current_labware = self._state_store.labware.get_definition(labware_id)
130
174
  async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement(
131
175
  labware_location=current_location
132
176
  ):
@@ -135,14 +179,14 @@ class LabwareMovementHandler:
135
179
  from_location=current_location,
136
180
  to_location=new_location,
137
181
  additional_offset_vector=user_offset_data,
138
- current_labware=current_labware,
182
+ current_labware=labware_definition,
139
183
  )
140
184
  )
141
185
  from_labware_center = self._state_store.geometry.get_labware_grip_point(
142
- labware_id=labware_id, location=current_location
186
+ labware_definition=labware_definition, location=current_location
143
187
  )
144
188
  to_labware_center = self._state_store.geometry.get_labware_grip_point(
145
- labware_id=labware_id, location=new_location
189
+ labware_definition=labware_definition, location=new_location
146
190
  )
147
191
  movement_waypoints = get_gripper_labware_movement_waypoints(
148
192
  from_labware_center=from_labware_center,
@@ -151,7 +195,9 @@ class LabwareMovementHandler:
151
195
  offset_data=final_offsets,
152
196
  post_drop_slide_offset=post_drop_slide_offset,
153
197
  )
154
- labware_grip_force = self._state_store.labware.get_grip_force(labware_id)
198
+ labware_grip_force = self._state_store.labware.get_grip_force(
199
+ labware_definition
200
+ )
155
201
  holding_labware = False
156
202
  for waypoint_data in movement_waypoints:
157
203
  if waypoint_data.jaw_open:
@@ -174,9 +220,11 @@ class LabwareMovementHandler:
174
220
  # should be holding labware
175
221
  if holding_labware:
176
222
  labware_bbox = self._state_store.labware.get_dimensions(
177
- labware_id
223
+ labware_definition=labware_definition
224
+ )
225
+ well_bbox = self._state_store.labware.get_well_bbox(
226
+ labware_definition=labware_definition
178
227
  )
179
- well_bbox = self._state_store.labware.get_well_bbox(labware_id)
180
228
  # todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError.
181
229
  # Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError,
182
230
  # depending on what waypoint we're at, to propagate a more specific error code to users.
@@ -4,9 +4,10 @@ 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
7
+ from opentrons.types import Point, MountType, StagingSlotName
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
10
11
 
11
12
  from ..types import (
12
13
  WellLocation,
@@ -93,9 +94,13 @@ class MovementHandler:
93
94
  self._state_store.modules.get_heater_shaker_movement_restrictors()
94
95
  )
95
96
 
96
- dest_slot_int = self._state_store.geometry.get_ancestor_slot_name(
97
- labware_id
98
- ).as_int()
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()
99
104
 
100
105
  self._hs_movement_flagger.raise_if_movement_restricted(
101
106
  hs_movement_restrictors=hs_movement_restrictors,
@@ -59,7 +59,6 @@ from .actions import (
59
59
  HardwareStoppedAction,
60
60
  ResetTipsAction,
61
61
  SetPipetteMovementSpeedAction,
62
- AddAbsorbanceReaderLidAction,
63
62
  )
64
63
 
65
64
 
@@ -577,12 +576,6 @@ class ProtocolEngine:
577
576
  AddAddressableAreaAction(addressable_area=area)
578
577
  )
579
578
 
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
-
586
579
  def reset_tips(self, labware_id: str) -> None:
587
580
  """Reset the tip state of a given labware."""
588
581
  # TODO(mm, 2023-03-10): Safely raise an error if the given labware isn't a
@@ -17,11 +17,9 @@ from ..types import (
17
17
  DeckSlotLocation,
18
18
  DeckType,
19
19
  LabwareLocation,
20
- AddressableAreaLocation,
21
20
  DeckConfigurationType,
22
21
  )
23
22
  from .labware_data_provider import LabwareDataProvider
24
- from ..resources import deck_configuration_provider
25
23
 
26
24
 
27
25
  @final
@@ -71,43 +69,6 @@ class DeckDataProvider:
71
69
  slot = cast(Optional[str], fixture.get("slot"))
72
70
 
73
71
  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 (
111
72
  load_fixed_trash
112
73
  and load_name is not None
113
74
  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,13 +109,17 @@ 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(["Measurement started at", str(self.start_time)])
113
- rows.append(["Measurement finished at", str(self.finish_time)])
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
+ )
114
118
 
115
119
  # Ensure the filename adheres to ruleset contains the wavelength for a given measurement
116
120
  if filename.endswith(".csv"):
117
121
  filename = filename[:-4]
118
- filename = filename + "_" + str(measurement.wavelength) + ".csv"
122
+ filename = filename + str(measurement.wavelength) + "nm.csv"
119
123
 
120
124
  return GenericCsvTransform.build(
121
125
  filename=filename,
@@ -29,7 +29,12 @@ 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 addressable_area_name in {"movableTrash", "fixedTrash", "shortFixedTrash"}
32
+ return any(
33
+ [
34
+ s in addressable_area_name
35
+ for s in {"movableTrash", "fixedTrash", "shortFixedTrash"}
36
+ ]
37
+ )
33
38
 
34
39
 
35
40
  def is_staging_slot(addressable_area_name: str) -> bool: