Skip to content

Commit

Permalink
sync with edge
Browse files Browse the repository at this point in the history
Merge branch 'edge' into app_fix-robot-rename-regex
  • Loading branch information
koji committed May 17, 2022
2 parents 16b3bab + 678ba77 commit b366a46
Show file tree
Hide file tree
Showing 33 changed files with 1,001 additions and 360 deletions.
23 changes: 23 additions & 0 deletions api/src/opentrons/config/gripper_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import annotations
from dataclasses import dataclass
import logging
from typing import Tuple
from typing_extensions import Literal

log = logging.getLogger(__name__)

GripperName = Literal["gripper"]
GripperModel = Literal["gripper_v1"]


@dataclass(frozen=True)
class GripperConfig:
gripper_offset: Tuple[float, float, float]
gripper_current: float
display_name: str
name: GripperName
max_travel: float
home_position: float
steps_per_mm: float
idle_current: float
model: GripperModel
4 changes: 4 additions & 0 deletions api/src/opentrons/hardware_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .pause_manager import PauseManager
from .backends import Controller, Simulator
from .pipette import Pipette
from .gripper import Gripper
from .types import (
CriticalPoint,
NoTipAttachedError,
Expand All @@ -27,15 +28,18 @@
from .execution_manager import ExecutionManager
from .threaded_async_lock import ThreadedAsyncLock, ThreadedAsyncForbidden
from .protocols import HardwareControlAPI
from .instrument_abc import AbstractInstrument

ThreadManagedHardware = ThreadManager[HardwareControlAPI]
SyncHardwareAPI = SynchronousAdapter[HardwareControlAPI]

__all__ = [
"API",
"AbstractInstrument",
"Controller",
"Simulator",
"Pipette",
"Gripper",
"PauseManager",
"SynchronousAdapter",
"HardwareControlAPI",
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/hardware_control/backends/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
if TYPE_CHECKING:
from opentrons_shared_data.pipette.dev_types import PipetteModel, PipetteName
from ..dev_types import (
AttachedInstrument,
AttachedPipette,
AttachedInstruments,
InstrumentHardwareConfigs,
)
Expand Down Expand Up @@ -173,7 +173,7 @@ async def fast_home(self, axes: Sequence[str], margin: float) -> Dict[str, float

async def _query_mount(
self, mount: Mount, expected: Union[PipetteModel, PipetteName, None]
) -> AttachedInstrument:
) -> AttachedPipette:
found_model: Optional[
PipetteModel
] = await self._smoothie_driver.read_pipette_model( # type: ignore
Expand Down
16 changes: 6 additions & 10 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
if TYPE_CHECKING:
from opentrons_shared_data.pipette.dev_types import PipetteName, PipetteModel
from ..dev_types import (
AttachedInstrument,
AttachedPipette,
InstrumentHardwareConfigs,
)
from opentrons.drivers.rpi_drivers.dev_types import GPIODriverLike
Expand Down Expand Up @@ -325,7 +325,7 @@ async def fast_home(

async def get_attached_instruments(
self, expected: Dict[OT3Mount, PipetteName]
) -> Dict[OT3Mount, AttachedInstrument]:
) -> Dict[OT3Mount, AttachedPipette]:
"""Get attached instruments.
Args:
Expand All @@ -344,7 +344,7 @@ def _synthesize_model_name(

def _build_attached_instr(
attached: ohc_tool_types.PipetteInformation,
) -> AttachedInstrument:
) -> AttachedPipette:
return {
"config": pipette_config.load(
_synthesize_model_name(attached.name, attached.model)
Expand All @@ -354,7 +354,7 @@ def _build_attached_instr(

def _generate_attached_instrs(
attached: ohc_tool_types.ToolSummary,
) -> Iterator[Tuple[OT3Mount, AttachedInstrument]]:
) -> Iterator[Tuple[OT3Mount, AttachedPipette]]:
if attached.left:
yield (OT3Mount.LEFT, _build_attached_instr(attached.left))
if attached.right:
Expand Down Expand Up @@ -615,13 +615,9 @@ async def probe_network(self, timeout: float = 5.0) -> None:
# when that method actually does canbus stuff
instrs = await self.get_attached_instruments({})
expected = {NodeId.gantry_x, NodeId.gantry_y, NodeId.head}
if instrs.get(OT3Mount.LEFT, cast("AttachedInstrument", {})).get(
"config", None
):
if instrs.get(OT3Mount.LEFT, cast("AttachedPipette", {})).get("config", None):
expected.add(NodeId.pipette_left)
if instrs.get(OT3Mount.RIGHT, cast("AttachedInstrument", {})).get(
"config", None
):
if instrs.get(OT3Mount.RIGHT, cast("AttachedPipette", {})).get("config", None):
expected.add(NodeId.pipette_right)
present = await probe(self._messenger, expected, timeout)
self._present_nodes = self._replace_head_node(present)
Expand Down
10 changes: 5 additions & 5 deletions api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
from opentrons_shared_data.pipette.dev_types import PipetteName, PipetteModel
from opentrons.hardware_control.dev_types import (
InstrumentHardwareConfigs,
InstrumentSpec,
AttachedInstrument,
PipetteSpec,
AttachedPipette,
)
from opentrons.drivers.rpi_drivers.dev_types import GPIODriverLike

Expand Down Expand Up @@ -116,7 +116,7 @@ def __init__(

def _sanitize_attached_instrument(
passed_ai: Optional[Dict[str, Optional[str]]] = None
) -> InstrumentSpec:
) -> PipetteSpec:
if not passed_ai or not passed_ai.get("model"):
return {"model": None, "id": None}
if passed_ai["model"] in pipette_config.config_models:
Expand Down Expand Up @@ -222,7 +222,7 @@ async def fast_home(

def _attached_to_mount(
self, mount: OT3Mount, expected_instr: Optional[PipetteName]
) -> AttachedInstrument:
) -> AttachedPipette:
init_instr = self._attached_instruments.get(mount, {"model": None, "id": None})
found_model = init_instr["model"]
back_compat: List["PipetteName"] = []
Expand Down Expand Up @@ -273,7 +273,7 @@ def _attached_to_mount(

async def get_attached_instruments(
self, expected: Dict[OT3Mount, PipetteName]
) -> Dict[OT3Mount, AttachedInstrument]:
) -> Dict[OT3Mount, AttachedPipette]:
"""Get attached instruments.
Args:
Expand Down
8 changes: 4 additions & 4 deletions api/src/opentrons/hardware_control/backends/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
if TYPE_CHECKING:
from opentrons_shared_data.pipette.dev_types import PipetteName
from ..dev_types import (
AttachedInstrument,
AttachedPipette,
AttachedInstruments,
InstrumentSpec,
PipetteSpec,
InstrumentHardwareConfigs,
)
from opentrons.drivers.rpi_drivers.dev_types import GPIODriverLike
Expand Down Expand Up @@ -119,7 +119,7 @@ def __init__(

def _sanitize_attached_instrument(
passed_ai: Optional[Dict[str, Optional[str]]] = None
) -> InstrumentSpec:
) -> PipetteSpec:
if not passed_ai or not passed_ai.get("model"):
return {"model": None, "id": None}
if passed_ai["model"] in config_models:
Expand Down Expand Up @@ -209,7 +209,7 @@ async def fast_home(self, axis: Sequence[str], margin: float) -> Dict[str, float

def _attached_to_mount(
self, mount: types.Mount, expected_instr: Optional[PipetteName]
) -> AttachedInstrument:
) -> AttachedPipette:
init_instr = self._attached_instruments.get(mount, {"model": None, "id": None})
found_model = init_instr["model"]
back_compat: List["PipetteName"] = []
Expand Down
12 changes: 9 additions & 3 deletions api/src/opentrons/hardware_control/dev_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@
from opentrons.drivers.types import MoveSplit
from opentrons.types import Mount
from opentrons.config.pipette_config import PipetteConfig
from opentrons.config.gripper_config import GripperConfig


class InstrumentSpec(TypedDict):
class PipetteSpec(TypedDict):
model: Union[PipetteModel, None]
id: Optional[str]


class AttachedInstrument(TypedDict):
class AttachedPipette(TypedDict):
config: Optional[PipetteConfig]
id: Optional[str]


AttachedInstruments = Dict[Mount, AttachedInstrument]
class AttachedGripper(TypedDict):
config: Optional[GripperConfig]
id: Optional[str]


AttachedInstruments = Dict[Mount, AttachedPipette]


EIGHT_CHANNELS = Literal[8]
Expand Down
91 changes: 91 additions & 0 deletions api/src/opentrons/hardware_control/gripper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from __future__ import annotations

""" Classes and functions for gripper state tracking
"""
from dataclasses import asdict, replace
import logging
from typing import Any, Dict, Optional, Union

from opentrons.types import Point
from opentrons.config import gripper_config
from .instrument_abc import AbstractInstrument
from .types import CriticalPoint

RECONFIG_KEYS = {"quirks"}


mod_log = logging.getLogger(__name__)


class Gripper(AbstractInstrument[gripper_config.GripperConfig]):
"""A class to gather and track gripper state and configs.
This class should not touch hardware or call back out to the hardware
control API. Its only purpose is to gather state.
"""

DictType = Dict[str, Union[str, float, bool]]
#: The type of this data class as a dict

def __init__(
self,
config: gripper_config.GripperConfig,
gripper_id: Optional[str] = None,
) -> None:
self._config = config
self._name = self._config.name
self._model = self._config.model
self._gripper_id = gripper_id
self._log = mod_log.getChild(
self._gripper_id if self._gripper_id else "<unknown>"
)
# cache a dict representation of config for improved performance of
# as_dict.
self._config_as_dict = asdict(config)

@property
def config(self) -> gripper_config.GripperConfig:
return self._config

def update_config_item(self, elem_name: str, elem_val: Any) -> None:
self._log.info(f"updated config: {elem_name}={elem_val}")
self._config = replace(self._config, **{elem_name: elem_val})
# Update the cached dict representation
self._config_as_dict = asdict(self._config)

@property
def name(self) -> gripper_config.GripperName:
return self._name

@property
def model(self) -> gripper_config.GripperModel:
return self._model

@property
def gripper_id(self) -> Optional[str]:
return self._gripper_id

def critical_point(self, cp_override: Optional[CriticalPoint] = None) -> Point:
"""
The vector from the gripper's origin to its critical point. The
critical point for a pipette is the end of the nozzle if no tip is
attached, or the end of the tip if a tip is attached.
"""
# TODO: add critical point implementation
return Point(0, 0, 0)

def __str__(self) -> str:
return f"{self._config.display_name}"

def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {self._config.display_name} {id(self)}"

def as_dict(self) -> "Gripper.DictType":
self._config_as_dict.update(
{
"name": self.name,
"model": self.model,
"gripper_id": self.gripper_id,
}
)
return self._config_as_dict
37 changes: 37 additions & 0 deletions api/src/opentrons/hardware_control/instrument_abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from abc import ABC, abstractmethod
from typing import Any, Optional, Generic, TypeVar

from opentrons.types import Point
from .types import CriticalPoint


InstrumentConfig = TypeVar("InstrumentConfig")


class AbstractInstrument(ABC, Generic[InstrumentConfig]):
"""Defines the common methods of an instrument."""

@property
def name(self) -> str:
"""Return name of the instrument."""
...

@property
def model(self) -> str:
"""Return model of the instrument."""
...

@property
def config(self) -> InstrumentConfig:
"""Instrument config in dataclass format."""
...

@abstractmethod
def update_config_item(self, elem_name: str, elem_val: Any) -> None:
"""Update instrument config item."""
...

@abstractmethod
def critical_point(self, cp_override: Optional[CriticalPoint] = None) -> Point:
"""Computate critical point of an instrument."""
...
3 changes: 2 additions & 1 deletion api/src/opentrons/hardware_control/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from opentrons.config import pipette_config, robot_configs
from opentrons.config.types import RobotConfig, OT3Config
from opentrons.drivers.types import MoveSplit
from .instrument_abc import AbstractInstrument
from .types import CriticalPoint, BoardRevision, OT3AxisKind


Expand All @@ -33,7 +34,7 @@
mod_log = logging.getLogger(__name__)


class Pipette:
class Pipette(AbstractInstrument[pipette_config.PipetteConfig]):
"""A class to gather and track pipette state and configs.
This class should not touch hardware or call back out to the hardware
Expand Down
Loading

0 comments on commit b366a46

Please sign in to comment.