Skip to content

Commit

Permalink
feat(shared-data, api): return latest pipette version from pipetteName (
Browse files Browse the repository at this point in the history
#15002)

closes AUTH-357

This PR updates the `version_from_generation` def in `shared-data` to return the latest version.
  • Loading branch information
jerader authored Apr 25, 2024
1 parent faff667 commit 264883a
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 35 deletions.
4 changes: 2 additions & 2 deletions api/tests/opentrons/hardware_control/test_instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def fake_func2(mount, value):
{types.Mount.LEFT: "p10_single", types.Mount.RIGHT: "p300_single_gen2"}
)
attached = sim.attached_instruments
assert attached[types.Mount.LEFT]["model"] == "p10_single_v1"
assert attached[types.Mount.LEFT]["model"] == "p10_single_v1.5"
assert attached[types.Mount.LEFT]["name"] == "p10_single"

steps_mm_calls = [mock.call({"B": 768}), mock.call({"C": 3200})]
Expand Down Expand Up @@ -291,7 +291,7 @@ def fake_func2(mount, value):
# If we use prefixes, that should work too
await sim.cache_instruments({types.Mount.RIGHT: "p300_single"})
attached = sim.attached_instruments
assert attached[types.Mount.RIGHT]["model"] == "p300_single_v1"
assert attached[types.Mount.RIGHT]["model"] == "p300_single_v1.5"
assert attached[types.Mount.RIGHT]["name"] == "p300_single"
# If we specify instruments at init time, we should get them without
# passing an expectation
Expand Down
2 changes: 1 addition & 1 deletion api/tests/opentrons/protocol_api_old/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async def test_motion(ctx, hardware):
old_pos[Axis.X] = 0.0
old_pos[Axis.Y] = 0.0
old_pos[Axis.A] = 0.0
old_pos[Axis.C] = 2.0
old_pos[Axis.C] = 2.5
assert await hardware.current_position(instr._core.get_mount()) == pytest.approx(
old_pos
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_get_virtual_pipette_static_config(
)

assert result == LoadedStaticPipetteData(
model="p20_single_v2.0",
model="p20_single_v2.2",
display_name="P20 Single-Channel GEN2",
min_volume=1,
max_volume=20.0,
Expand Down Expand Up @@ -69,17 +69,17 @@ def test_configure_virtual_pipette_for_volume(
PipetteNameType.P50_SINGLE_FLEX.value, "my-pipette"
)
assert result1 == LoadedStaticPipetteData(
model="p50_single_v3.0",
model="p50_single_v3.6",
display_name="Flex 1-Channel 50 μL",
min_volume=5,
max_volume=50.0,
channels=1,
nozzle_offset_z=-259.15,
home_position=230.15,
flow_rates=FlowRates(
default_blow_out={"2.14": 4.0},
default_aspirate={"2.14": 8.0},
default_dispense={"2.14": 8.0},
default_blow_out={"2.14": 57},
default_aspirate={"2.14": 35},
default_dispense={"2.14": 57},
),
tip_configuration_lookup_table=result1.tip_configuration_lookup_table,
nominal_tip_overlap=result1.nominal_tip_overlap,
Expand All @@ -94,17 +94,17 @@ def test_configure_virtual_pipette_for_volume(
PipetteNameType.P50_SINGLE_FLEX.value, "my-pipette"
)
assert result2 == LoadedStaticPipetteData(
model="p50_single_v3.0",
model="p50_single_v3.6",
display_name="Flex 1-Channel 50 μL",
min_volume=1,
max_volume=30,
channels=1,
nozzle_offset_z=-259.15,
home_position=230.15,
flow_rates=FlowRates(
default_blow_out={"2.14": 4.0},
default_aspirate={"2.14": 8.0},
default_dispense={"2.14": 8.0},
default_blow_out={"2.14": 26.7},
default_aspirate={"2.14": 26.7},
default_dispense={"2.14": 26.7},
),
tip_configuration_lookup_table=result2.tip_configuration_lookup_table,
nominal_tip_overlap=result2.nominal_tip_overlap,
Expand Down Expand Up @@ -162,19 +162,19 @@ def test_load_virtual_pipette_nozzle_layout(
assert result.configuration.value == "FULL"

subject_instance.configure_virtual_pipette_nozzle_layout(
"my-96-pipette", "p1000_96_v3.5", "A1", "A12", "A1"
"my-96-pipette", "p1000_96_v3.6", "A1", "A12", "A1"
)
result = subject_instance.get_nozzle_layout_for_pipette("my-96-pipette")
assert result.configuration.value == "ROW"

subject_instance.configure_virtual_pipette_nozzle_layout(
"my-96-pipette", "p1000_96_v3.5", "A1", "A1"
"my-96-pipette", "p1000_96_v3.6", "A1", "A1"
)
result = subject_instance.get_nozzle_layout_for_pipette("my-96-pipette")
assert result.configuration.value == "SINGLE"

subject_instance.configure_virtual_pipette_nozzle_layout(
"my-96-pipette", "p1000_96_v3.5", "A1", "H1"
"my-96-pipette", "p1000_96_v3.6", "A1", "H1"
)
result = subject_instance.get_nozzle_layout_for_pipette("my-96-pipette")
assert result.configuration.value == "COLUMN"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"uiMaxFlowRate": 26.7,
"defaultAspirateFlowRate": {
"default": 35,
"valuesByApiLevel": { "2.14": 35 }
"valuesByApiLevel": { "2.14": 26.7 }
},
"defaultDispenseFlowRate": {
"default": 57,
"valuesByApiLevel": { "2.14": 57 }
"valuesByApiLevel": { "2.14": 26.7 }
},
"defaultBlowOutFlowRate": {
"default": 57,
"valuesByApiLevel": { "2.14": 57 }
"valuesByApiLevel": { "2.14": 26.7 }
},
"defaultFlowAcceleration": 1200.0,
"defaultTipLength": 57.9,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import re
from typing import List, Optional, Union, cast
from functools import lru_cache
from typing import List, Optional, Union, cast, Literal, Tuple
from opentrons_shared_data import get_shared_data_root
from .dev_types import PipetteModel, PipetteName

from .types import (
PipetteChannelType,
PipetteModelType,
Expand Down Expand Up @@ -106,26 +109,78 @@ def version_from_string(version: str) -> PipetteVersionType:
return PipetteVersionType(major, minor)


def version_from_generation(pipette_name_list: List[str]) -> PipetteVersionType:
"""Convert a string generation name to a py:obj:PipetteVersionType.
def get_channel_from_pipette_name(pipette_name_tuple: Tuple[str, ...]) -> str:
if "single" in pipette_name_tuple:
return "single_channel"
elif "96" in pipette_name_tuple:
return "ninety_six_channel"
else:
return "eight_channel"


def get_major_version_from_pipette_name(
pipette_name_tuple: Tuple[str, ...],
) -> Literal[1, 2, 3]:
# special-casing for 96-channel to return version 3
if (
"flex" in pipette_name_tuple
or "gen3" in pipette_name_tuple
or "96" in pipette_name_tuple
):
return 3
elif "gen2" in pipette_name_tuple:
return 2
else:
return 1


@lru_cache(4)
def version_from_generation(pipette_name_tuple: Tuple[str, ...]) -> PipetteVersionType:
"""Convert pipetteName to a py:obj:PipetteVersionType
Pipette generations are strings in the format of "gen1" or "gen2", and
usually associated withe :py:data:PipetteName.
Given the pipette_name_tuple, cycle through each definition file path
and find the latest version (major and minor version combined) that
exists and return that version.
Args:
pipette_name_list (List[str]): A list of strings from the separated by `_`
py:data:PipetteName.
pipette_name_tuple (Tuple[str, ...]): A tuple of strings from the separated
by `_` py:data:PipetteName.
Returns:
PipetteVersionType: A pipette version object.
"""
if "flex" in pipette_name_list or "gen3" in pipette_name_list:
return PipetteVersionType(3, 0)
elif "gen2" in pipette_name_list:
return PipetteVersionType(2, 0)
else:
return PipetteVersionType(1, 0)
major_version_from_pipette_name = get_major_version_from_pipette_name(
pipette_name_tuple
)
model_from_pipette_name = pipette_name_tuple[0]
channel_from_pipette_name = get_channel_from_pipette_name(pipette_name_tuple)

paths_to_validate = (
get_shared_data_root() / "pipette" / "definitions" / "2" / "general"
)
version_paths = (
paths_to_validate / channel_from_pipette_name / model_from_pipette_name
)

highest_minor_version: PipetteModelMinorVersionType = 0

for version_file in version_paths.iterdir():
version_list = version_file.stem.split("_")
major_version = version_list[0]
minor_version = version_list[1]

# Check if the major version matches the expected major version
if major_version == str(major_version_from_pipette_name):
minor_version_int = int(minor_version)
minor_version_lit: PipetteModelMinorVersionType = cast(
PipetteModelMinorVersionType, minor_version_int
)

# Update the highest minor version if this version is higher
if highest_minor_version < minor_version_lit:
highest_minor_version = minor_version_lit

return PipetteVersionType(major_version_from_pipette_name, highest_minor_version)


def generation_from_string(pipette_name_list: List[str]) -> PipetteGenerationType:
Expand Down Expand Up @@ -194,7 +249,12 @@ def convert_pipette_name(
if provided_version:
version = version_from_string(provided_version)
else:
version = version_from_generation(split_pipette_name)
pipette_name_tuple: Tuple[str, str, str] = (
split_pipette_name[0],
split_pipette_name[1],
split_pipette_name[2] if len(split_pipette_name) > 2 else "",
)
version = version_from_generation(pipette_name_tuple)

pipette_type = PipetteModelType[split_pipette_name[0]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,15 @@ def test_convert_pipette_model_provided_version(
pc.PipetteModelVersionType(
PipetteModelType.p1000,
PipetteChannelType.EIGHT_CHANNEL,
PipetteVersionType(3, 0),
PipetteVersionType(3, 5),
),
],
[
"p1000_96",
pc.PipetteModelVersionType(
PipetteModelType.p1000,
PipetteChannelType.NINETY_SIX_CHANNEL,
PipetteVersionType(1, 0),
PipetteVersionType(3, 6),
),
],
],
Expand Down

0 comments on commit 264883a

Please sign in to comment.