Skip to content

Commit

Permalink
Merge branch 'edge' into 7.3.1-shapshot-testing-2.19-protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
y3rsh committed Jun 26, 2024
2 parents cf87a95 + ce86430 commit c8d555e
Show file tree
Hide file tree
Showing 283 changed files with 32,618 additions and 9,423 deletions.
1 change: 1 addition & 0 deletions api/src/opentrons/hardware_control/dev_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class PipetteDict(InstrumentDict):
supported_tips: Dict[PipetteTipType, SupportedTipsDefinition]
pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition
current_nozzle_map: NozzleMap
lld_settings: Optional[Dict[str, Dict[str, float]]]


class PipetteStateDict(TypedDict):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
"default_dispense_flow_rates",
"back_compat_names",
"supported_tips",
"lld_settings",
]

instr_dict = instr.as_dict()
Expand Down Expand Up @@ -259,6 +260,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
result[
"pipette_bounding_box_offsets"
] = instr.config.pipette_bounding_box_offsets
result["lld_settings"] = instr.config.lld_settings
return cast(PipetteDict, result)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
"default_dispense_flow_rates",
"back_compat_names",
"supported_tips",
"lld_settings",
]

instr_dict = instr.as_dict()
Expand Down Expand Up @@ -280,6 +281,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
result[
"pipette_bounding_box_offsets"
] = instr.config.pipette_bounding_box_offsets
result["lld_settings"] = instr.config.lld_settings
return cast(PipetteDict, result)

@property
Expand Down
6 changes: 5 additions & 1 deletion api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,10 @@ def _get_module_core(
)

