Skip to content

Commit

Permalink
in place liquid failure handling
Browse files Browse the repository at this point in the history
  • Loading branch information
sfoster1 committed Oct 25, 2024
1 parent 15094da commit 93d431c
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 54 deletions.
16 changes: 14 additions & 2 deletions api/src/opentrons/protocol_engine/commands/aspirate_in_place.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn:
" The first aspirate following a blow-out must be from a specific well"
" so the plunger can be reset in a known safe position."
)

state_update = StateUpdate()
current_location = self._state_view.pipettes.get_current_location()

try:
current_position = await self._gantry_mover.get_position(params.pipetteId)
volume = await self._pipetting.aspirate_in_place(
Expand All @@ -103,6 +107,15 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn:
)
except PipetteOverpressureError as e:
# TODO(pbm, 10-24-24): if location is a well, get new tip and LiquidProbe in error recovery to reestablish well liquid level
if (
isinstance(current_location, CurrentWell)
and current_location.pipette_id == params.pipetteId
):
state_update.set_liquid_operated(
labware_id=current_location.labware_id,
well_name=current_location.well_name,
volume=None,
)
return DefinedErrorData(
public=OverpressureError(
id=self._model_utils.generate_id(),
Expand All @@ -124,14 +137,13 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn:
}
),
),
state_update=state_update,
)
else:
current_location = self._state_view.pipettes.get_current_location()
if (
isinstance(current_location, CurrentWell)
and current_location.pipette_id == params.pipetteId
):
state_update = StateUpdate()
state_update.set_liquid_operated(
labware_id=current_location.labware_id,
well_name=current_location.well_name,
Expand Down
14 changes: 12 additions & 2 deletions api/src/opentrons/protocol_engine/commands/dispense_in_place.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def __init__(

async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn:
"""Dispense without moving the pipette."""
state_update = StateUpdate()
current_location = self._state_view.pipettes.get_current_location()
try:
current_position = await self._gantry_mover.get_position(params.pipetteId)
volume = await self._pipetting.dispense_in_place(
Expand All @@ -84,6 +86,15 @@ async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn:
)
except PipetteOverpressureError as e:
# TODO(pbm, 10-24-24): if location is a well, get new tip and LiquidProbe in error recovery to reestablish well liquid level
if (
isinstance(current_location, CurrentWell)
and current_location.pipette_id == params.pipetteId
):
state_update.set_liquid_operated(
labware_id=current_location.labware_id,
well_name=current_location.well_name,
volume=None,
)
return DefinedErrorData(
public=OverpressureError(
id=self._model_utils.generate_id(),
Expand All @@ -105,14 +116,13 @@ async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn:
}
),
),
state_update=state_update,
)
else:
current_location = self._state_view.pipettes.get_current_location()
if (
isinstance(current_location, CurrentWell)
and current_location.pipette_id == params.pipetteId
):
state_update = StateUpdate()
state_update.set_liquid_operated(
labware_id=current_location.labware_id,
well_name=current_location.well_name,
Expand Down
113 changes: 88 additions & 25 deletions api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
from opentrons.protocol_engine.resources import ModelUtils
from opentrons.protocol_engine.state.state import StateStore
from opentrons.protocol_engine.commands.pipetting_common import OverpressureError
from opentrons.protocol_engine.types import CurrentWell
from opentrons.protocol_engine.types import (
CurrentWell,
CurrentPipetteLocation,
CurrentAddressableArea,
)
from opentrons.protocol_engine.state import update_types


Expand Down Expand Up @@ -63,13 +67,32 @@ def subject(
)


@pytest.mark.parametrize(
"location,stateupdateLabware,stateupdateWell",
[
(
CurrentWell(
pipette_id="pipette-id-abc",
labware_id="labware-id-1",
well_name="well-name-1",
),
"labware-id-1",
"well-name-1",
),
(None, None, None),
(CurrentAddressableArea("pipette-id-abc", "addressable-area-1"), None, None),
],
)
async def test_aspirate_in_place_implementation(
decoy: Decoy,
pipetting: PipettingHandler,
state_store: StateStore,
hardware_api: HardwareAPI,
mock_command_note_adder: CommandNoteAdder,
subject: AspirateInPlaceImplementation,
location: CurrentPipetteLocation | None,
stateupdateLabware: str,
stateupdateWell: str,
) -> None:
"""It should aspirate in place."""
data = AspirateInPlaceParams(
Expand All @@ -93,25 +116,27 @@ async def test_aspirate_in_place_implementation(
)
).then_return(123)

decoy.when(state_store.pipettes.get_current_location()).then_return(
CurrentWell(
pipette_id="pipette-id-abc",
labware_id="labware-id-1",
well_name="well-name-1",
)
)
decoy.when(state_store.pipettes.get_current_location()).then_return(location)

result = await subject.execute(params=data)

assert result == SuccessData(
public=AspirateInPlaceResult(volume=123),
private=None,
state_update=update_types.StateUpdate(
liquid_operated=update_types.LiquidOperatedUpdate(
labware_id="labware-id-1", well_name="well-name-1", volume=-123
)
),
)
if isinstance(location, CurrentWell):
assert result == SuccessData(
public=AspirateInPlaceResult(volume=123),
private=None,
state_update=update_types.StateUpdate(
liquid_operated=update_types.LiquidOperatedUpdate(
labware_id=stateupdateLabware,
well_name=stateupdateWell,
volume=-123,
)
),
)
else:
assert result == SuccessData(
public=AspirateInPlaceResult(volume=123),
private=None,
)


async def test_handle_aspirate_in_place_request_not_ready_to_aspirate(
Expand Down Expand Up @@ -171,13 +196,33 @@ async def test_aspirate_raises_volume_error(
await subject.execute(data)


@pytest.mark.parametrize(
"location,stateupdateLabware,stateupdateWell",
[
(
CurrentWell(
pipette_id="pipette-id",
labware_id="labware-id-1",
well_name="well-name-1",
),
"labware-id-1",
"well-name-1",
),
(None, None, None),
(CurrentAddressableArea("pipette-id", "addressable-area-1"), None, None),
],
)
async def test_overpressure_error(
decoy: Decoy,
gantry_mover: GantryMover,
pipetting: PipettingHandler,
subject: AspirateInPlaceImplementation,
model_utils: ModelUtils,
mock_command_note_adder: CommandNoteAdder,
state_store: StateStore,
location: CurrentPipetteLocation | None,
stateupdateLabware: str,
stateupdateWell: str,
) -> None:
"""It should return an overpressure error if the hardware API indicates that."""
pipette_id = "pipette-id"
Expand Down Expand Up @@ -209,14 +254,32 @@ async def test_overpressure_error(
decoy.when(model_utils.generate_id()).then_return(error_id)
decoy.when(model_utils.get_timestamp()).then_return(error_timestamp)
decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position)
decoy.when(state_store.pipettes.get_current_location()).then_return(location)

result = await subject.execute(data)

assert result == DefinedErrorData(
public=OverpressureError.construct(
id=error_id,
createdAt=error_timestamp,
wrappedErrors=[matchers.Anything()],
errorInfo={"retryLocation": (position.x, position.y, position.z)},
),
)
if isinstance(location, CurrentWell):
assert result == DefinedErrorData(
public=OverpressureError.construct(
id=error_id,
createdAt=error_timestamp,
wrappedErrors=[matchers.Anything()],
errorInfo={"retryLocation": (position.x, position.y, position.z)},
),
state_update=update_types.StateUpdate(
liquid_operated=update_types.LiquidOperatedUpdate(
labware_id=stateupdateLabware,
well_name=stateupdateWell,
volume=None,
)
),
)
else:
assert result == DefinedErrorData(
public=OverpressureError.construct(
id=error_id,
createdAt=error_timestamp,
wrappedErrors=[matchers.Anything()],
errorInfo={"retryLocation": (position.x, position.y, position.z)},
),
)
Loading

0 comments on commit 93d431c

Please sign in to comment.