Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): Disable tip presence check on 8ch single and partial 2 thru 3 nozzle #16312

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate in the in-code comments about where 4 comes from?


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
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
Loading