Skip to content

Commit f1c2a49

Browse files
authored
feat(api,robot-server,shared-data): Specify tip overlap by version (#15323)
We need to have multiple versions of the tip overlap data for at least flex pipettes so that we can ship updated overlap data without inadvertently changing the behavior of protocols on stable API versions. This PR accomplishes that. # Details ## separate versioning The data in shared-data has a version, but that version is separate from versions in other parts of the stack. This is on purpose, because the version has to be written in shared-data, and read in protocol engine. I think it's gross to write API versions in shared data, but we've done it before and could do it again; the real sticking point was the engine. The engine, in modern protocols, loads the tip overlap and does all that data management itself; it would need to select the appropriate version from the dict. We really don't want API versions there. Therefore, there's a separate simple versioning scheme that's a little more like the alternate liquid handling function scheme that we haven't used in years, but with an actual string key for the version instead of implicit versioning by positioning in an array. There will instead be a specific map of API versions to overlap versions living inside the source of the engine core for the python protocol API (that's the only place it needs to be - for why, read on). ## engine state management and data lifecycle There were basically two ways to implement this. The first way is to save all the data - save the full versioned tip overlap dictionary, and save which version we've selected. Since I listed this first, you can guess I didn't do this. Instead, what I did was add a version to the two engine commands that load pipette data - `load_pipette` and `configure_for_volume` - and then have the functions those commands call pull the specified version out of the full config, which means that we only have to save the specific version in state and state doesn't have to change. This also means that we don't have to worry about data storage migration and compatibility. One huge note here: **the engine argument parsing gives you the MOST RECENT tip overlap if you don't specify one**. That's in bold because it's not the way we've implemented similar things. It's necessary because we want PD protocols to get the new overlap values without having to change anything. ## python api integration The python API, however, really wants to keep the old values (until you're on a certain version - we'll implement that when we actually add the new values). We do that in a couple ways: - the old pipette dictionary value that has the tip overlap dict is still there, using "v0". That means that protocol API versions that use that dict, which is all those not using the engine core, get the old values consistently for free - for protocol api versions that do use the engine core, for now we hard specify v0. ## json protocol integration New JSON protocols, any that directly create engine commands rather than being mapped through python protocol API values, will get the new overlaps because they won't specify the parameter to the load pipette or configure for volume engine commands. Older JSON protocols that do adapt the python protocol API commands will get the python behavior. # Testing - This PR gets regression tested - it doesn't really contain any new features, because it doesn't really contain any new data. - OT-2 protocol behavior should be adequately covered by the g-code testing - OT-2 calibration should probably be run real quick - Flex behavior should be adequately tested by running LPC and a protocol - this applies to all protocols and can be easily tested. - Flex calibration and LPC doesn't use tips and therefore isn't affected Closes EXEC-451
1 parent c34aed7 commit f1c2a49

File tree

109 files changed

+1286
-807
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+1286
-807
lines changed

api/src/opentrons/hardware_control/dev_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class PipetteDict(InstrumentDict):
8484
tip_length: float
8585
working_volume: float
8686
tip_overlap: Dict[str, float]
87+
versioned_tip_overlap: Dict[str, Dict[str, float]]
8788
available_volume: float
8889
return_tip_height: float
8990
default_aspirate_flow_rates: Dict[str, float]

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def __init__(
148148
self._active_tip_settings.default_blowout_flowrate.default
149149
)
150150

151-
self._tip_overlap_lookup = self._liquid_class.tip_overlap_dictionary
151+
self._tip_overlap_lookup = self._liquid_class.versioned_tip_overlap_dictionary
152152

153153
if use_old_aspiration_functions:
154154
self._pipetting_function_version = PIPETTING_FUNCTION_FALLBACK_VERSION
@@ -216,7 +216,7 @@ def pipette_offset(self) -> PipetteOffsetByPipetteMount:
216216
return self._pipette_offset
217217

218218
@property
219-
def tip_overlap(self) -> Dict[str, float]:
219+
def tip_overlap(self) -> Dict[str, Dict[str, float]]:
220220
return self._tip_overlap_lookup
221221

222222
@property
@@ -290,7 +290,7 @@ def reset_state(self) -> None:
290290
self.active_tip_settings.default_blowout_flowrate.default
291291
)
292292

