Skip to content

Commit b985fc1

Browse files
feat(api): Support changing return tip height by tip size type (#13157)
1 parent a8ecf08 commit b985fc1

File tree

23 files changed

+444
-37
lines changed

23 files changed

+444
-37
lines changed

api/src/opentrons/hardware_control/dev_types.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
PipetteName,
1616
ChannelCount,
1717
)
18-
from opentrons_shared_data.pipette.pipette_definition import PipetteConfigurations
18+
from opentrons_shared_data.pipette.types import PipetteTipType
19+
from opentrons_shared_data.pipette.pipette_definition import (
20+
PipetteConfigurations,
21+
SupportedTipsDefinition,
22+
)
1923
from opentrons_shared_data.gripper import (
2024
GripperModel,
2125
GripperDefinition,
@@ -89,6 +93,7 @@ class PipetteDict(InstrumentDict):
8993
ready_to_aspirate: bool
9094
has_tip: bool
9195
default_blow_out_volume: float
96+
supported_tips: Dict[PipetteTipType, SupportedTipsDefinition]
9297

9398

9499
class GripperDict(InstrumentDict):

api/src/opentrons/hardware_control/instruments/ot2/pipette.py

+1
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ def as_dict(self) -> "Pipette.DictType":
537537
"return_tip_height": self.active_tip_settings.default_return_tip_height,
538538
"tip_overlap": self.tip_overlap,
539539
"back_compat_names": self._config.pipette_backcompat_names,
540+
"supported_tips": self._config.supported_tips,
540541
}
541542
)
542543
return self._config_as_dict

api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py

+1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
218218
"default_blow_out_flow_rates",
219219
"default_dispense_flow_rates",
220220
"back_compat_names",
221+
"supported_tips",
221222
]
222223

223224
instr_dict = instr.as_dict()

api/src/opentrons/hardware_control/instruments/ot3/pipette.py

+1
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ def as_dict(self) -> "Pipette.DictType":
532532
"return_tip_height": self.active_tip_settings.default_return_tip_height,
533533
"tip_overlap": self.tip_overlap,
534534
"back_compat_names": self._config.pipette_backcompat_names,
535+
"supported_tips": self._config.supported_tips,
535536
}
536537
)
537538
return self._config_as_dict

api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py

+1
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
229229
"default_blow_out_flow_rates",
230230
"default_dispense_flow_rates",
231231
"back_compat_names",
232+
"supported_tips",
232233
]
233234

234235
instr_dict = instr.as_dict()

api/src/opentrons/protocol_engine/execution/tip_handler.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ async def pick_up_tip(
6060
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
6161

6262
nominal_tip_geometry = self._state_view.geometry.get_nominal_tip_geometry(
63-
pipette_id=pipette_id,
64-
labware_id=labware_id,
65-
well_name=well_name,
63+
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
6664
)
6765

6866
actual_tip_length = await self._labware_data_provider.get_calibrated_tip_length(

api/src/opentrons/protocol_engine/resources/pipette_data_provider.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
pipette_load_name_conversions as pipette_load_name,
88
load_data as load_pipette_data,
99
types as pip_types,
10+
pipette_definition,
1011
)
1112

1213
from opentrons.hardware_control.dev_types import PipetteDict
@@ -26,7 +27,9 @@ class LoadedStaticPipetteData:
2627
home_position: float
2728
nozzle_offset_z: float
2829
flow_rates: FlowRates
29-
return_tip_scale: float
30+
tip_configuration_lookup_table: Dict[
31+
float, pipette_definition.SupportedTipsDefinition
32+
]
3033
nominal_tip_overlap: Dict[str, float]
3134

3235

@@ -52,12 +55,14 @@ def get_virtual_pipette_static_config(
5255
channels=config.channels,
5356
home_position=config.mount_configurations.homePosition,
5457
nozzle_offset_z=config.nozzle_offset[2],
58+
tip_configuration_lookup_table={
59+
k.value: v for k, v in config.supported_tips.items()
60+
},
5561
flow_rates=FlowRates(
5662
default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
5763
default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
5864
default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
5965
),
60-
return_tip_scale=tip_configuration.default_return_tip_height,
6166
nominal_tip_overlap=config.tip_overlap_dictionary,
6267
)
6368

@@ -75,7 +80,9 @@ def get_pipette_static_config(pipette_dict: PipetteDict) -> LoadedStaticPipetteD
7580
default_aspirate=pipette_dict["default_aspirate_flow_rates"],
7681
default_dispense=pipette_dict["default_dispense_flow_rates"],
7782
),
78-
return_tip_scale=pipette_dict["return_tip_height"],
83+
tip_configuration_lookup_table={
84+
k.value: v for k, v in pipette_dict["supported_tips"].items()
85+
},
7986
nominal_tip_overlap=pipette_dict["tip_overlap"],
8087
# TODO(mc, 2023-02-28): these two values are not present in PipetteDict
8188
# https://opentrons.atlassian.net/browse/RCORE-655

