Skip to content

Commit

Permalink
feat(api): disable return tip for partially configured pipette (#13972)
Browse files Browse the repository at this point in the history
It is unsafe for us to return tips to a tiprack while in partial tip configuration because we risk
the potential of picking up used tips.

Closes RSS-360
---------

Co-authored-by: Seth Foster <[email protected]>
  • Loading branch information
Laura-Danielle and sfoster1 authored Nov 16, 2023
1 parent dc93386 commit afe23d4
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 4 deletions.
6 changes: 6 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from opentrons_shared_data.pipette.dev_types import PipetteNameType
from opentrons.protocol_api._nozzle_layout import NozzleLayout
from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType

from ..instrument import AbstractInstrument
from .well import WellCore
Expand Down Expand Up @@ -574,6 +575,11 @@ def get_dispense_flow_rate(self, rate: float = 1.0) -> float:
def get_blow_out_flow_rate(self, rate: float = 1.0) -> float:
return self._blow_out_flow_rate * rate

def get_nozzle_configuration(self) -> NozzleConfigurationType:
return self._engine_client.state.pipettes.get_nozzle_layout_type(
self._pipette_id
)

def set_flow_rate(
self,
aspirate: Optional[float] = None,
Expand Down
8 changes: 7 additions & 1 deletion api/src/opentrons/protocol_engine/commands/drop_tip.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,14 @@ async def execute(self, params: DropTipParams) -> DropTipResult:
else:
well_location = params.wellLocation

is_partially_configured = self._state_view.pipettes.get_is_partially_configured(
pipette_id=pipette_id
)
tip_drop_location = self._state_view.geometry.get_checked_tip_drop_location(
pipette_id=pipette_id, labware_id=labware_id, well_location=well_location
pipette_id=pipette_id,
labware_id=labware_id,
well_location=well_location,
partially_configured=is_partially_configured,
)

position = await self._movement_handler.move_to_well(
Expand Down
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_engine/state/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,20 @@ def get_checked_tip_drop_location(
pipette_id: str,
labware_id: str,
well_location: DropTipWellLocation,
partially_configured: bool = False,
) -> WellLocation:
"""Get tip drop location given labware and hardware pipette.
This makes sure that the well location has an appropriate origin & offset
if one is not already set previously.
"""
if (
self._labware.get_definition(labware_id).parameters.isTiprack
and partially_configured
):
raise errors.UnexpectedProtocolError(
"Cannot return tip to a tiprack while the pipette is configured for partial tip."
)
if well_location.origin != DropTipWellOrigin.DEFAULT:
return WellLocation(
origin=WellOrigin(well_location.origin.value),
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_engine/state/pipettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,3 +623,7 @@ def get_nozzle_layout_type(self, pipette_id: str) -> NozzleConfigurationType:
return nozzle_map_for_pipette.configuration
else:
return NozzleConfigurationType.FULL

def get_is_partially_configured(self, pipette_id: str) -> bool:
"""Determine if the provided pipette is partially configured."""
return self.get_nozzle_layout_type(pipette_id) != NozzleConfigurationType.FULL
14 changes: 13 additions & 1 deletion api/tests/opentrons/protocol_engine/commands/test_drop_tip.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,16 @@ async def test_drop_tip_implementation(
homeAfter=True,
)

decoy.when(
mock_state_view.pipettes.get_is_partially_configured(pipette_id="abc")
).then_return(False)

decoy.when(
mock_state_view.geometry.get_checked_tip_drop_location(
pipette_id="abc",
labware_id="123",
well_location=DropTipWellLocation(offset=WellOffset(x=1, y=2, z=3)),
partially_configured=False,
)
).then_return(WellLocation(offset=WellOffset(x=4, y=5, z=6)))

Expand Down Expand Up @@ -142,9 +147,16 @@ async def test_drop_tip_with_alternating_locations(
)
).then_return(drop_location)

decoy.when(
mock_state_view.pipettes.get_is_partially_configured(pipette_id="abc")
).then_return(False)

decoy.when(
mock_state_view.geometry.get_checked_tip_drop_location(
pipette_id="abc", labware_id="123", well_location=drop_location
pipette_id="abc",
labware_id="123",
well_location=drop_location,
partially_configured=False,
)
).then_return(WellLocation(offset=WellOffset(x=4, y=5, z=6)))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def test_load_virtual_pipette_nozzle_layout(
)
result = subject_instance.get_nozzle_layout_for_pipette("my-96-pipette")
assert result.configuration.value == "ROW"

subject_instance.configure_virtual_pipette_nozzle_layout(
"my-96-pipette", "p1000_96_v3.5", "A1", "A1"
)
Expand Down
11 changes: 9 additions & 2 deletions api/tests/opentrons/protocol_engine/state/test_geometry_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,15 +1116,22 @@ def test_get_tip_drop_location_with_non_tiprack(
)


def test_get_tip_drop_explicit_location(subject: GeometryView) -> None:
def test_get_tip_drop_explicit_location(
decoy: Decoy,
labware_view: LabwareView,
subject: GeometryView,
tip_rack_def: LabwareDefinition,
) -> None:
"""It should pass the location through if origin is not WellOrigin.DROP_TIP."""
decoy.when(labware_view.get_definition("tip-rack-id")).then_return(tip_rack_def)

input_location = DropTipWellLocation(
origin=DropTipWellOrigin.TOP,
offset=WellOffset(x=1, y=2, z=3),
)

result = subject.get_checked_tip_drop_location(
pipette_id="pipette-id", labware_id="labware-id", well_location=input_location
pipette_id="pipette-id", labware_id="tip-rack-id", well_location=input_location
)

assert result == WellLocation(
Expand Down

0 comments on commit afe23d4

Please sign in to comment.