Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api,robot-server,shared-data): Specify tip overlap by version #15323

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/opentrons/hardware_control/dev_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class PipetteDict(InstrumentDict):
tip_length: float
working_volume: float
tip_overlap: Dict[str, float]
versioned_tip_overlap: Dict[str, Dict[str, float]]
available_volume: float
return_tip_height: float
default_aspirate_flow_rates: Dict[str, float]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def __init__(
self._active_tip_settings.default_blowout_flowrate.default
)

self._tip_overlap_lookup = self._liquid_class.tip_overlap_dictionary
self._tip_overlap_lookup = self._liquid_class.versioned_tip_overlap_dictionary

if use_old_aspiration_functions:
self._pipetting_function_version = PIPETTING_FUNCTION_FALLBACK_VERSION
Expand Down Expand Up @@ -216,7 +216,7 @@ def pipette_offset(self) -> PipetteOffsetByPipetteMount:
return self._pipette_offset

@property
def tip_overlap(self) -> Dict[str, float]:
def tip_overlap(self) -> Dict[str, Dict[str, float]]:
return self._tip_overlap_lookup

@property
Expand Down Expand Up @@ -290,7 +290,7 @@ def reset_state(self) -> None:
self.active_tip_settings.default_blowout_flowrate.default
)

self._tip_overlap_lookup = self.liquid_class.tip_overlap_dictionary
self._tip_overlap_lookup = self.liquid_class.versioned_tip_overlap_dictionary
self._nozzle_manager = (
nozzle_manager.NozzleConfigurationManager.build_from_config(self._config)
)
Expand Down Expand Up @@ -571,7 +571,8 @@ def as_dict(self) -> "Pipette.DictType":
"default_dispense_flow_rates": self.dispense_flow_rates_lookup,
"tip_length": self.current_tip_length,
"return_tip_height": self.active_tip_settings.default_return_tip_height,
"tip_overlap": self.tip_overlap,
"tip_overlap": self.tip_overlap["v0"],
"versioned_tip_overlap": self.tip_overlap,
"back_compat_names": self._config.pipette_backcompat_names,
"supported_tips": self.liquid_class.supported_tips,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
"blow_out_flow_rate",
"working_volume",
"tip_overlap",
"versioned_tip_overlap",
"available_volume",
"return_tip_height",
"default_aspirate_flow_rates",
Expand Down
11 changes: 6 additions & 5 deletions api/src/opentrons/hardware_control/instruments/ot3/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __init__(
)
self._flow_acceleration = self._active_tip_settings.default_flow_acceleration

self._tip_overlap_lookup = self._liquid_class.tip_overlap_dictionary
self._tip_overlap_lookup = self._liquid_class.versioned_tip_overlap_dictionary

if use_old_aspiration_functions:
self._pipetting_function_version = PIPETTING_FUNCTION_FALLBACK_VERSION
Expand Down Expand Up @@ -161,7 +161,7 @@ def backlash_distance(self) -> float:
return self._backlash_distance

@property
def tip_overlap(self) -> Dict[str, float]:
def tip_overlap(self) -> Dict[str, Dict[str, float]]:
return self._tip_overlap_lookup

@property
Expand Down Expand Up @@ -254,7 +254,7 @@ def reset_state(self) -> None:
)
self._flow_acceleration = self._active_tip_settings.default_flow_acceleration

self._tip_overlap_lookup = self.liquid_class.tip_overlap_dictionary
self._tip_overlap_lookup = self.liquid_class.versioned_tip_overlap_dictionary
self._nozzle_manager = (
nozzle_manager.NozzleConfigurationManager.build_from_config(self._config)
)
Expand Down Expand Up @@ -560,7 +560,8 @@ def as_dict(self) -> "Pipette.DictType":
"default_flow_acceleration": self.active_tip_settings.default_flow_acceleration,
"tip_length": self.current_tip_length,
"return_tip_height": self.active_tip_settings.default_return_tip_height,
"tip_overlap": self.tip_overlap,
"tip_overlap": self.tip_overlap["v0"],
"versioned_tip_overlap": self.tip_overlap,
"back_compat_names": self._config.pipette_backcompat_names,
"supported_tips": self.liquid_class.supported_tips,
}
Expand Down Expand Up @@ -655,7 +656,7 @@ def set_tip_type(self, tip_type: pip_types.PipetteTipType) -> None:
self._flow_acceleration = self._active_tip_settings.default_flow_acceleration