293-
self._tip_overlap_lookup = self.liquid_class.tip_overlap_dictionary
293+
self._tip_overlap_lookup = self.liquid_class.versioned_tip_overlap_dictionary
294294
self._nozzle_manager = (
295295
nozzle_manager.NozzleConfigurationManager.build_from_config(self._config)
296296
)
@@ -571,7 +571,8 @@ def as_dict(self) -> "Pipette.DictType":
571571
"default_dispense_flow_rates": self.dispense_flow_rates_lookup,
572572
"tip_length": self.current_tip_length,
573573
"return_tip_height": self.active_tip_settings.default_return_tip_height,
574-
"tip_overlap": self.tip_overlap,
574+
"tip_overlap": self.tip_overlap["v0"],
575+
"versioned_tip_overlap": self.tip_overlap,
575576
"back_compat_names": self._config.pipette_backcompat_names,
576577
"supported_tips": self.liquid_class.supported_tips,
577578
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
212212
"blow_out_flow_rate",
213213
"working_volume",
214214
"tip_overlap",
215+
"versioned_tip_overlap",
215216
"available_volume",
216217
"return_tip_height",
217218
"default_aspirate_flow_rates",

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def __init__(
133133
)
134134
self._flow_acceleration = self._active_tip_settings.default_flow_acceleration
135135

136-
self._tip_overlap_lookup = self._liquid_class.tip_overlap_dictionary
136+
self._tip_overlap_lookup = self._liquid_class.versioned_tip_overlap_dictionary
137137

138138
if use_old_aspiration_functions:
139139
self._pipetting_function_version = PIPETTING_FUNCTION_FALLBACK_VERSION
@@ -161,7 +161,7 @@ def backlash_distance(self) -> float:
161161
return self._backlash_distance
162162

163163
@property
164-
def tip_overlap(self) -> Dict[str, float]:
164+
def tip_overlap(self) -> Dict[str, Dict[str, float]]:
165165
return self._tip_overlap_lookup
166166

167167
@property
@@ -254,7 +254,7 @@ def reset_state(self) -> None:
254254
)
255255
self._flow_acceleration = self._active_tip_settings.default_flow_acceleration
256256

257-
self._tip_overlap_lookup = self.liquid_class.tip_overlap_dictionary
257+
self._tip_overlap_lookup = self.liquid_class.versioned_tip_overlap_dictionary
258258
self._nozzle_manager = (
259259
nozzle_manager.NozzleConfigurationManager.build_from_config(self._config)
260260
)
@@ -560,7 +560,8 @@ def as_dict(self) -> "Pipette.DictType":
560560
"default_flow_acceleration": self.active_tip_settings.default_flow_acceleration,
561561
"tip_length": self.current_tip_length,
562562
"return_tip_height": self.active_tip_settings.default_return_tip_height,
563-
"tip_overlap": self.tip_overlap,
563+
"tip_overlap": self.tip_overlap["v0"],
564+
"versioned_tip_overlap": self.tip_overlap,
564565
"back_compat_names": self._config.pipette_backcompat_names,
565566
"supported_tips": self.liquid_class.supported_tips,
566567
}
@@ -655,7 +656,7 @@ def set_tip_type(self, tip_type: pip_types.PipetteTipType) -> None:
655656
self._flow_acceleration = self._active_tip_settings.default_flow_acceleration
656657

657658
self._fallback_tip_length = self._active_tip_settings.default_tip_length
658-
self._tip_overlap_lookup = self.liquid_class.tip_overlap_dictionary
659+
self._tip_overlap_lookup = self.liquid_class.versioned_tip_overlap_dictionary
659660
self._working_volume = min(tip_type.value, self.liquid_class.max_volume)
660661

