From fe0fc7d89f0d957057920f85d8fca2a1e3821982 Mon Sep 17 00:00:00 2001 From: Ryan Howard Date: Thu, 5 Dec 2024 11:08:58 -0500 Subject: [PATCH] feat(shared-data): Add support for PEEK pipettes (#17036) This Adds support for the new oem pipette by doing the following: - Add support for the flashing and factory testing pipettes with the new serial number prefix P1KP - Add the shared data definitions with updated max flowrates - Add ability to change the max speed when the pipette definition has the new "highSpeed" quirk - Disable support for the pressure sensor - Don't monitor for over pressure - Throw errors if trying to enable liquid presence detection on a pipette - Throw errors if trying to explicitly use LLD - Support UI differences for the new pipette name --------- Co-authored-by: Caila Marashaj <98041399+caila-marashaj@users.noreply.github.com> Co-authored-by: Jethary --- .../backends/ot3controller.py | 1 + .../hardware_control/backends/ot3simulator.py | 3 + .../instruments/ot2/pipette.py | 7 +- .../instruments/ot3/pipette.py | 6 + api/src/opentrons/protocol_api/validation.py | 1 + .../resources/pipette_data_provider.py | 5 + .../backends/test_ot3_controller.py | 2 +- .../instruments/test_nozzle_manager.py | 46 ++- .../hardware_control/test_ot3_api.py | 155 ++++++++- .../hardware_control/test_pipette.py | 5 +- .../protocol_engine/state/test_tip_state.py | 6 + api/tests/opentrons/test_execute.py | 6 + .../service/legacy/routers/test_settings.py | 1 + shared-data/command/schemas/10.json | 1 + shared-data/js/pipettes.ts | 18 +- .../definitions/1/pipetteModelSpecs.json | 195 +++++++++++ .../definitions/1/pipetteNameSpecs.json | 48 +++ .../2/general/eight_channel_em/p1000/1_0.json | 320 ++++++++++++++++++ .../geometry/eight_channel_em/p1000/1_0.json | 55 +++ .../eight_channel_em/p1000/default/1_0.json | 236 +++++++++++++ .../pipette/load_data.py | 29 +- .../pipette/mutable_configurations.py | 3 + .../pipette/pipette_definition.py | 16 +- .../pipette/pipette_load_name_conversions.py | 17 +- .../opentrons_shared_data/pipette/types.py | 19 +- .../python/tests/pipette/test_load_data.py | 8 +- .../pipette/test_max_flow_rates_per_volume.py | 1 + .../pipette/test_mutable_configurations.py | 6 +- .../test_pipette_load_name_conversions.py | 28 +- .../tests/pipette/test_validate_schema.py | 5 + 30 files changed, 1190 insertions(+), 59 deletions(-) create mode 100644 shared-data/pipette/definitions/2/general/eight_channel_em/p1000/1_0.json create mode 100644 shared-data/pipette/definitions/2/geometry/eight_channel_em/p1000/1_0.json create mode 100644 shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/1_0.json diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index cdbd8818111..84ffbffd8da 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -1072,6 +1072,7 @@ def _build_attached_pip( converted_name.pipette_type, converted_name.pipette_channels, converted_name.pipette_version, + converted_name.oem_type, ), "id": OT3Controller._combine_serial_number(attached), } diff --git a/api/src/opentrons/hardware_control/backends/ot3simulator.py b/api/src/opentrons/hardware_control/backends/ot3simulator.py index 10f4ebcfe2a..b7466386be6 100644 --- a/api/src/opentrons/hardware_control/backends/ot3simulator.py +++ b/api/src/opentrons/hardware_control/backends/ot3simulator.py @@ -511,6 +511,7 @@ def _attached_pipette_to_mount( converted_name.pipette_type, converted_name.pipette_channels, converted_name.pipette_version, + converted_name.oem_type, ), "id": None, } @@ -533,6 +534,7 @@ def _attached_pipette_to_mount( converted_name.pipette_type, converted_name.pipette_channels, converted_name.pipette_version, + converted_name.oem_type, ), "id": init_instr["id"], } @@ -544,6 +546,7 @@ def _attached_pipette_to_mount( converted_name.pipette_type, converted_name.pipette_channels, converted_name.pipette_version, + converted_name.oem_type, ), "id": None, } diff --git a/api/src/opentrons/hardware_control/instruments/ot2/pipette.py b/api/src/opentrons/hardware_control/instruments/ot2/pipette.py index 0881999d435..2205da161f1 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/pipette.py @@ -56,6 +56,7 @@ UlPerMmAction, PipetteName, PipetteModel, + PipetteOEMType, ) from opentrons.hardware_control.dev_types import InstrumentHardwareConfigs @@ -112,17 +113,20 @@ def __init__( pipette_type=config.pipette_type, pipette_channels=config.channels, pipette_generation=config.display_category, + oem_type=PipetteOEMType.OT, ) self._acting_as = self._pipette_name self._pipette_model = PipetteModelVersionType( pipette_type=config.pipette_type, pipette_channels=config.channels, pipette_version=config.version, + oem_type=PipetteOEMType.OT, ) self._valid_nozzle_maps = load_pipette_data.load_valid_nozzle_maps( self._pipette_model.pipette_type, self._pipette_model.pipette_channels, self._pipette_model.pipette_version, + PipetteOEMType.OT, ) self._nozzle_offset = self._config.nozzle_offset self._nozzle_manager = ( @@ -189,7 +193,7 @@ def act_as(self, name: PipetteNameType) -> None: ], f"{self.name} is not back-compatible with {name}" liquid_model = load_pipette_data.load_liquid_model( - name.pipette_type, name.pipette_channels, name.get_version() + name.pipette_type, name.pipette_channels, name.get_version(), name.oem_type ) # TODO need to grab name config here to deal with act as test self._liquid_class.max_volume = liquid_model["default"].max_volume @@ -280,6 +284,7 @@ def reload_configurations(self) -> None: self._pipette_model.pipette_type, self._pipette_model.pipette_channels, self._pipette_model.pipette_version, + self._pipette_model.oem_type, ) self._config_as_dict = self._config.model_dump() diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py index 6098b88b964..bd7671d745d 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py @@ -42,6 +42,7 @@ PipetteName, PipetteModel, Quirks, + PipetteOEMType, ) from opentrons_shared_data.pipette import ( load_data as load_pipette_data, @@ -93,22 +94,26 @@ def __init__( self._liquid_class_name = pip_types.LiquidClasses.default self._liquid_class = self._config.liquid_properties[self._liquid_class_name] + oem = PipetteOEMType.get_oem_from_quirks(config.quirks) # TODO (lc 12-05-2022) figure out how we can safely deprecate "name" and "model" self._pipette_name = PipetteNameType( pipette_type=config.pipette_type, pipette_channels=config.channels, pipette_generation=config.display_category, + oem_type=oem, ) self._acting_as = self._pipette_name self._pipette_model = PipetteModelVersionType( pipette_type=config.pipette_type, pipette_channels=config.channels, pipette_version=config.version, + oem_type=oem, ) self._valid_nozzle_maps = load_pipette_data.load_valid_nozzle_maps( self._pipette_model.pipette_type, self._pipette_model.pipette_channels, self._pipette_model.pipette_version, + self._pipette_model.oem_type, ) self._nozzle_offset = self._config.nozzle_offset self._nozzle_manager = ( @@ -250,6 +255,7 @@ def reload_configurations(self) -> None: self._pipette_model.pipette_type, self._pipette_model.pipette_channels, self._pipette_model.pipette_version, + self._pipette_model.oem_type, ) self._config_as_dict = self._config.model_dump() diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index f0db8a71e5e..cd1e5112718 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -72,6 +72,7 @@ "flex_8channel_50": PipetteNameType.P50_MULTI_FLEX, "flex_1channel_1000": PipetteNameType.P1000_SINGLE_FLEX, "flex_8channel_1000": PipetteNameType.P1000_MULTI_FLEX, + "flex_8channel_1000_em": PipetteNameType.P1000_MULTI_EM, "flex_96channel_1000": PipetteNameType.P1000_96, "flex_96channel_200": PipetteNameType.P200_96, } 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 4df6b0d4d77..ee721c88f2c 100644 --- a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py @@ -98,6 +98,7 @@ def configure_virtual_pipette_nozzle_layout( config.pipette_type, config.channels, config.version, + pip_types.PipetteOEMType.OT, ) new_nozzle_manager = NozzleConfigurationManager.build_from_config( config, valid_nozzle_maps @@ -130,6 +131,7 @@ def configure_virtual_pipette_for_volume( pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) liquid_class = pipette_definition.liquid_class_for_volume_between_default_and_defaultlowvolume( @@ -163,6 +165,7 @@ def _get_virtual_pipette_full_config_by_model_string( pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) def _get_virtual_pipette_static_config_by_model( # noqa: C901 @@ -179,6 +182,7 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901 pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) try: tip_type = pip_types.PipetteTipType( @@ -195,6 +199,7 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901 pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) if pipette_id not in self._nozzle_manager_layout_by_id: nozzle_manager = NozzleConfigurationManager.build_from_config( diff --git a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py index 9bd87fe62ec..85da7e206d2 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py @@ -1434,7 +1434,7 @@ async def test_pressure_disable( mock_check_overpressure: None, pipette_has_sensor: bool, ) -> None: - config = {"run.side_effect": move_group_run_side_effect_home(controller, axes)} + config = {"run.side_effect": move_group_run_side_effect(controller, axes)} with mock.patch( # type: ignore [call-overload] "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner", spec=MoveGroupRunner, diff --git a/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py b/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py index ba1f10aaaef..066cdbc3caf 100644 --- a/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py +++ b/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py @@ -10,6 +10,7 @@ PipetteModelType, PipetteChannelType, PipetteVersionType, + PipetteOEMType, ) from opentrons_shared_data.pipette.pipette_definition import ( PipetteConfigurations, @@ -261,7 +262,10 @@ def test_single_pipettes_always_full( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.SINGLE_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.SINGLE_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, ValidNozzleMaps(maps=A1) @@ -289,7 +293,10 @@ def test_single_pipette_map_entries( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.SINGLE_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.SINGLE_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, ValidNozzleMaps(maps=A1) @@ -326,7 +333,10 @@ def test_single_pipette_map_geometry( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.SINGLE_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.SINGLE_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, ValidNozzleMaps(maps=A1) @@ -359,7 +369,10 @@ def test_multi_config_identification( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.EIGHT_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.EIGHT_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, @@ -419,7 +432,10 @@ def test_multi_config_map_entries( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.EIGHT_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.EIGHT_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, @@ -485,7 +501,10 @@ def test_multi_config_geometry( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.EIGHT_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.EIGHT_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, @@ -536,7 +555,10 @@ def test_96_config_identification( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.NINETY_SIX_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.NINETY_SIX_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, @@ -651,7 +673,10 @@ def test_96_config_map_entries( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.NINETY_SIX_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.NINETY_SIX_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, @@ -988,7 +1013,10 @@ def test_96_config_geometry( pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( - pipette_details[0], PipetteChannelType.NINETY_SIX_CHANNEL, pipette_details[1] + pipette_details[0], + PipetteChannelType.NINETY_SIX_CHANNEL, + pipette_details[1], + PipetteOEMType.OT, ) subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, diff --git a/api/tests/opentrons/hardware_control/test_ot3_api.py b/api/tests/opentrons/hardware_control/test_ot3_api.py index a0775d48e19..200549108db 100644 --- a/api/tests/opentrons/hardware_control/test_ot3_api.py +++ b/api/tests/opentrons/hardware_control/test_ot3_api.py @@ -82,6 +82,7 @@ PipetteChannelType, PipetteVersionType, LiquidClasses, + PipetteOEMType, ) from opentrons_shared_data.pipette import ( load_data as load_pipette_data, @@ -381,6 +382,7 @@ class PipetteLoadConfig(TypedDict): channels: Literal[1, 8, 96] version: Tuple[Literal[1, 2, 3], Literal[0, 1, 2, 3, 4, 5, 6]] model: PipetteModel + oem_type: PipetteOEMType class GripperLoadConfig(TypedDict): @@ -402,8 +404,24 @@ class GripperLoadConfig(TypedDict): ( ( [ - (OT3Mount.RIGHT, {"channels": 8, "version": (3, 3), "model": "p50"}), - (OT3Mount.LEFT, {"channels": 1, "version": (3, 3), "model": "p1000"}), + ( + OT3Mount.RIGHT, + { + "channels": 8, + "version": (3, 3), + "model": "p50", + "oem_type": PipetteOEMType.OT, + }, + ), + ( + OT3Mount.LEFT, + { + "channels": 1, + "version": (3, 3), + "model": "p1000", + "oem_type": PipetteOEMType.OT, + }, + ), ], GantryLoad.LOW_THROUGHPUT, ), @@ -413,34 +431,88 @@ class GripperLoadConfig(TypedDict): GantryLoad.LOW_THROUGHPUT, ), ( - [(OT3Mount.LEFT, {"channels": 8, "version": (3, 3), "model": "p1000"})], + [ + ( + OT3Mount.LEFT, + { + "channels": 8, + "version": (3, 3), + "model": "p1000", + "oem_type": "ot", + }, + ) + ], GantryLoad.LOW_THROUGHPUT, ), ( - [(OT3Mount.RIGHT, {"channels": 8, "version": (3, 3), "model": "p1000"})], + [ + ( + OT3Mount.RIGHT, + { + "channels": 8, + "version": (3, 3), + "model": "p1000", + "oem_type": "ot", + }, + ) + ], GantryLoad.LOW_THROUGHPUT, ), ( - [(OT3Mount.LEFT, {"channels": 96, "model": "p1000", "version": (3, 3)})], + [ + ( + OT3Mount.LEFT, + { + "channels": 96, + "model": "p1000", + "version": (3, 3), + "oem_type": "ot", + }, + ) + ], GantryLoad.HIGH_THROUGHPUT, ), ( [ - (OT3Mount.LEFT, {"channels": 1, "version": (3, 3), "model": "p1000"}), + ( + OT3Mount.LEFT, + { + "channels": 1, + "version": (3, 3), + "model": "p1000", + "oem_type": "ot", + }, + ), (OT3Mount.GRIPPER, {"model": GripperModel.v1, "id": "g12345"}), ], GantryLoad.LOW_THROUGHPUT, ), ( [ - (OT3Mount.RIGHT, {"channels": 8, "version": (3, 3), "model": "p1000"}), + ( + OT3Mount.RIGHT, + { + "channels": 8, + "version": (3, 3), + "model": "p1000", + "oem_type": "ot", + }, + ), (OT3Mount.GRIPPER, {"model": GripperModel.v1, "id": "g12345"}), ], GantryLoad.LOW_THROUGHPUT, ), ( [ - (OT3Mount.LEFT, {"channels": 96, "model": "p1000", "version": (3, 3)}), + ( + OT3Mount.LEFT, + { + "channels": 96, + "model": "p1000", + "version": (3, 3), + "oem_type": "ot", + }, + ), (OT3Mount.GRIPPER, {"model": GripperModel.v1, "id": "g12345"}), ], GantryLoad.HIGH_THROUGHPUT, @@ -463,6 +535,7 @@ async def test_gantry_load_transform( PipetteModelType(pair[1]["model"]), PipetteChannelType(pair[1]["channels"]), PipetteVersionType(*pair[1]["version"]), + PipetteOEMType(pair[1]["oem_type"]), ) instr_data = AttachedPipette(config=pipette_config, id="fakepip") await ot3_hardware.cache_pipette(pair[0], instr_data, None) @@ -557,9 +630,30 @@ def mock_verify_tip_presence( load_pipette_configs = [ - {OT3Mount.LEFT: {"channels": 1, "version": (3, 3), "model": "p1000"}}, - {OT3Mount.RIGHT: {"channels": 8, "version": (3, 3), "model": "p50"}}, - {OT3Mount.LEFT: {"channels": 96, "model": "p1000", "version": (3, 3)}}, + { + OT3Mount.LEFT: { + "channels": 1, + "version": (3, 3), + "model": "p1000", + "oem_type": PipetteOEMType.OT, + } + }, + { + OT3Mount.RIGHT: { + "channels": 8, + "version": (3, 3), + "model": "p50", + "oem_type": PipetteOEMType.OT, + } + }, + { + OT3Mount.LEFT: { + "channels": 96, + "model": "p1000", + "version": (3, 3), + "oem_type": PipetteOEMType.OT, + } + }, ] @@ -573,6 +667,7 @@ async def prepare_for_mock_blowout( PipetteModelType(configs["model"]), PipetteChannelType(configs["channels"]), PipetteVersionType(*configs["version"]), + PipetteOEMType(configs["oem_type"]), ) instr_data = AttachedPipette(config=pipette_config, id="fakepip") await ot3_hardware.cache_pipette(mount, instr_data, None) @@ -804,7 +899,10 @@ async def test_liquid_probe( ) -> None: instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -895,7 +993,10 @@ async def test_liquid_probe_plunger_moves( # when approaching its max z distance instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1002,7 +1103,10 @@ async def test_liquid_probe_mount_moves( """Verify move targets for one singular liquid pass probe.""" instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1064,7 +1168,10 @@ async def test_multi_liquid_probe( ) -> None: instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1131,7 +1238,10 @@ async def test_liquid_not_found( ) -> None: instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1599,7 +1709,10 @@ async def test_home_plunger( mount = OT3Mount.LEFT instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1621,6 +1734,7 @@ async def test_prepare_for_aspirate( PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1655,6 +1769,7 @@ async def test_plunger_ready_to_aspirate_after_dispense( PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -1680,7 +1795,10 @@ async def test_move_to_plunger_bottom( mount = OT3Mount.LEFT instr_data = AttachedPipette( config=load_pipette_data.load_definition( - PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 4) + PipetteModelType("p1000"), + PipetteChannelType(1), + PipetteVersionType(3, 4), + PipetteOEMType.OT, ), id="fakepip", ) @@ -2130,6 +2248,7 @@ async def test_home_axis( PipetteModelType("p1000"), PipetteChannelType(1), PipetteVersionType(3, 3), + PipetteOEMType.OT, ) instr_data = AttachedPipette(config=pipette_config, id="fakepip") await ot3_hardware.cache_pipette(Axis.to_ot3_mount(axis), instr_data, None) diff --git a/api/tests/opentrons/hardware_control/test_pipette.py b/api/tests/opentrons/hardware_control/test_pipette.py index 25ac7b0298a..610fcc2a022 100644 --- a/api/tests/opentrons/hardware_control/test_pipette.py +++ b/api/tests/opentrons/hardware_control/test_pipette.py @@ -73,7 +73,10 @@ def _create_pipette( ) -> ot3_pipette.Pipette: return ot3_pipette.Pipette( load_pipette_data.load_definition( - model.pipette_type, model.pipette_channels, model.pipette_version + model.pipette_type, + model.pipette_channels, + model.pipette_version, + model.oem_type, ), calibration, id, 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 7246a5f4cb2..8e81f4a2102 100644 --- a/api/tests/opentrons/protocol_engine/state/test_tip_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_tip_state.py @@ -35,6 +35,12 @@ _tip_rack_parameters = LabwareParameters.model_construct(isTiprack=True) # type: ignore[call-arg] +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + @pytest.fixture def available_sensors() -> AvailableSensorDefinition: """Provide a list of sensors.""" diff --git a/api/tests/opentrons/test_execute.py b/api/tests/opentrons/test_execute.py index b9f791f4253..1e72a3757bf 100644 --- a/api/tests/opentrons/test_execute.py +++ b/api/tests/opentrons/test_execute.py @@ -131,6 +131,7 @@ def test_execute_function_apiv2( converted_model_v15.pipette_type, converted_model_v15.pipette_channels, converted_model_v15.pipette_version, + converted_model_v15.oem_type, ), "id": "testid", } @@ -139,6 +140,7 @@ def test_execute_function_apiv2( converted_model_v1.pipette_type, converted_model_v1.pipette_channels, converted_model_v1.pipette_version, + converted_model_v1.oem_type, ), "id": "testid2", } @@ -177,6 +179,7 @@ def emit_runlog(entry: Any) -> None: converted_model_v15.pipette_type, converted_model_v15.pipette_channels, converted_model_v15.pipette_version, + converted_model_v15.oem_type, ), "id": "testid", } @@ -215,6 +218,7 @@ def emit_runlog(entry: Any) -> None: converted_model_v15.pipette_type, converted_model_v15.pipette_channels, converted_model_v15.pipette_version, + converted_model_v15.oem_type, ), "id": "testid", } @@ -253,6 +257,7 @@ def emit_runlog(entry: Any) -> None: converted_model_v15.pipette_type, converted_model_v15.pipette_channels, converted_model_v15.pipette_version, + converted_model_v15.oem_type, ), "id": "testid", } @@ -292,6 +297,7 @@ def emit_runlog(entry: Any) -> None: converted_model_v15.pipette_type, converted_model_v15.pipette_channels, converted_model_v15.pipette_version, + converted_model_v15.oem_type, ), "id": "testid", } diff --git a/robot-server/tests/service/legacy/routers/test_settings.py b/robot-server/tests/service/legacy/routers/test_settings.py index 9c52256b82d..4e8ee53b138 100644 --- a/robot-server/tests/service/legacy/routers/test_settings.py +++ b/robot-server/tests/service/legacy/routers/test_settings.py @@ -146,6 +146,7 @@ def test_receive_attached_pipette_settings( pip_types.PipetteModelType.p20, pip_types.PipetteChannelType.EIGHT_CHANNEL, pip_types.PipetteVersionType(3, 5), + pip_types.PipetteOEMType.OT, ), pipette_serial_number="P12345", pipette_override_path="nope", diff --git a/shared-data/command/schemas/10.json b/shared-data/command/schemas/10.json index aced561bdff..fe43bffd92f 100644 --- a/shared-data/command/schemas/10.json +++ b/shared-data/command/schemas/10.json @@ -1774,6 +1774,7 @@ "p1000_single_gen2", "p1000_single_flex", "p1000_multi_flex", + "p1000_multi_em_flex", "p1000_96", "p200_96" ], diff --git a/shared-data/js/pipettes.ts b/shared-data/js/pipettes.ts index 2921696b820..134f4efbd82 100644 --- a/shared-data/js/pipettes.ts +++ b/shared-data/js/pipettes.ts @@ -186,9 +186,9 @@ const getHighestVersion = ( } const V2_DEFINITION_TYPES = ['general', 'geometry'] -/* takes in pipetteName such as 'p300_single' or 'p300_single_gen1' +/* takes in pipetteName such as 'p300_single' or 'p300_single_gen1' or PipetteModel such as 'p300_single_v1.3' and converts it to channels, -model, and version in order to return the correct pipette schema v2 json files. +model, and version in order to return the correct pipette schema v2 json files. **/ export const getPipetteSpecsV2 = ( name?: PipetteName | PipetteModel @@ -200,7 +200,15 @@ export const getPipetteSpecsV2 = ( const nameSplit = name.split('_') const pipetteModel = nameSplit[0] // ex: p300 const channels = getChannelsFromString(nameSplit[1] as PipChannelString) // ex: single -> single_channel - const pipetteGen = getVersionFromGen(nameSplit[2] as Gen) + let version_index: number + let oemString: string = '' + if (nameSplit.length === 4) { + version_index = 3 + oemString = `_${nameSplit[2]}` + } else { + version_index = 2 + } + const pipetteGen = getVersionFromGen(nameSplit[version_index] as Gen) let version: string = '' let majorVersion: number // the first 2 conditions are to accommodate version from the pipetteName @@ -215,7 +223,7 @@ export const getPipetteSpecsV2 = ( majorVersion = pipetteGen // ex: gen1 -> 1 // the 'else' is to accommodate the exact version if PipetteModel was added } else { - const versionNumber = nameSplit[2].split('v')[1] + const versionNumber = nameSplit[version_index].split('v')[1] if (versionNumber.includes('.')) { version = versionNumber.replace('.', '_') // ex: 1.0 -> 1_0 } else { @@ -236,7 +244,7 @@ export const getPipetteSpecsV2 = ( ) V2_DEFINITION_TYPES.forEach(type => { if ( - `../pipette/definitions/2/${type}/${channels}/${pipetteModel}/${ + `../pipette/definitions/2/${type}/${channels}${oemString}/${pipetteModel}/${ version === '' ? highestVersion : version }.json` === path ) { diff --git a/shared-data/pipette/definitions/1/pipetteModelSpecs.json b/shared-data/pipette/definitions/1/pipetteModelSpecs.json index a66312eb522..6a1eea16bd4 100644 --- a/shared-data/pipette/definitions/1/pipetteModelSpecs.json +++ b/shared-data/pipette/definitions/1/pipetteModelSpecs.json @@ -8788,6 +8788,201 @@ "returnTipHeight": 0.71, "idleCurrent": 0.3 }, + "p1000_multi_em_v3.0": { + "name": "p1000_multi_em_flex", + "backCompatNames": [], + "top": { + "value": 0.5, + "min": 0, + "max": 45, + "units": "mm", + "type": "float" + }, + "bottom": { + "value": 71.5, + "min": 55, + "max": 80, + "type": "float", + "units": "mm" + }, + "blowout": { + "value": 76.5, + "min": 60, + "max": 85, + "units": "mm", + "type": "float" + }, + "dropTip": { + "value": 92.5, + "min": 78, + "max": 110, + "units": "mm", + "type": "float" + }, + "pickUpCurrent": { + "value": 0.5, + "min": 0.05, + "max": 2.0, + "units": "amps", + "type": "float" + }, + "pickUpDistance": { + "value": 13, + "min": 1, + "max": 30, + "units": "mm", + "type": "float" + }, + "pickUpIncrement": { + "value": 0.0, + "min": 0.0, + "max": 10.0, + "units": "mm", + "type": "float" + }, + "pickUpPresses": { + "value": 1, + "min": 0, + "max": 10, + "units": "presses", + "type": "int" + }, + "pickUpSpeed": { + "value": 10, + "min": 1, + "max": 30, + "units": "mm/s", + "type": "float" + }, + "nozzleOffset": [-8.0, -16.0, -259.15], + "modelOffset": [0.0, 0.0, 25.14], + "ulPerMm": [ + { + "aspirate": [ + [0.7511, 3.9556, 6.455], + [1.3075, 2.1664, 5.8839], + [1.8737, 1.1513, 7.2111], + [3.177, 0.9374, 7.612], + [4.5368, 0.5531, 8.8328], + [7.31, 0.3035, 9.9651], + [10.0825, 0.1513, 11.0781], + [12.9776, 0.1293, 11.2991], + [15.9173, 0.0976, 11.7115], + [18.8243, 0.06244, 12.2706], + [21.8529, 0.07004, 12.1275], + [24.8068, 0.04182, 12.7442], + [27.7744, 0.0356, 12.8984], + [35.2873, 0.03031, 13.04544], + [42.799, 0.02015, 13.4038], + [50.4562, 0.01956, 13.4293], + [58.1081, 0.0145, 13.6843], + [65.7267, 0.01036, 13.9252], + [73.2857, 0.006776, 14.1606], + [81.00159, 0.009126, 13.9883], + [88.6617, 0.006448, 14.2052], + [103.9829, 0.005074, 14.3271], + [119.4408, 0.004878, 14.3476], + [134.889, 0.003727, 14.485], + [150.273, 0.00258, 14.6402], + [181.2798, 0.002559, 14.6427], + [212.4724, 0.002242, 14.7002], + [243.577, 0.00151, 14.856], + [274.7216, 0.001244, 14.9205], + [305.8132, 0.0009118, 15.0118], + [368.06968, 0.0007321, 15.06677], + [430.2513, 0.0004805, 15.1594], + [492.3487, 0.0003186, 15.2291], + [554.5713, 0.0003031, 15.237], + [616.6825, 0.0001981, 15.2948], + [694.4168, 0.0001855, 15.3027], + [772.0327, 0.0001181, 15.3494], + [849.617, 0.00008929, 15.3717], + [927.2556, 0.00008601, 15.3745], + [1004.87, 0.00006801, 15.3912], + [1051.4648, 0.00006824, 15.391] + ], + "dispense": [ + [0.7511, 3.9556, 6.455], + [1.3075, 2.1664, 5.8839], + [1.8737, 1.1513, 7.2111], + [3.177, 0.9374, 7.612], + [4.5368, 0.5531, 8.8328], + [7.31, 0.3035, 9.9651], + [10.0825, 0.1513, 11.0781], + [12.9776, 0.1293, 11.2991], + [15.9173, 0.0976, 11.7115], + [18.8243, 0.06244, 12.2706], + [21.8529, 0.07004, 12.1275], + [24.8068, 0.04182, 12.7442], + [27.7744, 0.0356, 12.8984], + [35.2873, 0.03031, 13.04544], + [42.799, 0.02015, 13.4038], + [50.4562, 0.01956, 13.4293], + [58.1081, 0.0145, 13.6843], + [65.7267, 0.01036, 13.9252], + [73.2857, 0.006776, 14.1606], + [81.00159, 0.009126, 13.9883], + [88.6617, 0.006448, 14.2052], + [103.9829, 0.005074, 14.3271], + [119.4408, 0.004878, 14.3476], + [134.889, 0.003727, 14.485], + [150.273, 0.00258, 14.6402], + [181.2798, 0.002559, 14.6427], + [212.4724, 0.002242, 14.7002], + [243.577, 0.00151, 14.856], + [274.7216, 0.001244, 14.9205], + [305.8132, 0.0009118, 15.0118], + [368.06968, 0.0007321, 15.06677], + [430.2513, 0.0004805, 15.1594], + [492.3487, 0.0003186, 15.2291], + [554.5713, 0.0003031, 15.237], + [616.6825, 0.0001981, 15.2948], + [694.4168, 0.0001855, 15.3027], + [772.0327, 0.0001181, 15.3494], + [849.617, 0.00008929, 15.3717], + [927.2556, 0.00008601, 15.3745], + [1004.87, 0.00006801, 15.3912], + [1051.4648, 0.00006824, 15.391] + ] + } + ], + "plungerCurrent": { + "value": 1, + "min": 0.1, + "max": 1.5, + "units": "amps", + "type": "float" + }, + "dropTipCurrent": { + "value": 1, + "min": 0.1, + "max": 1.25, + "units": "amps", + "type": "float" + }, + "dropTipSpeed": { + "value": 10, + "min": 0.001, + "max": 30, + "units": "mm/sec", + "type": "float" + }, + "tipOverlap": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.5 + }, + "tipLength": { + "value": 78.3, + "units": "mm", + "type": "float", + "min": 0, + "max": 100 + }, + "quirks": ["highSpeed"], + "returnTipHeight": 0.71, + "idleCurrent": 0.3 + }, "p50_multi_v3.0": { "name": "p50_multi_flex", "backCompatNames": [], diff --git a/shared-data/pipette/definitions/1/pipetteNameSpecs.json b/shared-data/pipette/definitions/1/pipetteNameSpecs.json index 61cd56d32f6..7beeb92aabc 100644 --- a/shared-data/pipette/definitions/1/pipetteNameSpecs.json +++ b/shared-data/pipette/definitions/1/pipetteNameSpecs.json @@ -599,6 +599,54 @@ "opentrons/opentrons_flex_96_filtertiprack_50ul/1" ] }, + "p1000_multi_em_flex": { + "displayName": "Flex 8-Channel EM 1000 μL", + "displayCategory": "FLEX", + "defaultAspirateFlowRate": { + "value": 478, + "min": 3, + "max": 812, + "valuesByApiLevel": { + "2.0": 159.04, + "2.6": 159.04, + "2.14": 478 + } + }, + "defaultDispenseFlowRate": { + "value": 478, + "min": 3, + "max": 812, + "valuesByApiLevel": { + "2.0": 159.04, + "2.14": 478 + } + }, + "defaultBlowOutFlowRate": { + "value": 478, + "min": 3, + "max": 812, + "valuesByApiLevel": { + "2.0": 78.52, + "2.14": 478 + } + }, + "channels": 8, + "minVolume": 5, + "maxVolume": 1000, + "smoothieConfigs": { + "stepsPerMM": 2133.33, + "homePosition": 230.15, + "travelDistance": 80 + }, + "defaultTipracks": [ + "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1", + "opentrons/opentrons_flex_96_tiprack_200ul/1", + "opentrons/opentrons_flex_96_filtertiprack_200ul/1", + "opentrons/opentrons_flex_96_tiprack_50ul/1", + "opentrons/opentrons_flex_96_filtertiprack_50ul/1" + ] + }, "p50_multi_flex": { "displayName": "Flex 8-Channel 50 μL", "displayCategory": "FLEX", diff --git a/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/1_0.json b/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/1_0.json new file mode 100644 index 00000000000..c267504b404 --- /dev/null +++ b/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/1_0.json @@ -0,0 +1,320 @@ +{ + "$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json", + "displayName": "FLEX 8-Channel EM 1000 μL", + "model": "p1000", + "displayCategory": "FLEX", + "validNozzleMaps": { + "maps": { + "SingleA1": ["A1"], + "SingleH1": ["H1"], + "H1toG1": ["G1", "H1"], + "H1toF1": ["F1", "G1", "H1"], + "H1toE1": ["E1", "F1", "G1", "H1"], + "H1toD1": ["D1", "E1", "F1", "G1", "H1"], + "H1toC1": ["C1", "D1", "E1", "F1", "G1", "H1"], + "H1toB1": ["B1", "C1", "D1", "E1", "F1", "G1", "H1"], + "Full": ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"] + } + }, + "pickUpTipConfigurations": { + "pressFit": { + "presses": 1, + "increment": 0.0, + "configurationsByNozzleMap": { + "SingleA1": { + "default": { + "speed": 10.0, + "distance": 11.0, + "current": 0.15, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 10.08, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.08, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.26, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.08, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.26 + } + } + } + }, + "SingleH1": { + "default": { + "speed": 10.0, + "distance": 11.0, + "current": 0.15, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.52, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.71, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.52, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.2, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.71, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.52, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.2 + } + } + } + }, + "H1toG1": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.2, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.24, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.52, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.24, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.73, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.52, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.24, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.73 + } + } + } + }, + "H1toF1": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.2, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.2, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.8, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.8 + } + } + } + }, + "H1toE1": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.35, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.2, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.8, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.8 + } + } + } + }, + "H1toD1": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.4, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.2, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.8, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.8 + } + } + } + }, + "H1toC1": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.4, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.2, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.8, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.3, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.2, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.8 + } + } + } + }, + "H1toB1": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.5, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 9.13, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.23, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.13, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.9, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.23, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.13, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.9 + } + } + } + }, + "Full": { + "default": { + "speed": 10.0, + "distance": 13.0, + "current": 0.55, + "tipOverlaps": { + "v0": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 10.17, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 10.1, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 10.05, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 10.17 + }, + "v1": { + "default": 10.5, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.42, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.27, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.67, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.42, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.27, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.67 + }, + "v3": { + "default": 9.28, + "opentrons/opentrons_flex_96_tiprack_200ul/1": 9.37, + "opentrons/opentrons_flex_96_tiprack_50ul/1": 9.28, + "opentrons/opentrons_flex_96_tiprack_1000ul/1": 9.67, + "opentrons/opentrons_flex_96_filtertiprack_200ul/1": 9.37, + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": 9.28, + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1": 9.67 + } + } + } + } + } + } + }, + "dropTipConfigurations": { + "plungerEject": { + "current": 1.0, + "speed": 10 + } + }, + "plungerMotorConfigurations": { + "idle": 0.3, + "run": 1.0 + }, + "plungerPositionsConfigurations": { + "default": { + "top": 0.0, + "bottom": 71.5, + "blowout": 76.5, + "drop": 91.5 + } + }, + "availableSensors": { + "sensors": ["capacitive", "environment"], + "capacitive": { + "count": 2 + }, + "environment": { + "count": 1 + } + }, + "partialTipConfigurations": { + "partialTipSupported": true, + "availableConfigurations": [1, 2, 3, 4, 5, 6, 7, 8] + }, + "backCompatNames": [], + "channels": 8, + "shaftDiameter": 4.5, + "shaftULperMM": 15.904, + "backlashDistance": 0.1, + "quirks": ["highSpeed"], + "plungerHomingConfigurations": { + "current": 1.0, + "speed": 30 + } +} diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel_em/p1000/1_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel_em/p1000/1_0.json new file mode 100644 index 00000000000..b92e7415fe3 --- /dev/null +++ b/shared-data/pipette/definitions/2/geometry/eight_channel_em/p1000/1_0.json @@ -0,0 +1,55 @@ +{ + "$otSharedSchema": "#/pipette/schemas/2/pipetteGeometrySchema.json", + "pathTo3D": "pipette/definitions/2/geometry/eight_channel_em/p1000/placeholder.gltf", + "nozzleOffset": [-8.0, -16.0, -259.15], + "pipetteBoundingBoxOffsets": { + "backLeftCorner": [-38.5, 0.0, -259.15], + "frontRightCorner": [11.5, -95.0, -259.15] + }, + "orderedRows": [ + { + "key": "A", + "orderedNozzles": ["A1"] + }, + { + "key": "B", + "orderedNozzles": ["B1"] + }, + { "key": "C", "orderedNozzles": ["C1"] }, + { "key": "D", "orderedNozzles": ["D1"] }, + { "key": "E", "orderedNozzles": ["E1"] }, + { "key": "F", "orderedNozzles": ["F1"] }, + { "key": "G", "orderedNozzles": ["G1"] }, + { "key": "H", "orderedNozzles": ["H1"] } + ], + "orderedColumns": [ + { + "key": "1", + "orderedNozzles": ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"] + } + ], + "nozzleMap": { + "A1": [-8.0, -16.0, -259.15], + "B1": [-8.0, -25.0, -259.15], + "C1": [-8.0, -34.0, -259.15], + "D1": [-8.0, -43.0, -259.15], + "E1": [-8.0, -52.0, -259.15], + "F1": [-8.0, -61.0, -259.15], + "G1": [-8.0, -70.0, -259.15], + "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 1.0, + "minVolume": 0 + }, + "t200": { + "minHeight": 1.0, + "minVolume": 0 + }, + "t1000": { + "minHeight": 1.5, + "minVolume": 0 + } + } +} diff --git a/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/1_0.json b/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/1_0.json new file mode 100644 index 00000000000..52c7b58171d --- /dev/null +++ b/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/1_0.json @@ -0,0 +1,236 @@ +{ + "$otSharedSchema": "#/pipette/schemas/2/pipetteLiquidPropertiesSchema.json", + "supportedTips": { + "t50": { + "uiMaxFlowRate": 1431.0, + "defaultAspirateFlowRate": { + "default": 478, + "valuesByApiLevel": { "2.14": 478 } + }, + "defaultDispenseFlowRate": { + "default": 478, + "valuesByApiLevel": { "2.14": 478 } + }, + "defaultBlowOutFlowRate": { + "default": 478, + "valuesByApiLevel": { "2.14": 478 } + }, + "defaultFlowAcceleration": 24000.0, + "defaultTipLength": 57.9, + "defaultReturnTipHeight": 0.71, + "aspirate": { + "default": { + "1": [ + [0.12, -57.973785, 8.495981], + [0.11, 40.31047, -3.298129], + [0.09, 19.330223, -0.990302], + [0.375, 6.200306, 0.19139], + [1.17, 4.795927, 0.718032], + [1.92, 2.746428, 3.115947], + [2.145, 1.592373, 5.331732], + [2.4, 1.336497, 5.880586], + [2.66, 1.043996, 6.582588], + [2.84, 0.280189, 8.614315], + [2.985, -0.698973, 11.395134], + [3.085, -5.627462, 26.106674], + [3.625, 1.899561, 2.885808], + [4.43, 1.977851, 2.602006], + [5.155, 0.596916, 8.71955], + [6.71, 0.366092, 9.909446], + [8.62, 0.233878, 10.796602], + [11.015, 0.158281, 11.448248], + [13.97, 0.101002, 12.079177], + [17.545, 0.047056, 12.832813], + [22.075, 0.043416, 12.896662], + [27.955, 0.049456, 12.763333], + [34.695, 0.00096, 14.119053], + [43.535, 0.018347, 13.515795], + [54.08, 0.001949, 14.229706] + ] + } + }, + "dispense": { + "default": { + "1": [ + [0.12, -57.973785, 8.495981], + [0.11, 40.31047, -3.298129], + [0.09, 19.330223, -0.990302], + [0.375, 6.200306, 0.19139], + [1.17, 4.795927, 0.718032], + [1.92, 2.746428, 3.115947], + [2.145, 1.592373, 5.331732], + [2.4, 1.336497, 5.880586], + [2.66, 1.043996, 6.582588], + [2.84, 0.280189, 8.614315], + [2.985, -0.698973, 11.395134], + [3.085, -5.627462, 26.106674], + [3.625, 1.899561, 2.885808], + [4.43, 1.977851, 2.602006], + [5.155, 0.596916, 8.71955], + [6.71, 0.366092, 9.909446], + [8.62, 0.233878, 10.796602], + [11.015, 0.158281, 11.448248], + [13.97, 0.101002, 12.079177], + [17.545, 0.047056, 12.832813], + [22.075, 0.043416, 12.896662], + [27.955, 0.049456, 12.763333], + [34.695, 0.00096, 14.119053], + [43.535, 0.018347, 13.515795], + [54.08, 0.001949, 14.229706] + ] + } + }, + "defaultPushOutVolume": 7 + }, + "t200": { + "uiMaxFlowRate": 1431.0, + "defaultAspirateFlowRate": { + "default": 716, + "valuesByApiLevel": { "2.14": 716 } + }, + "defaultDispenseFlowRate": { + "default": 716, + "valuesByApiLevel": { "2.14": 716 } + }, + "defaultBlowOutFlowRate": { + "default": 716, + "valuesByApiLevel": { "2.14": 716 } + }, + "defaultFlowAcceleration": 24000.0, + "defaultTipLength": 58.35, + "defaultReturnTipHeight": 0.71, + "aspirate": { + "default": { + "1": [ + [0.28375, -141.180627, 42.499381], + [0.26125, 27.065799, -5.240543], + [0.715, 4.916546, 0.54595], + [1.685, 3.844391, 1.31254], + [2.6025, 2.148973, 4.169319], + [3.75875, 1.461751, 5.957816], + [4.9975, 0.733738, 8.694235], + [6.41375, 0.377599, 10.474036], + [8.1225, 0.214926, 11.517382], + [10.2425, 0.152451, 12.024835], + [12.80125, 0.081051, 12.75615], + [15.9875, 0.062849, 12.989161], + [19.9625, 0.051585, 13.169235], + [24.83625, 0.030593, 13.588301], + [30.89125, 0.024593, 13.737307], + [38.42625, 0.020128, 13.875257], + [47.71875, 0.014091, 14.107204], + [59.28375, 0.011625, 14.224918], + [73.41375, 0.00635, 14.537608], + [90.84375, 0.004458, 14.676515], + [112.32, 0.003084, 14.801312], + [138.7675, 0.002045, 14.917998], + [171.29875, 0.001319, 15.018758], + [211.27375, 0.000719, 15.121662] + ] + } + }, + "dispense": { + "default": { + "1": [ + [0.28375, -141.180627, 42.499381], + [0.26125, 27.065799, -5.240543], + [0.715, 4.916546, 0.54595], + [1.685, 3.844391, 1.31254], + [2.6025, 2.148973, 4.169319], + [3.75875, 1.461751, 5.957816], + [4.9975, 0.733738, 8.694235], + [6.41375, 0.377599, 10.474036], + [8.1225, 0.214926, 11.517382], + [10.2425, 0.152451, 12.024835], + [12.80125, 0.081051, 12.75615], + [15.9875, 0.062849, 12.989161], + [19.9625, 0.051585, 13.169235], + [24.83625, 0.030593, 13.588301], + [30.89125, 0.024593, 13.737307], + [38.42625, 0.020128, 13.875257], + [47.71875, 0.014091, 14.107204], + [59.28375, 0.011625, 14.224918], + [73.41375, 0.00635, 14.537608], + [90.84375, 0.004458, 14.676515], + [112.32, 0.003084, 14.801312], + [138.7675, 0.002045, 14.917998], + [171.29875, 0.001319, 15.018758], + [211.27375, 0.000719, 15.121662] + ] + } + }, + "defaultPushOutVolume": 5 + }, + "t1000": { + "uiMaxFlowRate": 1431.0, + "defaultAspirateFlowRate": { + "default": 716, + "valuesByApiLevel": { "2.14": 716 } + }, + "defaultDispenseFlowRate": { + "default": 716, + "valuesByApiLevel": { "2.14": 716 } + }, + "defaultBlowOutFlowRate": { + "default": 716, + "valuesByApiLevel": { "2.14": 716 } + }, + "defaultFlowAcceleration": 24000.0, + "defaultTipLength": 95.6, + "defaultReturnTipHeight": 0.82, + "aspirate": { + "default": { + "1": [ + [2.1443, 1.9858, 4.2677], + [3.0286, 1.2526, 5.84], + [4.9557, 0.6268, 7.7351], + [9.7943, 0.2745, 9.4811], + [12.1514, 0.1715, 10.4901], + [14.9414, 0.0897, 11.4833], + [51.46, 0.0424, 12.1913], + [92.68, 0.0095, 13.881], + [112.4886, 0.0049, 14.3053], + [243.5986, 0.0028, 14.5507], + [356.5686, 0.0009, 15.0019], + [430.99, 0.0005, 15.1492], + [628.7886, 0.0003, 15.2496], + [1001.15, 0.0001, 15.3472], + [1106.0857, 0.0001, 15.3551] + ] + } + }, + "dispense": { + "default": { + "1": [ + [2.1443, 1.9858, 4.2677], + [3.0286, 1.2526, 5.84], + [4.9557, 0.6268, 7.7351], + [9.7943, 0.2745, 9.4811], + [12.1514, 0.1715, 10.4901], + [14.9414, 0.0897, 11.4833], + [51.46, 0.0424, 12.1913], + [92.68, 0.0095, 13.881], + [112.4886, 0.0049, 14.3053], + [243.5986, 0.0028, 14.5507], + [356.5686, 0.0009, 15.0019], + [430.99, 0.0005, 15.1492], + [628.7886, 0.0003, 15.2496], + [1001.15, 0.0001, 15.3472], + [1106.0857, 0.0001, 15.3551] + ] + } + }, + "defaultPushOutVolume": 20 + } + }, + "maxVolume": 1000, + "minVolume": 5, + "defaultTipracks": [ + "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "opentrons/opentrons_flex_96_tiprack_200ul/1", + "opentrons/opentrons_flex_96_tiprack_50ul/1", + "opentrons/opentrons_flex_96_filtertiprack_1000ul/1", + "opentrons/opentrons_flex_96_filtertiprack_200ul/1", + "opentrons/opentrons_flex_96_filtertiprack_50ul/1" + ] +} diff --git a/shared-data/python/opentrons_shared_data/pipette/load_data.py b/shared-data/python/opentrons_shared_data/pipette/load_data.py index 66ba690356a..f495713b3d8 100644 --- a/shared-data/python/opentrons_shared_data/pipette/load_data.py +++ b/shared-data/python/opentrons_shared_data/pipette/load_data.py @@ -22,6 +22,7 @@ PipetteModelMajorVersion, PipetteModelMinorVersion, LiquidClasses, + PipetteOEMType, ) @@ -35,8 +36,10 @@ def _get_configuration_dictionary( channels: PipetteChannelType, model: PipetteModelType, version: PipetteVersionType, + oem: PipetteOEMType, liquid_class: Optional[LiquidClasses] = None, ) -> LoadedConfiguration: + oem_extension = f"_{oem.value}" if oem != PipetteOEMType.OT else "" if liquid_class: config_path = ( get_shared_data_root() @@ -44,7 +47,7 @@ def _get_configuration_dictionary( / "definitions" / "2" / config_type - / channels.name.lower() + / f"{channels.name.lower()}{oem_extension}" / model.value / liquid_class.name / f"{version.major}_{version.minor}.json" @@ -56,7 +59,7 @@ def _get_configuration_dictionary( / "definitions" / "2" / config_type - / channels.name.lower() + / f"{channels.name.lower()}{oem_extension}" / model.value / f"{version.major}_{version.minor}.json" ) @@ -68,8 +71,9 @@ def _geometry( channels: PipetteChannelType, model: PipetteModelType, version: PipetteVersionType, + oem: PipetteOEMType, ) -> LoadedConfiguration: - return _get_configuration_dictionary("geometry", channels, model, version) + return _get_configuration_dictionary("geometry", channels, model, version, oem) @lru_cache(maxsize=None) @@ -77,12 +81,13 @@ def _liquid( channels: PipetteChannelType, model: PipetteModelType, version: PipetteVersionType, + oem: PipetteOEMType, ) -> Dict[str, LoadedConfiguration]: liquid_dict = {} for liquid_class in LiquidClasses: try: liquid_dict[liquid_class.name] = _get_configuration_dictionary( - "liquid", channels, model, version, liquid_class + "liquid", channels, model, version, oem, liquid_class ) except FileNotFoundError: continue @@ -95,8 +100,9 @@ def _physical( channels: PipetteChannelType, model: PipetteModelType, version: PipetteVersionType, + oem: PipetteOEMType, ) -> LoadedConfiguration: - return _get_configuration_dictionary("general", channels, model, version) + return _get_configuration_dictionary("general", channels, model, version, oem) def _dirs_in(path: Path) -> Iterator[Path]: @@ -152,8 +158,9 @@ def load_liquid_model( model: PipetteModelType, channels: PipetteChannelType, version: PipetteVersionType, + oem: PipetteOEMType, ) -> Dict[str, PipetteLiquidPropertiesDefinition]: - liquid_dict = _liquid(channels, model, version) + liquid_dict = _liquid(channels, model, version, oem) return { k: PipetteLiquidPropertiesDefinition.model_validate(v) for k, v in liquid_dict.items() @@ -252,6 +259,7 @@ def load_definition( model: PipetteModelType, channels: PipetteChannelType, version: PipetteVersionType, + oem: PipetteOEMType, ) -> PipetteConfigurations: if ( version.major not in PipetteModelMajorVersion @@ -259,9 +267,9 @@ def load_definition( ): raise KeyError("Pipette version not found.") - geometry_dict = _geometry(channels, model, version) - physical_dict = _physical(channels, model, version) - liquid_dict = _liquid(channels, model, version) + geometry_dict = _geometry(channels, model, version, oem) + physical_dict = _physical(channels, model, version, oem) + liquid_dict = _liquid(channels, model, version, oem) generation = PipetteGenerationType(physical_dict["displayCategory"]) mount_configs = MOUNT_CONFIG_LOOKUP_TABLE[generation][channels] @@ -281,6 +289,7 @@ def load_valid_nozzle_maps( model: PipetteModelType, channels: PipetteChannelType, version: PipetteVersionType, + oem: PipetteOEMType, ) -> ValidNozzleMaps: if ( version.major not in PipetteModelMajorVersion @@ -288,5 +297,5 @@ def load_valid_nozzle_maps( ): raise KeyError("Pipette version not found.") - physical_dict = _physical(channels, model, version) + physical_dict = _physical(channels, model, version, oem) return ValidNozzleMaps.model_validate(physical_dict["validNozzleMaps"]) diff --git a/shared-data/python/opentrons_shared_data/pipette/mutable_configurations.py b/shared-data/python/opentrons_shared_data/pipette/mutable_configurations.py index 23943ef9082..7214adf2483 100644 --- a/shared-data/python/opentrons_shared_data/pipette/mutable_configurations.py +++ b/shared-data/python/opentrons_shared_data/pipette/mutable_configurations.py @@ -238,6 +238,7 @@ def _load_full_mutable_configs( pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) base_configs_dict = base_configs.model_dump(by_alias=True) full_mutable_configs = _list_all_mutable_configs(overrides, base_configs_dict) @@ -334,6 +335,7 @@ def load_with_mutable_configurations( pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) # Load overrides if we have a pipette id if pipette_serial_number: @@ -431,6 +433,7 @@ def save_overrides( pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) base_configs_dict = base_configs.model_dump(by_alias=True) try: diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index 2e10ede5093..9f52eaf85f2 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -41,9 +41,15 @@ class PipetteNameType: pipette_type: pip_types.PipetteModelType pipette_channels: pip_types.PipetteChannelType pipette_generation: pip_types.PipetteGenerationType + oem_type: pip_types.PipetteOEMType def __repr__(self) -> str: - base_name = f"{self.pipette_type.name}_{str(self.pipette_channels)}" + oem_name = ( + f"_{self.oem_type.value}" + if self.oem_type != pip_types.PipetteOEMType.OT + else "" + ) + base_name = f"{self.pipette_type.name}_{str(self.pipette_channels)}{oem_name}" if self.pipette_generation == pip_types.PipetteGenerationType.GEN1: return base_name elif self.pipette_channels == pip_types.PipetteChannelType.NINETY_SIX_CHANNEL: @@ -65,9 +71,15 @@ class PipetteModelVersionType: pipette_type: pip_types.PipetteModelType pipette_channels: pip_types.PipetteChannelType pipette_version: pip_types.PipetteVersionType + oem_type: pip_types.PipetteOEMType def __repr__(self) -> str: - base_name = f"{self.pipette_type.name}_{str(self.pipette_channels)}" + oem_name = ( + f"_{self.oem_type.value}" + if self.oem_type != pip_types.PipetteOEMType.OT + else "" + ) + base_name = f"{self.pipette_type.name}_{str(self.pipette_channels)}{oem_name}" return f"{base_name}_v{self.pipette_version}" diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_load_name_conversions.py b/shared-data/python/opentrons_shared_data/pipette/pipette_load_name_conversions.py index 865862bfec2..743ed66713e 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_load_name_conversions.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_load_name_conversions.py @@ -11,6 +11,7 @@ PipetteGenerationType, PipetteModelMajorVersionType, PipetteModelMinorVersionType, + PipetteOEMType, ) from .pipette_definition import ( PipetteNameType, @@ -21,6 +22,7 @@ DEFAULT_MODEL = PipetteModelType.p1000 DEFAULT_CHANNELS = PipetteChannelType.SINGLE_CHANNEL DEFAULT_MODEL_VERSION = PipetteVersionType(major=1, minor=0) +DEFAULT_OEM = PipetteOEMType.OT PIPETTE_AVAILABLE_TYPES = [m.name for m in PipetteModelType] PIPETTE_CHANNELS_INTS = [c.value for c in PipetteChannelType] @@ -229,8 +231,8 @@ def convert_to_pipette_name_type( channels = channels_from_string(split_pipette_model_or_name[1]) generation = generation_from_string(split_pipette_model_or_name) pipette_type = PipetteModelType[split_pipette_model_or_name[0]] - - return PipetteNameType(pipette_type, channels, generation) + oem = PipetteOEMType.get_oem_from_model_str(model_or_name) + return PipetteNameType(pipette_type, channels, generation, oem) def convert_pipette_name( @@ -266,8 +268,8 @@ def convert_pipette_name( version = version_from_generation(pipette_name_tuple) pipette_type = PipetteModelType[split_pipette_name[0]] - - return PipetteModelVersionType(pipette_type, channels, version) + oem = PipetteOEMType.get_oem_from_model_str(name) + return PipetteModelVersionType(pipette_type, channels, version, oem) def convert_pipette_model( @@ -304,12 +306,17 @@ def convert_pipette_model( pipette_type, parsed_channels, parsed_oem, parsed_version = exploded channels = channels_from_string(f"{parsed_channels}_{parsed_oem}") version = version_from_string(parsed_version) + oem = PipetteOEMType.get_oem_from_model_str(str(model)) elif model and provided_version: pipette_type, parsed_channels = model.split("_") channels = channels_from_string(parsed_channels) version = version_from_string(provided_version) + oem = PipetteOEMType.get_oem_from_model_str(str(model)) else: pipette_type = DEFAULT_MODEL.value channels = DEFAULT_CHANNELS version = DEFAULT_MODEL_VERSION - return PipetteModelVersionType(PipetteModelType[pipette_type], channels, version) + oem = DEFAULT_OEM + return PipetteModelVersionType( + PipetteModelType[pipette_type], channels, version, oem + ) diff --git a/shared-data/python/opentrons_shared_data/pipette/types.py b/shared-data/python/opentrons_shared_data/pipette/types.py index 685dae89957..6d462bc5234 100644 --- a/shared-data/python/opentrons_shared_data/pipette/types.py +++ b/shared-data/python/opentrons_shared_data/pipette/types.py @@ -115,6 +115,21 @@ class Quirks(enum.Enum): highSpeed = "highSpeed" +class PipetteOEMType(enum.Enum): + OT = "ot" # opentrons type + EM = "em" # Emulsifying Pipette + + @classmethod + def get_oem_from_quirks(cls, quirks: List[Quirks]) -> "PipetteOEMType": + """Return an oem type if true based on the quirks.""" + return cls.EM if Quirks.highSpeed in quirks else cls.OT + + @classmethod + def get_oem_from_model_str(cls, model_str: str) -> "PipetteOEMType": + """Return an oem type if true based on the model string.""" + return cls.EM if "multi_em" in model_str else cls.OT + + class AvailableUnits(enum.Enum): mm = "mm" amps = "amps" @@ -220,7 +235,7 @@ def dict_for_encode(self) -> bool: "p1000_single_gen2", "p1000_single_flex", "p1000_multi_flex", - "p1000_multi_em", + "p1000_multi_em_flex", "p1000_96", "p200_96", ] @@ -247,7 +262,7 @@ class PipetteNameType(str, enum.Enum): P1000_SINGLE_GEN2 = "p1000_single_gen2" P1000_SINGLE_FLEX = "p1000_single_flex" P1000_MULTI_FLEX = "p1000_multi_flex" - P1000_MULTI_EM = "p1000_multi_em" + P1000_MULTI_EM = "p1000_multi_em_flex" P1000_96 = "p1000_96" P200_96 = "p200_96" diff --git a/shared-data/python/tests/pipette/test_load_data.py b/shared-data/python/tests/pipette/test_load_data.py index 386af05de5b..78b7ac74c5b 100644 --- a/shared-data/python/tests/pipette/test_load_data.py +++ b/shared-data/python/tests/pipette/test_load_data.py @@ -9,6 +9,7 @@ PipetteChannelType, PipetteModelType, PipetteVersionType, + PipetteOEMType, PipetteTipType, Quirks, LiquidClasses, @@ -20,6 +21,7 @@ def test_load_pipette_definition() -> None: PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(major=3, minor=3), + PipetteOEMType.OT, ) assert pipette_config_one.channels == 1 @@ -38,6 +40,7 @@ def test_load_pipette_definition() -> None: PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(major=1, minor=0), + PipetteOEMType.OT, ) assert pipette_config_two.channels == 1 @@ -83,7 +86,10 @@ def test_update_pipette_configuration( cast(types.PipetteModel, pipette_model) ) base_configurations = load_data.load_definition( - model_name.pipette_type, model_name.pipette_channels, model_name.pipette_version + model_name.pipette_type, + model_name.pipette_channels, + model_name.pipette_version, + model_name.oem_type, ) updated_configurations = load_data.update_pipette_configuration( diff --git a/shared-data/python/tests/pipette/test_max_flow_rates_per_volume.py b/shared-data/python/tests/pipette/test_max_flow_rates_per_volume.py index aae0c1a4e1b..e0797ddec08 100644 --- a/shared-data/python/tests/pipette/test_max_flow_rates_per_volume.py +++ b/shared-data/python/tests/pipette/test_max_flow_rates_per_volume.py @@ -75,6 +75,7 @@ def test_max_flow_rates_per_volume(pipette: PipetteModel, action: str) -> None: pipette_model_version.pipette_type, pipette_model_version.pipette_channels, pipette_model_version.pipette_version, + pipette_model_version.oem_type, ) pipette_model_version_str = f"{pipette_model_version}" diff --git a/shared-data/python/tests/pipette/test_mutable_configurations.py b/shared-data/python/tests/pipette/test_mutable_configurations.py index d7a6c8ed1db..322682360c8 100644 --- a/shared-data/python/tests/pipette/test_mutable_configurations.py +++ b/shared-data/python/tests/pipette/test_mutable_configurations.py @@ -76,6 +76,7 @@ def test_load_old_overrides_regression( pipette_type=types.PipetteModelType.p20, pipette_channels=types.PipetteChannelType.SINGLE_CHANNEL, pipette_version=types.PipetteVersionType(2, 2), + oem_type=types.PipetteOEMType.OT, ), override_configuration_path, "P20SV222021040709", @@ -269,6 +270,7 @@ def test_load_with_overrides( pipette_model.pipette_type, pipette_model.pipette_channels, pipette_model.pipette_version, + pipette_model.oem_type, ) if serial_number == TEST_SERIAL_NUMBER: @@ -454,7 +456,9 @@ def test_loading_does_not_log_warnings( load_with_mutable_configurations() suppresses and logs internal exceptions to protect its caller, but those are still bugs, and we still want tests to catch them. """ - model = pipette_definition.PipetteModelVersionType(type, channels, version) + model = pipette_definition.PipetteModelVersionType( + type, channels, version, types.PipetteOEMType.OT + ) (override_configuration_path / filename).write_text(file_contents) with caplog.at_level(logging.WARNING): mutable_configurations.load_with_mutable_configurations( diff --git a/shared-data/python/tests/pipette/test_pipette_load_name_conversions.py b/shared-data/python/tests/pipette/test_pipette_load_name_conversions.py index 6e792560e9c..fdd9ec434cd 100644 --- a/shared-data/python/tests/pipette/test_pipette_load_name_conversions.py +++ b/shared-data/python/tests/pipette/test_pipette_load_name_conversions.py @@ -6,6 +6,7 @@ PipetteModelType, PipetteVersionType, PipetteGenerationType, + PipetteOEMType, ) from opentrons_shared_data.pipette.types import PipetteModel, PipetteName from opentrons_shared_data.pipette import ( @@ -23,6 +24,7 @@ PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(2, 0), + PipetteOEMType.OT, ), ], [ @@ -31,6 +33,7 @@ PipetteModelType.p1000, PipetteChannelType.EIGHT_CHANNEL, PipetteVersionType(1, 0), + PipetteOEMType.OT, ), ], [ @@ -39,6 +42,7 @@ PipetteModelType.p1000, PipetteChannelType.NINETY_SIX_CHANNEL, PipetteVersionType(1, 0), + PipetteOEMType.OT, ), ], ], @@ -59,6 +63,7 @@ def test_convert_pipette_model( PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(2, 0), + PipetteOEMType.OT, ), ], [ @@ -68,6 +73,7 @@ def test_convert_pipette_model( PipetteModelType.p1000, PipetteChannelType.EIGHT_CHANNEL, PipetteVersionType(3, 3), + PipetteOEMType.OT, ), ], [ @@ -77,6 +83,7 @@ def test_convert_pipette_model( PipetteModelType.p1000, PipetteChannelType.NINETY_SIX_CHANNEL, PipetteVersionType(1, 1), + PipetteOEMType.OT, ), ], ], @@ -96,6 +103,7 @@ def test_convert_pipette_model_provided_version( PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(2, 0), + PipetteOEMType.OT, ), ], [ @@ -104,6 +112,7 @@ def test_convert_pipette_model_provided_version( PipetteModelType.p1000, PipetteChannelType.EIGHT_CHANNEL, PipetteVersionType(3, 5), + PipetteOEMType.OT, ), ], [ @@ -112,6 +121,7 @@ def test_convert_pipette_model_provided_version( PipetteModelType.p1000, PipetteChannelType.NINETY_SIX_CHANNEL, PipetteVersionType(3, 6), + PipetteOEMType.OT, ), ], ], @@ -123,19 +133,21 @@ def test_convert_pipette_name( @pytest.mark.parametrize( - argnames=["model_type", "channels", "generation", "output"], + argnames=["model_type", "channels", "generation", "output", "oem"], argvalues=[ [ PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteGenerationType.GEN2, "p50_single_gen2", + PipetteOEMType.OT, ], [ PipetteModelType.p1000, PipetteChannelType.EIGHT_CHANNEL, PipetteGenerationType.GEN2, "p1000_multi_gen2", + PipetteOEMType.OT, ], [ # 96 channel has a unique "name" right now @@ -143,6 +155,7 @@ def test_convert_pipette_name( PipetteChannelType.NINETY_SIX_CHANNEL, PipetteGenerationType.FLEX, "p1000_96", + PipetteOEMType.OT, ], ], ) @@ -151,35 +164,40 @@ def test_model_version_type_string_version( channels: PipetteChannelType, generation: PipetteGenerationType, output: PipetteName, + oem: PipetteOEMType, ) -> None: data = pc.PipetteNameType( pipette_type=model_type, pipette_channels=channels, pipette_generation=generation, + oem_type=oem, ) assert output == str(data) @pytest.mark.parametrize( - argnames=["model_type", "channels", "version", "output"], + argnames=["model_type", "channels", "version", "output", "oem"], argvalues=[ [ PipetteModelType.p50, PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(1, 0), "p50_single_v1", + PipetteOEMType.OT, ], [ PipetteModelType.p1000, PipetteChannelType.EIGHT_CHANNEL, PipetteVersionType(2, 1), "p1000_multi_v2.1", + PipetteOEMType.OT, ], [ PipetteModelType.p1000, PipetteChannelType.NINETY_SIX_CHANNEL, PipetteVersionType(3, 3), "p1000_96_v3.3", + PipetteOEMType.OT, ], ], ) @@ -188,9 +206,13 @@ def test_name_type_string_generation( channels: PipetteChannelType, version: PipetteVersionType, output: PipetteModel, + oem: PipetteOEMType, ) -> None: data = pc.PipetteModelVersionType( - pipette_type=model_type, pipette_channels=channels, pipette_version=version + pipette_type=model_type, + pipette_channels=channels, + pipette_version=version, + oem_type=oem, ) assert output == str(data) diff --git a/shared-data/python/tests/pipette/test_validate_schema.py b/shared-data/python/tests/pipette/test_validate_schema.py index a002c38cfb2..12f276a1f3b 100644 --- a/shared-data/python/tests/pipette/test_validate_schema.py +++ b/shared-data/python/tests/pipette/test_validate_schema.py @@ -45,6 +45,7 @@ def test_check_all_models_are_valid() -> None: model_version.pipette_type, model_version.pipette_channels, model_version.pipette_version, + model_version.oem_type, ) except json.JSONDecodeError: print( @@ -81,11 +82,13 @@ def test_pick_up_configs_configuration_by_nozzle_map_keys() -> None: model_version.pipette_type, model_version.pipette_channels, model_version.pipette_version, + model_version.oem_type, ) valid_nozzle_maps = load_valid_nozzle_maps( model_version.pipette_type, model_version.pipette_channels, model_version.pipette_version, + model_version.oem_type, ) pipette_maps = list( @@ -123,11 +126,13 @@ def test_pick_up_configs_configuration_ordered_from_smallest_to_largest() -> Non model_version.pipette_type, model_version.pipette_channels, model_version.pipette_version, + model_version.oem_type, ) valid_nozzle_maps = load_valid_nozzle_maps( model_version.pipette_type, model_version.pipette_channels, model_version.pipette_version, + model_version.oem_type, ) map_keys = list(valid_nozzle_maps.maps.keys())