self._fallback_tip_length = self._active_tip_settings.default_tip_length
self._tip_overlap_lookup = self.liquid_class.tip_overlap_dictionary
self._tip_overlap_lookup = self.liquid_class.versioned_tip_overlap_dictionary
self._working_volume = min(tip_type.value, self.liquid_class.max_volume)

def get_pick_up_configuration_for_tip_count(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
"blow_out_flow_rate",
"working_volume",
"tip_overlap",
"versioned_tip_overlap",
"available_volume",
"return_tip_height",
"default_aspirate_flow_rates",
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ def set_flow_rate(

def configure_for_volume(self, volume: float) -> None:
self._engine_client.configure_for_volume(
pipette_id=self._pipette_id, volume=volume
pipette_id=self._pipette_id, volume=volume, tip_overlap_version="v0"
)

def prepare_to_aspirate(self) -> None:
Expand Down
4 changes: 3 additions & 1 deletion api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,9 @@ def load_instrument(
An instrument core configured to use the requested instrument.
"""
engine_mount = MountType[mount.name]
load_result = self._engine_client.load_pipette(instrument_name, engine_mount)
load_result = self._engine_client.load_pipette(
instrument_name, engine_mount, tip_overlap_version="v0"
)

return InstrumentCore(
pipette_id=load_result.pipetteId,
Expand Down
13 changes: 10 additions & 3 deletions api/src/opentrons/protocol_engine/clients/sync_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,15 @@ def load_pipette(
self,
pipette_name: PipetteNameType,
mount: MountType,
tip_overlap_version: Optional[str] = None,
) -> commands.LoadPipetteResult:
"""Execute a LoadPipette command and return the result."""
request = commands.LoadPipetteCreate(
params=commands.LoadPipetteParams(mount=mount, pipetteName=pipette_name)
params=commands.LoadPipetteParams(
mount=mount,
pipetteName=pipette_name,
tipOverlapNotAfterVersion=tip_overlap_version,
)
)
result = self._transport.execute_command(request=request)

Expand Down Expand Up @@ -376,12 +381,14 @@ def drop_tip_in_place(
return cast(commands.DropTipInPlaceResult, result)

def configure_for_volume(
self, pipette_id: str, volume: float
self, pipette_id: str, volume: float, tip_overlap_version: Optional[str] = None
) -> commands.ConfigureForVolumeResult:
"""Execute a ConfigureForVolume command."""
request = commands.ConfigureForVolumeCreate(
params=commands.ConfigureForVolumeParams(
pipetteId=pipette_id, volume=volume
pipetteId=pipette_id,
volume=volume,
tipOverlapNotAfterVersion=tip_overlap_version,
)
)
result = self._transport.execute_command(request=request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ class ConfigureForVolumeParams(PipetteIdMixin):
"than a pipette-specific maximum volume.",
ge=0,
)
tipOverlapNotAfterVersion: Optional[str] = Field(
Copy link
Contributor

@SyntaxColoring SyntaxColoring Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a Protocol Engine caller's perspective, does it make sense for configureForVolume to deal with tip overlaps? Doesn't this command only affect plunger stuff?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, really, certainly not with what's currently implemented. It is a minor sin.

None,
description="A version of tip overlap data to not exceed. The highest-versioned "
"tip overlap data that does not exceed this version will be used. Versions are "
"expressed as vN where N is an integer, counting up from v0. If None, the current "
"highest version will be used.",
)


class ConfigureForVolumePrivateResult(PipetteConfigUpdateResultMixin):
Expand Down Expand Up @@ -61,6 +68,7 @@ async def execute(
pipette_result = await self._equipment.configure_for_volume(
pipette_id=params.pipetteId,
volume=params.volume,
tip_overlap_version=params.tipOverlapNotAfterVersion,
)

return ConfigureForVolumeResult(), ConfigureForVolumePrivateResult(
Expand Down
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_engine/commands/load_pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ class LoadPipetteParams(BaseModel):
description="An optional ID to assign to this pipette. If None, an ID "
"will be generated.",
)
tipOverlapNotAfterVersion: Optional[str] = Field(
None,
description="A version of tip overlap data to not exceed. The highest-versioned "
"tip overlap data that does not exceed this version will be used. Versions are "
"expressed as vN where N is an integer, counting up from v0. If None, the current "
"highest version will be used.",
)
Comment on lines +58 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nit that doesn't really matter, but since these are always integers, should we make this field an int instead of a str?

I guess we're using "vN" strings for readability in the underlying JSON files? I'm 50/50 on whether it's better to use a str here to be consistent with those, or to translate to an int for API niceness.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to keep it a string to promote "this is an arbitrary value please do not do math on it"



class LoadPipetteResult(BaseModel):
Expand Down Expand Up @@ -112,6 +119,7 @@ async def execute(
pipette_name=params.pipetteName,
mount=params.mount,
pipette_id=params.pipetteId,
tip_overlap_version=params.tipOverlapNotAfterVersion,
)

return LoadPipetteResult(
Expand Down
29 changes: 22 additions & 7 deletions api/src/opentrons/protocol_engine/execution/equipment.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ async def load_pipette(
pipette_name: PipetteNameType,
mount: MountType,
pipette_id: Optional[str],
tip_overlap_version: Optional[str],
) -> LoadedPipetteData:
"""Ensure the requested pipette is attached.

Expand All @@ -211,6 +212,8 @@ async def load_pipette(
mount: The mount on which pipette must be attached.
pipette_id: An optional identifier to assign the pipette. If None, an
identifier will be generated.
tip_overlap_version: An optional specifier for the version of tip overlap data to use.
If None, defaults to v0. Does not need to be format checked - this function does it.

Returns:
A LoadedPipetteData object.
Expand All @@ -225,6 +228,11 @@ async def load_pipette(
if isinstance(pipette_name, PipetteNameType)
else pipette_name
)
sanitized_overlap_version = (
pipette_data_provider.validate_and_default_tip_overlap_version(
tip_overlap_version
)
)

pipette_id = pipette_id or self._model_utils.generate_id()
if not use_virtual_pipettes:
Expand Down Expand Up @@ -257,14 +265,16 @@ async def load_pipette(

serial_number = pipette_dict["pipette_id"]
static_pipette_config = pipette_data_provider.get_pipette_static_config(
pipette_dict
pipette_dict=pipette_dict, tip_overlap_version=sanitized_overlap_version
)

else:
serial_number = self._model_utils.generate_id(prefix="fake-serial-number-")
static_pipette_config = (
self._virtual_pipette_data_provider.get_virtual_pipette_static_config(
pipette_name_value, pipette_id
pipette_name=pipette_name_value,
pipette_id=pipette_id,
tip_overlap_version=sanitized_overlap_version,
)
)
serial = serial_number or ""
Expand Down Expand Up @@ -367,9 +377,7 @@ async def load_module(
)

async def configure_for_volume(
self,
pipette_id: str,
volume: float,
self, pipette_id: str, volume: float, tip_overlap_version: Optional[str]
) -> LoadedConfigureForVolumeData:
"""Ensure the requested volume can be configured for the given pipette.

Expand All @@ -381,6 +389,11 @@ async def configure_for_volume(
A LoadedConfiguredVolumeData object.
"""
use_virtual_pipettes = self._state_store.config.use_virtual_pipettes
sanitized_overlap_version = (
pipette_data_provider.validate_and_default_tip_overlap_version(
tip_overlap_version
)
)

if not use_virtual_pipettes:
mount = self._state_store.pipettes.get_mount(pipette_id).to_hw_mount()
Expand All @@ -390,7 +403,7 @@ async def configure_for_volume(

serial_number = pipette_dict["pipette_id"]
static_pipette_config = pipette_data_provider.get_pipette_static_config(
pipette_dict
pipette_dict=pipette_dict, tip_overlap_version=sanitized_overlap_version
)

else:
Expand All @@ -401,7 +414,9 @@ async def configure_for_volume(

serial_number = self._model_utils.generate_id(prefix="fake-serial-number-")
static_pipette_config = self._virtual_pipette_data_provider.get_virtual_pipette_static_config_by_model_string(
model, pipette_id
pipette_model_string=model,
pipette_id=pipette_id,
tip_overlap_version=sanitized_overlap_version,
)

return LoadedConfigureForVolumeData(
Expand Down
Loading
Loading