Skip to content

Commit

Permalink
Merge branch 'edge' into pd_step-summary
Browse files Browse the repository at this point in the history
  • Loading branch information
ncdiehl11 committed Oct 15, 2024
2 parents 060cdd8 + b0e5188 commit d8f9f03
Show file tree
Hide file tree
Showing 63 changed files with 1,024 additions and 359 deletions.
2 changes: 0 additions & 2 deletions api/src/opentrons/protocol_engine/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@
ConfigureNozzleLayoutCreate,
ConfigureNozzleLayoutParams,
ConfigureNozzleLayoutResult,
ConfigureNozzleLayoutPrivateResult,
ConfigureNozzleLayoutCommandType,
)

Expand Down Expand Up @@ -569,7 +568,6 @@
"ConfigureNozzleLayoutParams",
"ConfigureNozzleLayoutResult",
"ConfigureNozzleLayoutCommandType",
"ConfigureNozzleLayoutPrivateResult",
# get pipette tip presence bundle
"GetTipPresence",
"GetTipPresenceCreate",
Expand Down
2 changes: 0 additions & 2 deletions api/src/opentrons/protocol_engine/commands/command_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,6 @@
ConfigureNozzleLayoutParams,
ConfigureNozzleLayoutResult,
ConfigureNozzleLayoutCommandType,
ConfigureNozzleLayoutPrivateResult,
)

from .verify_tip_presence import (
Expand Down Expand Up @@ -709,7 +708,6 @@
None,
LoadPipettePrivateResult,
ConfigureForVolumePrivateResult,
ConfigureNozzleLayoutPrivateResult,
]

# All `DefinedErrorData`s that implementations will actually return in practice.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
)
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ..errors.error_occurrence import ErrorOccurrence
from .configuring_common import (
PipetteNozzleLayoutResultMixin,
)
from ..types import (
AllNozzleLayoutConfiguration,
SingleNozzleLayoutConfiguration,
Expand Down Expand Up @@ -40,12 +37,6 @@ class ConfigureNozzleLayoutParams(PipetteIdMixin):
]


class ConfigureNozzleLayoutPrivateResult(PipetteNozzleLayoutResultMixin):
"""Result sent to the store but not serialized."""

pass


class ConfigureNozzleLayoutResult(BaseModel):
"""Result data from execution of an configureNozzleLayout command."""

