Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api, shared-data): Expand Labware architecture to accommodate Lids #17072

Open
wants to merge 13 commits into
base: edge
Choose a base branch
from
Open
Prev Previous commit
Next Next commit
labware load lid update
CaseyBatten committed Dec 5, 2024
commit 7a28027bd5c882b69fd7f7e4f05aa7d146a555b3
11 changes: 1 addition & 10 deletions api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
@@ -215,9 +215,6 @@ def load_labware(
label: Optional[str],
namespace: Optional[str],
version: Optional[int],
lid_load_name: Optional[str],
lid_namespace: Optional[str],
lid_version: Optional[int],
) -> LabwareCore:
"""Load a labware using its identifying parameters."""
load_location = self._convert_labware_location(location=location)
@@ -228,20 +225,14 @@ def load_labware(
namespace, version = load_labware_params.resolve(
load_name, namespace, version, custom_labware_params
)
lid_namespace, lid_version = load_labware_params.resolve(
lid_load_name, lid_namespace, lid_version, custom_labware_params
)


load_result = self._engine_client.execute_command_without_recovery(
cmd.LoadLabwareParams(
loadName=load_name,
location=load_location,
namespace=namespace,
version=version,
displayName=label,
lidLoadName=lid_load_name,
lidNamespace=lid_namespace,
lid_version=lid_version,
)
)
# FIXME(jbl, 2023-08-14) validating after loading the object issue
Original file line number Diff line number Diff line change
@@ -171,9 +171,6 @@ def load_labware(
label: Optional[str],
namespace: Optional[str],
version: Optional[int],
lid_load_name: Optional[str],
lid_namespace: Optional[str],
lid_version: Optional[int],
) -> LegacyLabwareCore:
"""Load a labware using its identifying parameters."""
if isinstance(location, OffDeckType):
3 changes: 0 additions & 3 deletions api/src/opentrons/protocol_api/core/protocol.py
Original file line number Diff line number Diff line change
@@ -79,9 +79,6 @@ def load_labware(
label: Optional[str],
namespace: Optional[str],
version: Optional[int],
lid_load_name: Optional[str],
lid_namespace: Optional[str],
lid_version: Optional[int],
) -> LabwareCoreType:
"""Load a labware using its identifying parameters."""
...
3 changes: 0 additions & 3 deletions api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
@@ -490,9 +490,6 @@ def load_labware(
label=label,
namespace=namespace,
version=version,
lid_load_name=lid,
lid_namespace=namespace,
lid_version=version,
)

labware = Labware(
48 changes: 9 additions & 39 deletions api/src/opentrons/protocol_engine/commands/load_labware.py
Original file line number Diff line number Diff line change
@@ -61,18 +61,6 @@ class LoadLabwareParams(BaseModel):
# user-specified label OR the displayName property of the labware's definition.
# TODO: Make sure v6 JSON protocols don't do that.
)
lidLoadName: Optional[str] = Field(
None,
description="Name used to reference an optional lid labware definition.",
)
lidNamespace: Optional[str] = Field(
None,
description="The namespace the lid labware definition belongs to.",
)
lidVersion: Optional[int] = Field(
None,
description="The lid labware definition version.",
)


class LoadLabwareResult(BaseModel):
@@ -99,10 +87,6 @@ class LoadLabwareResult(BaseModel):
" so the default of (0, 0, 0) will be used."
),
)
lidId: Optional[str] = Field(
None,
description="An ID to reference the optional lid labware loaded on the primary labware in subsequent commands.",
)