api/src/opentrons/protocol_engine/state/pipettes.py

+40-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dataclasses import dataclass
44
from typing import Dict, List, Mapping, Optional, Tuple
55

6+
from opentrons_shared_data.pipette import pipette_definition
67
from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE
78
from opentrons.hardware_control.dev_types import PipetteDict
89
from opentrons.types import MountType, Mount as HwMount
@@ -71,7 +72,9 @@ class StaticPipetteConfig:
7172
min_volume: float
7273
max_volume: float
7374
channels: int
74-
return_tip_scale: float
75+
tip_configuration_lookup_table: Dict[
76+
float, pipette_definition.SupportedTipsDefinition
77+
]
7578
nominal_tip_overlap: Dict[str, float]
7679
home_position: float
7780
nozzle_offset_z: float
@@ -124,7 +127,7 @@ def handle_action(self, action: Action) -> None:
124127
min_volume=config.min_volume,
125128
max_volume=config.max_volume,
126129
channels=config.channels,
127-
return_tip_scale=config.return_tip_scale,
130+
tip_configuration_lookup_table=config.tip_configuration_lookup_table,
128131
nominal_tip_overlap=config.nominal_tip_overlap,
129132
home_position=config.home_position,
130133
nozzle_offset_z=config.nozzle_offset_z,
@@ -171,11 +174,32 @@ def _handle_command(self, command: Command) -> None:
171174
self._state.attached_tip_by_id[pipette_id] = attached_tip
172175
self._state.aspirated_volume_by_id[pipette_id] = 0
173176

177+
static_config = self._state.static_config_by_id.get(pipette_id)
178+
if static_config:
179+
tip_configuration = static_config.tip_configuration_lookup_table[
180+
attached_tip.volume
181+
]
182+
self._state.flow_rates_by_id[pipette_id] = FlowRates(
183+
default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
184+
default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
185+
default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
186+
)
187+
174188
elif isinstance(command.result, (DropTipResult, DropTipInPlaceResult)):
175189
pipette_id = command.params.pipetteId
176190
self._state.aspirated_volume_by_id[pipette_id] = None
177191
self._state.attached_tip_by_id[pipette_id] = None
178192

193+
static_config = self._state.static_config_by_id.get(pipette_id)
194+
if static_config:
195+
tip_configuration = static_config.tip_configuration_lookup_table[
196+
static_config.max_volume
197+
]
198+
self._state.flow_rates_by_id[pipette_id] = FlowRates(
199+
default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
200+
default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
201+
default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level,
202+
)
179203
elif isinstance(command.result, BlowOutResult):
180204
pipette_id = command.params.pipetteId
181205
self._state.aspirated_volume_by_id[pipette_id] = None
@@ -504,7 +528,20 @@ def get_instrument_max_height_ot2(self, pipette_id: str) -> float:
504528

505529
def get_return_tip_scale(self, pipette_id: str) -> float:
506530
"""Return the given pipette's return tip height scale."""
507-
return self.get_config(pipette_id).return_tip_scale
531+
max_volume = self.get_maximum_volume(pipette_id)
532+
working_volume = max_volume
533+
if self.get_attached_tip(pipette_id):
534+
working_volume = self.get_working_volume(pipette_id)
535+
536+
if working_volume in self.get_config(pipette_id).tip_configuration_lookup_table:
537+
tip_lookup = self.get_config(pipette_id).tip_configuration_lookup_table[
538+
working_volume
539+
]
540+
else:
541+
tip_lookup = self.get_config(pipette_id).tip_configuration_lookup_table[
542+
working_volume
543+
]
544+
return tip_lookup.default_return_tip_height
508545