Expand All @@ -55,7 +46,7 @@ class ConfigureNozzleLayoutResult(BaseModel):
class ConfigureNozzleLayoutImplementation(
AbstractCommandImpl[
ConfigureNozzleLayoutParams,
SuccessData[ConfigureNozzleLayoutResult, ConfigureNozzleLayoutPrivateResult],
SuccessData[ConfigureNozzleLayoutResult, None],
]
):
"""Configure nozzle layout command implementation."""
Expand All @@ -68,7 +59,7 @@ def __init__(

async def execute(
self, params: ConfigureNozzleLayoutParams
) -> SuccessData[ConfigureNozzleLayoutResult, ConfigureNozzleLayoutPrivateResult]:
) -> SuccessData[ConfigureNozzleLayoutResult, None]:
"""Check that requested pipette can support the requested nozzle layout."""
primary_nozzle = params.configurationParams.dict().get("primaryNozzle")
front_right_nozzle = params.configurationParams.dict().get("frontRightNozzle")
Expand All @@ -93,10 +84,7 @@ async def execute(

return SuccessData(
public=ConfigureNozzleLayoutResult(),
private=ConfigureNozzleLayoutPrivateResult(
pipette_id=params.pipetteId,
nozzle_map=nozzle_map,
),
private=None,
state_update=update_state,
)

Expand Down
13 changes: 0 additions & 13 deletions api/src/opentrons/protocol_engine/commands/configuring_common.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
"""Common configuration command base models."""

from dataclasses import dataclass
from opentrons.hardware_control.nozzle_manager import (
NozzleMap,
)
from ..resources import pipette_data_provider


Expand All @@ -14,13 +11,3 @@ class PipetteConfigUpdateResultMixin:
pipette_id: str
serial_number: str
config: pipette_data_provider.LoadedStaticPipetteData


@dataclass
class PipetteNozzleLayoutResultMixin:
"""A nozzle layout result for updating the pipette state."""

pipette_id: str

nozzle_map: NozzleMap
"""A dataclass object holding information about the current nozzle configuration."""
32 changes: 11 additions & 21 deletions api/src/opentrons/protocol_engine/state/pipettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@ class StaticPipetteConfig:
nozzle_offset_z: float
pipette_bounding_box_offsets: PipetteBoundingBoxOffsets
bounding_nozzle_offsets: BoundingNozzlesOffsets
default_nozzle_map: NozzleMap
default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove?
lld_settings: Optional[Dict[str, Dict[str, float]]]


@dataclasses.dataclass
class PipetteState:
"""Basic pipette data state and getter methods."""

# todo(mm, 2024-10-14): It's getting difficult to ensure that all of these
# attributes are populated at the appropriate times. Refactor to a
# single dict-of-many-things instead of many dicts-of-single-things.
pipettes_by_id: Dict[str, LoadedPipette]
aspirated_volume_by_id: Dict[str, Optional[float]]
current_location: Optional[CurrentPipetteLocation]
Expand All @@ -112,7 +115,7 @@ class PipetteState:
movement_speed_by_id: Dict[str, Optional[float]]
static_config_by_id: Dict[str, StaticPipetteConfig]
flow_rates_by_id: Dict[str, FlowRates]
nozzle_configuration_by_id: Dict[str, Optional[NozzleMap]]
nozzle_configuration_by_id: Dict[str, NozzleMap]
liquid_presence_detection_by_id: Dict[str, bool]


Expand Down Expand Up @@ -167,11 +170,6 @@ def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
self._state.aspirated_volume_by_id[pipette_id] = None
self._state.movement_speed_by_id[pipette_id] = None
self._state.attached_tip_by_id[pipette_id] = None
static_config = self._state.static_config_by_id.get(pipette_id)
if static_config:
self._state.nozzle_configuration_by_id[
pipette_id
] = static_config.default_nozzle_map

def _update_tip_state(self, state_update: update_types.StateUpdate) -> None:
if state_update.pipette_tip_state != update_types.NO_CHANGE:
Expand Down Expand Up @@ -632,31 +630,23 @@ def get_plunger_axis(self, pipette_id: str) -> MotorAxis:

def get_nozzle_layout_type(self, pipette_id: str) -> NozzleConfigurationType:
"""Get the current set nozzle layout configuration."""
nozzle_map_for_pipette = self._state.nozzle_configuration_by_id.get(pipette_id)
if nozzle_map_for_pipette:
return nozzle_map_for_pipette.configuration
else:
return NozzleConfigurationType.FULL
nozzle_map_for_pipette = self._state.nozzle_configuration_by_id[pipette_id]
return nozzle_map_for_pipette.configuration

def get_is_partially_configured(self, pipette_id: str) -> bool:
"""Determine if the provided pipette is partially configured."""
return self.get_nozzle_layout_type(pipette_id) != NozzleConfigurationType.FULL

def get_primary_nozzle(self, pipette_id: str) -> Optional[str]:
def get_primary_nozzle(self, pipette_id: str) -> str:
"""Get the primary nozzle, if any, related to the given pipette's nozzle configuration."""
nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id)
return nozzle_map.starting_nozzle if nozzle_map else None
nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
return nozzle_map.starting_nozzle

def _get_critical_point_offset_without_tip(
self, pipette_id: str, critical_point: Optional[CriticalPoint]
) -> Point:
"""Get the offset of the specified critical point from pipette's mount position."""
nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id)
# Nozzle map is unavailable only when there's no pipette loaded
# so this is merely for satisfying the type checker
assert (
nozzle_map is not None
), "Error getting critical point offset. Nozzle map not found."
nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
match critical_point:
case CriticalPoint.INSTRUMENT_XY_CENTER:
return nozzle_map.instrument_xy_center_offset
Expand Down
21 changes: 10 additions & 11 deletions api/src/opentrons/protocol_engine/state/tips.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
Command,
LoadLabwareResult,
)
from ..commands.configuring_common import (
PipetteConfigUpdateResultMixin,
PipetteNozzleLayoutResultMixin,
)
from ..commands.configuring_common import PipetteConfigUpdateResultMixin

from opentrons.hardware_control.nozzle_manager import NozzleMap

Expand Down Expand Up @@ -82,13 +79,6 @@ def handle_action(self, action: Action) -> None:

self._handle_succeeded_command(action.command)

if isinstance(action.private_result, PipetteNozzleLayoutResultMixin):
pipette_id = action.private_result.pipette_id
nozzle_map = action.private_result.nozzle_map
pipette_info = self._state.pipette_info_by_pipette_id[pipette_id]
pipette_info.active_channels = nozzle_map.tip_count
pipette_info.nozzle_map = nozzle_map

elif isinstance(action, ResetTipsAction):
labware_id = action.labware_id

Expand Down Expand Up @@ -121,6 +111,15 @@ def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
well_name=state_update.tips_used.well_name,
)

if state_update.pipette_nozzle_map != update_types.NO_CHANGE:
pipette_info = self._state.pipette_info_by_pipette_id[
state_update.pipette_nozzle_map.pipette_id
]
pipette_info.active_channels = (
state_update.pipette_nozzle_map.nozzle_map.tip_count
)
pipette_info.nozzle_map = state_update.pipette_nozzle_map.nozzle_map

def _set_used_tips( # noqa: C901
self, pipette_id: str, well_name: str, labware_id: str
) -> None:
Expand Down
50 changes: 36 additions & 14 deletions api/src/opentrons/protocol_engine/state/update_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ class AddressableArea:

