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
@@ -9,7 +9,6 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, Succe
9
9
  from ...errors.error_occurrence import ErrorOccurrence
10
10
  from ...errors import CannotPerformModuleAction
11
11
 
12
- from opentrons.protocol_engine.resources import labware_validation
13
12
  from opentrons.protocol_engine.types import AddressableAreaLocation
14
13
 
15
14
  from opentrons.drivers.types import AbsorbanceReaderLidStatus
@@ -54,39 +53,35 @@ class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]
54
53
 
55
54
  async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]:
56
55
  """Move the absorbance reader lid from the module to the lid dock."""
56
+ state_update = StateUpdate()
57
57
  mod_substate = self._state_view.modules.get_absorbance_reader_substate(
58
58
  module_id=params.moduleId
59
59
  )
60
- # lid should currently be on the module
61
- assert mod_substate.lid_id is not None
62
- loaded_lid = self._state_view.labware.get(mod_substate.lid_id)
63
- assert labware_validation.is_absorbance_reader_lid(loaded_lid.loadName)
64
60
 
65
61
  hardware_lid_status = AbsorbanceReaderLidStatus.ON
66
- # If the lid is closed, if the lid is open No-op out
67
62
  if not self._state_view.config.use_virtual_modules:
68
63
  abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id)
69
64
 
70
65
  if abs_reader is not None:
71
- result = await abs_reader.get_current_lid_status()
72
- hardware_lid_status = result
66
+ hardware_lid_status = await abs_reader.get_current_lid_status()
73
67
  else:
74
68
  raise CannotPerformModuleAction(
75
69
  "Could not reach the Hardware API for Opentrons Plate Reader Module."
76
70
  )
77
71
 
78
- # If the lid is already OFF, no-op the lid removal
79
72
  if hardware_lid_status is AbsorbanceReaderLidStatus.OFF:
80
- assert isinstance(loaded_lid.location, AddressableAreaLocation)
81
- new_location = loaded_lid.location
82
- new_offset_id = self._equipment.find_applicable_labware_offset_id(
83
- labware_definition_uri=loaded_lid.definitionUri,
84
- labware_location=loaded_lid.location,
73
+ # The lid is already physically OFF, so we can no-op physically closing it
74
+ state_update.set_absorbance_reader_lid(
75
+ module_id=mod_substate.module_id, is_lid_on=False
85
76
  )
86
77
  else:
87
78
  # Allow propagation of ModuleNotAttachedError.
88
79
  _ = self._equipment.get_module_hardware_api(mod_substate.module_id)
89
80
 
81
+ lid_definition = (
82
+ self._state_view.labware.get_absorbance_reader_lid_definition()
83
+ )
84
+
90
85
  absorbance_model = self._state_view.modules.get_requested_model(
91
86
  params.moduleId
92
87
  )
@@ -106,35 +101,28 @@ class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]
106
101
  mod_substate.module_id
107
102
  )
108
103
 
