Skip to content

Commit

Permalink
fix(app): handle dtwiz after estop (#16168)
Browse files Browse the repository at this point in the history
# Overview

Fixes some issues around estop and drop tip. Specifically,
- When you hit the estop, we surface an error and lose positioning -
normal for errors like this - but we also turn off the motors, and they
need to be turned back on, so add a command to do that and use it in
dtwiz
- on desktop, the estop takeover wouldn't work if you were anywhere but
the top level devices page because of either the react-router update or
because of an oversight that was made during the flex launch
- the estop takeover is polling, and slowly. we should add notifications
at some point but for now we can do some strategic query invalidations
and fast polling when the estop is triggered to make it more responsive

## Test Plan and Hands on Testing

- run a protocol, stop it with estop, and check that the modal fires; is
responsive; and leaves you to a dtwiz, which opertaes correctly
  - [x] on desktop, viewing the run, while network connected
  - [x] on desktop, viewing the run, while usb connected
  - [x] on odd

---------


Closes RQA-3103, RQA-3133
Co-authored-by: Seth Foster <[email protected]>
  • Loading branch information
TamarZanzouri authored Sep 3, 2024
1 parent 631eeff commit 5999238
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ async def disengage_axes(self, which: List[Axis]) -> None:
"""Disengage some axes."""
...

async def engage_axes(self, which: List[Axis]) -> None:
"""Engage some axes."""
...

async def retract(self, mount: MountArgType, margin: float = 10) -> None:
"""Pull the specified mount up to its home position.
Expand Down
5 changes: 5 additions & 0 deletions api/src/opentrons/protocol_engine/commands/command_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@
unsafe.UnsafeBlowOutInPlace,
unsafe.UnsafeDropTipInPlace,
unsafe.UpdatePositionEstimators,
unsafe.UnsafeEngageAxes,
],
Field(discriminator="commandType"),
]
Expand Down Expand Up @@ -463,6 +464,7 @@
unsafe.UnsafeBlowOutInPlaceParams,
unsafe.UnsafeDropTipInPlaceParams,
unsafe.UpdatePositionEstimatorsParams,
unsafe.UnsafeEngageAxesParams,
]

CommandType = Union[
Expand Down Expand Up @@ -533,6 +535,7 @@
unsafe.UnsafeBlowOutInPlaceCommandType,
unsafe.UnsafeDropTipInPlaceCommandType,
unsafe.UpdatePositionEstimatorsCommandType,
unsafe.UnsafeEngageAxesCommandType,
]

CommandCreate = Annotated[
Expand Down Expand Up @@ -604,6 +607,7 @@
unsafe.UnsafeBlowOutInPlaceCreate,
unsafe.UnsafeDropTipInPlaceCreate,
unsafe.UpdatePositionEstimatorsCreate,
unsafe.UnsafeEngageAxesCreate,
],
Field(discriminator="commandType"),
]
Expand Down Expand Up @@ -676,6 +680,7 @@
unsafe.UnsafeBlowOutInPlaceResult,
unsafe.UnsafeDropTipInPlaceResult,
unsafe.UpdatePositionEstimatorsResult,
unsafe.UnsafeEngageAxesResult,
]

# todo(mm, 2024-06-12): Ideally, command return types would have specific
Expand Down
14 changes: 14 additions & 0 deletions api/src/opentrons/protocol_engine/commands/unsafe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
UpdatePositionEstimatorsCreate,
)

from .unsafe_engage_axes import (
UnsafeEngageAxesCommandType,
UnsafeEngageAxesParams,
UnsafeEngageAxesResult,
UnsafeEngageAxes,
UnsafeEngageAxesCreate,
)

__all__ = [
# Unsafe blow-out-in-place command models
"UnsafeBlowOutInPlaceCommandType",
Expand All @@ -42,4 +50,10 @@
"UpdatePositionEstimatorsResult",
"UpdatePositionEstimators",
"UpdatePositionEstimatorsCreate",
# Unsafe engage axes
"UnsafeEngageAxesCommandType",
"UnsafeEngageAxesParams",
"UnsafeEngageAxesResult",
"UnsafeEngageAxes",
"UnsafeEngageAxesCreate",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Update position estimators payload, result, and implementaiton."""

from __future__ import annotations
from pydantic import BaseModel, Field
from typing import TYPE_CHECKING, Optional, List, Type
from typing_extensions import Literal

from ...types import MotorAxis
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ...errors.error_occurrence import ErrorOccurrence
from ...resources import ensure_ot3_hardware

from opentrons.hardware_control import HardwareControlAPI

if TYPE_CHECKING:
from ...execution import GantryMover


UnsafeEngageAxesCommandType = Literal["unsafe/engageAxes"]


class UnsafeEngageAxesParams(BaseModel):
"""Payload required for an UnsafeEngageAxes command."""

axes: List[MotorAxis] = Field(..., description="The axes for which to enable.")


class UnsafeEngageAxesResult(BaseModel):
"""Result data from the execution of an UnsafeEngageAxes command."""


class UnsafeEngageAxesImplementation(
AbstractCommandImpl[
UnsafeEngageAxesParams,
SuccessData[UnsafeEngageAxesResult, None],
]
):
"""Enable axes command implementation."""

def __init__(
self,
hardware_api: HardwareControlAPI,
gantry_mover: GantryMover,
**kwargs: object,
) -> None:
self._hardware_api = hardware_api
self._gantry_mover = gantry_mover

async def execute(
self, params: UnsafeEngageAxesParams
) -> SuccessData[UnsafeEngageAxesResult, None]:
"""Enable exes."""
ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
await ot3_hardware_api.engage_axes(
[
self._gantry_mover.motor_axis_to_hardware_axis(axis)
for axis in params.axes
]
)
return SuccessData(public=UnsafeEngageAxesResult(), private=None)


class UnsafeEngageAxes(
BaseCommand[UnsafeEngageAxesParams, UnsafeEngageAxesResult, ErrorOccurrence]
):
"""UnsafeEngageAxes command model."""

commandType: UnsafeEngageAxesCommandType = "unsafe/engageAxes"
params: UnsafeEngageAxesParams
result: Optional[UnsafeEngageAxesResult]

_ImplementationCls: Type[
UnsafeEngageAxesImplementation
] = UnsafeEngageAxesImplementation


class UnsafeEngageAxesCreate(BaseCommandCreate[UnsafeEngageAxesParams]):
"""UnsafeEngageAxes command request model."""

commandType: UnsafeEngageAxesCommandType = "unsafe/engageAxes"
params: UnsafeEngageAxesParams

_CommandCls: Type[UnsafeEngageAxes] = UnsafeEngageAxes
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Test update-position-estimator commands."""
from decoy import Decoy

from opentrons.protocol_engine.commands.unsafe.unsafe_engage_axes import (
UnsafeEngageAxesParams,
UnsafeEngageAxesResult,
UnsafeEngageAxesImplementation,
)
from opentrons.protocol_engine.commands.command import SuccessData
from opentrons.protocol_engine.execution import GantryMover
from opentrons.protocol_engine.types import MotorAxis
from opentrons.hardware_control import OT3HardwareControlAPI
from opentrons.hardware_control.types import Axis


async def test_engage_axes_implementation(
decoy: Decoy, ot3_hardware_api: OT3HardwareControlAPI, gantry_mover: GantryMover
) -> None:
"""Test EngageAxes command execution."""
subject = UnsafeEngageAxesImplementation(
hardware_api=ot3_hardware_api, gantry_mover=gantry_mover
)

data = UnsafeEngageAxesParams(
axes=[MotorAxis.LEFT_Z, MotorAxis.LEFT_PLUNGER, MotorAxis.X, MotorAxis.Y]
)

decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.LEFT_Z)).then_return(
Axis.Z_L
)
decoy.when(
gantry_mover.motor_axis_to_hardware_axis(MotorAxis.LEFT_PLUNGER)
).then_return(Axis.P_L)
decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.X)).then_return(
Axis.X
)
decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.Y)).then_return(
Axis.Y
)
decoy.when(
await ot3_hardware_api.update_axis_position_estimations(
[Axis.Z_L, Axis.P_L, Axis.X, Axis.Y]
)
).then_return(None)

result = await subject.execute(data)

assert result == SuccessData(public=UnsafeEngageAxesResult(), private=None)

decoy.verify(
await ot3_hardware_api.engage_axes([Axis.Z_L, Axis.P_L, Axis.X, Axis.Y]),
)
9 changes: 4 additions & 5 deletions app/src/App/DesktopApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { ProtocolTimeline } from '../pages/Protocols/ProtocolDetails/ProtocolTim
import { PortalRoot as ModalPortalRoot } from './portal'
import { DesktopAppFallback } from './DesktopAppFallback'

import type { RouteProps, DesktopRouteParams } from './types'
import type { RouteProps } from './types'

export const DesktopApp = (): JSX.Element => {
useSoftwareUpdatePoll()
Expand Down Expand Up @@ -158,11 +158,10 @@ export const DesktopApp = (): JSX.Element => {
}

function RobotControlTakeover(): JSX.Element | null {
const deviceRouteMatch = useMatch('/devices/:robotName')
const params = deviceRouteMatch?.params as DesktopRouteParams
const robotName = params?.robotName
const deviceRouteMatch = useMatch('/devices/:robotName/*')
const params = deviceRouteMatch?.params
const robotName = params?.robotName ?? null
const robot = useRobot(robotName)

if (deviceRouteMatch == null || robot == null || robotName == null)
return null

Expand Down
18 changes: 17 additions & 1 deletion app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function useDropTipCommands({
)
return chainRunCommands(
isFlex
? [UPDATE_ESTIMATORS_EXCEPT_PLUNGERS, moveToAACommand]
? [ENGAGE_AXES, UPDATE_ESTIMATORS_EXCEPT_PLUNGERS, moveToAACommand]
: [moveToAACommand],
true
)
Expand Down Expand Up @@ -288,6 +288,13 @@ const HOME: CreateCommand = {
params: {},
}

const ENGAGE_AXES: CreateCommand = {
commandType: 'unsafe/engageAxes' as const,
params: {
axes: ['leftZ', 'rightZ', 'x', 'y', 'leftPlunger', 'rightPlunger'],
},
}

const HOME_EXCEPT_PLUNGERS: CreateCommand = {
commandType: 'home' as const,
params: { axes: ['leftZ', 'rightZ', 'x', 'y'] },
Expand All @@ -298,6 +305,11 @@ const UPDATE_ESTIMATORS_EXCEPT_PLUNGERS: CreateCommand = {
params: { axes: ['leftZ', 'rightZ', 'x', 'y'] },
}

const UPDATE_PLUNGER_ESTIMATORS: CreateCommand = {
commandType: 'unsafe/updatePositionEstimators' as const,
params: { axes: ['leftPlunger', 'rightPlunger'] },
}

const buildDropTipInPlaceCommand = (
isFlex: boolean,
pipetteId: string | null
Expand All @@ -323,6 +335,8 @@ const buildBlowoutCommands = (
): CreateCommand[] =>
isFlex
? [
ENGAGE_AXES,
UPDATE_PLUNGER_ESTIMATORS,
{
commandType: 'unsafe/blowOutInPlace',
params: {
Expand All @@ -342,6 +356,8 @@ const buildBlowoutCommands = (
},
]
: [
ENGAGE_AXES,
UPDATE_PLUNGER_ESTIMATORS,
{
commandType: 'blowOutInPlace',
params: {
Expand Down
Loading

0 comments on commit 5999238

Please sign in to comment.