Skip to content

Commit

Permalink
fix(api): Disable tip presence check on 8ch single and partial 2 thru…
Browse files Browse the repository at this point in the history
… 3 nozzle (#16312)

Covers RABR-623, RABR-624
Disable tip presence sensing on the 8ch Flex pipette for Single tip
configuration and for Partial Column for 1-3 tips.
  • Loading branch information
CaseyBatten authored Sep 20, 2024
1 parent 5e1dabf commit 1e268d3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 6 deletions.
25 changes: 21 additions & 4 deletions api/src/opentrons/protocol_engine/execution/tip_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,29 @@ async def verify_tip_presence(
This function will raise an exception if the specified tip presence status
isn't matched.
"""
nozzle_configuration = (
self._state_view.pipettes.state.nozzle_configuration_by_id[pipette_id]
)

# Configuration metrics by which tip presence checking is ignored
unsupported_pipette_types = [8, 96]
unsupported_layout_types = [
NozzleConfigurationType.SINGLE,
NozzleConfigurationType.COLUMN,
]
# NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor
supported_partial_nozzle_minimum = 4

if (
self._state_view.pipettes.get_nozzle_layout_type(pipette_id)
== NozzleConfigurationType.SINGLE
and self._state_view.pipettes.get_channels(pipette_id) == 96
nozzle_configuration is not None
and self._state_view.pipettes.get_channels(pipette_id)
in unsupported_pipette_types
and nozzle_configuration.configuration in unsupported_layout_types
and len(nozzle_configuration.map_store) < supported_partial_nozzle_minimum
):
# Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette
# Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette.
# This is due in part to a press distance tolerance which creates a risk case for false positives. In the case of single tip, the mechanical tolerance
# for presses with 100% success is below the minimum average achieved press distance for a given multi channel pipette in that configuration.
return
try:
ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)
Expand Down
32 changes: 30 additions & 2 deletions api/tests/opentrons/protocol_engine/execution/test_tip_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from decoy import Decoy
from mock import AsyncMock, patch

from typing import Dict, ContextManager, Optional
from typing import Dict, ContextManager, Optional, OrderedDict
from contextlib import nullcontext as does_not_raise

from opentrons.types import Mount, MountType
from opentrons.types import Mount, MountType, Point
from opentrons.hardware_control import API as HardwareAPI
from opentrons.hardware_control.types import TipStateType
from opentrons.hardware_control.protocols.types import OT2RobotType, FlexRobotType
Expand All @@ -25,6 +25,8 @@
VirtualTipHandler,
create_tip_handler,
)
from opentrons.hardware_control.nozzle_manager import NozzleMap
from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps


@pytest.fixture
Expand Down Expand Up @@ -53,6 +55,17 @@ def tip_rack_definition() -> LabwareDefinition:
return LabwareDefinition.construct(namespace="test", version=42) # type: ignore[call-arg]


MOCK_MAP = NozzleMap.build(
physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}),
physical_rows=OrderedDict({"A": ["A1"]}),
physical_columns=OrderedDict({"1": ["A1"]}),
starting_nozzle="A1",
back_left_nozzle="A1",
front_right_nozzle="A1",
valid_nozzle_maps=ValidNozzleMaps(maps={"Full": ["A1"]}),
)


async def test_create_tip_handler(
decoy: Decoy,
mock_state_view: StateView,
Expand Down Expand Up @@ -102,6 +115,9 @@ async def test_flex_pick_up_tip_state(
decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return(
MountType.LEFT
)
decoy.when(mock_state_view.pipettes.state.nozzle_configuration_by_id).then_return(
{"pipette-id": MOCK_MAP}
)
decoy.when(
mock_state_view.geometry.get_nominal_tip_geometry(
pipette_id="pipette-id",
Expand Down Expand Up @@ -171,6 +187,10 @@ async def test_pick_up_tip(
MountType.LEFT
)

decoy.when(mock_state_view.pipettes.state.nozzle_configuration_by_id).then_return(
{"pipette-id": MOCK_MAP}
)

decoy.when(
mock_state_view.geometry.get_nominal_tip_geometry(
pipette_id="pipette-id",
Expand Down Expand Up @@ -225,6 +245,9 @@ async def test_drop_tip(
decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return(
MountType.RIGHT
)
decoy.when(mock_state_view.pipettes.state.nozzle_configuration_by_id).then_return(
{"pipette-id": MOCK_MAP}
)

await subject.drop_tip(pipette_id="pipette-id", home_after=True)

Expand Down Expand Up @@ -499,6 +522,11 @@ async def test_verify_tip_presence_on_ot3(
decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return(
MountType.LEFT
)

decoy.when(
mock_state_view.pipettes.state.nozzle_configuration_by_id
).then_return({"pipette-id": MOCK_MAP})

await subject.verify_tip_presence("pipette-id", expected, None)

decoy.verify(
Expand Down

0 comments on commit 1e268d3

Please sign in to comment.