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.
- opentrons/drivers/absorbance_reader/async_byonoy.py +3 -3
- opentrons/hardware_control/ot3api.py +5 -5
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/protocol_api/core/engine/labware.py +10 -2
- opentrons/protocol_api/core/engine/module_core.py +38 -1
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +12 -5
- opentrons/protocol_api/core/engine/protocol.py +5 -30
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -0
- opentrons/protocol_api/core/protocol.py +1 -0
- opentrons/protocol_api/module_contexts.py +13 -0
- opentrons/protocol_api/protocol_context.py +12 -2
- opentrons/protocol_engine/actions/__init__.py +0 -2
- opentrons/protocol_engine/actions/actions.py +0 -12
- opentrons/protocol_engine/clients/sync_client.py +0 -6
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +18 -31
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -7
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +17 -29
- opentrons/protocol_engine/commands/load_labware.py +9 -0
- opentrons/protocol_engine/commands/load_module.py +0 -39
- opentrons/protocol_engine/commands/move_labware.py +49 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +49 -35
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -1
- opentrons/protocol_engine/create_protocol_engine.py +18 -1
- opentrons/protocol_engine/errors/__init__.py +2 -0
- opentrons/protocol_engine/errors/exceptions.py +13 -0
- opentrons/protocol_engine/execution/labware_movement.py +69 -21
- opentrons/protocol_engine/execution/movement.py +9 -4
- opentrons/protocol_engine/protocol_engine.py +0 -7
- opentrons/protocol_engine/resources/deck_data_provider.py +0 -39
- opentrons/protocol_engine/resources/file_provider.py +11 -7
- opentrons/protocol_engine/resources/fixture_validation.py +6 -1
- opentrons/protocol_engine/state/geometry.py +91 -49
- opentrons/protocol_engine/state/labware.py +102 -25
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +3 -1
- opentrons/protocol_engine/state/modules.py +49 -79
- opentrons/protocol_engine/state/motion.py +17 -5
- opentrons/protocol_engine/state/update_types.py +16 -0
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/METADATA +4 -4
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/RECORD +47 -47
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/WHEEL +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
|
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
|
|
142
|
-
area_name
|
|
143
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
await ot3api.
|
|
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,
|
|
142
|
+
await self._start_movement(ot3api, definition, location, drop_offset)
|
|
124
143
|
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...,
|
|
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
|
-
|
|
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
|
|