class LoadLabwareImplementation(
@@ -161,36 +145,23 @@ async def execute(
)

state_update = StateUpdate()
lid_id = None
if params.lidLoadName is not None:
if params.lidNamespace is None or params.lidVersion is None:
raise ValueError(
f"Labware Namespace and Version required to load Lid Labware {params.lidLoadName}."

if labware_validation.validate_definition_is_lid(loaded_labware.definition):
if isinstance(params.location, OnLabwareLocation):
state_update.set_lid(
parent_labware_id=params.location.labwareId,
lid_id=loaded_labware.labware_id,
)
loaded_lid = await self._equipment.load_labware(
load_name=params.lidLoadName,
namespace=params.lidNamespace,
version=params.lidVersion,
location=OnLabwareLocation(labwareId=loaded_labware.labware_id),
labware_id=params.labwareId,
)
lid_id = loaded_lid.labware_id
state_update.set_loaded_labware(
labware_id=loaded_lid.labware_id,
offset_id=loaded_lid.offsetId,
definition=loaded_lid.definition,
location=OnLabwareLocation(labwareId=loaded_labware.labware_id),
display_name=None,
lid_id=lid_id,
)
else:
# todo(chb, 2024-12-05): This is redundant, we're already doing this check on the equipment handler. Theoretically this can never raise?
raise ValueError("Load Labware location must be another Labware when loading a Lid outside of a stack.")

state_update.set_loaded_labware(
labware_id=loaded_labware.labware_id,
offset_id=loaded_labware.offsetId,
definition=loaded_labware.definition,
location=verified_location,
display_name=params.displayName,
lid_id=lid_id,
)

# TODO(jbl 2023-06-23) these validation checks happen after the labware is loaded, because they rely on
@@ -214,7 +185,6 @@ async def execute(
labwareId=loaded_labware.labware_id,
definition=loaded_labware.definition,
offsetId=loaded_labware.offsetId,
lidId=lid_id,
),
state_update=state_update,
)
4 changes: 1 addition & 3 deletions api/src/opentrons/protocol_engine/execution/equipment.py
Original file line number Diff line number Diff line change
@@ -180,9 +180,7 @@ async def load_labware(
f"Labware Lid {load_name} may not be loaded on parent labware {self._state_store.labware.get_display_name(location.labwareId)}."
)
else:
raise ValueError(
f"Load Labware location must be another Labware when loading a lid as a Labware."
)
raise ValueError("Load Labware location must be another Labware when loading a Lid outside of a stack.")

labware_id = (
labware_id if labware_id is not None else self._model_utils.generate_id()
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_engine/state/labware.py
Original file line number Diff line number Diff line change
@@ -157,6 +157,7 @@ def handle_action(self, action: Action) -> None:
for state_update in get_state_updates(action):
self._add_loaded_labware(state_update)
self._set_labware_location(state_update)
self._set_labware_lid(state_update)

if isinstance(action, AddLabwareOffsetAction):
labware_offset = LabwareOffset.construct(
@@ -220,6 +221,13 @@ def _add_loaded_labware(self, state_update: update_types.StateUpdate) -> None:
offsetId=loaded_labware_update.offset_id,
displayName=display_name,
)

def _set_labware_lid(self, state_update: update_types.StateUpdate) -> None:
labware_lid_update = state_update.labware_lid
if labware_lid_update != update_types.NO_CHANGE:
parent_labware_id = labware_lid_update.parent_labware_id
lid_id = labware_lid_update.lid_id
self._state.labware_by_id[parent_labware_id].lid_id = lid_id

def _set_labware_location(self, state_update: update_types.StateUpdate) -> None:
labware_location_update = state_update.labware_location
2 changes: 2 additions & 0 deletions api/src/opentrons/protocol_engine/state/state.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
AddressableAreaView,
)
from .labware import LabwareState, LabwareStore, LabwareView
from .lid_stack import LidStackState, LidStackStore, LidStackView
from .pipettes import PipetteState, PipetteStore, PipetteView
from .modules import ModuleState, ModuleStore, ModuleView
from .liquids import LiquidState, LiquidView, LiquidStore
@@ -216,6 +217,7 @@ def __init__(
deck_fixed_labware=deck_fixed_labware,
deck_definition=deck_definition,
)
self._lid_stack_store = LidStackStore()
self._module_store = ModuleStore(
config=config,
deck_fixed_labware=deck_fixed_labware,
31 changes: 28 additions & 3 deletions api/src/opentrons/protocol_engine/state/update_types.py
Original file line number Diff line number Diff line change
@@ -132,6 +132,15 @@ class LoadedLidStackUpdate:

definition: LabwareDefinition

@dataclasses.dataclass
class LabwareLidUpdate:
"""An update that identifies a lid on a given parent labware."""

parent_labware_id: str
"""The unique ID of the parent labware."""

lid_id: str
"""The unique IDs of the new lids."""

@dataclasses.dataclass
class LoadPipetteUpdate:
@@ -302,6 +311,12 @@ class StateUpdate:

loaded_lid_stack: LoadedLidStackUpdate | NoChangeType = NO_CHANGE

# TODO NOTE: Do I add a new Update class for labware specifically updating the lid info?
# This will update under two cases:
# 1. the lid has been loaded with a labware
# 2. the lid has been moved to a labware
labware_lid: LabwareLidUpdate | NoChangeType = NO_CHANGE

tips_used: TipsUsedUpdate | NoChangeType = NO_CHANGE

liquid_loaded: LiquidLoadedUpdate | NoChangeType = NO_CHANGE
@@ -423,7 +438,6 @@ def set_loaded_labware(
labware_id: str,
offset_id: typing.Optional[str],
display_name: typing.Optional[str],
lid_id: typing.Optional[str],
location: LabwareLocation,
) -> Self:
"""Add a new labware to state. See `LoadedLabwareUpdate`."""
@@ -433,7 +447,6 @@ def set_loaded_labware(
offset_id=offset_id,
new_location=location,
display_name=display_name,
lid_id=lid_id,
)
return self

@@ -445,13 +458,25 @@ def set_loaded_lid_stack(
location: LabwareLocation,
) -> Self:
"""Add a new lid stack to state. See `LoadedLidStackUpdate`."""
self.loaded_labware = LoadedLidStackUpdate(
self.loaded_lid_stack = LoadedLidStackUpdate(
definition=definition,
labware_ids=labware_ids,
new_location=location,
display_name=display_name,
)
return self

def set_lid(
self: Self,
parent_labware_id: str,
lid_id: str,
) -> Self:
"""Update the labware parent of a loaded or moved lid. See `LabwareLidUpdate`."""
self.labware_lid = LabwareLidUpdate(
parent_labware_id=parent_labware_id,
lid_id=lid_id,
)
return self

def set_load_pipette(
self: Self,