Skip to content

Commit

Permalink
feat(api): Support 96 channel in the hardware controller (#11866)
Browse files Browse the repository at this point in the history
  • Loading branch information
Laura-Danielle authored Dec 27, 2022
1 parent a9e3123 commit 62962c5
Show file tree
Hide file tree
Showing 77 changed files with 5,172 additions and 476 deletions.
16 changes: 11 additions & 5 deletions api/src/opentrons/config/defaults_ot3.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
OT3AxisKind.X: 500,
OT3AxisKind.Y: 500,
OT3AxisKind.Z: 35,
OT3AxisKind.P: 45,
OT3AxisKind.P: 5,
OT3AxisKind.Q: 40,
},
low_throughput={
OT3AxisKind.X: 500,
Expand All @@ -100,7 +101,8 @@
OT3AxisKind.X: 1000,
OT3AxisKind.Y: 1000,
OT3AxisKind.Z: 100,
OT3AxisKind.P: 50,
OT3AxisKind.P: 10,
OT3AxisKind.Q: 10,
},
low_throughput={
OT3AxisKind.X: 1000,
Expand All @@ -125,13 +127,14 @@
OT3AxisKind.Y: 10,
OT3AxisKind.Z: 10,
OT3AxisKind.Z_G: 15,
OT3AxisKind.P: 10,
OT3AxisKind.P: 5,
},
high_throughput={
OT3AxisKind.X: 10,
OT3AxisKind.Y: 10,
OT3AxisKind.Z: 10,
OT3AxisKind.P: 10,
OT3AxisKind.Q: 10,
},
low_throughput={
OT3AxisKind.X: 10,
Expand Down Expand Up @@ -163,6 +166,7 @@
OT3AxisKind.Y: 5,
OT3AxisKind.Z: 5,
OT3AxisKind.P: 5,
OT3AxisKind.Q: 5,
},
low_throughput={
OT3AxisKind.X: 5,
Expand Down Expand Up @@ -190,8 +194,9 @@
high_throughput={
OT3AxisKind.X: 0.5,
OT3AxisKind.Y: 0.5,
OT3AxisKind.Z: 0.1,
OT3AxisKind.Z: 0.8,
OT3AxisKind.P: 0.3,
OT3AxisKind.Q: 0.3,
},
low_throughput={
OT3AxisKind.X: 0.5,
Expand Down Expand Up @@ -220,7 +225,8 @@
OT3AxisKind.X: 1.4,
OT3AxisKind.Y: 1.4,
OT3AxisKind.Z: 1.4,
OT3AxisKind.P: 1.0,
OT3AxisKind.P: 2.0,
OT3AxisKind.Q: 2.0,
},
low_throughput={
OT3AxisKind.X: 1.4,
Expand Down
206 changes: 206 additions & 0 deletions api/src/opentrons/config/ot3_pipette_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import re
from typing import List, Optional, Union, cast
from dataclasses import dataclass
from opentrons_shared_data.pipette import load_data
from opentrons_shared_data.pipette.dev_types import PipetteModel, PipetteName
from opentrons_shared_data.pipette.pipette_definition import (
PipetteChannelType,
PipetteModelType,
PipetteVersionType,
PipetteGenerationType,
PipetteConfigurations,
PIPETTE_AVAILABLE_TYPES,
PIPETTE_CHANNELS_INTS,
PipetteModelMajorVersionType,
PipetteModelMinorVersionType,
)

DEFAULT_CALIBRATION_OFFSET = [0.0, 0.0, 0.0]
DEFAULT_MODEL = PipetteModelType.p1000
DEFAULT_CHANNELS = PipetteChannelType.SINGLE_CHANNEL
DEFAULT_MODEL_VERSION = PipetteVersionType(major=1, minor=0)


# TODO (lc 12-5-2022) Ideally we can deprecate this
# at somepoint once we load pipettes by channels and type
@dataclass
class PipetteNameType:
pipette_type: PipetteModelType
pipette_channels: PipetteChannelType
pipette_generation: PipetteGenerationType

def __repr__(self) -> str:
base_name = f"{self.pipette_type.name}_{self.pipette_channels}"
if self.pipette_generation == PipetteGenerationType.GEN1:
return base_name
elif self.pipette_channels == PipetteChannelType.NINETY_SIX_CHANNEL:
return base_name
else:
return f"{base_name}_{self.pipette_generation.name.lower()}"


@dataclass
class PipetteModelVersionType:
pipette_type: PipetteModelType
pipette_channels: PipetteChannelType
pipette_version: PipetteVersionType

def __repr__(self) -> str:
base_name = f"{self.pipette_type.name}_{self.pipette_channels}"

return f"{base_name}_v{self.pipette_version}"


def channels_from_string(channels: str) -> PipetteChannelType:
"""Convert channels from a string.
With both `py:data:PipetteName` and `py:data:PipetteObject`, we refer to channel types
as `single`, `multi` or `96`.
Args:
channels (str): The channel string we wish to convert.
Returns:
PipetteChannelType: A `py:obj:PipetteChannelType`
representing the number of channels on a pipette.
"""
if channels == "96":
return PipetteChannelType.NINETY_SIX_CHANNEL
elif channels == "multi":
return PipetteChannelType.EIGHT_CHANNEL
else:
return PipetteChannelType.SINGLE_CHANNEL


def version_from_string(version: str) -> PipetteVersionType:
"""Convert a version string to a py:obj:PipetteVersionType.
The version string will either be in the format of `int.int` or `vint.int`.
Args:
version (str): The string version we wish to convert.
Returns:
PipetteVersionType: A pipette version object.
"""
version_list = [v for v in re.split("\\.|[v]", version) if v]
major = cast(PipetteModelMajorVersionType, int(version_list[0]))
minor = cast(PipetteModelMinorVersionType, int(version_list[1]))
return PipetteVersionType(major, minor)


def version_from_generation(pipette_name_list: List[str]) -> PipetteVersionType:
"""Convert a string generation name to a py:obj:PipetteVersionType.
Pipette generations are strings in the format of "gen1" or "gen2", and
usually associated withe :py:data:PipetteName.
Args:
pipette_name_list (List[str]): A list of strings from the separated by `_`
py:data:PipetteName.
Returns:
PipetteVersionType: A pipette version object.
"""
if "gen3" in pipette_name_list:
return PipetteVersionType(3, 0)
elif "gen2" in pipette_name_list:
return PipetteVersionType(2, 0)
else:
return PipetteVersionType(1, 0)


def convert_pipette_name(
name: PipetteName, provided_version: Optional[str] = None
) -> PipetteModelVersionType:
"""Convert the py:data:PipetteName to a py:obj:PipetteModelVersionType.
`PipetteNames` are in the format of "p300_single" or "p300_single_gen1".
Args:
name (PipetteName): The pipette name we want to convert.
Returns:
PipetteModelVersionType: An object representing a broken out PipetteName
string.
"""
split_pipette_name = name.split("_")
channels = channels_from_string(split_pipette_name[1])
if provided_version:
version = version_from_string(provided_version)
else:
version = version_from_generation(split_pipette_name)

pipette_type = PipetteModelType[split_pipette_name[0]]

return PipetteModelVersionType(pipette_type, channels, version)


def convert_pipette_model(
model: Optional[PipetteModel], provided_version: Optional[str] = ""
) -> PipetteModelVersionType:
"""Convert the py:data:PipetteModel to a py:obj:PipetteModelVersionType.
`PipetteModel` are in the format of "p300_single_v1.0" or "p300_single_v3.3".
Sometimes, models may not have a version, in which case the `provided_version` arg
allows you to specify a version to search for.
Args:
model (PipetteModel): The pipette model we want to convert.
provided_version (str, Optional): The provided version we'd like to look for.
Returns:
PipetteModelVersionType: An object representing a broken out PipetteName
string.
"""
# TODO (lc 12-5-2022) This helper function is needed
# until we stop using "name" and "model" to refer
# to attached pipettes.
# We need to figure out how to default the pipette model as well
# rather than returning a p1000
if model and not provided_version:
pipette_type, parsed_channels, parsed_version = model.split("_")
channels = channels_from_string(parsed_channels)
version = version_from_string(parsed_version)
elif model and provided_version:
pipette_type, parsed_channels = model.split("_")
channels = channels_from_string(parsed_channels)
version = version_from_string(provided_version)
else:
pipette_type = DEFAULT_MODEL.value
channels = DEFAULT_CHANNELS
version = DEFAULT_MODEL_VERSION
return PipetteModelVersionType(PipetteModelType[pipette_type], channels, version)


def supported_pipette(model_or_name: Union[PipetteName, PipetteModel, None]) -> bool:
"""Determine if a pipette type is supported.
Args:
model_or_name (Union[PipetteName, PipetteModel, None]): The pipette we want to check.
Returns:
bool: Whether or not the given pipette name or model is supported.
"""
if not model_or_name:
return False
split_model_or_name = model_or_name.split("_")
channels_as_int = channels_from_string(split_model_or_name[1]).as_int
if (
split_model_or_name[0] in PIPETTE_AVAILABLE_TYPES
or channels_as_int in PIPETTE_CHANNELS_INTS
):
return True
return False


def load_ot3_pipette(model_type: PipetteModelVersionType) -> PipetteConfigurations:
return load_data.load_definition(
model_type.pipette_type, model_type.pipette_channels, model_type.pipette_version
)
7 changes: 6 additions & 1 deletion api/src/opentrons/hardware_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@
from .execution_manager import ExecutionManager
from .threaded_async_lock import ThreadedAsyncLock, ThreadedAsyncForbidden
from .protocols import HardwareControlAPI
from .instruments import AbstractInstrument, Pipette, Gripper
from .instruments import AbstractInstrument, Gripper

# TODO (lc 12-05-2022) We should 1. figure out if we need
# to globally export a class that is strictly used in the hardware controller
# and 2. how to properly export an ot2 and ot3 pipette.
from .instruments.ot2.pipette import Pipette


ThreadManagedHardware = ThreadManager[HardwareControlAPI]
Expand Down
Loading

0 comments on commit 62962c5

Please sign in to comment.