diff --git a/api/src/opentrons/hardware_control/protocols/motion_controller.py b/api/src/opentrons/hardware_control/protocols/motion_controller.py index 8387e4a907c..daaf166f283 100644 --- a/api/src/opentrons/hardware_control/protocols/motion_controller.py +++ b/api/src/opentrons/hardware_control/protocols/motion_controller.py @@ -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. diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index eeafb1770b6..1f979f45f60 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -391,6 +391,7 @@ unsafe.UnsafeBlowOutInPlace, unsafe.UnsafeDropTipInPlace, unsafe.UpdatePositionEstimators, + unsafe.UnsafeEngageAxes, ], Field(discriminator="commandType"), ] @@ -463,6 +464,7 @@ unsafe.UnsafeBlowOutInPlaceParams, unsafe.UnsafeDropTipInPlaceParams, unsafe.UpdatePositionEstimatorsParams, + unsafe.UnsafeEngageAxesParams, ] CommandType = Union[ @@ -533,6 +535,7 @@ unsafe.UnsafeBlowOutInPlaceCommandType, unsafe.UnsafeDropTipInPlaceCommandType, unsafe.UpdatePositionEstimatorsCommandType, + unsafe.UnsafeEngageAxesCommandType, ] CommandCreate = Annotated[ @@ -604,6 +607,7 @@ unsafe.UnsafeBlowOutInPlaceCreate, unsafe.UnsafeDropTipInPlaceCreate, unsafe.UpdatePositionEstimatorsCreate, + unsafe.UnsafeEngageAxesCreate, ], Field(discriminator="commandType"), ] @@ -676,6 +680,7 @@ unsafe.UnsafeBlowOutInPlaceResult, unsafe.UnsafeDropTipInPlaceResult, unsafe.UpdatePositionEstimatorsResult, + unsafe.UnsafeEngageAxesResult, ] # todo(mm, 2024-06-12): Ideally, command return types would have specific diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py b/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py index 2875d38cb8e..6b92cc2e18e 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py @@ -23,6 +23,14 @@ UpdatePositionEstimatorsCreate, ) +from .unsafe_engage_axes import ( + UnsafeEngageAxesCommandType, + UnsafeEngageAxesParams, + UnsafeEngageAxesResult, + UnsafeEngageAxes, + UnsafeEngageAxesCreate, +) + __all__ = [ # Unsafe blow-out-in-place command models "UnsafeBlowOutInPlaceCommandType", @@ -42,4 +50,10 @@ "UpdatePositionEstimatorsResult", "UpdatePositionEstimators", "UpdatePositionEstimatorsCreate", + # Unsafe engage axes + "UnsafeEngageAxesCommandType", + "UnsafeEngageAxesParams", + "UnsafeEngageAxesResult", + "UnsafeEngageAxes", + "UnsafeEngageAxesCreate", ] diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py new file mode 100644 index 00000000000..500347d84b0 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py @@ -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 diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_engage_axes.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_engage_axes.py new file mode 100644 index 00000000000..0130d7ce16b --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_engage_axes.py @@ -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]), + ) diff --git a/app/src/App/DesktopApp.tsx b/app/src/App/DesktopApp.tsx index 9bcae5fd201..15bdcad3c46 100644 --- a/app/src/App/DesktopApp.tsx +++ b/app/src/App/DesktopApp.tsx @@ -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() @@ -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 diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts index 78e905ae5e1..3b0fa137afa 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts @@ -117,7 +117,7 @@ export function useDropTipCommands({ ) return chainRunCommands( isFlex - ? [UPDATE_ESTIMATORS_EXCEPT_PLUNGERS, moveToAACommand] + ? [ENGAGE_AXES, UPDATE_ESTIMATORS_EXCEPT_PLUNGERS, moveToAACommand] : [moveToAACommand], true ) @@ -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'] }, @@ -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 @@ -323,6 +335,8 @@ const buildBlowoutCommands = ( ): CreateCommand[] => isFlex ? [ + ENGAGE_AXES, + UPDATE_PLUNGER_ESTIMATORS, { commandType: 'unsafe/blowOutInPlace', params: { @@ -342,6 +356,8 @@ const buildBlowoutCommands = ( }, ] : [ + ENGAGE_AXES, + UPDATE_PLUNGER_ESTIMATORS, { commandType: 'blowOutInPlace', params: { diff --git a/app/src/organisms/EmergencyStop/EstopPressedModal.tsx b/app/src/organisms/EmergencyStop/EstopPressedModal.tsx index 78b0c440931..597e7190513 100644 --- a/app/src/organisms/EmergencyStop/EstopPressedModal.tsx +++ b/app/src/organisms/EmergencyStop/EstopPressedModal.tsx @@ -42,6 +42,8 @@ interface EstopPressedModalProps { closeModal: () => void isDismissedModal?: boolean setIsDismissedModal?: (isDismissedModal: boolean) => void + isWaitingForLogicalDisengage: boolean + setShouldSeeLogicalDisengage: () => void } export function EstopPressedModal({ @@ -49,11 +51,18 @@ export function EstopPressedModal({ closeModal, isDismissedModal, setIsDismissedModal, + isWaitingForLogicalDisengage, + setShouldSeeLogicalDisengage, }: EstopPressedModalProps): JSX.Element { const isOnDevice = useSelector(getIsOnDevice) return createPortal( isOnDevice ? ( - + ) : ( <> {isDismissedModal === false ? ( @@ -61,6 +70,8 @@ export function EstopPressedModal({ isEngaged={isEngaged} closeModal={closeModal} setIsDismissedModal={setIsDismissedModal} + isWaitingForLogicalDisengage={isWaitingForLogicalDisengage} + setShouldSeeLogicalDisengage={setShouldSeeLogicalDisengage} /> ) : null} @@ -72,6 +83,8 @@ export function EstopPressedModal({ function TouchscreenModal({ isEngaged, closeModal, + isWaitingForLogicalDisengage, + setShouldSeeLogicalDisengage, }: EstopPressedModalProps): JSX.Element { const { t } = useTranslation(['device_settings', 'branded']) const [isResuming, setIsResuming] = React.useState(false) @@ -88,6 +101,7 @@ function TouchscreenModal({ const handleClick = (): void => { setIsResuming(true) acknowledgeEstopDisengage(null) + setShouldSeeLogicalDisengage() closeModal() } return ( @@ -116,10 +130,16 @@ function TouchscreenModal({ @@ -131,6 +151,8 @@ function DesktopModal({ isEngaged, closeModal, setIsDismissedModal, + isWaitingForLogicalDisengage, + setShouldSeeLogicalDisengage, }: EstopPressedModalProps): JSX.Element { const { t } = useTranslation('device_settings') const [isResuming, setIsResuming] = React.useState(false) @@ -155,14 +177,19 @@ function DesktopModal({ const handleClick: React.MouseEventHandler = (e): void => { e.preventDefault() setIsResuming(true) - acknowledgeEstopDisengage({ - onSuccess: () => { - closeModal() - }, - onError: () => { - setIsResuming(false) - }, - }) + acknowledgeEstopDisengage( + {}, + { + onSuccess: () => { + setShouldSeeLogicalDisengage() + closeModal() + }, + onError: (error: any) => { + setIsResuming(false) + console.error(error) + }, + } + ) } return ( @@ -177,14 +204,16 @@ function DesktopModal({ - {isResuming ? : null} + {isResuming || isWaitingForLogicalDisengage ? ( + + ) : null} {t('resume_robot_operations')} diff --git a/app/src/organisms/EmergencyStop/EstopTakeover.tsx b/app/src/organisms/EmergencyStop/EstopTakeover.tsx index 7c3b07ce062..c8e294e2bfa 100644 --- a/app/src/organisms/EmergencyStop/EstopTakeover.tsx +++ b/app/src/organisms/EmergencyStop/EstopTakeover.tsx @@ -14,16 +14,33 @@ import { DISENGAGED, } from './constants' -const ESTOP_REFETCH_INTERVAL_MS = 10000 +const ESTOP_CURRENTLY_DISENGAGED_REFETCH_INTERVAL_MS = 10000 +const ESTOP_CURRENTLY_ENGAGED_REFETCH_INTERVAL_MS = 1000 interface EstopTakeoverProps { robotName?: string } export function EstopTakeover({ robotName }: EstopTakeoverProps): JSX.Element { + const [estopEngaged, setEstopEngaged] = React.useState(false) + const [ + isWaitingForLogicalDisengage, + setIsWaitingForLogicalDisengage, + ] = React.useState(false) const { data: estopStatus } = useEstopQuery({ - refetchInterval: ESTOP_REFETCH_INTERVAL_MS, + refetchInterval: estopEngaged + ? ESTOP_CURRENTLY_ENGAGED_REFETCH_INTERVAL_MS + : ESTOP_CURRENTLY_DISENGAGED_REFETCH_INTERVAL_MS, + onSuccess: response => { + setEstopEngaged( + [PHYSICALLY_ENGAGED || LOGICALLY_ENGAGED].includes( + response?.data.status + ) + ) + setIsWaitingForLogicalDisengage(false) + }, }) + const { isEmergencyStopModalDismissed, setIsEmergencyStopModalDismissed, @@ -47,6 +64,10 @@ export function EstopTakeover({ robotName }: EstopTakeoverProps): JSX.Element { closeModal={closeModal} isDismissedModal={isEmergencyStopModalDismissed} setIsDismissedModal={setIsEmergencyStopModalDismissed} + isWaitingForLogicalDisengage={isWaitingForLogicalDisengage} + setShouldSeeLogicalDisengage={() => { + setIsWaitingForLogicalDisengage(true) + }} /> ) case NOT_PRESENT: diff --git a/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx b/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx index 4a530858afe..2fd2733bea3 100644 --- a/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx +++ b/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx @@ -25,6 +25,8 @@ describe('EstopPressedModal - Touchscreen', () => { props = { isEngaged: true, closeModal: vi.fn(), + isWaitingForLogicalDisengage: false, + setShouldSeeLogicalDisengage: vi.fn(), } vi.mocked(getIsOnDevice).mockReturnValue(true) vi.mocked(useAcknowledgeEstopDisengageMutation).mockReturnValue({ @@ -69,6 +71,8 @@ describe('EstopPressedModal - Desktop', () => { closeModal: vi.fn(), isDismissedModal: false, setIsDismissedModal: vi.fn(), + isWaitingForLogicalDisengage: false, + setShouldSeeLogicalDisengage: vi.fn(), } vi.mocked(getIsOnDevice).mockReturnValue(false) vi.mocked(useAcknowledgeEstopDisengageMutation).mockReturnValue({ diff --git a/react-api-client/src/maintenance_runs/useCreateMaintenanceCommandMutation.ts b/react-api-client/src/maintenance_runs/useCreateMaintenanceCommandMutation.ts index ea05e64306a..be8faca4dc6 100644 --- a/react-api-client/src/maintenance_runs/useCreateMaintenanceCommandMutation.ts +++ b/react-api-client/src/maintenance_runs/useCreateMaintenanceCommandMutation.ts @@ -50,16 +50,21 @@ export function useCreateMaintenanceCommandMutation(): UseCreateMaintenanceComma createMaintenanceCommand(host as HostConfig, maintenanceRunId, command, { waitUntilComplete, timeout, - }).then(response => { - queryClient - .invalidateQueries([host, 'maintenance_runs']) - .catch((e: Error) => { - console.error( - `error invalidating maintenance runs query: ${e.message}` - ) - }) - return response.data }) + .then(response => { + queryClient + .invalidateQueries([host, 'maintenance_runs']) + .catch((e: Error) => { + console.error( + `error invalidating maintenance runs query: ${e.message}` + ) + }) + return response.data + }) + .catch((e: any) => { + queryClient.invalidateQueries([host, 'robot/control/estopStatus']) + throw e + }) ) return { diff --git a/react-api-client/src/maintenance_runs/useCreateMaintenanceRunMutation.ts b/react-api-client/src/maintenance_runs/useCreateMaintenanceRunMutation.ts index 298c0576587..5bdbf076f37 100644 --- a/react-api-client/src/maintenance_runs/useCreateMaintenanceRunMutation.ts +++ b/react-api-client/src/maintenance_runs/useCreateMaintenanceRunMutation.ts @@ -1,5 +1,5 @@ import { createMaintenanceRun } from '@opentrons/api-client' -import { useMutation } from 'react-query' +import { useMutation, useQueryClient } from 'react-query' import { useHost } from '../api' import type { AxiosError } from 'axios' import type { @@ -40,6 +40,7 @@ export function useCreateMaintenanceRunMutation( const contextHost = useHost() const host = hostOverride != null ? { ...contextHost, ...hostOverride } : contextHost + const queryClient = useQueryClient() const mutation = useMutation< MaintenanceRun, AxiosError, @@ -50,6 +51,7 @@ export function useCreateMaintenanceRunMutation( createMaintenanceRun(host as HostConfig, createMaintenanceRunData) .then(response => response.data) .catch(e => { + queryClient.invalidateQueries([host, 'robot/control/estopStatus']) throw e }), options diff --git a/react-api-client/src/robot/useAcknowledgeEstopDisengageMutation.ts b/react-api-client/src/robot/useAcknowledgeEstopDisengageMutation.ts index 90585699e80..b8790fd18ae 100644 --- a/react-api-client/src/robot/useAcknowledgeEstopDisengageMutation.ts +++ b/react-api-client/src/robot/useAcknowledgeEstopDisengageMutation.ts @@ -1,7 +1,7 @@ -import { useMutation } from 'react-query' +import { useMutation, useQueryClient } from 'react-query' import { acknowledgeEstopDisengage } from '@opentrons/api-client' import { useHost } from '../api' -import type { AxiosError } from 'axios' +import type { AxiosError, AxiosResponse } from 'axios' import type { UseMutationResult, UseMutateFunction, @@ -29,15 +29,23 @@ export function useAcknowledgeEstopDisengageMutation( const contextHost = useHost() const host = hostOverride != null ? { ...contextHost, ...hostOverride } : contextHost - + const queryClient = useQueryClient() const mutation = useMutation( [host, 'robot/control/acknowledgeEstopDisengage'], - () => - acknowledgeEstopDisengage(host as HostConfig) - .then(response => response.data) - .catch(e => { + () => { + return acknowledgeEstopDisengage(host as HostConfig) + .then((response: AxiosResponse) => { + queryClient.setQueryData( + [host, 'robot/control/estopStatus'], + response.data + ) + return response.data + }) + .catch((e: any) => { + queryClient.invalidateQueries([host, 'robot/control/estopStatus']) throw e - }), + }) + }, options ) diff --git a/react-api-client/src/runs/useRunQuery.ts b/react-api-client/src/runs/useRunQuery.ts index 4cb231eb5df..2f0b2fd71e5 100644 --- a/react-api-client/src/runs/useRunQuery.ts +++ b/react-api-client/src/runs/useRunQuery.ts @@ -1,9 +1,11 @@ import { getRun } from '@opentrons/api-client' -import { useQuery } from 'react-query' +import { useQuery, useQueryClient } from 'react-query' import { useHost } from '../api' +import { useEffect } from 'react' +import { some } from 'lodash' +import type { HostConfig, Run, RunError } from '@opentrons/api-client' import type { UseQueryResult, UseQueryOptions } from 'react-query' -import type { HostConfig, Run } from '@opentrons/api-client' export function useRunQuery( runId: string | null, @@ -13,6 +15,7 @@ export function useRunQuery( const contextHost = useHost() const host = hostOverride != null ? { ...contextHost, ...hostOverride } : contextHost + const queryClient = useQueryClient() const query = useQuery( [host, 'runs', runId, 'details'], () => @@ -25,5 +28,31 @@ export function useRunQuery( } ) + const estopInErrorTree = (error: RunError): boolean => + error?.errorCode === '3008' || + some( + (error?.wrappedErrors ?? []).map((wrapped: RunError) => + estopInErrorTree(wrapped) + ) + ) + + // If the run contains an estop error, invalidate the estop query so we get the + // estop modal as fast as we can + useEffect(() => { + if ( + query.data?.data?.current && + some( + ((query.data?.data?.errors ?? []) as RunError[]).map(estopInErrorTree) + ) + ) { + queryClient.invalidateQueries([host, '/robot/control']) + } + }, [ + runId, + query.isSuccess, + query.data?.data?.current, + query.data?.data?.errors, + ]) + return query } diff --git a/shared-data/command/schemas/9.json b/shared-data/command/schemas/9.json index 1cb30c99d69..546753b736d 100644 --- a/shared-data/command/schemas/9.json +++ b/shared-data/command/schemas/9.json @@ -71,7 +71,8 @@ "calibration/moveToMaintenancePosition": "#/definitions/MoveToMaintenancePositionCreate", "unsafe/blowOutInPlace": "#/definitions/UnsafeBlowOutInPlaceCreate", "unsafe/dropTipInPlace": "#/definitions/UnsafeDropTipInPlaceCreate", - "unsafe/updatePositionEstimators": "#/definitions/UpdatePositionEstimatorsCreate" + "unsafe/updatePositionEstimators": "#/definitions/UpdatePositionEstimatorsCreate", + "unsafe/engageAxes": "#/definitions/UnsafeEngageAxesCreate" } }, "oneOf": [ @@ -275,6 +276,9 @@ }, { "$ref": "#/definitions/UpdatePositionEstimatorsCreate" + }, + { + "$ref": "#/definitions/UnsafeEngageAxesCreate" } ], "definitions": { @@ -4374,6 +4378,51 @@ } }, "required": ["params"] + }, + "UnsafeEngageAxesParams": { + "title": "UnsafeEngageAxesParams", + "description": "Payload required for an UnsafeEngageAxes command.", + "type": "object", + "properties": { + "axes": { + "description": "The axes for which to enable.", + "type": "array", + "items": { + "$ref": "#/definitions/MotorAxis" + } + } + }, + "required": ["axes"] + }, + "UnsafeEngageAxesCreate": { + "title": "UnsafeEngageAxesCreate", + "description": "UnsafeEngageAxes command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/engageAxes", + "enum": ["unsafe/engageAxes"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UnsafeEngageAxesParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] } }, "$id": "opentronsCommandSchemaV9", diff --git a/shared-data/command/types/unsafe.ts b/shared-data/command/types/unsafe.ts index 8ff4d7e74aa..fd460f573b2 100644 --- a/shared-data/command/types/unsafe.ts +++ b/shared-data/command/types/unsafe.ts @@ -5,11 +5,13 @@ export type UnsafeRunTimeCommand = | UnsafeBlowoutInPlaceRunTimeCommand | UnsafeDropTipInPlaceRunTimeCommand | UnsafeUpdatePositionEstimatorsRunTimeCommand + | UnsafeEngageAxesRunTimeCommand export type UnsafeCreateCommand = | UnsafeBlowoutInPlaceCreateCommand | UnsafeDropTipInPlaceCreateCommand | UnsafeUpdatePositionEstimatorsCreateCommand + | UnsafeEngageAxesCreateCommand export interface UnsafeBlowoutInPlaceParams { pipetteId: string @@ -56,3 +58,17 @@ export interface UnsafeUpdatePositionEstimatorsRunTimeCommand UnsafeUpdatePositionEstimatorsCreateCommand { result?: any } + +export interface UnsafeEngageAxesParams { + axes: MotorAxes +} + +export interface UnsafeEngageAxesCreateCommand extends CommonCommandCreateInfo { + commandType: 'unsafe/engageAxes' + params: UnsafeUpdatePositionEstimatorsParams +} +export interface UnsafeEngageAxesRunTimeCommand + extends CommonCommandRunTimeInfo, + UnsafeEngageAxesCreateCommand { + result?: any +}