def load_instrument(
self, instrument_name: PipetteNameType, mount: Mount
self,
instrument_name: PipetteNameType,
mount: Mount,
liquid_presence_detection: bool = False,
) -> InstrumentCore:
"""Load an instrument into the protocol.
Expand All @@ -515,6 +518,7 @@ def load_instrument(
tipOverlapNotAfterVersion=overlap_versions.overlap_for_api_version(
self._api_version
),
liquidPresenceDetection=liquid_presence_detection,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,10 @@ def load_module(
return module_core

def load_instrument(
self, instrument_name: PipetteNameType, mount: Mount
self,
instrument_name: PipetteNameType,
mount: Mount,
liquid_presence_detection: bool = False,
) -> LegacyInstrumentCore:
"""Load an instrument."""
attached = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class LegacyProtocolCoreSimulator(
_instruments: Dict[Mount, Optional[LegacyInstrumentCoreSimulator]] # type: ignore[assignment]

def load_instrument( # type: ignore[override]
self, instrument_name: PipetteNameType, mount: Mount
self,
instrument_name: PipetteNameType,
mount: Mount,
liquid_presence_detection: bool = False,
) -> LegacyInstrumentCoreSimulator:
"""Create a simulating instrument context."""
pipette_generation = convert_to_pipette_name_type(
Expand Down
5 changes: 4 additions & 1 deletion api/src/opentrons/protocol_api/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ def load_module(

@abstractmethod
def load_instrument(
self, instrument_name: PipetteNameType, mount: Mount
self,
instrument_name: PipetteNameType,
mount: Mount,
liquid_presence_detection: bool = False,
) -> InstrumentCoreType:
...

Expand Down
18 changes: 18 additions & 0 deletions api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
AxisMaxSpeeds,
requires_version,
APIVersionError,
RobotTypeError,
)

from ._types import OffDeckType
Expand Down Expand Up @@ -870,6 +871,7 @@ def load_instrument(
mount: Union[Mount, str, None] = None,
tip_racks: Optional[List[Labware]] = None,
replace: bool = False,
liquid_presence_detection: Optional[bool] = None,
) -> InstrumentContext:
"""Load a specific instrument for use in the protocol.
Expand Down Expand Up @@ -897,6 +899,7 @@ def load_instrument(
control <advanced-control>` applications. You cannot
replace an instrument in the middle of a protocol being run
from the Opentrons App or touchscreen.
:param bool liquid_presence_detection: If ``True``, enable liquid presence detection for instrument. Only available on Flex robots in API Version 2.20 and above.
"""
instrument_name = validation.ensure_lowercase_name(instrument_name)
checked_instrument_name = validation.ensure_pipette_name(instrument_name)
Expand Down Expand Up @@ -928,9 +931,24 @@ def load_instrument(
f"Loading {checked_instrument_name} on {checked_mount.name.lower()} mount"
)

if (
self._api_version < APIVersion(2, 20)
and liquid_presence_detection is not None
):
raise APIVersionError(
"Liquid Presence Detection is only supported in API Version 2.20 and above."
)
if (
self._core.robot_type != "OT-3 Standard"
and liquid_presence_detection is not None
):
raise RobotTypeError(
"Liquid presence detection only available on Flex robot."
)
instrument_core = self._core.load_instrument(
instrument_name=checked_instrument_name,
mount=checked_mount,
liquid_presence_detection=liquid_presence_detection or False,
)

for tip_rack in tip_racks:
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_engine/commands/load_pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ class LoadPipetteParams(BaseModel):
"expressed as vN where N is an integer, counting up from v0. If None, the current "
"highest version will be used.",
)
liquidPresenceDetection: Optional[bool] = Field(
None,
description="Enable liquid presence detection for this pipette. Defaults to False.",
)


class LoadPipetteResult(BaseModel):
Expand Down
5 changes: 4 additions & 1 deletion api/src/opentrons/protocol_engine/execution/pipetting.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,11 @@ async def liquid_probe_in_place(
)
well_def = self._state_view.labware.get_well_definition(labware_id, well_name)
well_depth = well_def.depth
lld_min_height = self._state_view.pipettes.get_current_tip_lld_settings(
pipette_id=pipette_id
)
z_pos = await self._hardware_api.liquid_probe(
mount=hw_pipette.mount, max_z_dist=well_depth
mount=hw_pipette.mount, max_z_dist=well_depth - lld_min_height
)
return float(z_pos)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class LoadedStaticPipetteData:
nozzle_map: NozzleMap
back_left_corner_offset: Point
front_right_corner_offset: Point
pipette_lld_settings: Optional[Dict[str, Dict[str, float]]]


class VirtualPipetteDataProvider:
Expand Down Expand Up @@ -275,6 +276,7 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901
front_right_corner_offset=Point(
pip_front_right[0], pip_front_right[1], pip_front_right[2]
),
pipette_lld_settings=config.lld_settings,
)

def get_virtual_pipette_static_config(
Expand Down Expand Up @@ -321,6 +323,7 @@ def get_pipette_static_config(
front_right_corner_offset=Point(
front_right_offset[0], front_right_offset[1], front_right_offset[2]
),
pipette_lld_settings=pipette_dict["lld_settings"],
)


Expand Down
37 changes: 37 additions & 0 deletions api/src/opentrons/protocol_engine/state/pipettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class StaticPipetteConfig:
pipette_bounding_box_offsets: PipetteBoundingBoxOffsets
bounding_nozzle_offsets: BoundingNozzlesOffsets
default_nozzle_map: NozzleMap
lld_settings: Optional[Dict[str, Dict[str, float]]]


@dataclass
Expand All @@ -133,6 +134,7 @@ class PipetteState:
static_config_by_id: Dict[str, StaticPipetteConfig]
flow_rates_by_id: Dict[str, FlowRates]
nozzle_configuration_by_id: Dict[str, Optional[NozzleMap]]
liquid_presence_detection_by_id: Dict[str, bool]


class PipetteStore(HasState[PipetteState], HandlesActions):
Expand All @@ -152,6 +154,7 @@ def __init__(self) -> None:
static_config_by_id={},
flow_rates_by_id={},
nozzle_configuration_by_id={},
liquid_presence_detection_by_id={},
)

def handle_action(self, action: Action) -> None:
Expand Down Expand Up @@ -197,6 +200,7 @@ def _handle_command( # noqa: C901
front_right_offset=config.nozzle_map.front_right_nozzle_offset,
),
default_nozzle_map=config.nozzle_map,
lld_settings=config.pipette_lld_settings,
)
self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates
self._state.nozzle_configuration_by_id[
Expand All @@ -215,6 +219,9 @@ def _handle_command( # noqa: C901
pipetteName=command.params.pipetteName,
mount=command.params.mount,
)
self._state.liquid_presence_detection_by_id[pipette_id] = (
command.params.liquidPresenceDetection or False
)
self._state.aspirated_volume_by_id[pipette_id] = None
self._state.movement_speed_by_id[pipette_id] = None
self._state.attached_tip_by_id[pipette_id] = None
Expand Down Expand Up @@ -618,6 +625,27 @@ def get_available_volume(self, pipette_id: str) -> Optional[float]:

return max(0.0, working_volume - current_volume) if current_volume else None

def get_pipette_lld_settings(
self, pipette_id: str
) -> Optional[Dict[str, Dict[str, float]]]:
"""Get the liquid level settings for all possible tips for a single pipette."""
return self.get_config(pipette_id).lld_settings

def get_current_tip_lld_settings(self, pipette_id: str) -> float:
"""Get the liquid level settings for pipette and its current tip."""
attached_tip = self.get_attached_tip(pipette_id)
if attached_tip is None or attached_tip.volume is None:
return 0
lld_settings = self.get_pipette_lld_settings(pipette_id)
tipVolume = str(attached_tip.volume)
if (
lld_settings is None
or lld_settings[tipVolume] is None
or lld_settings[tipVolume]["minHeight"] is None
):
return 0
return float(lld_settings[tipVolume]["minHeight"])

def validate_tip_state(self, pipette_id: str, expected_has_tip: bool) -> None:
"""Validate that a pipette's tip state matches expectations."""
attached_tip = self.get_attached_tip(pipette_id)
Expand Down Expand Up @@ -801,3 +829,12 @@ def get_pipette_bounds_at_specified_move_to_position(
pip_back_right_bound,
pip_front_left_bound,
)

def get_liquid_presence_detection(self, pipette_id: str) -> bool:
"""Determine if liquid presence detection is enabled for this pipette."""
try:
return self._state.liquid_presence_detection_by_id[pipette_id]
except KeyError as e:
raise errors.PipetteNotLoadedError(
f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled."
) from e
1 change: 1 addition & 0 deletions api/src/opentrons/protocol_reader/file_hasher.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .file_reader_writer import BufferedFile


# TODO (spp: 2024-06-17): move file hasher to utils
class FileHasher:
"""Hashing utility class that hashes a combination of protocol and labware files."""

Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocols/api_support/definitions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .types import APIVersion

MAX_SUPPORTED_VERSION = APIVersion(2, 19)
MAX_SUPPORTED_VERSION = APIVersion(2, 20)
"""The maximum supported protocol API version in this release."""

MIN_SUPPORTED_VERSION = APIVersion(2, 0)
Expand Down
15 changes: 12 additions & 3 deletions api/src/opentrons/protocols/api_support/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
from opentrons.hardware_control.types import Axis
from opentrons.hardware_control.util import ot2_axis_to_string
from opentrons_shared_data.robot.dev_types import RobotType
from opentrons_shared_data.errors.exceptions import (
UnsupportedHardwareCommand,
)

if TYPE_CHECKING:
from opentrons.protocol_api.labware import Well, Labware
Expand All @@ -37,17 +40,23 @@
MODULE_LOG = logging.getLogger(__name__)


class RobotTypeError(UnsupportedHardwareCommand):
"""Error raised when a protocol attempts to access behavior not available to the robot type in use."""

pass


class APIVersionError(Exception):
"""
Error raised when a protocol attempts to access behavior not implemented
"""
"""Error raised when a protocol attempts to access behavior not implemented in the API in use."""

pass


class UnsupportedAPIError(Exception):
"""Error raised when a protocol attempts to use unsupported API."""

pass


def _assert_gzero(val: Any, message: str) -> float:
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ def test_load_instrument_pre_219(
pipetteName=PipetteNameType.P300_SINGLE,
mount=MountType.LEFT,
tipOverlapNotAfterVersion="v0",
liquidPresenceDetection=False,
)
)
).then_return(commands.LoadPipetteResult(pipetteId="cool-pipette"))
Expand Down Expand Up @@ -290,6 +291,7 @@ def test_load_instrument_post_219(
pipetteName=PipetteNameType.P300_SINGLE,
mount=MountType.LEFT,
tipOverlapNotAfterVersion="v1",
liquidPresenceDetection=False,
)
)
).then_return(commands.LoadPipetteResult(pipetteId="cool-pipette"))
Expand Down
Loading

0 comments on commit c8d555e

Please sign in to comment.