From 3099ca79999550aba9658877e62ec34503250083 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 20 Feb 2024 21:40:53 +0200 Subject: [PATCH 1/3] refactor(api): Save full nozzle map configuration and update state store accordingly --- .../protocol_engine/execution/equipment.py | 6 ++-- .../resources/pipette_data_provider.py | 14 ++------ .../protocol_engine/state/pipettes.py | 7 ++-- .../commands/test_configure_for_volume.py | 7 ++-- .../commands/test_configure_nozzle_layout.py | 4 +-- .../commands/test_load_pipette.py | 9 +++-- .../execution/test_equipment_handler.py | 5 +-- .../protocol_engine/pipette_fixtures.py | 34 +++++++++++++++++++ .../resources/test_pipette_data_provider.py | 29 +++++----------- .../state/test_pipette_store.py | 10 +++--- .../protocol_engine/state/test_tip_state.py | 21 +++++------- 11 files changed, 78 insertions(+), 68 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/equipment.py b/api/src/opentrons/protocol_engine/execution/equipment.py index c1ac272a64d..37f9470b84d 100644 --- a/api/src/opentrons/protocol_engine/execution/equipment.py +++ b/api/src/opentrons/protocol_engine/execution/equipment.py @@ -202,7 +202,6 @@ async def load_pipette( ) pipette_id = pipette_id or self._model_utils.generate_id() - if not use_virtual_pipettes: cache_request = {mount.to_hw_mount(): pipette_name_value} @@ -244,11 +243,10 @@ async def load_pipette( ) ) serial = serial_number or "" - return LoadedPipetteData( pipette_id=pipette_id, serial_number=serial, - static_config=static_pipette_config, + static_config=static_pipette_config ) async def load_magnetic_block( @@ -390,7 +388,7 @@ async def configure_nozzle_layout( primary_nozzle: Optional[str] = None, front_right_nozzle: Optional[str] = None, back_left_nozzle: Optional[str] = None, - ) -> Optional[NozzleMap]: + ) -> NozzleMap: """Ensure the requested nozzle layout is compatible with the current pipette. Args: diff --git a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py index 940440826e7..f21dfe8ee35 100644 --- a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py @@ -37,8 +37,7 @@ class LoadedStaticPipetteData: float, pipette_definition.SupportedTipsDefinition ] nominal_tip_overlap: Dict[str, float] - back_left_nozzle_offset: Point - front_right_nozzle_offset: Point + nozzle_map: NozzleMap class VirtualPipetteDataProvider: @@ -173,8 +172,7 @@ def _get_virtual_pipette_static_config_by_model( nominal_tip_overlap=config.liquid_properties[ liquid_class ].tip_overlap_dictionary, - back_left_nozzle_offset=nozzle_manager.current_configuration.back_left_nozzle_offset, - front_right_nozzle_offset=nozzle_manager.current_configuration.front_right_nozzle_offset, + nozzle_map=nozzle_manager.current_configuration ) def get_virtual_pipette_static_config( @@ -208,11 +206,5 @@ def get_pipette_static_config(pipette_dict: PipetteDict) -> LoadedStaticPipetteD # https://opentrons.atlassian.net/browse/RCORE-655 home_position=0, nozzle_offset_z=0, - # TODO (spp): Confirm that the nozzle map is populated by the hardware api by default - back_left_nozzle_offset=pipette_dict[ - "current_nozzle_map" - ].back_left_nozzle_offset, - front_right_nozzle_offset=pipette_dict[ - "current_nozzle_map" - ].front_right_nozzle_offset, + nozzle_map=pipette_dict["current_nozzle_map"] ) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index a1269c9a8f5..7cb7401581f 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -167,11 +167,14 @@ def _handle_command( # noqa: C901 home_position=config.home_position, nozzle_offset_z=config.nozzle_offset_z, bounding_nozzle_offsets=BoundingNozzlesOffsets( - back_left_offset=config.back_left_nozzle_offset, - front_right_offset=config.front_right_nozzle_offset, + back_left_offset=config.nozzle_map.back_left_nozzle_offset, + front_right_offset=config.nozzle_map.front_right_nozzle_offset, ), ) 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): self._state.nozzle_configuration_by_id[ private_result.pipette_id 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 8ccc3d3f8cc..e4bfe048bda 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 @@ -16,8 +16,8 @@ ConfigureForVolumePrivateResult, ConfigureForVolumeImplementation, ) -from opentrons.types import Point - +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map async def test_configure_for_volume_implementation( decoy: Decoy, @@ -44,8 +44,7 @@ async def test_configure_for_volume_implementation( ), tip_configuration_lookup_table={}, nominal_tip_overlap={}, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI), ) decoy.when( 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 f6b45376f7e..43b140c81fe 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,7 +1,7 @@ """Test configure nozzle layout commands.""" import pytest from decoy import Decoy -from typing import Union, Optional, Dict +from typing import Union, Dict from collections import OrderedDict from opentrons.protocol_engine.execution import ( @@ -90,7 +90,7 @@ async def test_configure_nozzle_layout_implementation( QuadrantNozzleLayoutConfiguration, SingleNozzleLayoutConfiguration, ], - expected_nozzlemap: Optional[NozzleMap], + expected_nozzlemap: NozzleMap, nozzle_params: Dict[str, str], ) -> None: """A ConfigureForVolume command should have an execution implementation.""" 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 967d60be945..a7d7859da3d 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -19,7 +19,8 @@ LoadPipettePrivateResult, LoadPipetteImplementation, ) - +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map async def test_load_pipette_implementation( decoy: Decoy, @@ -41,8 +42,7 @@ async def test_load_pipette_implementation( ), tip_configuration_lookup_table={}, nominal_tip_overlap={}, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI), ) data = LoadPipetteParams( pipetteName=PipetteNameType.P300_SINGLE, @@ -98,8 +98,7 @@ async def test_load_pipette_implementation_96_channel( ), tip_configuration_lookup_table={}, nominal_tip_overlap={}, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), ) decoy.when( diff --git a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py index 0ef6a1b00bb..c4502c9575f 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py @@ -56,6 +56,8 @@ LoadedPipetteData, LoadedModuleData, ) +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map def _make_config(use_virtual_modules: bool) -> Config: @@ -147,8 +149,7 @@ def loaded_static_pipette_data( nominal_tip_overlap={"default": 9.87}, home_position=10.11, nozzle_offset_z=12.13, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE) ) diff --git a/api/tests/opentrons/protocol_engine/pipette_fixtures.py b/api/tests/opentrons/protocol_engine/pipette_fixtures.py index b2d2e6bafe3..26c2ed33448 100644 --- a/api/tests/opentrons/protocol_engine/pipette_fixtures.py +++ b/api/tests/opentrons/protocol_engine/pipette_fixtures.py @@ -3,6 +3,9 @@ from collections import OrderedDict from opentrons.types import Point +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons_shared_data.pipette.dev_types import PipetteNameType + NINETY_SIX_ROWS = OrderedDict( ( @@ -317,3 +320,34 @@ ("H1", Point(0.0, -31.5, 35.52)), ) ) + + +def get_default_nozzle_map(pipette_type: PipetteNameType) -> NozzleMap: + """Get default nozzle map for a given pipette type.""" + if "multi" in pipette_type.value: + return NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + ) + elif "96" in pipette_type.value: + return NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + ) + else: + return NozzleMap.build( + physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}), + physical_rows=OrderedDict({"A": ["A1"]}), + physical_columns=OrderedDict({"1": ["A1"]}), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + ) diff --git a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py index 432c408e43e..14da9dceefb 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py @@ -6,7 +6,6 @@ from opentrons_shared_data.pipette import pipette_definition, types as pip_types from opentrons.hardware_control.dev_types import PipetteDict -from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_engine.types import FlowRates from opentrons.protocol_engine.resources.pipette_data_provider import ( LoadedStaticPipetteData, @@ -14,7 +13,8 @@ ) from opentrons.protocol_engine.resources import pipette_data_provider as subject -from opentrons.types import Point +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map @pytest.fixture @@ -54,8 +54,7 @@ def test_get_virtual_pipette_static_config( "opentrons/opentrons_96_tiprack_10ul/1": 8.25, "opentrons/opentrons_96_tiprack_20ul/1": 8.25, }, - back_left_nozzle_offset=Point(x=0.0, y=0.0, z=10.45), - front_right_nozzle_offset=Point(x=0.0, y=0.0, z=10.45), + nozzle_map=result.nozzle_map, ) @@ -81,8 +80,7 @@ def test_configure_virtual_pipette_for_volume( ), tip_configuration_lookup_table=result1.tip_configuration_lookup_table, nominal_tip_overlap=result1.nominal_tip_overlap, - back_left_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), - front_right_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), + nozzle_map=result1.nozzle_map, ) subject_instance.configure_virtual_pipette_for_volume( "my-pipette", 1, result1.model @@ -105,8 +103,7 @@ def test_configure_virtual_pipette_for_volume( ), tip_configuration_lookup_table=result2.tip_configuration_lookup_table, nominal_tip_overlap=result2.nominal_tip_overlap, - back_left_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), - front_right_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), + nozzle_map=result2.nozzle_map, ) @@ -132,8 +129,7 @@ def test_load_virtual_pipette_by_model_string( ), tip_configuration_lookup_table=result.tip_configuration_lookup_table, nominal_tip_overlap=result.nominal_tip_overlap, - back_left_nozzle_offset=Point(x=0.0, y=31.5, z=35.52), - front_right_nozzle_offset=Point(x=0.0, y=-31.5, z=35.52), + nozzle_map=result.nozzle_map, ) @@ -179,6 +175,7 @@ def test_get_pipette_static_config( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, ) -> None: """It should return config data given a PipetteDict.""" + dummy_nozzle_map = get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), pipette_dict: PipetteDict = { "name": "p300_single_gen2", "min_volume": 20, @@ -214,14 +211,7 @@ def test_get_pipette_static_config( "default_aspirate_speeds": {"2.0": 5.021202, "2.6": 10.042404}, "default_push_out_volume": 3, "supported_tips": {pip_types.PipetteTipType.t300: supported_tip_fixture}, - "current_nozzle_map": NozzleMap.build( - physical_nozzles=OrderedDict({"A1": Point(1, 2, 3), "B1": Point(4, 5, 6)}), - physical_rows=OrderedDict({"A": ["A1"], "B": ["B1"]}), - physical_columns=OrderedDict({"1": ["A1", "B1"]}), - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="B1", - ), + "current_nozzle_map": dummy_nozzle_map, } result = subject.get_pipette_static_config(pipette_dict) @@ -247,6 +237,5 @@ def test_get_pipette_static_config( # https://opentrons.atlassian.net/browse/RCORE-655 nozzle_offset_z=0, home_position=0, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=dummy_nozzle_map, ) 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 a8a03539848..fbd41a971d3 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -51,7 +51,8 @@ create_move_relative_command, create_prepare_to_aspirate_command, ) - +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map @pytest.fixture def subject() -> PipetteStore: @@ -682,8 +683,7 @@ def test_add_pipette_config( nominal_tip_overlap={"default": 5}, home_position=8.9, nozzle_offset_z=10.11, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE) ), ) subject.handle_action( @@ -702,8 +702,8 @@ def test_add_pipette_config( home_position=8.9, nozzle_offset_z=10.11, bounding_nozzle_offsets=BoundingNozzlesOffsets( - back_left_offset=Point(x=1, y=2, z=3), - front_right_offset=Point(x=4, y=5, z=6), + back_left_offset=Point(x=0, y=0, z=0), + front_right_offset=Point(x=0, y=0, z=0) ), ) assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} diff --git a/api/tests/opentrons/protocol_engine/state/test_tip_state.py b/api/tests/opentrons/protocol_engine/state/test_tip_state.py index 59be5e927f5..3b0a3598191 100644 --- a/api/tests/opentrons/protocol_engine/state/test_tip_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_tip_state.py @@ -19,11 +19,12 @@ LoadedStaticPipetteData, ) from opentrons.types import Point - +from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import ( NINETY_SIX_MAP, NINETY_SIX_COLS, NINETY_SIX_ROWS, + get_default_nozzle_map, ) _tip_rack_parameters = LabwareParameters.construct(isTiprack=True) # type: ignore[call-arg] @@ -218,8 +219,7 @@ def test_get_next_tip_skips_picked_up_tip( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -412,8 +412,7 @@ def test_reset_tips( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) @@ -462,8 +461,7 @@ def test_handle_pipette_config_action( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -544,8 +542,7 @@ def test_drop_tip( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -649,8 +646,7 @@ def test_active_channels( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -713,8 +709,7 @@ def test_next_tip_uses_active_channels( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( From 2b89518d727a06c0432bd7548ce63d3800b3f417 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 20 Feb 2024 22:16:24 +0200 Subject: [PATCH 2/3] fix: format, lint and typo --- api/src/opentrons/protocol_engine/execution/equipment.py | 2 +- .../protocol_engine/resources/pipette_data_provider.py | 5 ++--- .../protocol_engine/commands/test_configure_for_volume.py | 1 + .../opentrons/protocol_engine/commands/test_load_pipette.py | 4 ++-- .../protocol_engine/execution/test_equipment_handler.py | 5 ++--- .../protocol_engine/resources/test_pipette_data_provider.py | 5 +---- .../opentrons/protocol_engine/state/test_pipette_store.py | 6 +++--- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/equipment.py b/api/src/opentrons/protocol_engine/execution/equipment.py index 37f9470b84d..2487ad50aaa 100644 --- a/api/src/opentrons/protocol_engine/execution/equipment.py +++ b/api/src/opentrons/protocol_engine/execution/equipment.py @@ -246,7 +246,7 @@ async def load_pipette( return LoadedPipetteData( pipette_id=pipette_id, serial_number=serial, - static_config=static_pipette_config + static_config=static_pipette_config, ) async def load_magnetic_block( diff --git a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py index f21dfe8ee35..0d8a484d4db 100644 --- a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py @@ -18,7 +18,6 @@ ) from ..types import FlowRates -from ...types import Point @dataclass(frozen=True) @@ -172,7 +171,7 @@ def _get_virtual_pipette_static_config_by_model( nominal_tip_overlap=config.liquid_properties[ liquid_class ].tip_overlap_dictionary, - nozzle_map=nozzle_manager.current_configuration + nozzle_map=nozzle_manager.current_configuration, ) def get_virtual_pipette_static_config( @@ -206,5 +205,5 @@ def get_pipette_static_config(pipette_dict: PipetteDict) -> LoadedStaticPipetteD # https://opentrons.atlassian.net/browse/RCORE-655 home_position=0, nozzle_offset_z=0, - nozzle_map=pipette_dict["current_nozzle_map"] + nozzle_map=pipette_dict["current_nozzle_map"], ) 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 e4bfe048bda..6163aa6b2bc 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 @@ -19,6 +19,7 @@ from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import get_default_nozzle_map + async def test_configure_for_volume_implementation( decoy: Decoy, equipment: EquipmentHandler, 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 a7d7859da3d..b0874d33c0f 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -4,7 +4,7 @@ from opentrons_shared_data.pipette.dev_types import PipetteNameType from opentrons_shared_data.robot.dev_types import RobotType -from opentrons.types import MountType, Point +from opentrons.types import MountType from opentrons.protocol_engine.errors import InvalidSpecificationForRobotTypeError from opentrons.protocol_engine.types import FlowRates @@ -19,9 +19,9 @@ LoadPipettePrivateResult, LoadPipetteImplementation, ) -from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import get_default_nozzle_map + async def test_load_pipette_implementation( decoy: Decoy, equipment: EquipmentHandler, diff --git a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py index c4502c9575f..f52cf5e7dcd 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py @@ -10,7 +10,7 @@ from opentrons_shared_data.labware.dev_types import LabwareUri from opentrons.calibration_storage.helpers import uri_from_details -from opentrons.types import Mount as HwMount, MountType, DeckSlotName, Point +from opentrons.types import Mount as HwMount, MountType, DeckSlotName from opentrons.hardware_control import HardwareControlAPI from opentrons.hardware_control.modules import ( TempDeck, @@ -56,7 +56,6 @@ LoadedPipetteData, LoadedModuleData, ) -from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import get_default_nozzle_map @@ -149,7 +148,7 @@ def loaded_static_pipette_data( nominal_tip_overlap={"default": 9.87}, home_position=10.11, nozzle_offset_z=12.13, - nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE) + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) diff --git a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py index 14da9dceefb..94d9e5bef38 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py @@ -1,6 +1,4 @@ """Test pipette data provider.""" -from collections import OrderedDict - import pytest from opentrons_shared_data.pipette.dev_types import PipetteNameType, PipetteModel from opentrons_shared_data.pipette import pipette_definition, types as pip_types @@ -13,7 +11,6 @@ ) from opentrons.protocol_engine.resources import pipette_data_provider as subject -from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import get_default_nozzle_map @@ -175,7 +172,7 @@ def test_get_pipette_static_config( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, ) -> None: """It should return config data given a PipetteDict.""" - dummy_nozzle_map = get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), + dummy_nozzle_map = get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2) pipette_dict: PipetteDict = { "name": "p300_single_gen2", "min_volume": 20, 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 fbd41a971d3..4bf509176a1 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -51,9 +51,9 @@ create_move_relative_command, create_prepare_to_aspirate_command, ) -from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import get_default_nozzle_map + @pytest.fixture def subject() -> PipetteStore: """Get a PipetteStore test subject for all subsequent tests.""" @@ -683,7 +683,7 @@ def test_add_pipette_config( nominal_tip_overlap={"default": 5}, home_position=8.9, nozzle_offset_z=10.11, - nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE) + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ), ) subject.handle_action( @@ -703,7 +703,7 @@ def test_add_pipette_config( nozzle_offset_z=10.11, bounding_nozzle_offsets=BoundingNozzlesOffsets( back_left_offset=Point(x=0, y=0, z=0), - front_right_offset=Point(x=0, y=0, z=0) + front_right_offset=Point(x=0, y=0, z=0), ), ) assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} From 76d6846b84ca004fdafa011aca48ee7baa3d8ff0 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Wed, 21 Feb 2024 21:51:27 +0200 Subject: [PATCH 3/3] add a default nozzle map to the static pipette config --- .../protocol_engine/commands/configuring_common.py | 5 ++--- api/src/opentrons/protocol_engine/state/pipettes.py | 8 +++++++- .../commands/test_configure_nozzle_layout.py | 5 ----- .../opentrons/protocol_engine/state/test_geometry_view.py | 8 ++++++++ .../opentrons/protocol_engine/state/test_pipette_store.py | 1 + .../opentrons/protocol_engine/state/test_pipette_view.py | 8 ++++++++ .../opentrons/protocol_engine/state/test_tip_state.py | 3 +-- 7 files changed, 27 insertions(+), 11 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/configuring_common.py b/api/src/opentrons/protocol_engine/commands/configuring_common.py index ec5917d9931..17ffc2adef4 100644 --- a/api/src/opentrons/protocol_engine/commands/configuring_common.py +++ b/api/src/opentrons/protocol_engine/commands/configuring_common.py @@ -1,7 +1,6 @@ """Common configuration command base models.""" from pydantic import BaseModel, Field -from typing import Optional from dataclasses import dataclass from opentrons.hardware_control.nozzle_manager import ( NozzleMap, @@ -22,7 +21,7 @@ class PipetteNozzleLayoutResultMixin(BaseModel): """A nozzle layout result for updating the pipette state.""" pipette_id: str - nozzle_map: Optional[NozzleMap] = Field( - default=None, + nozzle_map: NozzleMap = Field( + ..., description="A dataclass object holding information about the current nozzle configuration.", ) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 7cb7401581f..16e8a96aeed 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -102,6 +102,7 @@ class StaticPipetteConfig: home_position: float nozzle_offset_z: float bounding_nozzle_offsets: BoundingNozzlesOffsets + default_nozzle_map: NozzleMap @dataclass @@ -170,6 +171,7 @@ def _handle_command( # noqa: C901 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, ) self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates self._state.nozzle_configuration_by_id[ @@ -191,7 +193,11 @@ def _handle_command( # noqa: C901 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 - self._state.nozzle_configuration_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, (AspirateResult, AspirateInPlaceResult)): pipette_id = command.params.pipetteId 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 43b140c81fe..23cdddd98be 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 @@ -73,11 +73,6 @@ ), {"primary_nozzle": "A1", "front_right_nozzle": "E1"}, ], - [ - AllNozzleLayoutConfiguration(), - None, - {}, - ], ], ) async def test_configure_nozzle_layout_implementation( diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index 93c3be8ebbe..b1b5453f144 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -57,6 +57,7 @@ ) from opentrons.protocol_engine.state.addressable_areas import AddressableAreaView from opentrons.protocol_engine.state.geometry import GeometryView, _GripperMoveType +from ..pipette_fixtures import get_default_nozzle_map @pytest.fixture @@ -1839,6 +1840,12 @@ def test_get_next_drop_tip_location( decoy.when( labware_view.get_well_size(labware_id="abc", well_name="A1") ).then_return((well_size, 0, 0)) + if pipette_channels == 96: + pip_type = PipetteNameType.P1000_96 + elif pipette_channels == 8: + pip_type = PipetteNameType.P300_MULTI + else: + pip_type = PipetteNameType.P300_SINGLE decoy.when(mock_pipette_view.get_config("pip-123")).then_return( StaticPipetteConfig( min_volume=1, @@ -1855,6 +1862,7 @@ def test_get_next_drop_tip_location( back_left_offset=Point(x=10, y=20, z=30), front_right_offset=Point(x=40, y=50, z=60), ), + default_nozzle_map=get_default_nozzle_map(pip_type), ) ) decoy.when(mock_pipette_view.get_mount("pip-123")).then_return(pipette_mount) 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 4bf509176a1..6b43e8e5f77 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -705,6 +705,7 @@ def test_add_pipette_config( back_left_offset=Point(x=0, y=0, z=0), front_right_offset=Point(x=0, y=0, z=0), ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} assert subject.state.flow_rates_by_id["pipette-id"].default_dispense == {"b": 2.0} diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py index b272ca6aa54..e4ef0dae930 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py @@ -37,6 +37,7 @@ EIGHT_CHANNEL_ROWS, EIGHT_CHANNEL_COLS, EIGHT_CHANNEL_MAP, + get_default_nozzle_map, ) _SAMPLE_NOZZLE_BOUNDS_OFFSETS = BoundingNozzlesOffsets( @@ -268,6 +269,7 @@ def test_get_pipette_working_volume( home_position=0, nozzle_offset_z=0, bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) }, ) @@ -296,6 +298,7 @@ def test_get_pipette_working_volume_raises_if_tip_volume_is_none( home_position=0, nozzle_offset_z=0, bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) }, ) @@ -333,6 +336,7 @@ def test_get_pipette_available_volume( home_position=0, nozzle_offset_z=0, bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ), "pipette-id-none": StaticPipetteConfig( min_volume=1, @@ -346,6 +350,7 @@ def test_get_pipette_available_volume( home_position=0, nozzle_offset_z=0, bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ), }, ) @@ -455,6 +460,7 @@ def test_get_static_config( home_position=10.12, nozzle_offset_z=12.13, bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) subject = get_pipette_view( @@ -503,6 +509,7 @@ def test_get_nominal_tip_overlap( home_position=0, nozzle_offset_z=0, bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) subject = get_pipette_view(static_config_by_id={"pipette-id": config}) @@ -738,6 +745,7 @@ def test_get_nozzle_bounds_at_location( home_position=0, nozzle_offset_z=0, bounding_nozzle_offsets=bounding_nozzle_offsets, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), ) }, ) diff --git a/api/tests/opentrons/protocol_engine/state/test_tip_state.py b/api/tests/opentrons/protocol_engine/state/test_tip_state.py index 3b0a3598191..f7f86434e4e 100644 --- a/api/tests/opentrons/protocol_engine/state/test_tip_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_tip_state.py @@ -614,7 +614,6 @@ def test_drop_tip( ), 5, ), - (None, 9), ], ) def test_active_channels( @@ -646,7 +645,7 @@ def test_active_channels( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), + nozzle_map=nozzle_map, ), ) subject.handle_action(