Skip to content

Commit

Permalink
feat(api): add command to configure pipette volume in Protocol Engine (
Browse files Browse the repository at this point in the history
…#13473)

Adds a new protocol engine command ConfigureForLiquidVolume that calls the hardware control method configure_for_liquid_volume (though in this protocol, there is no upward interface other than direct command injection).

In addition, this PR has a major refactor of PE command execution to support "private results" - values that can come from a command that do not have to be (and in fact cannot be and are not) serialized, whether to persistent storage or over the wire to clients - they're held entirely internally. This functionality lets us replace side-effect emitting actions and therefore get rid of PipetteConfigAction, which only existed so loading pipettes could go update pipette config data. Instead, that's now a private result, both of LoadPipette and of the new action. 

Finally, this PR makes the virtual pipette data provider stateful because it's emulating something stateful, and we should not just move the state to state because it would be doubling for unavoidable hardware state like the position of the pipette, and hardware controller really wants to run this.

Co-authored-by: Max Marrone <[email protected]>
Co-authored-by: Seth Foster <[email protected]>
  • Loading branch information
3 people committed Sep 15, 2023
1 parent 0cfaf25 commit b4f99bf
Show file tree
Hide file tree
Showing 36 changed files with 1,364 additions and 393 deletions.
4 changes: 4 additions & 0 deletions api/src/opentrons/hardware_control/instruments/ot2/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ def config(self) -> PipetteConfigurations:
def liquid_class(self) -> PipetteLiquidPropertiesDefinition:
return self._liquid_class

@property
def liquid_class_name(self) -> pip_types.LiquidClasses:
return self._liquid_class_name

@property
def nozzle_offset(self) -> List[float]:
return self._nozzle_offset
Expand Down
11 changes: 7 additions & 4 deletions api/src/opentrons/hardware_control/instruments/ot3/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
PipetteNameType,
PipetteModelVersionType,
PipetteLiquidPropertiesDefinition,
default_tip_for_liquid_class,
)
from opentrons_shared_data.errors.exceptions import (
InvalidLiquidClassName,
Expand Down Expand Up @@ -110,9 +111,7 @@ def __init__(
)
self.ready_to_aspirate = False

self._active_tip_setting_name = pip_types.PipetteTipType(
self._liquid_class.max_volume
)
self._active_tip_setting_name = default_tip_for_liquid_class(self._liquid_class)
self._active_tip_settings = self._liquid_class.supported_tips[
self._active_tip_setting_name
]
Expand Down Expand Up @@ -144,6 +143,10 @@ def config(self) -> PipetteConfigurations:
def liquid_class(self) -> PipetteLiquidPropertiesDefinition:
return self._liquid_class

@property
def liquid_class_name(self) -> pip_types.LiquidClasses:
return self._liquid_class_name

@property
def channels(self) -> pip_types.PipetteChannelType:
return self._max_channels
Expand Down Expand Up @@ -227,7 +230,7 @@ def reset_state(self) -> None:
self.ready_to_aspirate = False
#: True if ready to aspirate
self.set_liquid_class_by_name("default")
self.set_tip_type_by_volume(self.liquid_class.max_volume)
self.set_tip_type(default_tip_for_liquid_class(self._liquid_class))

self._aspirate_flow_rate = (
self._active_tip_settings.default_aspirate_flowrate.default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
from typing_extensions import Final
import numpy
from opentrons_shared_data.pipette.dev_types import UlPerMmAction

from opentrons_shared_data.errors.exceptions import (
CommandPreconditionViolated,
CommandParameterLimitViolated,
)
from opentrons_shared_data.pipette.pipette_definition import (
liquid_class_for_volume_between_default_and_defaultlowvolume,
)

from opentrons import types as top_types
from opentrons.hardware_control.types import (
Expand Down Expand Up @@ -899,3 +903,15 @@ def get_pipette(self, mount: OT3Mount) -> Pipette:
async def set_liquid_class(self, mount: OT3Mount, liquid_class: str) -> None:
pip = self.get_pipette(mount)
pip.set_liquid_class_by_name(liquid_class)

async def configure_for_volume(self, mount: OT3Mount, volume: float) -> None:
pip = self.get_pipette(mount)
if pip.current_volume > 0:
# Switching liquid classes can't happen when there's already liquid
return
new_class_name = liquid_class_for_volume_between_default_and_defaultlowvolume(
volume,
pip.liquid_class_name,
pip.config.liquid_properties,
)
pip.set_liquid_class_by_name(new_class_name.name)
6 changes: 6 additions & 0 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,12 @@ async def _move_to_plunger_bottom(
acquire_lock=acquire_lock,
)

async def configure_for_volume(
self, mount: Union[top_types.Mount, OT3Mount], volume: float
) -> None:
checked_mount = OT3Mount.from_mount(mount)
await self._pipette_handler.configure_for_volume(checked_mount, volume)

async def set_liquid_class(
self, mount: Union[top_types.Mount, OT3Mount], liquid_class: str
) -> None:
Expand Down
2 changes: 0 additions & 2 deletions api/src/opentrons/protocol_engine/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
DoorChangeAction,
ResetTipsAction,
SetPipetteMovementSpeedAction,
AddPipetteConfigAction,
)

__all__ = [
Expand All @@ -49,7 +48,6 @@
"DoorChangeAction",
"ResetTipsAction",
"SetPipetteMovementSpeedAction",
"AddPipetteConfigAction",
# action payload values
"PauseSource",
"FinishErrorDetails",
Expand Down
14 changes: 2 additions & 12 deletions api/src/opentrons/protocol_engine/actions/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from opentrons_shared_data.errors import EnumeratedError

from ..resources import pipette_data_provider
from ..commands import Command, CommandCreate
from ..commands import Command, CommandCreate, CommandPrivateResult
from ..types import LabwareOffsetCreate, ModuleDefinition, Liquid


Expand Down Expand Up @@ -111,6 +110,7 @@ class UpdateCommandAction:
"""Update a given command."""

command: Command
private_result: CommandPrivateResult


@dataclass(frozen=True)
Expand Down Expand Up @@ -180,15 +180,6 @@ class SetPipetteMovementSpeedAction:
speed: Optional[float]


@dataclass(frozen=True)
class AddPipetteConfigAction:
"""Adds a pipette's static config to the state store."""

pipette_id: str
serial_number: str
config: pipette_data_provider.LoadedStaticPipetteData


Action = Union[
PlayAction,
PauseAction,
Expand All @@ -205,5 +196,4 @@ class AddPipetteConfigAction:
AddLiquidAction,
ResetTipsAction,
SetPipetteMovementSpeedAction,
AddPipetteConfigAction,
]
19 changes: 19 additions & 0 deletions api/src/opentrons/protocol_engine/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CommandCreate,
CommandResult,
CommandType,
CommandPrivateResult,
)

from .aspirate import (
Expand Down Expand Up @@ -141,6 +142,7 @@
LoadPipetteCreate,
LoadPipetteResult,
LoadPipetteCommandType,
LoadPipettePrivateResult,
)

from .move_labware import (
Expand Down Expand Up @@ -256,15 +258,25 @@
RetractAxisCommandType,
)

from .configure_for_volume import (
ConfigureForVolume,
ConfigureForVolumeCreate,
ConfigureForVolumeParams,
ConfigureForVolumeResult,
ConfigureForVolumeCommandType,
)

__all__ = [
# command type unions
"Command",
"CommandParams",
"CommandCreate",
"CommandResult",
"CommandType",
"CommandPrivateResult",
# base interfaces
"AbstractCommandImpl",
"AbstractCommandWithPrivateResultImpl",
"BaseCommand",
"BaseCommandCreate",
"CommandStatus",
Expand Down Expand Up @@ -351,6 +363,7 @@
"LoadPipetteParams",
"LoadPipetteResult",
"LoadPipetteCommandType",
"LoadPipettePrivateResult",
# move labware command models
"MoveLabware",
"MoveLabwareCreate",
Expand Down Expand Up @@ -444,4 +457,10 @@
"thermocycler",
# calibration command bundle
"calibration",
# configure pipette volume command bundle
"ConfigureForVolume",
"ConfigureForVolumeCreate",
"ConfigureForVolumeParams",
"ConfigureForVolumeResult",
"ConfigureForVolumeCommandType",
]
51 changes: 50 additions & 1 deletion api/src/opentrons/protocol_engine/commands/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from typing import TYPE_CHECKING, Generic, Optional, TypeVar
from typing import TYPE_CHECKING, Generic, Optional, TypeVar, Tuple

from pydantic import BaseModel, Field
from pydantic.generics import GenericModel
Expand All @@ -25,6 +25,8 @@

CommandResultT = TypeVar("CommandResultT", bound=BaseModel)

CommandPrivateResultT = TypeVar("CommandPrivateResultT")


class CommandStatus(str, Enum):
"""Command execution status."""
Expand Down Expand Up @@ -155,6 +157,10 @@ class AbstractCommandImpl(
- Create a command resource from the request model
- Execute the command, mapping data from execution into the result model
This class should be used as the base class for new commands by default. You should only
use AbstractCommandWithPrivateResultImpl if you actually need private results to send to
the rest of the engine wihtout being published outside of it.
"""

def __init__(
Expand All @@ -178,3 +184,46 @@ def __init__(
async def execute(self, params: CommandParamsT) -> CommandResultT:
"""Execute the command, mapping data from execution into a response model."""
...


class AbstractCommandWithPrivateResultImpl(
ABC,
Generic[CommandParamsT, CommandResultT, CommandPrivateResultT],
):
"""Abstract command creation and execution implementation if the command has private results.
A given command request should map to a specific command implementation,
which defines how to:
- Create a command resource from the request model
- Execute the command, mapping data from execution into the result model
This class should be used instead of AbstractCommandImpl as a base class if your command needs
to send data to result handlers that should not be published outside of the engine.
Note that this class needs an extra type-parameter for the private result.
"""

def __init__(
self,
state_view: StateView,
hardware_api: HardwareControlAPI,
equipment: execution.EquipmentHandler,
movement: execution.MovementHandler,
gantry_mover: execution.GantryMover,
labware_movement: execution.LabwareMovementHandler,
pipetting: execution.PipettingHandler,
tip_handler: execution.TipHandler,
run_control: execution.RunControlHandler,
rail_lights: execution.RailLightsHandler,
status_bar: execution.StatusBarHandler,
) -> None:
"""Initialize the command implementation with execution handlers."""
pass

@abstractmethod
async def execute(
self, params: CommandParamsT
) -> Tuple[CommandResultT, CommandPrivateResultT]:
"""Execute the command, mapping data from execution into a response model."""
...
19 changes: 19 additions & 0 deletions api/src/opentrons/protocol_engine/commands/command_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
LoadPipetteCreate,
LoadPipetteResult,
LoadPipetteCommandType,
LoadPipettePrivateResult,
)

from .move_labware import (
Expand Down Expand Up @@ -225,6 +226,15 @@
RetractAxisCommandType,
)

from .configure_for_volume import (
ConfigureForVolume,
ConfigureForVolumeParams,
ConfigureForVolumeCreate,
ConfigureForVolumeResult,
ConfigureForVolumeCommandType,
ConfigureForVolumePrivateResult,
)

Command = Union[
Aspirate,
AspirateInPlace,
Expand All @@ -234,6 +244,7 @@
DispenseInPlace,
BlowOut,
BlowOutInPlace,
ConfigureForVolume,
DropTip,
DropTipInPlace,
Home,
Expand Down Expand Up @@ -284,6 +295,7 @@
AspirateParams,
AspirateInPlaceParams,
CommentParams,
ConfigureForVolumeParams,
CustomParams,
DispenseParams,
DispenseInPlaceParams,
Expand Down Expand Up @@ -340,6 +352,7 @@
AspirateCommandType,
AspirateInPlaceCommandType,
CommentCommandType,
ConfigureForVolumeCommandType,
CustomCommandType,
DispenseCommandType,
DispenseInPlaceCommandType,
Expand Down Expand Up @@ -395,6 +408,7 @@
AspirateCreate,
AspirateInPlaceCreate,
CommentCreate,
ConfigureForVolumeCreate,
CustomCreate,
DispenseCreate,
DispenseInPlaceCreate,
Expand Down Expand Up @@ -450,6 +464,7 @@
AspirateResult,
AspirateInPlaceResult,
CommentResult,
ConfigureForVolumeResult,
CustomResult,
DispenseResult,
DispenseInPlaceResult,
Expand Down Expand Up @@ -500,3 +515,7 @@
calibration.CalibrateModuleResult,
calibration.MoveToMaintenancePositionResult,
]

CommandPrivateResult = Union[
None, LoadPipettePrivateResult, ConfigureForVolumePrivateResult
]
Loading

0 comments on commit b4f99bf

Please sign in to comment.