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

refactor(shared-data, robot-server, api): Pipette configuration architecture refactor to organize by nozzle map and tip type #15250

Merged
merged 32 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
09b2e01
pipette configuration values adjusted by nozzle map first pass
CaseyBatten May 3, 2024
af38b12
engine updates to support nested map and tip type configurations and …
CaseyBatten May 6, 2024
cd11ee0
tip overlap conversion to source from physical definition tree
CaseyBatten May 8, 2024
7d3d79e
Merge branch 'edge' into pipette_configuration_architecture_by_nozzle…
CaseyBatten May 10, 2024
6004a9a
tip overlap by nozzle configuration engine infrastructure
CaseyBatten May 13, 2024
350ebd0
tip overlap by dict fix for v3_6 p1000 and engine updates to reflect …
CaseyBatten May 17, 2024
45d2899
single channel definition updates
CaseyBatten May 20, 2024
d2d8b47
ninety six channel definition updates
CaseyBatten May 20, 2024
cf13f9b
eight channel v 1 0 upgrade
CaseyBatten May 21, 2024
4364c08
eight channel pipette definition updates
CaseyBatten May 22, 2024
8247821
test run with 8ch p50 and format fixes and error catching for configu…
CaseyBatten May 22, 2024
939995f
relocate valid map key to nozzle map
CaseyBatten May 24, 2024
a21f592
linting fixes test corrections and remove impossible nozzle configura…
CaseyBatten May 28, 2024
88f4f66
pipette handler test corrections
CaseyBatten May 29, 2024
6e390b4
mutable configurations updates
CaseyBatten Jun 3, 2024
2e03700
front to back bias for 8ch and single ch map naming convention
CaseyBatten Jun 3, 2024
2e19184
single channel SingleA1 map naming
CaseyBatten Jun 3, 2024
5e67680
Merge branch 'edge' into pipette_configuration_architecture_by_nozzle…
CaseyBatten Jun 3, 2024
1505f11
tip overlap versioning definition migration
CaseyBatten Jun 3, 2024
9a3d2d6
consolidate pipette config value logic and addition of tip overlap ve…
CaseyBatten Jun 4, 2024
8f4040b
addition of MissingConfigurationData error code and robot server fixes
CaseyBatten Jun 4, 2024
8ca7773
typescript type updates and schema definition updates
CaseyBatten Jun 5, 2024
9e23a59
hardware testing updates to utilize configurations by nozzle map
CaseyBatten Jun 5, 2024
36aef87
step generation updates
CaseyBatten Jun 5, 2024
49e2967
js pipette test data
CaseyBatten Jun 5, 2024
397c4ac
error formatting fix
CaseyBatten Jun 7, 2024
f4fa638
Merge branch 'edge' into pipette_configuration_architecture_by_nozzle…
CaseyBatten Jun 10, 2024
6ff32cc
single channel p1000 3_7 schema migration
CaseyBatten Jun 10, 2024
4ee4853
js test updates for single channel p1000 pipette v3_7
CaseyBatten Jun 10, 2024
4bda970
schema clarifications and unit test for map consistency
CaseyBatten Jun 11, 2024
6702599
typo correction
CaseyBatten Jun 11, 2024
d33c225
pipette data provider error update
CaseyBatten Jun 11, 2024
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
122 changes: 119 additions & 3 deletions api/src/opentrons/hardware_control/instruments/ot2/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
PipetteModelVersionType,
PipetteNameType,
PipetteLiquidPropertiesDefinition,
PressFitPickUpTipConfiguration,
)
from opentrons_shared_data.pipette import (
load_data as load_pipette_data,
Expand Down Expand Up @@ -112,6 +113,11 @@ def __init__(
pipette_channels=config.channels,
pipette_version=config.version,
)
self._valid_nozzle_maps = load_pipette_data.load_valid_nozzle_maps(
self._pipette_model.pipette_type,
self._pipette_model.pipette_channels,
self._pipette_model.pipette_version,
)
self._nozzle_offset = self._config.nozzle_offset
self._nozzle_manager = (
nozzle_manager.NozzleConfigurationManager.build_from_config(self._config)
Expand Down Expand Up @@ -148,7 +154,9 @@ def __init__(
self._active_tip_settings.default_blowout_flowrate.default
)

self._tip_overlap_lookup = self._liquid_class.tip_overlap_dictionary
self._tip_overlap_dictionary = (
self.get_nominal_tip_overlap_dictionary_by_configuration()
)

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

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

@property
def channels(self) -> pip_types.PipetteChannelType:
Expand Down Expand Up @@ -290,7 +298,10 @@ 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_dictionary = (
self.get_nominal_tip_overlap_dictionary_by_configuration()
)

self._nozzle_manager = (
nozzle_manager.NozzleConfigurationManager.build_from_config(self._config)
)
Expand Down Expand Up @@ -520,6 +531,111 @@ def remove_tip(self) -> None:
def has_tip(self) -> bool:
return self._has_tip

def _get_matching_approved_nozzle_map(self) -> str:
for map_key in self._valid_nozzle_maps.maps.keys():
if self._valid_nozzle_maps.maps[map_key] == list(
self._nozzle_manager.current_configuration.map_store.keys()
Copy link
Member

Choose a reason for hiding this comment

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

this is a whole lot to be doing just here, and it adds this error category. instead of doing this, let's tell the nozzle manager what map key it's using and then we can look up the configuration based on that directly.

):
return map_key
raise ValueError(
"Nozzle Configuration does not match any approved map layout for the current pipette."
)

def get_pick_up_speed_by_configuration(
self,
config: PressFitPickUpTipConfiguration,
) -> float:
approved_map = None
for map_key in self._valid_nozzle_maps.maps.keys():
if self._valid_nozzle_maps.maps[map_key] == list(
self._nozzle_manager.current_configuration.map_store.keys()
):
approved_map = map_key
if approved_map is None:
raise ValueError(
"Pick up tip speed request error. Nozzle Configuration does not match any approved map layout for the current pipette."
)

try:
return config.configuration_by_nozzle_map[approved_map][
pip_types.PipetteTipType(self._liquid_class.max_volume).name
].speed
except KeyError:
Copy link
Member

Choose a reason for hiding this comment

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

we should have these errors be enumerated errors defined in shared data using an error code, like a new 4000-series "missing configuration data" or something

default = config.configuration_by_nozzle_map[approved_map].get("default")
if default is not None:
return default.speed
raise KeyError(
f"Default tip type configuration values do not exist for Nozzle Map {approved_map}."
)

def get_pick_up_distance_by_configuration(
self,
config: PressFitPickUpTipConfiguration,
) -> float:
approved_map = self._get_matching_approved_nozzle_map()

try:
return config.configuration_by_nozzle_map[approved_map][
pip_types.PipetteTipType(self._liquid_class.max_volume).name
].distance
except KeyError:
default = config.configuration_by_nozzle_map[approved_map].get("default")
if default is not None:
return default.distance
raise KeyError(
f"Default tip type configuration values do not exist for Nozzle Map {approved_map}."
)

def get_pick_up_current_by_configuration(
self,
config: PressFitPickUpTipConfiguration,
) -> float:
approved_map = self._get_matching_approved_nozzle_map()

try:
return config.configuration_by_nozzle_map[approved_map][
pip_types.PipetteTipType(self._liquid_class.max_volume).name
].current
except KeyError:
default = config.configuration_by_nozzle_map[approved_map].get("default")
if default is not None:
return default.current
raise KeyError(
f"Default tip type configuration values do not exist for Nozzle Map {approved_map}."
)

def get_nominal_tip_overlap_dictionary_by_configuration(
self,
) -> Dict[str, float]:
for config in (
self._config.pick_up_tip_configurations.press_fit,
self._config.pick_up_tip_configurations.cam_action,
):
if not config:
continue
approved_map = self._get_matching_approved_nozzle_map()

try:
return config.configuration_by_nozzle_map[approved_map][
pip_types.PipetteTipType(self._liquid_class.max_volume).name
].tip_overlap_dictionary
except KeyError:
try:
default = config.configuration_by_nozzle_map[approved_map].get(
"default"
)
if default is not None:
return default.tip_overlap_dictionary
raise KeyError(
f"Default tip type configuration values do not exist for Nozzle Map {approved_map}."
)
except KeyError:
# No valid key found for the approved nozzle map under this configuration - try the next
continue
raise CommandPreconditionViolated(
message="No valid tip overlap dictionary identified.",
)

# Cache max is chosen somewhat arbitrarily. With a float is input we don't
# want this to unbounded.
@functools.lru_cache(maxsize=100)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ def plan_check_pick_up_tip( # type: ignore[no-untyped-def]
if instrument.has_tip:
raise UnexpectedTipAttachError("pick_up_tip", instrument.name, mount.name)
self._ihp_log.debug(f"Picking up tip on {mount.name}")
tip_count = instrument.nozzle_manager.current_configuration.tip_count

if presses is None or presses < 0:
checked_presses = instrument.pick_up_configurations.press_fit.presses
else:
Expand All @@ -783,11 +783,12 @@ def plan_check_pick_up_tip( # type: ignore[no-untyped-def]
else:
check_incr = increment

pick_up_speed = instrument.pick_up_configurations.press_fit.speed_by_tip_count[
tip_count
]
pick_up_distance = (
instrument.pick_up_configurations.press_fit.distance_by_tip_count[tip_count]
pick_up_speed = instrument.get_pick_up_speed_by_configuration(
instrument.pick_up_configurations.press_fit
)

pick_up_distance = instrument.get_pick_up_distance_by_configuration(
instrument.pick_up_configurations.press_fit
)

def build_presses() -> Iterator[Tuple[float, float]]:
Expand Down Expand Up @@ -817,9 +818,9 @@ def add_tip_to_instr() -> None:
current={
Axis.by_mount(
mount
): instrument.pick_up_configurations.press_fit.current_by_tip_count[
tip_count
]
): instrument.get_pick_up_current_by_configuration(
instrument.pick_up_configurations.press_fit
)
},
speed=pick_up_speed,
relative_down=top_types.Point(0, 0, press_dist),
Expand All @@ -846,9 +847,9 @@ def add_tip_to_instr() -> None:
current={
Axis.by_mount(
mount
): instrument.pick_up_configurations.press_fit.current_by_tip_count[
instrument.nozzle_manager.current_configuration.tip_count
]
): instrument.get_pick_up_current_by_configuration(
instrument.pick_up_configurations.press_fit
)
},
speed=pick_up_speed,
relative_down=top_types.Point(0, 0, press_dist),
Expand Down
Loading
Loading