Skip to content

Commit 347a23e

Browse files
authored
refactor(api,robot-server,app): use labwareURI instead of labwareID for placeLabwareState and unsafe/placeLabware command. (#16719)
The `placeLabwareState` and `unsafe/placeLabware` commands rely on the `labwareID` to place labware held by the gripper back down to a deck slot. However, as correctly pointed out, this is wrong because the labwareID changes across runs. This was working for the plate reader lid because the lid is loaded with a fixed labwareID, however, that would not be the case for other labware across different runs. This pull request replaces the labwareID with labwareURI used by the `placeLabwareState` and `unsafe/placeLabware` commands so the behavior is consistent across runs.
1 parent 8203e3a commit 347a23e

File tree

7 files changed

+63
-29
lines changed

7 files changed

+63
-29
lines changed

api-client/src/runs/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export interface NozzleLayoutValues {
214214
}
215215

216216
export interface PlaceLabwareState {
217-
labwareId: string
217+
labwareURI: string
218218
location: OnDeckLabwareLocation
219219
shouldPlaceDown: boolean
220220
}

api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, Optional, Type
66
from typing_extensions import Literal
77

8+
from opentrons.calibration_storage.helpers import details_from_uri
89
from opentrons.hardware_control.types import Axis, OT3Mount
910
from opentrons.motion_planning.waypoints import get_gripper_labware_placement_waypoints
1011
from opentrons.protocol_engine.errors.exceptions import (
@@ -13,7 +14,12 @@
1314
)
1415
from opentrons.types import Point
1516

16-
from ...types import DeckSlotLocation, ModuleModel, OnDeckLabwareLocation
17+
from ...types import (
18+
DeckSlotLocation,
19+
LoadedLabware,
20+
ModuleModel,
21+
OnDeckLabwareLocation,
22+
)
1723
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
1824
from ...errors.error_occurrence import ErrorOccurrence
1925
from ...resources import ensure_ot3_hardware
@@ -32,7 +38,7 @@
3238
class UnsafePlaceLabwareParams(BaseModel):
3339
"""Payload required for an UnsafePlaceLabware command."""
3440

35-
labwareId: str = Field(..., description="The id of the labware to place.")
41+
labwareURI: str = Field(..., description="Labware URI for labware.")
3642
location: OnDeckLabwareLocation = Field(
3743
..., description="Where to place the labware."
3844
)
@@ -71,8 +77,8 @@ async def execute(
7177
is pressed, get into error recovery, etc).
7278
7379
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.
7682
7783
"""
7884
ot3api = ensure_ot3_hardware(self._hardware_api)
@@ -84,10 +90,35 @@ async def execute(
8490
"Cannot place labware when gripper is not gripping."
8591
)
8692

87-
labware_id = params.labwareId
88-
# Allow propagation of LabwareNotLoadedError.
89-
definition_uri = self._state_view.labware.get(labware_id).definitionUri
93+
location = self._state_view.geometry.ensure_valid_gripper_location(
94+
params.location,
95+
)
96+
97+
# TODO: We need a way to create temporary labware for moving around,
98+
# the labware should get deleted once its used.
99+
details = details_from_uri(params.labwareURI)
100+
labware = await self._equipment.load_labware(
101+
load_name=details.load_name,
102+
namespace=details.namespace,
103+
version=details.version,
104+
location=location,
105+
labware_id=None,
106+
)
107+
108+
self._state_view.labware._state.definitions_by_uri[
109+
params.labwareURI
110+
] = labware.definition
111+
self._state_view.labware._state.labware_by_id[
112+
labware.labware_id
113+
] = LoadedLabware.construct(
114+
id=labware.labware_id,
115+
location=location,
116+
loadName=labware.definition.parameters.loadName,
117+
definitionUri=params.labwareURI,
118+
offsetId=labware.offsetId,
119+
)
90120

121+
labware_id = labware.labware_id
91122
# todo(mm, 2024-11-06): This is only correct in the special case of an
92123
# absorbance reader lid. Its definition currently puts the offsets for *itself*
93124
# in the property that's normally meant for offsets for its *children.*
@@ -109,10 +140,6 @@ async def execute(
109140
params.location.slotName.id
110141
)
111142

112-
location = self._state_view.geometry.ensure_valid_gripper_location(
113-
params.location,
114-
)
115-
116143
# This is an absorbance reader, move the lid to its dock (staging area).
117144
if isinstance(location, DeckSlotLocation):
118145
module = self._state_view.modules.get_by_slot(location.slotName)
@@ -122,7 +149,7 @@ async def execute(
122149
)
123150

124151
new_offset_id = self._equipment.find_applicable_labware_offset_id(
125-
labware_definition_uri=definition_uri,
152+
labware_definition_uri=params.labwareURI,
126153
labware_location=location,
127154
)
128155

app/src/resources/modules/hooks/usePlacePlateReaderLid.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function usePlacePlateReaderLid(
3636
const location = placeLabware.location
3737
const loadModuleCommand = buildLoadModuleCommand(location as ModuleLocation)
3838
const placeLabwareCommand = buildPlaceLabwareCommand(
39-
placeLabware.labwareId as string,
39+
placeLabware.labwareURI as string,
4040
location
4141
)
4242
commandsToExecute = [loadModuleCommand, placeLabwareCommand]
@@ -72,11 +72,11 @@ const buildLoadModuleCommand = (location: ModuleLocation): CreateCommand => {
7272
}
7373

7474
const buildPlaceLabwareCommand = (
75-
labwareId: string,
75+
labwareURI: string,
7676
location: OnDeckLabwareLocation
7777
): CreateCommand => {
7878
return {
7979
commandType: 'unsafe/placeLabware' as const,
80-
params: { labwareId, location },
80+
params: { labwareURI, location },
8181
}
8282
}

robot-server/robot_server/runs/router/base_router.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ async def get_current_state( # noqa: C901
602602
for pipetteId, nozzle_map in active_nozzle_maps.items()
603603
}
604604

605+
run = run_data_manager.get(run_id=runId)
605606
current_command = run_data_manager.get_current_command(run_id=runId)
606607
last_completed_command = run_data_manager.get_last_completed_command(
607608
run_id=runId
@@ -636,11 +637,14 @@ async def get_current_state( # noqa: C901
636637
if isinstance(command, MoveLabware):
637638
location = command.params.newLocation
638639
if isinstance(location, DeckSlotLocation):
639-
place_labware = PlaceLabwareState(
640-
location=location,
641-
labwareId=command.params.labwareId,
642-
shouldPlaceDown=False,
643-
)
640+
for labware in run.labware:
641+
if labware.id == command.params.labwareId:
642+
place_labware = PlaceLabwareState(
643+
location=location,
644+
labwareURI=labware.definitionUri,
645+
shouldPlaceDown=False,
646+
)
647+
break
644648
# Handle absorbance reader lid
645649
elif isinstance(command, (OpenLid, CloseLid)):
646650
for mod in run.modules:
@@ -655,10 +659,13 @@ async def get_current_state( # noqa: C901
655659
and hw_mod.serial_number == mod.serialNumber
656660
):
657661
location = mod.location
658-
labware_id = f"{mod.model}Lid{location.slotName}"
662+
# TODO: Not the best location for this, we should
663+
# remove this once we are no longer defining the plate reader lid
664+
# as a labware.
665+
labware_uri = "opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1"
659666
place_labware = PlaceLabwareState(
660667
location=location,
661-
labwareId=labware_id,
668+
labwareURI=labware_uri,
662669
shouldPlaceDown=estop_engaged,
663670
)
664671
break

robot-server/robot_server/runs/run_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ class ActiveNozzleLayout(BaseModel):
319319
class PlaceLabwareState(BaseModel):
320320
"""Details the labware being placed by the gripper."""
321321

322-
labwareId: str = Field(..., description="The ID of the labware to place.")
322+
labwareURI: str = Field(..., description="The URI of the labware to place.")
323323
location: OnDeckLabwareLocation = Field(
324324
..., description="The location the labware should be in."
325325
)

shared-data/command/schemas/10.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4749,9 +4749,9 @@
47494749
"description": "Payload required for an UnsafePlaceLabware command.",
47504750
"type": "object",
47514751
"properties": {
4752-
"labwareId": {
4753-
"title": "Labwareid",
4754-
"description": "The id of the labware to place.",
4752+
"labwareURI": {
4753+
"title": "Labwareuri",
4754+
"description": "Labware URI for labware.",
47554755
"type": "string"
47564756
},
47574757
"location": {
@@ -4773,7 +4773,7 @@
47734773
]
47744774
}
47754775
},
4776-
"required": ["labwareId", "location"]
4776+
"required": ["labwareURI", "location"]
47774777
},
47784778
"UnsafePlaceLabwareCreate": {
47794779
"title": "UnsafePlaceLabwareCreate",

shared-data/command/types/unsafe.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export interface UnsafeUngripLabwareRunTimeCommand
9292
result?: any
9393
}
9494
export interface UnsafePlaceLabwareParams {
95-
labwareId: string
95+
labwareURI: string
9696
location: OnDeckLabwareLocation
9797
}
9898
export interface UnsafePlaceLabwareCreateCommand

0 commit comments

Comments
 (0)