From 3501d5033d9bf8a62a36f7ec1885d858ce9c164b Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 25 Sep 2024 15:34:32 -0400 Subject: [PATCH 01/11] load pipette --- .../protocol_engine/commands/load_pipette.py | 9 ++++ .../protocol_engine/state/pipettes.py | 47 +++++++++++-------- .../protocol_engine/state/update_types.py | 28 +++++++++++ 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/load_pipette.py b/api/src/opentrons/protocol_engine/commands/load_pipette.py index d791a251873..22ca25056a2 100644 --- a/api/src/opentrons/protocol_engine/commands/load_pipette.py +++ b/api/src/opentrons/protocol_engine/commands/load_pipette.py @@ -1,6 +1,7 @@ """Load pipette command request, result, and implementation models.""" from __future__ import annotations +from opentrons.protocol_engine.state.update_types import StateUpdate from opentrons_shared_data.pipette.pipette_load_name_conversions import ( convert_to_pipette_name_type, ) @@ -123,6 +124,14 @@ async def execute( tip_overlap_version=params.tipOverlapNotAfterVersion, ) + state_update = StateUpdate() + state_update.set_load_pipette( + pipette_id=loaded_pipette.pipette_id, + pipette_name=params.pipetteName, + mount=params.mount, + liquid_presence_detection=params.liquidPresenceDetection, + ) + return SuccessData( public=LoadPipetteResult(pipetteId=loaded_pipette.pipette_id), private=LoadPipettePrivateResult( diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 6f942281906..dc3bab30a19 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -152,6 +152,7 @@ def handle_action(self, action: Action) -> None: def _handle_command( # noqa: C901 self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: + self._set_load_pipette(action) self._update_current_location(action) self._update_volumes(action) @@ -205,26 +206,6 @@ def _handle_command( # noqa: C901 private_result.pipette_id ] = private_result.nozzle_map - if isinstance(command.result, commands.LoadPipetteResult): - pipette_id = command.result.pipetteId - - self._state.pipettes_by_id[pipette_id] = LoadedPipette( - id=pipette_id, - pipetteName=command.params.pipetteName, - mount=command.params.mount, - ) - self._state.liquid_presence_detection_by_id[pipette_id] = ( - command.params.liquidPresenceDetection or False - ) - 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 - elif isinstance(command.result, commands.PickUpTipResult): pipette_id = command.params.pipetteId attached_tip = TipGeometry( @@ -281,6 +262,32 @@ def _handle_command( # noqa: C901 default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, ) + def _set_load_pipette( + self, action: Union[SucceedCommandAction, FailCommandAction] + ) -> None: + if ( + isinstance(action, SucceedCommandAction) + and action.state_update.loaded_pipette != update_types.NO_CHANGE + ): + pipette_id = action.state_update.loaded_pipette.pipette_id + + self._state.pipettes_by_id[pipette_id] = LoadedPipette( + id=pipette_id, + pipetteName=action.state_update.loaded_pipette.pipette_name, + mount=action.state_update.loaded_pipette.mount, + ) + self._state.liquid_presence_detection_by_id[pipette_id] = ( + action.state_update.loaded_pipette.liquid_presence_detection or False + ) + 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_current_location( self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index 6d5e28e3e6b..c46f54bff0e 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -6,7 +6,9 @@ import typing from opentrons.protocol_engine.types import DeckPoint, LabwareLocation +from opentrons.types import MountType from opentrons_shared_data.labware.labware_definition import LabwareDefinition +from opentrons_shared_data.pipette.types import PipetteNameType class _NoChangeEnum(enum.Enum): @@ -97,12 +99,24 @@ class LoadedLabwareUpdate(LabwareLocationUpdate): definition: LabwareDefinition +@dataclasses.dataclass +class LoadPipetteUpdate: + """Update loaded pipette.""" + + pipette_id: str + pipette_name: PipetteNameType + mount: MountType + liquid_presence_detection: typing.Optional[bool] + + @dataclasses.dataclass class StateUpdate: """Represents an update to perform on engine state.""" pipette_location: PipetteLocationUpdate | NoChangeType | ClearType = NO_CHANGE + loaded_pipette: LoadPipetteUpdate | NoChangeType = NO_CHANGE + labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE @@ -193,3 +207,17 @@ def set_loaded_labware( def clear_all_pipette_locations(self) -> None: """Mark all pipettes as having an unknown location.""" self.pipette_location = CLEAR + + def set_load_pipette( + self, + pipette_id: str, + pipette_name: PipetteNameType, + mount: MountType, + liquid_presence_detection: typing.Optional[bool], + ) -> None: + self.loaded_pipette = LoadPipetteUpdate( + pipette_id=pipette_id, + pipette_name=pipette_name, + mount=mount, + liquid_presence_detection=liquid_presence_detection, + ) From 2bfea4186ed0fed8aa648fd826ee4c9df875f6bf Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 25 Sep 2024 16:57:56 -0400 Subject: [PATCH 02/11] update pipette config --- .../commands/configure_for_volume.py | 9 ++ .../protocol_engine/commands/load_pipette.py | 6 ++ .../protocol_engine/state/pipettes.py | 92 ++++++++++--------- .../protocol_engine/state/update_types.py | 20 ++++ 4 files changed, 86 insertions(+), 41 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/configure_for_volume.py b/api/src/opentrons/protocol_engine/commands/configure_for_volume.py index 8415c401fe7..93a56ca8805 100644 --- a/api/src/opentrons/protocol_engine/commands/configure_for_volume.py +++ b/api/src/opentrons/protocol_engine/commands/configure_for_volume.py @@ -8,6 +8,7 @@ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence from .configuring_common import PipetteConfigUpdateResultMixin +from ..state.update_types import StateUpdate if TYPE_CHECKING: from ..execution import EquipmentHandler @@ -67,6 +68,13 @@ async def execute( tip_overlap_version=params.tipOverlapNotAfterVersion, ) + state_update = StateUpdate() + state_update.update_pipette_config( + pipette_id=pipette_result.pipette_id, + config=pipette_result.static_config, + serial_number=pipette_result.serial_number, + ) + return SuccessData( public=ConfigureForVolumeResult(), private=ConfigureForVolumePrivateResult( @@ -74,6 +82,7 @@ async def execute( serial_number=pipette_result.serial_number, config=pipette_result.static_config, ), + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/load_pipette.py b/api/src/opentrons/protocol_engine/commands/load_pipette.py index 22ca25056a2..5961272ae7c 100644 --- a/api/src/opentrons/protocol_engine/commands/load_pipette.py +++ b/api/src/opentrons/protocol_engine/commands/load_pipette.py @@ -131,6 +131,11 @@ async def execute( mount=params.mount, liquid_presence_detection=params.liquidPresenceDetection, ) + state_update.update_pipette_config( + pipette_id=loaded_pipette.pipette_id, + serial_number=loaded_pipette.serial_number, + config=loaded_pipette.static_config, + ) return SuccessData( public=LoadPipetteResult(pipetteId=loaded_pipette.pipette_id), @@ -139,6 +144,7 @@ async def execute( serial_number=loaded_pipette.serial_number, config=loaded_pipette.static_config, ), + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index dc3bab30a19..a6d4eab6bb0 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -154,6 +154,7 @@ def _handle_command( # noqa: C901 ) -> None: self._set_load_pipette(action) self._update_current_location(action) + self._update_pipette_config(action) self._update_volumes(action) if not isinstance(action, SucceedCommandAction): @@ -161,47 +162,7 @@ def _handle_command( # noqa: C901 command, private_result = action.command, action.private_result - if isinstance(private_result, PipetteConfigUpdateResultMixin): - config = private_result.config - self._state.static_config_by_id[ - private_result.pipette_id - ] = StaticPipetteConfig( - serial_number=private_result.serial_number, - model=config.model, - display_name=config.display_name, - min_volume=config.min_volume, - max_volume=config.max_volume, - channels=config.channels, - tip_configuration_lookup_table=config.tip_configuration_lookup_table, - nominal_tip_overlap=config.nominal_tip_overlap, - home_position=config.home_position, - nozzle_offset_z=config.nozzle_offset_z, - pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=config.back_left_corner_offset, - front_right_corner=config.front_right_corner_offset, - back_right_corner=Point( - config.front_right_corner_offset.x, - config.back_left_corner_offset.y, - config.back_left_corner_offset.z, - ), - front_left_corner=Point( - config.back_left_corner_offset.x, - config.front_right_corner_offset.y, - config.back_left_corner_offset.z, - ), - ), - bounding_nozzle_offsets=BoundingNozzlesOffsets( - back_left_offset=config.nozzle_map.back_left_nozzle_offset, - front_right_offset=config.nozzle_map.front_right_nozzle_offset, - ), - default_nozzle_map=config.nozzle_map, - lld_settings=config.pipette_lld_settings, - ) - self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates - self._state.nozzle_configuration_by_id[ - private_result.pipette_id - ] = config.nozzle_map - elif isinstance(private_result, PipetteNozzleLayoutResultMixin): + if isinstance(private_result, PipetteNozzleLayoutResultMixin): self._state.nozzle_configuration_by_id[ private_result.pipette_id ] = private_result.nozzle_map @@ -334,6 +295,55 @@ def _update_current_location( mount=loaded_pipette.mount, deck_point=new_deck_point ) + def _update_pipette_config( + self, action: Union[SucceedCommandAction, FailCommandAction] + ) -> None: + if ( + isinstance(action, SucceedCommandAction) + and action.state_update.pipette_config != update_types.NO_CHANGE + ): + config = action.state_update.pipette_config.config + self._state.static_config_by_id[ + action.state_update.pipette_config.pipette_id + ] = StaticPipetteConfig( + serial_number=action.state_update.pipette_config.serial_number, + model=config.model, + display_name=config.display_name, + min_volume=config.min_volume, + max_volume=config.max_volume, + channels=config.channels, + tip_configuration_lookup_table=config.tip_configuration_lookup_table, + nominal_tip_overlap=config.nominal_tip_overlap, + home_position=config.home_position, + nozzle_offset_z=config.nozzle_offset_z, + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=config.back_left_corner_offset, + front_right_corner=config.front_right_corner_offset, + back_right_corner=Point( + config.front_right_corner_offset.x, + config.back_left_corner_offset.y, + config.back_left_corner_offset.z, + ), + front_left_corner=Point( + config.back_left_corner_offset.x, + config.front_right_corner_offset.y, + config.back_left_corner_offset.z, + ), + ), + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=config.nozzle_map.back_left_nozzle_offset, + front_right_offset=config.nozzle_map.front_right_nozzle_offset, + ), + default_nozzle_map=config.nozzle_map, + lld_settings=config.pipette_lld_settings, + ) + self._state.flow_rates_by_id[ + action.state_update.pipette_config.pipette_id + ] = config.flow_rates + self._state.nozzle_configuration_by_id[ + action.state_update.pipette_config.pipette_id + ] = config.nozzle_map + def _update_volumes( self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index c46f54bff0e..f08faaafd01 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -5,6 +5,7 @@ import enum import typing +from opentrons.protocol_engine.resources import pipette_data_provider from opentrons.protocol_engine.types import DeckPoint, LabwareLocation from opentrons.types import MountType from opentrons_shared_data.labware.labware_definition import LabwareDefinition @@ -109,6 +110,13 @@ class LoadPipetteUpdate: liquid_presence_detection: typing.Optional[bool] +@dataclasses.dataclass +class PipetteConfigUpdate: + pipette_id: str + serial_number: str + config: pipette_data_provider.LoadedStaticPipetteData + + @dataclasses.dataclass class StateUpdate: """Represents an update to perform on engine state.""" @@ -117,6 +125,8 @@ class StateUpdate: loaded_pipette: LoadPipetteUpdate | NoChangeType = NO_CHANGE + pipette_config: PipetteConfigUpdate | NoChangeType = NO_CHANGE + labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE @@ -221,3 +231,13 @@ def set_load_pipette( mount=mount, liquid_presence_detection=liquid_presence_detection, ) + + def update_pipette_config( + self, + pipette_id: str, + config: pipette_data_provider.LoadedStaticPipetteData, + serial_number: str, + ) -> None: + self.pipette_config = PipetteConfigUpdate( + pipette_id=pipette_id, config=config, serial_number=serial_number + ) From 84f3bf5585141f29f10b54660d0934fa6e59e988 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 26 Sep 2024 10:51:06 -0400 Subject: [PATCH 03/11] update pipette nozzle --- .../commands/configure_nozzle_layout.py | 6 +++++ .../protocol_engine/state/pipettes.py | 22 ++++++++++++------- .../protocol_engine/state/update_types.py | 18 +++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py b/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py index 74681098ab9..530f2955b8a 100644 --- a/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py +++ b/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py @@ -1,5 +1,6 @@ """Configure nozzle layout command request, result, and implementation models.""" from __future__ import annotations +from opentrons.protocol_engine.state.update_types import StateUpdate from pydantic import BaseModel from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal @@ -85,6 +86,11 @@ async def execute( **nozzle_params, ) + update_state = StateUpdate() + update_state.update_pipette_nozzle( + pipette_id=params.pipetteId, nozzle_map=nozzle_map + ) + return SuccessData( public=ConfigureNozzleLayoutResult(), private=ConfigureNozzleLayoutPrivateResult( diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index a6d4eab6bb0..77eed79ac81 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -39,7 +39,6 @@ TipGeometry, ) from ..commands.configuring_common import ( - PipetteConfigUpdateResultMixin, PipetteNozzleLayoutResultMixin, ) from ..actions import ( @@ -155,19 +154,15 @@ def _handle_command( # noqa: C901 self._set_load_pipette(action) self._update_current_location(action) self._update_pipette_config(action) + self._update_pipette_nozzle_map(action) self._update_volumes(action) if not isinstance(action, SucceedCommandAction): return - command, private_result = action.command, action.private_result + command = action.command - if isinstance(private_result, PipetteNozzleLayoutResultMixin): - self._state.nozzle_configuration_by_id[ - private_result.pipette_id - ] = private_result.nozzle_map - - elif isinstance(command.result, commands.PickUpTipResult): + if isinstance(command.result, commands.PickUpTipResult): pipette_id = command.params.pipetteId attached_tip = TipGeometry( length=command.result.tipLength, @@ -344,6 +339,17 @@ def _update_pipette_config( action.state_update.pipette_config.pipette_id ] = config.nozzle_map + def _update_pipette_nozzle_map( + self, action: Union[SucceedCommandAction, FailCommandAction] + ) -> None: + if ( + isinstance(action, SucceedCommandAction) + and action.state_update.pipette_nozzle_map != update_types.NO_CHANGE + ): + self._state.nozzle_configuration_by_id[ + action.state_update.pipette_nozzle_map.pipette_id + ] = action.state_update.pipette_nozzle_map.nozzle_map + def _update_volumes( self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index f08faaafd01..2f3eb77ec4b 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -5,6 +5,7 @@ import enum import typing +from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_engine.resources import pipette_data_provider from opentrons.protocol_engine.types import DeckPoint, LabwareLocation from opentrons.types import MountType @@ -112,11 +113,21 @@ class LoadPipetteUpdate: @dataclasses.dataclass class PipetteConfigUpdate: + """Update pipette config.""" + pipette_id: str serial_number: str config: pipette_data_provider.LoadedStaticPipetteData +@dataclasses.dataclass +class PipetteNozzleMapUpdate: + """Update pipette nozzle map.""" + + pipette_id: str + nozzle_map: NozzleMap + + @dataclasses.dataclass class StateUpdate: """Represents an update to perform on engine state.""" @@ -127,6 +138,8 @@ class StateUpdate: pipette_config: PipetteConfigUpdate | NoChangeType = NO_CHANGE + pipette_nozzle_map: PipetteNozzleMapUpdate | NoChangeType = NO_CHANGE + labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE @@ -241,3 +254,8 @@ def update_pipette_config( 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: + self.pipette_nozzle_map = PipetteNozzleMapUpdate( + pipette_id=pipette_id, nozzle_map=nozzle_map + ) From 11f13fb609772b6d886a33e74e8b31df227ef341 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 26 Sep 2024 14:45:06 -0400 Subject: [PATCH 04/11] update tip state. fix tests in pipette store --- .../protocol_engine/commands/drop_tip.py | 2 + .../commands/drop_tip_in_place.py | 10 +- .../protocol_engine/commands/pick_up_tip.py | 10 +- .../unsafe/unsafe_drop_tip_in_place.py | 8 +- .../protocol_engine/state/pipettes.py | 117 ++++---- .../protocol_engine/state/update_types.py | 19 +- .../state/test_pipette_store.py | 250 +++++++++++++++--- 7 files changed, 311 insertions(+), 105 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip.py b/api/src/opentrons/protocol_engine/commands/drop_tip.py index 416472fc440..021c7a47bc5 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip.py @@ -114,6 +114,8 @@ async def execute(self, params: DropTipParams) -> SuccessData[DropTipResult, Non await self._tip_handler.drop_tip(pipette_id=pipette_id, home_after=home_after) + state_update.update_tip_state(pipette_id=params.pipetteId, tip_geometry=None) + return SuccessData( public=DropTipResult(position=deck_point), private=None, diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py index cf27732a6a5..724ab1b7f9d 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py @@ -1,5 +1,7 @@ """Drop tip in place command request, result, and implementation models.""" from __future__ import annotations +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.types import TipGeometry from pydantic import Field, BaseModel from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal @@ -54,7 +56,13 @@ async def execute( pipette_id=params.pipetteId, home_after=params.homeAfter ) - return SuccessData(public=DropTipInPlaceResult(), private=None) + state_update = update_types.StateUpdate() + + state_update.update_tip_state(pipette_id=params.pipetteId, tip_geometry=None) + + return SuccessData( + public=DropTipInPlaceResult(), private=None, state_update=state_update + ) class DropTipInPlace( diff --git a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py index c30d2f953db..86d64d3034e 100644 --- a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py +++ b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py @@ -9,7 +9,7 @@ from ..errors import ErrorOccurrence, TipNotAttachedError from ..resources import ModelUtils from ..state import update_types -from ..types import DeckPoint +from ..types import DeckPoint, TipGeometry from .pipetting_common import ( PipetteIdMixin, WellLocationMixin, @@ -130,6 +130,14 @@ async def execute( labware_id=labware_id, well_name=well_name, ) + state_update.update_tip_state( + pipette_id=pipette_id, + tip_geometry=TipGeometry( + volume=tip_geometry.volume, + length=tip_geometry.length, + diameter=tip_geometry.diameter, + ), + ) except TipNotAttachedError as e: return DefinedErrorData( public=TipPhysicallyMissingError( diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py index 6bf2d4a3a3f..e27a118ea60 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py @@ -1,5 +1,6 @@ """Command models to drop tip in place while plunger positions are unknown.""" from __future__ import annotations +from opentrons.protocol_engine.state.update_types import StateUpdate from pydantic import Field, BaseModel from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal @@ -72,7 +73,12 @@ async def execute( pipette_id=params.pipetteId, home_after=params.homeAfter ) - return SuccessData(public=UnsafeDropTipInPlaceResult(), private=None) + state_update = StateUpdate() + state_update.update_tip_state(pipette_id=params.pipetteId, tip_geometry=None) + + return SuccessData( + public=UnsafeDropTipInPlaceResult(), private=None, state_update=state_update + ) class UnsafeDropTipInPlace( diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 77eed79ac81..2aa55ea375a 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -155,69 +155,9 @@ def _handle_command( # noqa: C901 self._update_current_location(action) self._update_pipette_config(action) self._update_pipette_nozzle_map(action) + self._update_tip_state(action) self._update_volumes(action) - if not isinstance(action, SucceedCommandAction): - return - - command = action.command - - if isinstance(command.result, commands.PickUpTipResult): - pipette_id = command.params.pipetteId - attached_tip = TipGeometry( - length=command.result.tipLength, - volume=command.result.tipVolume, - diameter=command.result.tipDiameter, - ) - - self._state.attached_tip_by_id[pipette_id] = attached_tip - self._state.aspirated_volume_by_id[pipette_id] = 0 - - static_config = self._state.static_config_by_id.get(pipette_id) - if static_config: - try: - tip_configuration = static_config.tip_configuration_lookup_table[ - attached_tip.volume - ] - except KeyError: - # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume. - # we used to look up a default tip config via the pipette max volume, but if that isn't - # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using - # the first entry in the table is ok I guess but we really need to generally rethink how - # we identify tip classes - looking things up by volume is not enough. - tip_configuration = list( - static_config.tip_configuration_lookup_table.values() - )[0] - self._state.flow_rates_by_id[pipette_id] = FlowRates( - default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, - default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, - default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, - ) - - elif isinstance( - command.result, - ( - commands.DropTipResult, - commands.DropTipInPlaceResult, - commands.unsafe.UnsafeDropTipInPlaceResult, - ), - ): - pipette_id = command.params.pipetteId - self._state.aspirated_volume_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: - # TODO(seth,9/11/2023): bad way to do defaulting, see above. - tip_configuration = list( - static_config.tip_configuration_lookup_table.values() - )[0] - self._state.flow_rates_by_id[pipette_id] = FlowRates( - default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, - default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, - default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, - ) - def _set_load_pipette( self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: @@ -244,6 +184,61 @@ def _set_load_pipette( pipette_id ] = static_config.default_nozzle_map + def _update_tip_state( + self, action: Union[SucceedCommandAction, FailCommandAction] + ) -> None: + + if ( + isinstance(action, SucceedCommandAction) + and action.state_update.pipette_tip_state != update_types.NO_CHANGE + ): + pipette_id = action.state_update.pipette_tip_state.pipette_id + if action.state_update.pipette_tip_state.tip_geometry: + attached_tip = action.state_update.pipette_tip_state.tip_geometry + + self._state.attached_tip_by_id[pipette_id] = attached_tip + self._state.aspirated_volume_by_id[pipette_id] = 0 + + static_config = self._state.static_config_by_id.get(pipette_id) + if static_config: + try: + tip_configuration = ( + static_config.tip_configuration_lookup_table[ + attached_tip.volume + ] + ) + except KeyError: + # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume. + # we used to look up a default tip config via the pipette max volume, but if that isn't + # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using + # the first entry in the table is ok I guess but we really need to generally rethink how + # we identify tip classes - looking things up by volume is not enough. + tip_configuration = list( + static_config.tip_configuration_lookup_table.values() + )[0] + self._state.flow_rates_by_id[pipette_id] = FlowRates( + default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, + default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, + default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, + ) + + else: + pipette_id = action.state_update.pipette_tip_state.pipette_id + self._state.aspirated_volume_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: + # TODO(seth,9/11/2023): bad way to do defaulting, see above. + tip_configuration = list( + static_config.tip_configuration_lookup_table.values() + )[0] + self._state.flow_rates_by_id[pipette_id] = FlowRates( + default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, + default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, + default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, + ) + def _update_current_location( self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index 2f3eb77ec4b..a782ce96a26 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -7,7 +7,7 @@ from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_engine.resources import pipette_data_provider -from opentrons.protocol_engine.types import DeckPoint, LabwareLocation +from opentrons.protocol_engine.types import DeckPoint, LabwareLocation, TipGeometry from opentrons.types import MountType from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.pipette.types import PipetteNameType @@ -128,6 +128,14 @@ class PipetteNozzleMapUpdate: nozzle_map: NozzleMap +@dataclasses.dataclass +class PipetteTipStateUpdate: + """Update pipette tip state.""" + + pipette_id: str + tip_geometry: typing.Optional[TipGeometry] + + @dataclasses.dataclass class StateUpdate: """Represents an update to perform on engine state.""" @@ -140,6 +148,8 @@ class StateUpdate: pipette_nozzle_map: PipetteNozzleMapUpdate | NoChangeType = NO_CHANGE + pipette_tip_state: PipetteTipStateUpdate | NoChangeType = NO_CHANGE + labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE @@ -259,3 +269,10 @@ def update_pipette_nozzle(self, pipette_id: str, nozzle_map: NozzleMap) -> None: self.pipette_nozzle_map = PipetteNozzleMapUpdate( pipette_id=pipette_id, nozzle_map=nozzle_map ) + + def update_tip_state( + self, pipette_id: str, tip_geometry: typing.Optional[TipGeometry] + ) -> None: + self.pipette_tip_state = PipetteTipStateUpdate( + pipette_id=pipette_id, tip_geometry=tip_geometry + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index 0ba973e4cd1..81ddf13f5d8 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -99,7 +99,13 @@ def test_location_state_update(subject: PipetteStore) -> None: well_name="let's go party", ), new_deck_point=DeckPoint(x=111, y=222, z=333), - ) + ), + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + liquid_presence_detection=None, + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.RIGHT, + ), ), ) ) @@ -193,7 +199,20 @@ def test_handles_load_pipette(subject: PipetteStore) -> None: mount=MountType.LEFT, ) - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) + subject.handle_action( + SucceedCommandAction( + private_result=None, + command=command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) + ) result = subject.state @@ -224,10 +243,31 @@ def test_handles_pick_up_and_drop_tip(subject: PipetteStore) -> None: ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) + SucceedCommandAction( + private_result=None, + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="abc", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) + subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) + SucceedCommandAction( + private_result=None, + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ) + ), + ) ) assert subject.state.attached_tip_by_id["abc"] == TipGeometry( volume=42, length=101, diameter=8.0 @@ -235,7 +275,15 @@ def test_handles_pick_up_and_drop_tip(subject: PipetteStore) -> None: assert subject.state.aspirated_volume_by_id["abc"] == 0 subject.handle_action( - SucceedCommandAction(private_result=None, command=drop_tip_command) + SucceedCommandAction( + private_result=None, + command=drop_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ) + ), + ) ) assert subject.state.attached_tip_by_id["abc"] is None assert subject.state.aspirated_volume_by_id["abc"] is None @@ -258,10 +306,30 @@ def test_handles_drop_tip_in_place(subject: PipetteStore) -> None: ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) + SucceedCommandAction( + private_result=None, + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="xyz", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) + SucceedCommandAction( + private_result=None, + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ) + ), + ) ) assert subject.state.attached_tip_by_id["xyz"] == TipGeometry( volume=42, length=101, diameter=8.0 @@ -269,7 +337,15 @@ def test_handles_drop_tip_in_place(subject: PipetteStore) -> None: assert subject.state.aspirated_volume_by_id["xyz"] == 0 subject.handle_action( - SucceedCommandAction(private_result=None, command=drop_tip_in_place_command) + SucceedCommandAction( + private_result=None, + command=drop_tip_in_place_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", tip_geometry=None + ) + ), + ) ) assert subject.state.attached_tip_by_id["xyz"] is None assert subject.state.aspirated_volume_by_id["xyz"] is None @@ -292,10 +368,30 @@ def test_handles_unsafe_drop_tip_in_place(subject: PipetteStore) -> None: ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) + SucceedCommandAction( + private_result=None, + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="xyz", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) + SucceedCommandAction( + private_result=None, + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ) + ), + ) ) assert subject.state.attached_tip_by_id["xyz"] == TipGeometry( volume=42, length=101, diameter=8.0 @@ -304,7 +400,13 @@ def test_handles_unsafe_drop_tip_in_place(subject: PipetteStore) -> None: subject.handle_action( SucceedCommandAction( - private_result=None, command=unsafe_drop_tip_in_place_command + private_result=None, + command=unsafe_drop_tip_in_place_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", tip_geometry=None + ) + ), ) ) assert subject.state.attached_tip_by_id["xyz"] is None @@ -331,7 +433,18 @@ def test_aspirate_adds_volume( ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_command) + SucceedCommandAction( + private_result=None, + command=load_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) subject.handle_action( SucceedCommandAction(private_result=None, command=aspirate_command) @@ -373,7 +486,18 @@ def test_dispense_subtracts_volume( ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_command) + SucceedCommandAction( + private_result=None, + command=load_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) subject.handle_action( SucceedCommandAction(private_result=None, command=aspirate_command) @@ -415,7 +539,18 @@ def test_blow_out_clears_volume( ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_command) + SucceedCommandAction( + private_result=None, + command=load_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) subject.handle_action( SucceedCommandAction(private_result=None, command=aspirate_command) @@ -455,32 +590,42 @@ def test_add_pipette_config( ), result=cmd.LoadPipetteResult(pipetteId="pipette-id"), ) - private_result = cmd.LoadPipettePrivateResult( - pipette_id="pipette-id", - serial_number="pipette-serial", - config=LoadedStaticPipetteData( - model="pipette-model", - display_name="pipette name", - min_volume=1.23, - max_volume=4.56, - channels=7, - flow_rates=FlowRates( - default_aspirate={"a": 1}, - default_dispense={"b": 2}, - default_blow_out={"c": 3}, - ), - tip_configuration_lookup_table={4: supported_tip_fixture}, - nominal_tip_overlap={"default": 5}, - home_position=8.9, - nozzle_offset_z=10.11, - nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - back_left_corner_offset=Point(x=1, y=2, z=3), - front_right_corner_offset=Point(x=4, y=5, z=6), - pipette_lld_settings={}, + config = LoadedStaticPipetteData( + model="pipette-model", + display_name="pipette name", + min_volume=1.23, + max_volume=4.56, + channels=7, + flow_rates=FlowRates( + default_aspirate={"a": 1}, + default_dispense={"b": 2}, + default_blow_out={"c": 3}, ), + tip_configuration_lookup_table={4: supported_tip_fixture}, + nominal_tip_overlap={"default": 5}, + home_position=8.9, + nozzle_offset_z=10.11, + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + back_left_corner_offset=Point(x=1, y=2, z=3), + front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, + ) + + private_result = cmd.LoadPipettePrivateResult( + pipette_id="pipette-id", serial_number="pipette-serial", config=config ) subject.handle_action( - SucceedCommandAction(command=command, private_result=private_result) + SucceedCommandAction( + command=command, + private_result=private_result, + state_update=update_types.StateUpdate( + pipette_config=update_types.PipetteConfigUpdate( + pipette_id="pipette-id", + config=config, + serial_number="pipette-serial", + ) + ), + ) ) assert subject.state.static_config_by_id["pipette-id"] == StaticPipetteConfig( @@ -532,13 +677,38 @@ def test_prepare_to_aspirate_marks_pipette_ready( pipette_id="pipette-id", tip_volume=42, tip_length=101, tip_diameter=8.0 ) subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) + SucceedCommandAction( + private_result=None, + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P50_MULTI_FLEX, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) ) subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) + SucceedCommandAction( + private_result=None, + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ) + ), + ) ) - subject.handle_action(SucceedCommandAction(private_result=None, command=previous)) + subject.handle_action( + SucceedCommandAction( + private_result=None, + command=previous, + ) + ) prepare_to_aspirate_command = create_prepare_to_aspirate_command( pipette_id="pipette-id" From 6f70d210912c31a8fb4410524e6a66efefeec862 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 26 Sep 2024 15:01:04 -0400 Subject: [PATCH 05/11] test fixes --- .../protocol_engine/commands/test_drop_tip.py | 8 ++++++++ .../commands/test_drop_tip_in_place.py | 9 ++++++++- .../commands/test_load_pipette.py | 19 +++++++++++++++++++ .../commands/test_pick_up_tip.py | 4 ++++ .../unsafe/test_unsafe_drop_tip_in_place.py | 9 ++++++++- 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py index d0d69eeccfa..b79e4c59089 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py @@ -123,6 +123,10 @@ async def test_drop_tip_implementation( well_name="A3", ), new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=None ) ), ) @@ -196,6 +200,10 @@ async def test_drop_tip_with_alternating_locations( well_name="A3", ), new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=None ) ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py index 4bfefe4fdbe..e8c203b1ee5 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py @@ -1,4 +1,5 @@ """Test drop tip in place commands.""" +from opentrons.protocol_engine.state.update_types import PipetteTipStateUpdate, StateUpdate import pytest from decoy import Decoy @@ -29,7 +30,13 @@ async def test_drop_tip_implementation( result = await subject.execute(params) - assert result == SuccessData(public=DropTipInPlaceResult(), private=None) + assert result == SuccessData(public=DropTipInPlaceResult(), private=None, + state_update=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=None + ) + )) decoy.verify( await mock_tip_handler.drop_tip(pipette_id="abc", home_after=False), diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py index 6b7bd77c32c..c015dfc49a0 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -1,4 +1,5 @@ """Test load pipette commands.""" +from opentrons.protocol_engine.state.update_types import LoadPipetteUpdate, StateUpdate import pytest from decoy import Decoy @@ -88,6 +89,15 @@ async def test_load_pipette_implementation( private=LoadPipettePrivateResult( pipette_id="some id", serial_number="some-serial-number", config=config_data ), + state_update=StateUpdate( + loaded_pipette=LoadPipetteUpdate( + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + pipette_id="some id", + liquid_presence_detection=None + ), + pipette_config=config_data + ) ) @@ -143,6 +153,15 @@ async def test_load_pipette_implementation_96_channel( private=LoadPipettePrivateResult( pipette_id="pipette-id", serial_number="some id", config=config_data ), + state_update=StateUpdate( + loaded_pipette=LoadPipetteUpdate( + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + pipette_id="some id", + liquid_presence_detection=None + ), + pipette_config=config_data + ) ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py index 9b92fd219a0..539a26f03b0 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py @@ -78,6 +78,10 @@ async def test_success( pipette_id="pipette-id", new_location=update_types.Well(labware_id="labware-id", well_name="A3"), new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(length=42, diameter=5, volume=300) ) ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py index 6d739f97442..9171999ad01 100644 --- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py @@ -1,4 +1,5 @@ """Test unsafe drop tip in place commands.""" +from opentrons.protocol_engine.state.update_types import PipetteTipStateUpdate, StateUpdate import pytest from decoy import Decoy @@ -44,7 +45,13 @@ async def test_drop_tip_implementation( result = await subject.execute(params) - assert result == SuccessData(public=UnsafeDropTipInPlaceResult(), private=None) + assert result == SuccessData(public=UnsafeDropTipInPlaceResult(), private=None, + state_update=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=None + ) + )) decoy.verify( await ot3_hardware_api.update_axis_position_estimations([Axis.P_L]), From a213c1fe739c39e9330b0aa339be56b409671f3c Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 26 Sep 2024 15:59:26 -0400 Subject: [PATCH 06/11] legacy command mapper and test fixes --- .../protocol_runner/legacy_command_mapper.py | 9 +++++- .../commands/test_configure_for_volume.py | 10 ++++++ .../protocol_engine/commands/test_drop_tip.py | 10 +++--- .../commands/test_drop_tip_in_place.py | 19 ++++++----- .../commands/test_load_pipette.py | 32 +++++++++++++------ .../commands/test_pick_up_tip.py | 4 +-- .../unsafe/test_unsafe_drop_tip_in_place.py | 19 ++++++----- 7 files changed, 68 insertions(+), 35 deletions(-) diff --git a/api/src/opentrons/protocol_runner/legacy_command_mapper.py b/api/src/opentrons/protocol_runner/legacy_command_mapper.py index 8ffb67d3560..686560c1ca2 100644 --- a/api/src/opentrons/protocol_runner/legacy_command_mapper.py +++ b/api/src/opentrons/protocol_runner/legacy_command_mapper.py @@ -754,10 +754,17 @@ def _map_instrument_load( # We just set this above, so we know it's not None. started_at=succeeded_command.startedAt, # type: ignore[arg-type] ) + state_update = StateUpdate() + state_update.set_load_pipette( + pipette_id=pipette_id, + mount=succeeded_command.params.mount, + pipette_name=succeeded_command.params.pipetteName, + liquid_presence_detection=succeeded_command.params.liquidPresenceDetection, + ) succeed_action = pe_actions.SucceedCommandAction( command=succeeded_command, private_result=pipette_config_result, - state_update=StateUpdate(), + state_update=state_update, ) self._command_count["LOAD_PIPETTE"] = count + 1 diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py index 95e1e856bd4..309ab41d53e 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py @@ -1,4 +1,9 @@ """Test load pipette commands.""" +from opentrons.protocol_engine.state.update_types import ( + LoadPipetteUpdate, + PipetteConfigUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -84,4 +89,9 @@ async def test_configure_for_volume_implementation( private=ConfigureForVolumePrivateResult( pipette_id="pipette-id", serial_number="some number", config=config ), + state_update=StateUpdate( + pipette_config=PipetteConfigUpdate( + pipette_id="pipette-id", serial_number="some number", config=config + ) + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py index b79e4c59089..a44b6892401 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py @@ -125,9 +125,8 @@ async def test_drop_tip_implementation( new_deck_point=DeckPoint(x=111, y=222, z=333), ), pipette_tip_state=update_types.PipetteTipStateUpdate( - pipette_id="abc", - tip_geometry=None - ) + pipette_id="abc", tip_geometry=None + ), ), ) @@ -202,8 +201,7 @@ async def test_drop_tip_with_alternating_locations( new_deck_point=DeckPoint(x=111, y=222, z=333), ), pipette_tip_state=update_types.PipetteTipStateUpdate( - pipette_id="abc", - tip_geometry=None - ) + pipette_id="abc", tip_geometry=None + ), ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py index e8c203b1ee5..aa7854f6105 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py @@ -1,5 +1,8 @@ """Test drop tip in place commands.""" -from opentrons.protocol_engine.state.update_types import PipetteTipStateUpdate, StateUpdate +from opentrons.protocol_engine.state.update_types import ( + PipetteTipStateUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -30,13 +33,13 @@ async def test_drop_tip_implementation( result = await subject.execute(params) - assert result == SuccessData(public=DropTipInPlaceResult(), private=None, - state_update=StateUpdate( - pipette_tip_state=PipetteTipStateUpdate( - pipette_id="abc", - tip_geometry=None - ) - )) + assert result == SuccessData( + public=DropTipInPlaceResult(), + private=None, + state_update=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate(pipette_id="abc", tip_geometry=None) + ), + ) decoy.verify( await mock_tip_handler.drop_tip(pipette_id="abc", home_after=False), diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py index c015dfc49a0..5884e015342 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -1,5 +1,9 @@ """Test load pipette commands.""" -from opentrons.protocol_engine.state.update_types import LoadPipetteUpdate, StateUpdate +from opentrons.protocol_engine.state.update_types import ( + LoadPipetteUpdate, + PipetteConfigUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -94,10 +98,14 @@ async def test_load_pipette_implementation( pipette_name=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, pipette_id="some id", - liquid_presence_detection=None + liquid_presence_detection=None, ), - pipette_config=config_data - ) + pipette_config=PipetteConfigUpdate( + pipette_id="some id", + serial_number="some-serial-number", + config=config_data, + ), + ), ) @@ -153,15 +161,19 @@ async def test_load_pipette_implementation_96_channel( private=LoadPipettePrivateResult( pipette_id="pipette-id", serial_number="some id", config=config_data ), - state_update=StateUpdate( + state_update=StateUpdate( loaded_pipette=LoadPipetteUpdate( - pipette_name=PipetteNameType.P300_SINGLE, + pipette_name=PipetteNameType.P1000_96, mount=MountType.LEFT, - pipette_id="some id", - liquid_presence_detection=None + pipette_id="pipette-id", + liquid_presence_detection=None, ), - pipette_config=config_data - ) + pipette_config=PipetteConfigUpdate( + pipette_id="pipette-id", + serial_number="some id", + config=config_data, + ), + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py index 539a26f03b0..c4ff37c501d 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py @@ -81,8 +81,8 @@ async def test_success( ), pipette_tip_state=update_types.PipetteTipStateUpdate( pipette_id="pipette-id", - tip_geometry=TipGeometry(length=42, diameter=5, volume=300) - ) + tip_geometry=TipGeometry(length=42, diameter=5, volume=300), + ), ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py index 9171999ad01..4913fe3c444 100644 --- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py @@ -1,5 +1,8 @@ """Test unsafe drop tip in place commands.""" -from opentrons.protocol_engine.state.update_types import PipetteTipStateUpdate, StateUpdate +from opentrons.protocol_engine.state.update_types import ( + PipetteTipStateUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -45,13 +48,13 @@ async def test_drop_tip_implementation( result = await subject.execute(params) - assert result == SuccessData(public=UnsafeDropTipInPlaceResult(), private=None, - state_update=StateUpdate( - pipette_tip_state=PipetteTipStateUpdate( - pipette_id="abc", - tip_geometry=None - ) - )) + assert result == SuccessData( + public=UnsafeDropTipInPlaceResult(), + private=None, + state_update=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate(pipette_id="abc", tip_geometry=None) + ), + ) decoy.verify( await ot3_hardware_api.update_axis_position_estimations([Axis.P_L]), From 7dde0995a72392cc25c09e2303fc9880276f275e Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 27 Sep 2024 13:36:06 -0400 Subject: [PATCH 07/11] lint --- .../opentrons/protocol_engine/commands/drop_tip_in_place.py | 1 - api/src/opentrons/protocol_engine/state/pipettes.py | 5 +---- api/src/opentrons/protocol_engine/state/update_types.py | 5 +++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py index 724ab1b7f9d..a8e62354f40 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py @@ -1,7 +1,6 @@ """Drop tip in place command request, result, and implementation models.""" from __future__ import annotations from opentrons.protocol_engine.state import update_types -from opentrons.protocol_engine.types import TipGeometry from pydantic import Field, BaseModel from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 2aa55ea375a..e558d3a0fe6 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -38,9 +38,6 @@ CurrentPipetteLocation, TipGeometry, ) -from ..commands.configuring_common import ( - PipetteNozzleLayoutResultMixin, -) from ..actions import ( Action, SetPipetteMovementSpeedAction, @@ -148,7 +145,7 @@ def handle_action(self, action: Action) -> None: elif isinstance(action, SetPipetteMovementSpeedAction): self._state.movement_speed_by_id[action.pipette_id] = action.speed - def _handle_command( # noqa: C901 + def _handle_command( self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: self._set_load_pipette(action) diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index a782ce96a26..c2416b4bfb5 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -214,6 +214,7 @@ def set_labware_location( # noqa: D102 new_location: LabwareLocation, new_offset_id: str | None, ) -> None: + """Set labware location.""" self.labware_location = LabwareLocationUpdate( labware_id=labware_id, new_location=new_location, @@ -248,6 +249,7 @@ def set_load_pipette( mount: MountType, liquid_presence_detection: typing.Optional[bool], ) -> None: + """Add loaded pipette to state.""" self.loaded_pipette = LoadPipetteUpdate( pipette_id=pipette_id, pipette_name=pipette_name, @@ -261,11 +263,13 @@ def update_pipette_config( config: pipette_data_provider.LoadedStaticPipetteData, serial_number: str, ) -> None: + """Update pipette config.""" 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.""" self.pipette_nozzle_map = PipetteNozzleMapUpdate( pipette_id=pipette_id, nozzle_map=nozzle_map ) @@ -273,6 +277,7 @@ def update_pipette_nozzle(self, pipette_id: str, nozzle_map: NozzleMap) -> None: def update_tip_state( self, pipette_id: str, tip_geometry: typing.Optional[TipGeometry] ) -> None: + """Update tip state.""" self.pipette_tip_state = PipetteTipStateUpdate( pipette_id=pipette_id, tip_geometry=tip_geometry ) From a4c1620919dbdb6cac6105623865947ed99ce9d3 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 27 Sep 2024 13:42:18 -0400 Subject: [PATCH 08/11] fix test legacy command mapper --- .../protocol_runner/test_legacy_command_mapper.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py b/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py index b58032d6cba..8663c3e0a8d 100644 --- a/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py +++ b/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py @@ -4,6 +4,7 @@ from typing import cast from opentrons.protocol_engine.state.update_types import ( + LoadPipetteUpdate, LoadedLabwareUpdate, StateUpdate, ) @@ -382,6 +383,14 @@ def test_map_instrument_load(decoy: Decoy) -> None: private_result=pe_commands.LoadPipettePrivateResult( pipette_id="pipette-0", serial_number="fizzbuzz", config=pipette_config ), + state_update=StateUpdate( + loaded_pipette=LoadPipetteUpdate( + pipette_id="pipette-0", + mount=expected_params.mount, + pipette_name=expected_params.pipetteName, + liquid_presence_detection=expected_params.liquidPresenceDetection, + ) + ), ) [ From f445a32479710633c77c9de8e67d1fc970073dc7 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 27 Sep 2024 13:48:13 -0400 Subject: [PATCH 09/11] linting --- api/src/opentrons/protocol_engine/state/update_types.py | 2 +- .../protocol_engine/commands/test_configure_for_volume.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index c2416b4bfb5..91cdf0194a3 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -207,7 +207,7 @@ def set_pipette_location( # noqa: D102 new_deck_point=new_deck_point, ) - def set_labware_location( # noqa: D102 + def set_labware_location( self, *, labware_id: str, diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py index 309ab41d53e..2279f2a0ebf 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py @@ -1,6 +1,5 @@ """Test load pipette commands.""" from opentrons.protocol_engine.state.update_types import ( - LoadPipetteUpdate, PipetteConfigUpdate, StateUpdate, ) From aa132030a20e32bc3ee60d771916b9721925c621 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 27 Sep 2024 14:34:55 -0400 Subject: [PATCH 10/11] update state on nozzle map update --- .../protocol_engine/commands/configure_nozzle_layout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py b/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py index 530f2955b8a..0d9a5e8c5e7 100644 --- a/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py +++ b/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py @@ -97,6 +97,7 @@ async def execute( pipette_id=params.pipetteId, nozzle_map=nozzle_map, ), + state_update=update_state, ) From 8dbcdba3fda659cf578e2b86cfcc6ae60ce05e33 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 27 Sep 2024 14:58:31 -0400 Subject: [PATCH 11/11] configure nozzle test --- .../commands/test_configure_nozzle_layout.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py b/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py index 2f318b147ac..d4a9c671dd3 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py @@ -1,4 +1,8 @@ """Test configure nozzle layout commands.""" +from opentrons.protocol_engine.state.update_types import ( + PipetteNozzleMapUpdate, + StateUpdate, +) import pytest from decoy import Decoy from typing import Union, Dict @@ -146,4 +150,10 @@ async def test_configure_nozzle_layout_implementation( pipette_id="pipette-id", nozzle_map=expected_nozzlemap, ), + state_update=StateUpdate( + pipette_nozzle_map=PipetteNozzleMapUpdate( + pipette_id="pipette-id", + nozzle_map=expected_nozzlemap, + ) + ), )