Skip to content

Commit

Permalink
feat(api): set overlap versions from commands
Browse files Browse the repository at this point in the history
PE commands that load or alter pipette configuration now allow
specifying a version of the pipette tip overlap dictionary to use. This
specification is on a "no version above <x>" basis, for future proofing.

It's allowed to not specify a version; in this case, the _highest_
available tip overlap version is used as a default.

This is different from similar implementations. It is done so that users
of protocol interfaces that do not require bug-for-bug compatibility as
the python protocol API does get the positioning changes for free -
those protocols do not have to change to get the new positioning.
Consistent behavior here is an opt-in. This will be true of JSON
protocols that use the protocol engine, v7 and v8.

The python protocol API, however, has to stay stable. For this reason,
- The hardware controller continues to export v0 of tip overlap
unconditionally in its pipette_dict
  - This guarantees same behavior for pre-engine protocol APIs
  - This guarantees same behavior for old robot server interfaces (i.e.
  OT-2 calibration)
  - This guarantees same behavior for old JSON protocol interfaces,
  which we don't actually care about
- The _engine_, on the other hand, actively loads the latest values if a
version isn't specified, which will happen when ingesting v7 and v8 PD
protocols
- The _engine core_ currently hard specifies v0, and that will change
based on the API level when we actually add a new version of the overlap data.

This is implemented at pipette configuration time so that it can stay
stateless - the pipette configuration data that is loaded in the engine
continues to present only one dictionary of tip overlap data, which is
resolved at the point of loading.
  • Loading branch information
sfoster1 committed Jun 3, 2024
1 parent 74a25f9 commit df12f28
Show file tree
Hide file tree
Showing 22 changed files with 356 additions and 77 deletions.
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
9 changes: 5 additions & 4 deletions api/src/opentrons/hardware_control/instruments/ot2/pipette.py
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(
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.",
)


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

0 comments on commit df12f28

Please sign in to comment.