@dataclasses.dataclass
class PipetteLocationUpdate:
"""Represents an update to perform on a pipette's location."""
"""An update to a pipette's location."""

pipette_id: str
"""The ID of the already-loaded pipette."""

new_location: Well | AddressableArea | None | NoChangeType
"""The pipette's new logical location.
Expand All @@ -82,19 +83,30 @@ class PipetteLocationUpdate:

@dataclasses.dataclass
class LabwareLocationUpdate:
"""Represents an update to perform on a labware's location."""
"""An update to a labware's location."""

labware_id: str
"""The ID of the already-loaded labware."""

new_location: LabwareLocation
"""The labware's new logical location."""
"""The labware's new location."""

offset_id: typing.Optional[str]
"""The ID of the labware's new offset, for its new location."""


@dataclasses.dataclass
class LoadedLabwareUpdate(LabwareLocationUpdate):
"""Update loaded labware."""
class LoadedLabwareUpdate:
"""An update that loads a new labware."""

labware_id: str
"""The unique ID of the new labware."""

new_location: LabwareLocation
"""The labware's initial location."""

offset_id: typing.Optional[str]
"""The ID of the labware's offset."""

display_name: typing.Optional[str]

Expand All @@ -103,20 +115,30 @@ class LoadedLabwareUpdate(LabwareLocationUpdate):

@dataclasses.dataclass
class LoadPipetteUpdate:
"""Update loaded pipette."""
"""An update that loads a new pipette.
NOTE: Currently, if this is provided, a PipetteConfigUpdate must always be
provided alongside it to fully initialize everything.
"""

pipette_id: str
"""The unique ID of the new pipette."""

pipette_name: PipetteNameType
mount: MountType
liquid_presence_detection: typing.Optional[bool]


@dataclasses.dataclass
class PipetteConfigUpdate:
"""Update pipette config."""
"""An update to a pipette's config."""

pipette_id: str
"""The ID of the already-loaded pipette."""

# todo(mm, 2024-10-14): Does serial_number belong in LoadPipetteUpdate?
serial_number: str

config: pipette_data_provider.LoadedStaticPipetteData


Expand Down Expand Up @@ -237,7 +259,7 @@ def set_labware_location(
new_location: LabwareLocation,
new_offset_id: str | None,
) -> None:
"""Set labware location."""
"""Set a labware's location. See `LabwareLocationUpdate`."""
self.labware_location = LabwareLocationUpdate(
labware_id=labware_id,
new_location=new_location,
Expand All @@ -252,7 +274,7 @@ def set_loaded_labware(
display_name: typing.Optional[str],
location: LabwareLocation,
) -> None:
"""Add loaded labware to state."""
"""Add a new labware to state. See `LoadedLabwareUpdate`."""
self.loaded_labware = LoadedLabwareUpdate(
definition=definition,
labware_id=labware_id,
Expand All @@ -268,7 +290,7 @@ def set_load_pipette(
mount: MountType,
liquid_presence_detection: typing.Optional[bool],
) -> None:
"""Add loaded pipette to state."""
"""Add a new pipette to state. See `LoadPipetteUpdate`."""
self.loaded_pipette = LoadPipetteUpdate(
pipette_id=pipette_id,
pipette_name=pipette_name,
Expand All @@ -282,29 +304,29 @@ def update_pipette_config(
config: pipette_data_provider.LoadedStaticPipetteData,
serial_number: str,
) -> None:
"""Update pipette config."""
"""Update a pipette's config. See `PipetteConfigUpdate`."""
self.pipette_config = PipetteConfigUpdate(
pipette_id=pipette_id, config=config, serial_number=serial_number
)

def update_pipette_nozzle(self, pipette_id: str, nozzle_map: NozzleMap) -> None:
"""Update pipette nozzle map."""
"""Update a pipette's nozzle map. See `PipetteNozzleMapUpdate`."""
self.pipette_nozzle_map = PipetteNozzleMapUpdate(
pipette_id=pipette_id, nozzle_map=nozzle_map
)

def update_pipette_tip_state(
self, pipette_id: str, tip_geometry: typing.Optional[TipGeometry]
) -> None:
"""Update tip state."""
"""Update a pipette's tip state. See `PipetteTipStateUpdate`."""
self.pipette_tip_state = PipetteTipStateUpdate(
pipette_id=pipette_id, tip_geometry=tip_geometry
)

def mark_tips_as_used(
self, pipette_id: str, labware_id: str, well_name: str
) -> None:
"""Mark tips in a tip rack as used. See `MarkTipsUsedState`."""
"""Mark tips in a tip rack as used. See `TipsUsedUpdate`."""
self.tips_used = TipsUsedUpdate(
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
)
Loading

0 comments on commit d8f9f03

Please sign in to comment.