509546
def get_flow_rates(self, pipette_id: str) -> FlowRates:
510547
"""Get the default flow rates for the pipette."""

api/tests/opentrons/protocol_engine/conftest.py

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from opentrons_shared_data.deck import load as load_deck
1010
from opentrons_shared_data.deck.dev_types import DeckDefinitionV3
1111
from opentrons_shared_data.labware import load_definition
12+
from opentrons_shared_data.pipette import pipette_definition
1213
from opentrons.protocols.models import LabwareDefinition
1314
from opentrons.protocols.api_support.deck_type import (
1415
STANDARD_OT2_DECK,
@@ -187,3 +188,24 @@ def mag_block_v1_def() -> ModuleDefinition:
187188
"""Get the definition of a V1 Mag Block."""
188189
definition = load_shared_data("module/definitions/3/magneticBlockV1.json")
189190
return ModuleDefinition.parse_raw(definition)
191+
192+
193+
@pytest.fixture(scope="session")
194+
def supported_tip_fixture() -> pipette_definition.SupportedTipsDefinition:
195+
"""Get a mock supported tip definition."""
196+
return pipette_definition.SupportedTipsDefinition(
197+
defaultAspirateFlowRate=pipette_definition.FlowRateDefinition(
198+
default=10, valuesByApiLevel={}
199+
),
200+
defaultDispenseFlowRate=pipette_definition.FlowRateDefinition(
201+
default=10, valuesByApiLevel={}
202+
),
203+
defaultBlowOutFlowRate=pipette_definition.FlowRateDefinition(
204+
default=10, valuesByApiLevel={}
205+
),
206+
defaultTipLength=40,
207+
defaultReturnTipHeight=0.5,
208+
aspirate=pipette_definition.ulPerMMDefinition(default={"1": [(0, 0, 0)]}),
209+
dispense=pipette_definition.ulPerMMDefinition(default={"1": [(0, 0, 0)]}),
210+
defaultBlowoutVolume=5,
211+
)

api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, Optional, cast
77

88
from opentrons_shared_data.pipette.dev_types import PipetteNameType
9+
from opentrons_shared_data.pipette import pipette_definition
910
from opentrons_shared_data.labware.dev_types import LabwareUri
1011

1112
from opentrons.calibration_storage.helpers import uri_from_details
@@ -126,7 +127,9 @@ async def temp_module_v2(decoy: Decoy) -> TempDeck:
126127

127128

128129
@pytest.fixture
129-
def loaded_static_pipette_data() -> LoadedStaticPipetteData:
130+
def loaded_static_pipette_data(
131+
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
132+
) -> LoadedStaticPipetteData:
130133
"""Get a pipette config data value object."""
131134
return LoadedStaticPipetteData(
132135
model="pipette_model",
@@ -139,7 +142,7 @@ def loaded_static_pipette_data() -> LoadedStaticPipetteData:
139142
default_aspirate={"b": 4.56},
140143
default_dispense={"c": 7.89},
141144
),
142-
return_tip_scale=0.5,
145+
tip_configuration_lookup_table={4.56: supported_tip_fixture},
143146
nominal_tip_overlap={"default": 9.87},
144147
home_position=10.11,
145148
nozzle_offset_z=12.13,

api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test pipette data provider."""
22
from opentrons_shared_data.pipette.dev_types import PipetteNameType, PipetteModel
3+
from opentrons_shared_data.pipette import pipette_definition, types as pip_types
34

45
from opentrons.hardware_control.dev_types import PipetteDict
56
from opentrons.protocol_engine.types import FlowRates
@@ -29,7 +30,7 @@ def test_get_virtual_pipette_static_config() -> None:
2930
default_dispense={"2.0": 3.78, "2.6": 7.56},
3031
default_blow_out={"2.0": 3.78, "2.6": 7.56},
3132
),
32-
return_tip_scale=0.5,
33+
tip_configuration_lookup_table=result.tip_configuration_lookup_table,
3334
nominal_tip_overlap={
3435
"default": 8.25,
3536
"opentrons/eppendorf_96_tiprack_10ul_eptips/1": 8.4,
@@ -42,7 +43,9 @@ def test_get_virtual_pipette_static_config() -> None:
4243
)
4344

4445

45-
def test_get_pipette_static_config() -> None:
46+
def test_get_pipette_static_config(
47+
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
48+
) -> None:
4649
"""It should return config data given a PipetteDict."""
4750
pipette_dict: PipetteDict = {
4851
"name": "p300_single_gen2",
@@ -78,6 +81,7 @@ def test_get_pipette_static_config() -> None:
7881
"default_dispense_speeds": {"2.0": 5.021202, "2.6": 10.042404},
7982
"default_aspirate_speeds": {"2.0": 5.021202, "2.6": 10.042404},
8083
"default_blow_out_volume": 10,
84+
"supported_tips": {pip_types.PipetteTipType.t300: supported_tip_fixture},
8185
}
8286

8387
result = subject.get_pipette_static_config(pipette_dict)
@@ -93,7 +97,7 @@ def test_get_pipette_static_config() -> None:
9397
default_dispense={"2.0": 46.43, "2.3": 92.86},
9498
default_blow_out={"2.0": 46.43, "2.2": 92.86},
9599
),
96-
return_tip_scale=0.5,
100+
tip_configuration_lookup_table={300: supported_tip_fixture},
97101
nominal_tip_overlap={
98102
"default": 8.2,
99103
"opentrons/opentrons_96_tiprack_300ul/1": 8.2,

api/tests/opentrons/protocol_engine/state/test_geometry_view.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from opentrons_shared_data.deck.dev_types import DeckDefinitionV3
99
from opentrons_shared_data.labware.dev_types import LabwareUri
10+
from opentrons_shared_data.pipette import pipette_definition
1011
from opentrons.calibration_storage.helpers import uri_from_details
1112
from opentrons.protocols.models import LabwareDefinition
1213
from opentrons.types import Point, DeckSlotName, MountType
@@ -1354,6 +1355,7 @@ def test_get_next_drop_tip_location(
13541355
pipette_channels: int,
13551356
pipette_mount: MountType,
13561357
expected_locations: List[DropTipWellLocation],
1358+
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
13571359
) -> None:
13581360
"""It should provide the next location to drop tips into within a labware."""
13591361
decoy.when(labware_view.is_fixed_trash(labware_id="abc")).then_return(True)
@@ -1368,7 +1370,7 @@ def test_get_next_drop_tip_location(
13681370
model="blah",
13691371
display_name="bleh",
13701372
serial_number="",
1371-
return_tip_scale=0,
1373+
tip_configuration_lookup_table={9001: supported_tip_fixture},
13721374
nominal_tip_overlap={},
13731375
home_position=0,
13741376
nozzle_offset_z=0,

api/tests/opentrons/protocol_engine/state/test_pipette_store.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Optional
55

66
from opentrons_shared_data.pipette.dev_types import PipetteNameType
7+
from opentrons_shared_data.pipette import pipette_definition
78

89
from opentrons.types import DeckSlotName, MountType
910
from opentrons.protocol_engine import commands as cmd
@@ -589,7 +590,10 @@ def test_set_movement_speed(subject: PipetteStore) -> None:
589590
assert subject.state.movement_speed_by_id[pipette_id] == 123.456
590591

591592

592-
def test_add_pipette_config(subject: PipetteStore) -> None:
593+
def test_add_pipette_config(
594+
subject: PipetteStore,
595+
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
596+
) -> None:
593597
"""It should issue an action to add a pipette config."""
594598
subject.handle_action(
595599
AddPipetteConfigAction(
@@ -606,7 +610,7 @@ def test_add_pipette_config(subject: PipetteStore) -> None:
606610
default_dispense={"b": 2},
607611
default_blow_out={"c": 3},
608612
),
609-
return_tip_scale=4,
613+
tip_configuration_lookup_table={4: supported_tip_fixture},
610614
nominal_tip_overlap={"default": 5},
611615
home_position=8.9,
612616
nozzle_offset_z=10.11,
@@ -621,7 +625,7 @@ def test_add_pipette_config(subject: PipetteStore) -> None:
621625
min_volume=1.23,
622626
max_volume=4.56,
623627
channels=7,
624-
return_tip_scale=4,
628+
tip_configuration_lookup_table={4: supported_tip_fixture},
625629
nominal_tip_overlap={"default": 5},
626630
home_position=8.9,
627631
nozzle_offset_z=10.11,

0 commit comments

Comments
 (0)