Skip to content

Commit ad9fabd

Browse files
authored
fix(api): optimize liquid class transfers by only changing volume mode when necessary (#20009)
Fixes an inefficiency in the liquid class transfer code where configure_for_volume was being called unconditionally
1 parent bf74220 commit ad9fabd

File tree

19 files changed

+337
-8
lines changed

19 files changed

+337
-8
lines changed

api/src/opentrons/hardware_control/dev_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
SupportedTipsDefinition,
2323
PipetteBoundingBoxOffsetDefinition,
2424
AvailableSensorDefinition,
25+
PipetteLiquidPropertiesDefinition,
2526
)
2627
from opentrons_shared_data.gripper import (
2728
GripperModel,
@@ -106,6 +107,9 @@ class PipetteDict(InstrumentDict):
106107
shaft_ul_per_mm: float
107108
available_sensors: AvailableSensorDefinition
108109
volume_mode: LiquidClasses # LiquidClasses refer to volume mode in this context
110+
available_volume_modes: Dict[
111+
LiquidClasses, PipetteLiquidPropertiesDefinition
112+
] # Ditto
109113

110114

111115
class PipetteStateDict(TypedDict):

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
268268
}
269269
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
270270
result["volume_mode"] = instr.liquid_class_name
271+
result["available_volume_modes"] = instr.config.liquid_properties
271272
return cast(PipetteDict, result)
272273

273274
@property

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
295295
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
296296
result["available_sensors"] = instr.config.available_sensors
297297
result["volume_mode"] = instr.liquid_class_name
298+
result["available_volume_modes"] = instr.config.liquid_properties
298299
return cast(PipetteDict, result)
299300

300301
@property

api/src/opentrons/protocol_api/core/engine/instrument.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,7 +2153,12 @@ def aspirate_liquid_class(
21532153
air_gap=0,
21542154
)
21552155
)
2156-
if volume_for_pipette_mode_configuration is not None:
2156+
if volume_for_pipette_mode_configuration is not None and (
2157+
self._protocol_core.api_version < APIVersion(2, 28)
2158+
or self._engine_client.state.pipettes.get_will_volume_mode_change(
2159+
self._pipette_id, volume_for_pipette_mode_configuration
2160+
)
2161+
):
21572162
prep_location = Location(
21582163
point=source_well.get_top(LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP.z),
21592164
labware=source_loc.labware,
@@ -2171,10 +2176,14 @@ def aspirate_liquid_class(
21712176
location=prep_location,
21722177
)
21732178
last_liquid_and_airgap_in_tip.air_gap = 0
2174-
# TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
21752179
self.configure_for_volume(volume_for_pipette_mode_configuration)
21762180
self.prepare_to_aspirate()
21772181

2182+
elif not self._engine_client.state.pipettes.get_ready_to_aspirate(
2183+
self._pipette_id
2184+
):
2185+
self.prepare_to_aspirate()
2186+
21782187
aspirate_point = (
21792188
tx_comps_executor.absolute_point_from_position_reference_and_offset(
21802189
well=source_well,

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class LoadedStaticPipetteData:
7171
shaft_ul_per_mm: float
7272
available_sensors: pipette_definition.AvailableSensorDefinition
7373
volume_mode: pip_types.LiquidClasses # pip_types Liquid Classes refers to volume modes
74+
available_volume_modes_min_vol: Dict[pip_types.LiquidClasses, float] # Ditto
7475

7576

7677
class VirtualPipetteDataProvider:
@@ -300,6 +301,10 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901
300301
available_sensors=config.available_sensors
301302
or pipette_definition.AvailableSensorDefinition(sensors=[]),
302303
volume_mode=liquid_class,
304+
available_volume_modes_min_vol={
305+
volume_mode: props.min_volume
306+
for volume_mode, props in config.liquid_properties.items()
307+
},
303308
)
304309

305310
def get_virtual_pipette_static_config(
@@ -356,6 +361,10 @@ def get_pipette_static_config(
356361
shaft_ul_per_mm=pipette_dict["shaft_ul_per_mm"],
357362
available_sensors=available_sensors,
358363
volume_mode=pipette_dict["volume_mode"],
364+
available_volume_modes_min_vol={
365+
volume_mode: props.min_volume
366+
for volume_mode, props in pipette_dict["available_volume_modes"].items()
367+
},
359368
)
360369

361370

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class StaticPipetteConfig:
111111
shaft_ul_per_mm: float
112112
available_sensors: pipette_definition.AvailableSensorDefinition
113113
volume_mode: VolumeModes
114+
available_volume_modes_min_vol: Dict[VolumeModes, float]
114115

115116

116117
@dataclasses.dataclass
@@ -318,6 +319,7 @@ def _update_pipette_config(self, state_update: update_types.StateUpdate) -> None
318319
shaft_ul_per_mm=config.shaft_ul_per_mm,
319320
available_sensors=config.available_sensors,
320321
volume_mode=config.volume_mode,
322+
available_volume_modes_min_vol=config.available_volume_modes_min_vol,
321323
)
322324
self._state.flow_rates_by_id[
323325
state_update.pipette_config.pipette_id
@@ -876,6 +878,30 @@ def get_is_low_volume_mode(self, pipette_id: str) -> bool:
876878
"""Determine if the pipette is currently in low volume mode."""
877879
return self.get_config(pipette_id).volume_mode == VolumeModes.lowVolumeDefault
878880

881+
def get_volume_mode_from_volume(
882+
self, pipette_id: str, volume: float
883+
) -> VolumeModes:
884+
"""Get the volume mode for the given pipette and volume quantity."""
885+
available_volume_modes_min_vol = self.get_config(
886+
pipette_id
887+
).available_volume_modes_min_vol
888+
has_low_volume_mode = (
889+
VolumeModes.lowVolumeDefault in available_volume_modes_min_vol
890+
)
891+
892+
if not has_low_volume_mode:
893+
return VolumeModes.default
894+
if volume >= available_volume_modes_min_vol[VolumeModes.default]:
895+
return VolumeModes.default
896+
return VolumeModes.lowVolumeDefault
897+
898+
def get_will_volume_mode_change(self, pipette_id: str, volume: float) -> bool:
899+
"""Determine if the pipette will change volume mode based on current volume mode and new volume."""
900+
return (
901+
self.get_volume_mode_from_volume(pipette_id, volume)
902+
!= self.get_config(pipette_id).volume_mode
903+
)
904+
879905
def lookup_volume_to_mm_conversion(
880906
self, pipette_id: str, volume: float, action: str
881907
) -> float:

api/src/opentrons/protocols/api_support/definitions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .types import APIVersion
22

3-
MAX_SUPPORTED_VERSION = APIVersion(2, 27)
3+
MAX_SUPPORTED_VERSION = APIVersion(2, 28)
44
"""The maximum supported protocol API version in this release."""
55

66
MIN_SUPPORTED_VERSION = APIVersion(2, 0)

0 commit comments

Comments
 (0)