Skip to content

Commit

Permalink
First pass: make pickup errors nonfatal.
Browse files Browse the repository at this point in the history
This does not seem to handle the case where the gripper drops the labware in transit.
  • Loading branch information
SyntaxColoring committed Sep 20, 2024
1 parent 3f7d0b5 commit 10dda68
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 28 deletions.
2 changes: 2 additions & 0 deletions api/src/opentrons/protocol_engine/commands/command_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
)

from .move_labware import (
GripError,
MoveLabware,
MoveLabwareParams,
MoveLabwareCreate,
Expand Down Expand Up @@ -706,6 +707,7 @@
DefinedErrorData[TipPhysicallyMissingError],
DefinedErrorData[OverpressureError],
DefinedErrorData[LiquidNotFoundError],
DefinedErrorData[GripError],
]


Expand Down
99 changes: 71 additions & 28 deletions api/src/opentrons/protocol_engine/commands/move_labware.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Models and implementation for the ``moveLabware`` command."""

from __future__ import annotations
from opentrons_shared_data.errors import ErrorCodes
from opentrons_shared_data.errors.exceptions import FailedGripperPickupError
from pydantic import BaseModel, Field
from typing import TYPE_CHECKING, Optional, Type
from typing_extensions import Literal

from opentrons.protocol_engine.resources.model_utils import ModelUtils
from opentrons.types import Point
from ..state import update_types
from ..types import (
Expand All @@ -19,7 +22,13 @@
)
from ..errors import LabwareMovementNotAllowedError, NotSupportedOnRobotType
from ..resources import labware_validation, fixture_validation
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from .command import (
AbstractCommandImpl,
BaseCommand,
BaseCommandCreate,
DefinedErrorData,
SuccessData,
)
from ..errors.error_occurrence import ErrorOccurrence
from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH

Expand Down Expand Up @@ -76,27 +85,38 @@ class MoveLabwareResult(BaseModel):
)


class MoveLabwareImplementation(
AbstractCommandImpl[MoveLabwareParams, SuccessData[MoveLabwareResult, None]]
):
# TODO
class GripError(ErrorOccurrence):
isDefined: bool = True

errorType: Literal["grip"] = "grip"

errorCode: str = ErrorCodes.FAILED_GRIPPER_PICKUP_ERROR.value.code
detail: str = ErrorCodes.FAILED_GRIPPER_PICKUP_ERROR.value.detail


_ExecuteReturn = SuccessData[MoveLabwareResult, None] | DefinedErrorData[GripError]


class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteReturn]):
"""The execution implementation for ``moveLabware`` commands."""

def __init__(
self,
model_utils: ModelUtils,
state_view: StateView,
equipment: EquipmentHandler,
labware_movement: LabwareMovementHandler,
run_control: RunControlHandler,
**kwargs: object,
) -> None:
self._model_utils = model_utils
self._state_view = state_view
self._equipment = equipment
self._labware_movement = labware_movement
self._run_control = run_control

async def execute( # noqa: C901
self, params: MoveLabwareParams
) -> SuccessData[MoveLabwareResult, None]:
async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C901
"""Move a loaded labware to a new location."""
state_update = update_types.StateUpdate()

Expand Down Expand Up @@ -171,6 +191,18 @@ async def execute( # noqa: C901
labware_id=params.labwareId, new_location=available_new_location
)

# We might be moving the labware that contains the current well out from
# under the pipette. Clear the current location to reflect the fact that the
# pipette is no longer over any labware. This is necessary for safe path
# planning in case the next movement goes to the same labware (now in a new
# place).
pipette_location = self._state_view.pipettes.get_current_location()
if (
isinstance(pipette_location, CurrentWell)
and pipette_location.labware_id == params.labwareId
):
state_update.clear_all_pipette_locations()

if params.strategy == LabwareMovementStrategy.USING_GRIPPER:
if self._state_view.config.robot_type == "OT-2 Standard":
raise NotSupportedOnRobotType(
Expand Down Expand Up @@ -205,40 +237,51 @@ async def execute( # noqa: C901
dropOffset=params.dropOffset or LabwareOffsetVector(x=0, y=0, z=0),
)

# Skips gripper moves when using virtual gripper
await self._labware_movement.move_labware_with_gripper(
labware_id=params.labwareId,
current_location=validated_current_loc,
new_location=validated_new_loc,
user_offset_data=user_offset_data,
post_drop_slide_offset=post_drop_slide_offset,
)
try:
# Skips gripper moves when using virtual gripper
await self._labware_movement.move_labware_with_gripper(
labware_id=params.labwareId,
current_location=validated_current_loc,
new_location=validated_new_loc,
user_offset_data=user_offset_data,
post_drop_slide_offset=post_drop_slide_offset,
)
except FailedGripperPickupError as exception:
grip_error: GripError | None = GripError(
id=self._model_utils.generate_id(),
createdAt=self._model_utils.get_timestamp(),
wrappedErrors=[
ErrorOccurrence.from_failed(
id=self._model_utils.generate_id(),
createdAt=self._model_utils.get_timestamp(),
error=exception,
)
],
)
else:
grip_error = None

# All mounts will have been retracted as part of the gripper move.
state_update.clear_all_pipette_locations()

if grip_error:
return DefinedErrorData(
public=grip_error,
state_update=state_update,
)

elif params.strategy == LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE:
# Pause to allow for manual labware movement
await self._run_control.wait_for_resume()

# We may have just moved the labware that contains the current well out from
# under the pipette. Clear the current location to reflect the fact that the
# pipette is no longer over any labware. This is necessary for safe path
# planning in case the next movement goes to the same labware (now in a new
# place).
pipette_location = self._state_view.pipettes.get_current_location()
if (
isinstance(pipette_location, CurrentWell)
and pipette_location.labware_id == params.labwareId
):
state_update.clear_all_pipette_locations()

return SuccessData(
public=MoveLabwareResult(offsetId=new_offset_id),
private=None,
state_update=state_update,
)


class MoveLabware(BaseCommand[MoveLabwareParams, MoveLabwareResult, ErrorOccurrence]):
class MoveLabware(BaseCommand[MoveLabwareParams, MoveLabwareResult, GripError]):
"""A ``moveLabware`` command."""

commandType: MoveLabwareCommandType = "moveLabware"
Expand Down

0 comments on commit 10dda68

Please sign in to comment.