661662
def get_pick_up_configuration_for_tip_count(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
228228
"blow_out_flow_rate",
229229
"working_volume",
230230
"tip_overlap",
231+
"versioned_tip_overlap",
231232
"available_volume",
232233
"return_tip_height",
233234
"default_aspirate_flow_rates",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ def set_flow_rate(
745745

746746
def configure_for_volume(self, volume: float) -> None:
747747
self._engine_client.configure_for_volume(
748-
pipette_id=self._pipette_id, volume=volume
748+
pipette_id=self._pipette_id, volume=volume, tip_overlap_version="v0"
749749
)
750750

751751
def prepare_to_aspirate(self) -> None:

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,9 @@ def load_instrument(
498498
An instrument core configured to use the requested instrument.
499499
"""
500500
engine_mount = MountType[mount.name]
501-
load_result = self._engine_client.load_pipette(instrument_name, engine_mount)
501+
load_result = self._engine_client.load_pipette(
502+
instrument_name, engine_mount, tip_overlap_version="v0"
503+
)
502504

503505
return InstrumentCore(
504506
pipette_id=load_result.pipetteId,

api/src/opentrons/protocol_engine/clients/sync_client.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,15 @@ def load_pipette(
172172
self,
173173
pipette_name: PipetteNameType,
174174
mount: MountType,
175+
tip_overlap_version: Optional[str] = None,
175176
) -> commands.LoadPipetteResult:
176177
"""Execute a LoadPipette command and return the result."""
177178
request = commands.LoadPipetteCreate(
178-
params=commands.LoadPipetteParams(mount=mount, pipetteName=pipette_name)
179+
params=commands.LoadPipetteParams(
180+
mount=mount,
181+
pipetteName=pipette_name,
182+
tipOverlapNotAfterVersion=tip_overlap_version,
183+
)
179184
)
180185
result = self._transport.execute_command(request=request)
181186

@@ -376,12 +381,14 @@ def drop_tip_in_place(
376381
return cast(commands.DropTipInPlaceResult, result)
377382

378383
def configure_for_volume(
379-
self, pipette_id: str, volume: float
384+
self, pipette_id: str, volume: float, tip_overlap_version: Optional[str] = None
380385
) -> commands.ConfigureForVolumeResult:
381386
"""Execute a ConfigureForVolume command."""
382387
request = commands.ConfigureForVolumeCreate(
383388
params=commands.ConfigureForVolumeParams(
384-
pipetteId=pipette_id, volume=volume
389+
pipetteId=pipette_id,
390+
volume=volume,
391+
tipOverlapNotAfterVersion=tip_overlap_version,
385392
)
386393
)
387394
result = self._transport.execute_command(request=request)

api/src/opentrons/protocol_engine/commands/configure_for_volume.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class ConfigureForVolumeParams(PipetteIdMixin):
2828
"than a pipette-specific maximum volume.",
2929
ge=0,
3030
)
31+
tipOverlapNotAfterVersion: Optional[str] = Field(
32+
None,
33+
description="A version of tip overlap data to not exceed. The highest-versioned "
34+
"tip overlap data that does not exceed this version will be used. Versions are "
35+
"expressed as vN where N is an integer, counting up from v0. If None, the current "
36+
"highest version will be used.",
37+
)
3138

3239

3340
class ConfigureForVolumePrivateResult(PipetteConfigUpdateResultMixin):
@@ -61,6 +68,7 @@ async def execute(
6168
pipette_result = await self._equipment.configure_for_volume(
6269
pipette_id=params.pipetteId,
6370
volume=params.volume,
71+
tip_overlap_version=params.tipOverlapNotAfterVersion,
6472
)
6573

6674
return ConfigureForVolumeResult(), ConfigureForVolumePrivateResult(

api/src/opentrons/protocol_engine/commands/load_pipette.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ class LoadPipetteParams(BaseModel):
5252
description="An optional ID to assign to this pipette. If None, an ID "
5353
"will be generated.",
5454
)
55+
tipOverlapNotAfterVersion: Optional[str] = Field(
56+
None,
57+
description="A version of tip overlap data to not exceed. The highest-versioned "
58+
"tip overlap data that does not exceed this version will be used. Versions are "
59+
"expressed as vN where N is an integer, counting up from v0. If None, the current "
60+
"highest version will be used.",
61+
)
5562

5663

5764
class LoadPipetteResult(BaseModel):
@@ -112,6 +119,7 @@ async def execute(
112119
pipette_name=params.pipetteName,
113120
mount=params.mount,
114121
pipette_id=params.pipetteId,
122+
tip_overlap_version=params.tipOverlapNotAfterVersion,
115123
)
116124

117125
return LoadPipetteResult(

0 commit comments

Comments
 (0)