-
Notifications
You must be signed in to change notification settings - Fork 178
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): Support V2 pipette configs in the OT-2 and Protocol Engine #13104
Changes from 31 commits
1f2cbc8
a224613
8f997cc
a44b33b
7a67e68
cd42f7d
cdc34ec
70b2dbe
f91f2e4
bcc99bd
a91dc30
d58a7b6
a85f108
f01b9af
1b612f1
f585b56
dae2269
fae2363
3bc8db6
3d499a4
dd1e660
07b8d6c
dfa403b
d21d9f9
68d42df
63eafcf
e653868
bcbbdba
c3ec3e0
ea2d88a
2b19e8e
7919afd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,18 @@ | |
import copy | ||
import logging | ||
from threading import Event | ||
from typing import Dict, Optional, List, Tuple, TYPE_CHECKING, Sequence, Iterator | ||
from typing import Dict, Optional, List, Tuple, TYPE_CHECKING, Sequence, Iterator, cast | ||
from contextlib import contextmanager | ||
|
||
from opentrons_shared_data.pipette import dummy_model_for_name | ||
from opentrons_shared_data.pipette import ( | ||
pipette_load_name_conversions as pipette_load_name, | ||
mutable_configurations, | ||
pipette_definition, | ||
) | ||
|
||
from opentrons import types | ||
from opentrons.config.pipette_config import config_models, config_names, configs, load | ||
from opentrons.config.types import RobotConfig | ||
from opentrons.config import get_opentrons_path | ||
from opentrons.drivers.smoothie_drivers import SimulatingDriver | ||
|
||
from opentrons.drivers.rpi_drivers.gpio_simulator import SimulatingGPIOCharDev | ||
|
@@ -22,7 +26,7 @@ | |
from ..util import ot2_axis_to_string | ||
|
||
if TYPE_CHECKING: | ||
from opentrons_shared_data.pipette.dev_types import PipetteName | ||
from opentrons_shared_data.pipette.dev_types import PipetteName, PipetteModel | ||
from ..dev_types import ( | ||
AttachedPipette, | ||
AttachedInstruments, | ||
|
@@ -119,25 +123,31 @@ def __init__( | |
self._gpio_chardev = gpio_chardev | ||
|
||
def _sanitize_attached_instrument( | ||
passed_ai: Optional[Dict[str, Optional[str]]] = None | ||
passed_ai: Optional[PipetteSpec] = None, | ||
) -> PipetteSpec: | ||
if not passed_ai or not passed_ai.get("model"): | ||
return {"model": None, "id": None} | ||
if passed_ai["model"] in config_models: | ||
return passed_ai # type: ignore | ||
if passed_ai["model"] in config_names: | ||
return { | ||
"model": dummy_model_for_name(passed_ai["model"]), # type: ignore | ||
"id": passed_ai.get("id"), | ||
} | ||
if pipette_load_name.supported_pipette( | ||
cast("PipetteName", passed_ai["model"]) | ||
): | ||
if pipette_load_name.is_model(passed_ai["model"]): | ||
return passed_ai | ||
else: | ||
get_pip_model = pipette_load_name.convert_pipette_name( | ||
cast("PipetteName", passed_ai["model"]) | ||
) | ||
return { | ||
"model": PipetteModel(str(get_pip_model)), | ||
"id": passed_ai.get("id"), | ||
} | ||
raise KeyError( | ||
"If you specify attached_instruments, the model " | ||
"should be pipette names or pipette models, but " | ||
f'{passed_ai["model"]} is not' | ||
) | ||
|
||
self._attached_instruments = { | ||
m: _sanitize_attached_instrument(attached_instruments.get(m)) | ||
m: _sanitize_attached_instrument(attached_instruments.get(m)) # type: ignore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's this suppressing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just moved the #type: ignore from the internal function to have that be slightly better typed. It's suppressing a |
||
for m in types.Mount.ot2_mounts() | ||
} | ||
self._stubbed_attached_modules = attached_modules | ||
|
@@ -213,18 +223,33 @@ async def fast_home(self, axis: Sequence[str], margin: float) -> Dict[str, float | |
return self._position | ||
|
||
def _attached_to_mount( | ||
self, mount: types.Mount, expected_instr: Optional[PipetteName] | ||
self, mount: types.Mount, expected_instr: Optional["PipetteName"] | ||
) -> AttachedPipette: | ||
init_instr = self._attached_instruments.get(mount, {"model": None, "id": None}) | ||
found_model = init_instr["model"] | ||
back_compat: List["PipetteName"] = [] | ||
|
||
path_to_overrides = get_opentrons_path("pipette_config_overrides_dir") | ||
found_model_configs: Optional[pipette_definition.PipetteConfigurations] = None | ||
|
||
converted_found_name: Optional[pipette_definition.PipetteNameType] = None | ||
if found_model: | ||
back_compat = configs[found_model].get("backCompatNames", []) | ||
converted_found_model = pipette_load_name.convert_pipette_model(found_model) | ||
converted_found_name = pipette_load_name.convert_to_pipette_name_type( | ||
found_model | ||
) | ||
found_model_configs = ( | ||
mutable_configurations.load_with_mutable_configurations( | ||
converted_found_model, path_to_overrides, init_instr["id"] | ||
) | ||
) | ||
back_compat = found_model_configs.pipette_backcompat_names | ||
if ( | ||
expected_instr | ||
and found_model | ||
and found_model_configs | ||
and ( | ||
configs[found_model]["name"] != expected_instr | ||
str(converted_found_name) != expected_instr | ||
and expected_instr not in back_compat | ||
) | ||
): | ||
|
@@ -235,34 +260,42 @@ def _attached_to_mount( | |
) | ||
) | ||
else: | ||
converted_expected_model = pipette_load_name.convert_pipette_name( | ||
expected_instr | ||
) | ||
return { | ||
"config": load(dummy_model_for_name(expected_instr)), | ||
"config": mutable_configurations.load_with_mutable_configurations( | ||
converted_expected_model, path_to_overrides | ||
), | ||
"id": None, | ||
} | ||
elif found_model and expected_instr: | ||
elif found_model and found_model_configs: | ||
# Instrument detected matches instrument expected (note: | ||
# "instrument detected" means passed as an argument to the | ||
# constructor of this class) | ||
return { | ||
"config": load(found_model, init_instr["id"]), | ||
"id": init_instr["id"], | ||
} | ||
elif found_model: | ||
# constructor of this class) OR, | ||
# Instrument detected and no expected instrument specified | ||
return { | ||
"config": load(found_model, init_instr["id"]), | ||
"config": found_model_configs, | ||
"id": init_instr["id"], | ||
} | ||
elif expected_instr: | ||
# Expected instrument specified and no instrument detected | ||
return {"config": load(dummy_model_for_name(expected_instr)), "id": None} | ||
converted_expected_model = pipette_load_name.convert_pipette_name( | ||
expected_instr | ||
) | ||
return { | ||
"config": mutable_configurations.load_with_mutable_configurations( | ||
converted_expected_model, path_to_overrides | ||
), | ||
"id": None, | ||
} | ||
else: | ||
# No instrument detected or expected | ||
return {"config": None, "id": None} | ||
|
||
@ensure_yield | ||
async def get_attached_instruments( | ||
self, expected: Dict[types.Mount, PipetteName] | ||
self, expected: Dict[types.Mount, "PipetteName"] | ||
) -> AttachedInstruments: | ||
"""Update the internal cache of attached instruments. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly wondering if, just for the sake of this pr, we mapped at least some of the new values to the old names. most of the time we wouldn't use them, but it would prevent us from needing a million changes that have no semantic meaning but just need different letters in sequence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the config itself? Otherwise if we mapped it in the
Pipette
class there would still be a million changes. Plus mapping in a Pydantic model will get confusing since it's supposed to server as a schema.