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

feat(app): Only follow rear tip sensor during calibration setup #14984

Merged
merged 10 commits into from
Apr 23, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -1524,10 +1524,10 @@ async def teardown_tip_detector(self, mount: OT3Mount) -> None:
async def get_tip_status(
self,
mount: OT3Mount,
ht_operational_sensor: Optional[InstrumentProbeType] = None,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> TipStateType:
return await self.tip_presence_manager.get_tip_status(
mount, ht_operational_sensor
mount, follow_singular_sensor
)

def current_tip_state(self, mount: OT3Mount) -> Optional[bool]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ def subsystems(self) -> Dict[SubSystem, SubSystemState]:
async def get_tip_status(
self,
mount: OT3Mount,
ht_operational_sensor: Optional[InstrumentProbeType] = None,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> TipStateType:
return TipStateType(self._sim_tip_state[mount])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,21 @@ def current_tip_state(self, mount: OT3Mount) -> Optional[bool]:
@staticmethod
def _get_tip_presence(
results: List[tip_types.TipNotification],
ht_operational_sensor: Optional[InstrumentProbeType] = None,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> TipStateType:
"""
We can use ht_operational_sensor used to specify that we only care
We can use follow_singular_sensor used to specify that we only care
about the status of one tip presence sensor on a high throughput
pipette, and the other is allowed to be different.
"""
if ht_operational_sensor:
target_sensor_id = sensor_id_for_instrument(ht_operational_sensor)
if follow_singular_sensor:
target_sensor_id = sensor_id_for_instrument(follow_singular_sensor)
for r in results:
if r.sensor == target_sensor_id:
return TipStateType(r.presence)
# raise an error if requested sensor response isn't found
raise GeneralError(
message=f"Requested status for sensor {ht_operational_sensor} not found."
message=f"Requested status for sensor {follow_singular_sensor} not found."
)
# more than one sensor reported, we have to check if their states match
if len(set(r.presence for r in results)) > 1:
Expand All @@ -142,11 +142,11 @@ def _get_tip_presence(
async def get_tip_status(
self,
mount: OT3Mount,
ht_operational_sensor: Optional[InstrumentProbeType] = None,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> TipStateType:
detector = self.get_detector(mount)
return self._get_tip_presence(
await detector.request_tip_status(), ht_operational_sensor
await detector.request_tip_status(), follow_singular_sensor
)

def get_detector(self, mount: OT3Mount) -> TipDetector:
Expand Down
8 changes: 4 additions & 4 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2072,7 +2072,7 @@ async def _high_throughput_check_tip(self) -> AsyncIterator[None]:
async def get_tip_presence_status(
self,
mount: Union[top_types.Mount, OT3Mount],
ht_operational_sensor: Optional[InstrumentProbeType] = None,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> TipStateType:
"""
Check tip presence status. If a high throughput pipette is present,
Expand All @@ -2087,18 +2087,18 @@ async def get_tip_presence_status(
):
await stack.enter_async_context(self._high_throughput_check_tip())
result = await self._backend.get_tip_status(
real_mount, ht_operational_sensor
real_mount, follow_singular_sensor
)
return result

async def verify_tip_presence(
self,
mount: Union[top_types.Mount, OT3Mount],
expected: TipStateType,
ht_operational_sensor: Optional[InstrumentProbeType] = None,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> None:
real_mount = OT3Mount.from_mount(mount)
status = await self.get_tip_presence_status(real_mount, ht_operational_sensor)
status = await self.get_tip_presence_status(real_mount, follow_singular_sensor)
if status != expected:
raise FailedTipStateCheck(expected, status.value)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Flex-specific extensions to instrument configuration."""
from typing import Union
from typing import Union, Optional
from typing_extensions import Protocol

from .types import MountArgType
Expand All @@ -9,6 +9,7 @@
)
from opentrons.hardware_control.types import (
TipStateType,
InstrumentProbeType,
)
from opentrons.hardware_control.instruments.ot3.instrument_calibration import (
PipetteOffsetSummary,
Expand Down Expand Up @@ -42,7 +43,10 @@ async def get_tip_presence_status(
...

async def verify_tip_presence(
self, mount: MountArgType, expected: TipStateType
self,
mount: MountArgType,
expected: TipStateType,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> None:
"""Check tip presence status and raise if it does not match `expected`."""
...
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .pipetting_common import PipetteIdMixin
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate

from ..types import TipPresenceStatus
from ..types import TipPresenceStatus, InstrumentSensorId

if TYPE_CHECKING:
from ..execution import TipHandler
Expand All @@ -23,6 +23,9 @@ class VerifyTipPresenceParams(PipetteIdMixin):
expectedState: TipPresenceStatus = Field(
..., description="The expected tip presence status on the pipette."
)
followSingularSensor: Optional[InstrumentSensorId] = Field(
default=None, description="The sensor id to follow if the other can be ignored."
)


class VerifyTipPresenceResult(BaseModel):
Expand All @@ -47,10 +50,16 @@ async def execute(self, params: VerifyTipPresenceParams) -> VerifyTipPresenceRes
"""Verify if tip presence is as expected for the requested pipette."""
pipette_id = params.pipetteId
expected_state = params.expectedState
follow_singular_sensor = (
InstrumentSensorId.to_instrument_probe_type(params.followSingularSensor)
if params.followSingularSensor
else None
)

await self._tip_handler.verify_tip_presence(
pipette_id=pipette_id,
expected=expected_state,
follow_singular_sensor=follow_singular_sensor,
)

return VerifyTipPresenceResult()
Expand Down
21 changes: 16 additions & 5 deletions api/src/opentrons/protocol_engine/execution/tip_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing_extensions import Protocol as TypingProtocol

from opentrons.hardware_control import HardwareControlAPI
from opentrons.hardware_control.types import FailedTipStateCheck
from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType
from opentrons_shared_data.errors.exceptions import (
CommandPreconditionViolated,
CommandParameterLimitViolated,
Expand Down Expand Up @@ -74,7 +74,10 @@ async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus:
"""Get tip presence status on the pipette."""

async def verify_tip_presence(
self, pipette_id: str, expected: TipPresenceStatus
self,
pipette_id: str,
expected: TipPresenceStatus,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> None:
"""Verify the expected tip presence status."""

Expand Down Expand Up @@ -237,7 +240,10 @@ async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus:
return TipPresenceStatus.UNKNOWN

async def verify_tip_presence(
self, pipette_id: str, expected: TipPresenceStatus
self,
pipette_id: str,
expected: TipPresenceStatus,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> None:
"""Verify the expecterd tip presence status of the pipette.

Expand All @@ -247,7 +253,9 @@ async def verify_tip_presence(
try:
ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
await ot3api.verify_tip_presence(hw_mount, expected.to_hw_state())
await ot3api.verify_tip_presence(
hw_mount, expected.to_hw_state(), follow_singular_sensor
)
except HardwareNotSupportedError:
# Tip presence sensing is not supported on the OT2
pass
Expand Down Expand Up @@ -332,7 +340,10 @@ async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
assert False, "TipHandler.add_tip should not be used with virtual pipettes"

async def verify_tip_presence(
self, pipette_id: str, expected: TipPresenceStatus
self,
pipette_id: str,
expected: TipPresenceStatus,
follow_singular_sensor: Optional[InstrumentProbeType] = None,
) -> None:
"""Verify tip presence.

Expand Down
21 changes: 20 additions & 1 deletion api/src/opentrons/protocol_engine/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@

from opentrons_shared_data.pipette.dev_types import PipetteNameType
from opentrons.types import MountType, DeckSlotName, StagingSlotName
from opentrons.hardware_control.types import TipStateType as HwTipStateType
from opentrons.hardware_control.types import (
TipStateType as HwTipStateType,
InstrumentProbeType,
)
from opentrons.hardware_control.modules import (
ModuleType as ModuleType,
)
Expand Down Expand Up @@ -830,6 +833,22 @@ class QuadrantNozzleLayoutConfiguration(BaseModel):
] # cutout_id, cutout_fixture_id, opentrons_module_serial_number


class InstrumentSensorId(str, Enum):
"""Primary and secondary sensor ids."""

PRIMARY = "primary"
SECONDARY = "secondary"
BOTH = "both"

def to_instrument_probe_type(self) -> InstrumentProbeType:
"""Convert to InstrumentProbeType."""
return {
InstrumentSensorId.PRIMARY: InstrumentProbeType.PRIMARY,
InstrumentSensorId.SECONDARY: InstrumentProbeType.SECONDARY,
InstrumentSensorId.BOTH: InstrumentProbeType.BOTH,
}[self]


class TipPresenceStatus(str, Enum):
"""Tip presence status reported by a pipette."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,11 @@ async def test_verify_tip_presence_on_ot3(
decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return(
MountType.LEFT
)
await subject.verify_tip_presence("pipette-id", expected)
await subject.verify_tip_presence("pipette-id", expected, None)

decoy.verify(
await ot3_hardware_api.verify_tip_presence(
Mount.LEFT, expected.to_hw_state()
Mount.LEFT, expected.to_hw_state(), None
)
)

Expand Down
6 changes: 5 additions & 1 deletion app/src/organisms/LabwarePositionCheck/AttachProbe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => {
const verifyCommands: CreateCommand[] = [
{
commandType: 'verifyTipPresence',
params: { pipetteId: pipetteId, expectedState: 'present' },
params: {
pipetteId: pipetteId,
expectedState: 'present',
followSingularSensor: 'primary',
},
},
]
const homeCommands: CreateCommand[] = [
Expand Down
6 changes: 5 additions & 1 deletion app/src/organisms/PipetteWizardFlows/AttachProbe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => {
const verifyCommands: CreateCommand[] = [
{
commandType: 'verifyTipPresence',
params: { pipetteId: pipetteId, expectedState: 'present' },
params: {
pipetteId: pipetteId,
expectedState: 'present',
followSingularSensor: 'primary',
},
},
]
const homeCommands: CreateCommand[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ describe('AttachProbe', () => {
[
{
commandType: 'verifyTipPresence',
params: { pipetteId: 'abc', expectedState: 'present' },
params: {
pipetteId: 'abc',
expectedState: 'present',
followSingularSensor: 'primary',
},
},
],
false
Expand Down Expand Up @@ -205,7 +209,11 @@ describe('AttachProbe', () => {
[
{
commandType: 'verifyTipPresence',
params: { pipetteId: 'abc', expectedState: 'present' },
params: {
pipetteId: 'abc',
expectedState: 'present',
followSingularSensor: 'primary',
},
},
],
false
Expand Down
14 changes: 14 additions & 0 deletions shared-data/command/schemas/8.json
Original file line number Diff line number Diff line change
Expand Up @@ -2582,6 +2582,12 @@
"enum": ["present", "absent", "unknown"],
"type": "string"
},
"InstrumentSensorId": {
"title": "InstrumentSensorId",
"description": "Primary and secondary sensor ids.",
"enum": ["primary", "secondary", "both"],
"type": "string"
},
"VerifyTipPresenceParams": {
"title": "VerifyTipPresenceParams",
"description": "Payload required for a VerifyTipPresence command.",
Expand All @@ -2599,6 +2605,14 @@
"$ref": "#/definitions/TipPresenceStatus"
}
]
},
"followSingularSensor": {
"description": "The sensor id to follow if the other can be ignored.",
"allOf": [
{
"$ref": "#/definitions/InstrumentSensorId"
}
]
}
},
"required": ["pipetteId", "expectedState"]
Expand Down
1 change: 1 addition & 0 deletions shared-data/command/types/pipetting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ interface WellLocationParam {

interface VerifyTipPresenceParams extends PipetteIdentityParams {
expectedState?: 'present' | 'absent'
followSingularSensor?: 'primary' | 'secondary'
}

interface BasicLiquidHandlingResult {
Expand Down
Loading