109
- lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets(
110
- loaded_lid.id, None
104
+ # The lid's labware definition stores gripper offsets for itself in the
105
+ # space normally meant for offsets for labware stacked atop it.
106
+ lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets(
107
+ labware_definition=lid_definition,
108
+ slot_name=None,
111
109
  )
112
110
  if lid_gripper_offsets is None:
113
111
  raise ValueError(
114
112
  "Gripper Offset values for Absorbance Reader Lid labware must not be None."
115
113
  )
116
114
 
117
- # Skips gripper moves when using virtual gripper
118
115
  await self._labware_movement.move_labware_with_gripper(
119
- labware_id=loaded_lid.id,
116
+ labware_definition=lid_definition,
120
117
  current_location=current_location,
121
118
  new_location=new_location,
122
119
  user_offset_data=lid_gripper_offsets,
123
120
  post_drop_slide_offset=None,
124
121
  )
125
- new_offset_id = self._equipment.find_applicable_labware_offset_id(
126
- labware_definition_uri=loaded_lid.definitionUri,
127
- labware_location=new_location,
122
+ state_update.set_absorbance_reader_lid(
123
+ module_id=mod_substate.module_id, is_lid_on=False
128
124
  )
129
125
 
130
- state_update = StateUpdate()
131
-
132
- state_update.set_labware_location(
133
- labware_id=loaded_lid.id,
134
- new_location=new_location,
135
- new_offset_id=new_offset_id,
136
- )
137
-
138
126
  return SuccessData(
139
127
  public=OpenLidResult(),
140
128
  state_update=state_update,
@@ -10,6 +10,8 @@ from ..errors import LabwareIsNotAllowedInLocationError
10
10
  from ..resources import labware_validation, fixture_validation
11
11
  from ..types import (
12
12
  LabwareLocation,
13
+ ModuleLocation,
14
+ ModuleModel,
13
15
  OnLabwareLocation,
14
16
  DeckSlotLocation,
15
17
  AddressableAreaLocation,
@@ -160,6 +162,13 @@ class LoadLabwareImplementation(
160
162
  top_labware_definition=loaded_labware.definition,
161
163
  bottom_labware_id=verified_location.labwareId,
162
164
  )
165
+ # Validate labware for the absorbance reader
166
+ elif isinstance(params.location, ModuleLocation):
167
+ module = self._state_view.modules.get(params.location.moduleId)
168
+ if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1:
169
+ self._state_view.labware.raise_if_labware_incompatible_with_plate_reader(
170
+ loaded_labware.definition
171
+ )
163
172
 
164
173
  return SuccessData(
165
174
  public=LoadLabwareResult(
@@ -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,24 @@ 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
- # 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()
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])
121
140
 
122
141
  # Place the labware down
123
- await self._start_movement(ot3api, labware_id, location, drop_offset)
142
+ await self._start_movement(ot3api, definition, location, drop_offset)
124
143
 
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)
144
+ return SuccessData(public=UnsafePlaceLabwareResult())
131
145
 
132
146
  async def _start_movement(
133
147
  self,
134
148
  ot3api: OT3HardwareControlAPI,
135
- labware_id: str,
149
+ labware_definition: LabwareDefinition,
136
150
  location: OnDeckLabwareLocation,
137
151
  drop_offset: Optional[Point],
138
152
  ) -> None:
@@ -142,7 +156,7 @@ class UnsafePlaceLabwareImplementation(
142
156
  )
143
157
 
144
158
  to_labware_center = self._state_view.geometry.get_labware_grip_point(
145
- labware_id=labware_id, location=location
159
+ labware_definition=labware_definition, location=location
146
160
  )
147
161
 
148
162
  movement_waypoints = get_gripper_labware_placement_waypoints(
@@ -23,7 +23,11 @@ class UpdatePositionEstimatorsParams(BaseModel):
23
23
  """Payload required for an UpdatePositionEstimators command."""
24
24
 
25
25
  axes: List[MotorAxis] = Field(
26
- ..., description="The axes for which to update the position estimators."
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
+ ),
27
31
  )
28
32
 
29
33
 
@@ -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(
@@ -55,6 +55,7 @@ from .exceptions import (
55
55
  InvalidTargetTemperatureError,
56
56
  InvalidBlockVolumeError,
57
57
  InvalidHoldTimeError,
58
+ InvalidWavelengthError,
58
59
  CannotPerformModuleAction,
59
60
  PauseNotAllowedError,
60
61
  ResumeFromRecoveryNotAllowedError,
@@ -137,6 +138,7 @@ __all__ = [
137
138
  "InvalidTargetSpeedError",
138
139
  "InvalidBlockVolumeError",
139
140
  "InvalidHoldTimeError",
141
+ "InvalidWavelengthError",
140
142
  "CannotPerformModuleAction",
141
143
  "ResumeFromRecoveryNotAllowedError",
142
144
  "PauseNotAllowedError",
@@ -773,6 +773,19 @@ 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
+
776
789
  class InvalidHoldTimeError(ProtocolEngineError):
777
790
  """An error raised when attempting to set an invalid temperature hold time."""
778
791