Skip to content

Commit

Permalink
feat(app): Only follow rear tip sensor during calibration setup (#14984)
Browse files Browse the repository at this point in the history
  • Loading branch information
caila-marashaj authored Apr 23, 2024
1 parent 4c89730 commit 446da56
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 29 deletions.
4 changes: 2 additions & 2 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
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

0 comments on commit 446da56

Please sign in to comment.