From 5819f298737d2698d01395ce4cdf9d096120b3de Mon Sep 17 00:00:00 2001 From: Brian Arthur Cooper Date: Wed, 7 Oct 2020 15:33:02 -0400 Subject: [PATCH] feat(robot-server,app): extend pipette offset cal to include tip length cal if needed (#6641) --- .../CalibratePipetteOffset/index.js | 5 + .../__tests__/CalibrateTipLength.test.js | 4 +- .../components/CalibrateTipLength/index.js | 4 +- .../components/CalibrateTipLength/styles.css | 187 ------------------ .../CalibrationPanels/CompleteConfirmation.js | 36 +++- .../components/CalibrationPanels/DeckSetup.js | 23 ++- .../MeasureNozzle.js | 10 +- .../MeasureTip.js | 37 ++-- .../CalibrationPanels/TipConfirmation.js | 16 +- .../__tests__/MeasureNozzle.test.js | 6 +- .../__tests__/MeasureTip.test.js | 8 +- .../__tests__/TipConfirmation.test.js | 2 +- app/src/components/CalibrationPanels/index.js | 2 + app/src/components/CalibrationPanels/types.js | 1 + .../PipetteOffsetCalibrationControl.js | 5 +- .../pipette-offset-calibration.js | 3 + .../sessions/common-calibration/constants.js | 3 + .../pipette-offset-calibration/constants.js | 5 + .../pipette-offset-calibration/types.js | 3 + .../tip-length-calibration/constants.js | 8 +- .../robot/calibration/constants.py | 23 ++- .../robot_server/robot/calibration/errors.py | 6 + .../calibration/pipette_offset/constants.py | 26 ++- .../calibration/pipette_offset/models.py | 16 +- .../pipette_offset/state_machine.py | 117 ++++++++--- .../calibration/pipette_offset/user_flow.py | 185 ++++++++++++++--- .../robot/calibration/tip_length/constants.py | 26 --- .../calibration/tip_length/state_machine.py | 7 +- .../robot/calibration/tip_length/user_flow.py | 84 +++----- .../robot_server/robot/calibration/util.py | 48 ++++- .../service/session/models/command.py | 12 +- .../service/session/models/session.py | 2 +- .../pipette_offset_calibration.py | 19 +- .../test_tip_length_calibration.tavern.yaml | 1 + .../pipette_offset/test_user_flow.py | 29 ++- .../tip_length/test_state_machine.py | 7 +- .../calibration/tip_length/test_user_flow.py | 12 +- 37 files changed, 573 insertions(+), 415 deletions(-) rename app/src/components/{CalibrateTipLength => CalibrationPanels}/MeasureNozzle.js (94%) rename app/src/components/{CalibrateTipLength => CalibrationPanels}/MeasureTip.js (82%) rename app/src/components/{CalibrateTipLength => CalibrationPanels}/__tests__/MeasureNozzle.test.js (93%) rename app/src/components/{CalibrateTipLength => CalibrationPanels}/__tests__/MeasureTip.test.js (90%) diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index 8c1c3a7d902..cfd35ea495c 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -27,6 +27,8 @@ import { SaveXYPoint, CompleteConfirmation, ConfirmExitModal, + MeasureNozzle, + MeasureTip, } from '../CalibrationPanels' import type { StyleProps } from '@opentrons/components' @@ -69,6 +71,8 @@ const PANEL_BY_STEP: { } = { [Sessions.PIP_OFFSET_STEP_SESSION_STARTED]: Introduction, [Sessions.PIP_OFFSET_STEP_LABWARE_LOADED]: DeckSetup, + [Sessions.PIP_OFFSET_STEP_MEASURING_NOZZLE_OFFSET]: MeasureNozzle, + [Sessions.PIP_OFFSET_STEP_MEASURING_TIP_OFFSET]: MeasureTip, [Sessions.PIP_OFFSET_STEP_PREPARING_PIPETTE]: TipPickUp, [Sessions.PIP_OFFSET_STEP_INSPECTING_TIP]: TipConfirmation, [Sessions.PIP_OFFSET_STEP_JOGGING_TO_DECK]: SaveZPoint, @@ -168,6 +172,7 @@ export function CalibratePipetteOffset( mount={instrument?.mount.toLowerCase()} currentStep={currentStep} sessionType={session.sessionType} + hasCalibratedTipLength={session.details.hasCalibratedTipLength} /> {showConfirmExit && ( diff --git a/app/src/components/CalibrateTipLength/__tests__/CalibrateTipLength.test.js b/app/src/components/CalibrateTipLength/__tests__/CalibrateTipLength.test.js index 69811fde3fa..14a903cffe4 100644 --- a/app/src/components/CalibrateTipLength/__tests__/CalibrateTipLength.test.js +++ b/app/src/components/CalibrateTipLength/__tests__/CalibrateTipLength.test.js @@ -16,9 +16,9 @@ import { TipPickUp, TipConfirmation, CompleteConfirmation, + MeasureNozzle, + MeasureTip, } from '../../CalibrationPanels' -import { MeasureNozzle } from '../MeasureNozzle' -import { MeasureTip } from '../MeasureTip' import type { TipLengthCalibrationStep } from '../../../sessions/types' diff --git a/app/src/components/CalibrateTipLength/index.js b/app/src/components/CalibrateTipLength/index.js index e2144a88450..ff3a73295b1 100644 --- a/app/src/components/CalibrateTipLength/index.js +++ b/app/src/components/CalibrateTipLength/index.js @@ -32,9 +32,9 @@ import { TipConfirmation, CompleteConfirmation, ConfirmExitModal, + MeasureNozzle, + MeasureTip, } from '../CalibrationPanels' -import { MeasureNozzle } from './MeasureNozzle' -import { MeasureTip } from './MeasureTip' import type { CalibrateTipLengthParentProps } from './types' diff --git a/app/src/components/CalibrateTipLength/styles.css b/app/src/components/CalibrateTipLength/styles.css index 0b5d80ea667..40321c4bd15 100644 --- a/app/src/components/CalibrateTipLength/styles.css +++ b/app/src/components/CalibrateTipLength/styles.css @@ -1,197 +1,10 @@ @import '@opentrons/components'; -.success_status_icon { - width: 2.5rem; - margin-right: 0.75rem; - color: var(--c-success); -} - -.intro_header { - @apply --font-header-dark; - - margin-bottom: 1rem; - text-transform: uppercase; -} - -.intro_content { - @apply --font-body-2-dark; - - margin-bottom: 1.5rem; -} - .alert_modal_padding { padding: 4rem 1rem; } -.terminal_modal_contents, -.modal_contents { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - max-width: 48rem; - min-height: 14rem; - padding: 1rem; -} - -.terminal_modal_contents { - padding: 1rem 1.5rem; -} - -.complete_summary { - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - margin: 2rem 0; -} - -.continue_button { - margin: 1.5rem 5rem 1rem; -} - .block_image { max-height: 20rem; max-width: 16rem; } - -.required_tiprack { - width: 50%; - border: 1px solid var('--c-med-gray'); - padding: 0 1rem; - display: flex; - flex-direction: column; - align-items: center; -} - -.required_tiprack:not(:last-child) { - margin-right: 0.625rem; -} - -.tiprack_image_container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 1rem 0; - height: 70%; -} - -.tiprack_image { - width: 100%; - max-height: 100%; -} - -.tiprack_display_name { - font-size: var('--fs-body-2'); -} - -/* -TODO: BC 2020-04-01 consider abstracting this to a shared place - these styles were mostly copped from PD's VIEW MEASUREMENTs button - ideally these would both use the same component -*/ -.tiprack_measurements_link { - padding: 1rem 0.5rem; - flex: 0.6; - text-transform: uppercase; - text-align: center; - cursor: pointer; - text-decoration: none; - color: inherit; - font-size: var('--fs-body-2'); - - &:hover { - background-color: var(--c-bg-hover); - } -} - -.tipracks_note_header { - text-transform: uppercase; -} - -/* end copped styles */ - -.prompt { - flex: none; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.prompt_text { - @apply --font-header-light; - - font-weight: normal; - margin: 0.5rem 0; - text-align: center; -} - -.prompt_button { - display: block; - width: auto; - margin: 0.5rem 0 1rem 0; - padding-left: 3rem; - padding-right: 3rem; -} - -.page_content_dark { - display: flex; - padding: 1rem; - flex-direction: column; - align-items: center; - background-color: transparent; - height: 100%; -} - -.deck_map_wrapper { - flex: 1 1 0; - align-self: stretch; - display: flex; - background-color: var(--c-bg-light); - border-radius: 6px; -} - -.deck_map { - flex: 1; -} - -.labware_ui_wrapper { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - justify-content: flex-end; -} - -.display_name { - @apply --truncate; - - font-weight: var(--fw-semibold); - color: var(--c-font-light); - text-transform: uppercase; -} - -.command_button { - margin: 0 5rem; -} - -.pick_up_tip_confirmation_button { - margin-top: 1rem; - width: 80%; -} - -.step_check_video_wrapper { - margin-left: 1rem; -} - -.step_check_video { - max-width: 100%; - max-height: 15rem; -} - -.loading_spinner { - width: 7.5rem; - margin-bottom: 3rem; -} diff --git a/app/src/components/CalibrationPanels/CompleteConfirmation.js b/app/src/components/CalibrationPanels/CompleteConfirmation.js index 996db1d6596..5aec19b6469 100644 --- a/app/src/components/CalibrationPanels/CompleteConfirmation.js +++ b/app/src/components/CalibrationPanels/CompleteConfirmation.js @@ -32,6 +32,8 @@ const PIP_OFFSET_CAL_HEADER = 'Pipette Offset Calibration complete' const TIP_CAL_HEADER = 'Tip Length Calibration complete' const REMOVE_BLOCK = 'Remove Calibration Block from the deck.' const RETURN_TIP = 'Return tip to tip rack and exit' +const EXIT = 'exit' +const PROCEED_TO_PIP_OFFSET = 'continue to Pipette Offset Calibration' const contentsBySessionType: { [SessionType]: { headerText: string } } = { [Sessions.SESSION_TYPE_DECK_CALIBRATION]: { headerText: DECK_CAL_HEADER }, @@ -44,12 +46,19 @@ const contentsBySessionType: { [SessionType]: { headerText: string } } = { } export function CompleteConfirmation(props: CalibrationPanelProps): React.Node { - const { sessionType, calBlock } = props + const { + sessionType, + calBlock, + hasCalibratedTipLength, + cleanUpAndExit, + sendCommands, + } = props const { headerText } = contentsBySessionType[sessionType] - const exitSession = () => { - props.cleanUpAndExit() + const proceed = () => { + sendCommands({ command: Sessions.sharedCalCommands.MOVE_TO_DECK }) } + return ( )} - - - {RETURN_TIP} + + {hasCalibratedTipLength === false && ( + + {PROCEED_TO_PIP_OFFSET} + + )} + + {hasCalibratedTipLength === false ? EXIT : RETURN_TIP} diff --git a/app/src/components/CalibrationPanels/DeckSetup.js b/app/src/components/CalibrationPanels/DeckSetup.js index 6cf7d5c8049..64fe9b96eaf 100644 --- a/app/src/components/CalibrationPanels/DeckSetup.js +++ b/app/src/components/CalibrationPanels/DeckSetup.js @@ -42,18 +42,33 @@ const contentsBySessionType: { moveCommandString: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK, }, [Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION]: { - moveCommandString: Sessions.tipCalCommands.MOVE_TO_REFERENCE_POINT, + moveCommandString: Sessions.sharedCalCommands.MOVE_TO_REFERENCE_POINT, }, } export function DeckSetup(props: CalibrationPanelProps): React.Node { const deckDef = React.useMemo(() => getDeckDefinitions()['ot2_standard'], []) - const { tipRack, calBlock, sendCommands, sessionType } = props - const { moveCommandString } = contentsBySessionType[sessionType] + const { + tipRack, + calBlock, + sendCommands, + sessionType, + hasCalibratedTipLength, + } = props + + const isExtendedPipOffset = + sessionType === Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION && + hasCalibratedTipLength === false + + const lookupType = isExtendedPipOffset + ? Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION + : sessionType const proceed = () => { - sendCommands({ command: moveCommandString }) + sendCommands({ + command: contentsBySessionType[lookupType].moveCommandString, + }) } return ( diff --git a/app/src/components/CalibrateTipLength/MeasureNozzle.js b/app/src/components/CalibrationPanels/MeasureNozzle.js similarity index 94% rename from app/src/components/CalibrateTipLength/MeasureNozzle.js rename to app/src/components/CalibrationPanels/MeasureNozzle.js index 238c7684b7b..aebce8c0585 100644 --- a/app/src/components/CalibrateTipLength/MeasureNozzle.js +++ b/app/src/components/CalibrationPanels/MeasureNozzle.js @@ -25,9 +25,9 @@ import { import { JogControls } from '../JogControls' import * as Sessions from '../../sessions' import type { JogAxis, JogDirection, JogStep } from '../../http-api-client' -import type { CalibrationPanelProps } from '../CalibrationPanels/types' +import type { CalibrationPanelProps } from './types' -import { formatJogVector } from '../CalibrationPanels/utils' +import { formatJogVector } from './utils' import leftMultiBlockAsset from '../../assets/videos/tip-length-cal/Left_Multi_CalBlock_NO_TIP_(330x260)REV1.webm' import leftMultiTrashAsset from '../../assets/videos/tip-length-cal/Left_Multi_Trash_NO_TIP_(330x260)REV1.webm' import leftSingleBlockAsset from '../../assets/videos/tip-length-cal/Left_Single_CalBlock_NO_TIP_(330x260)REV1.webm' @@ -95,7 +95,7 @@ export function MeasureNozzle(props: CalibrationPanelProps): React.Node { const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { sendCommands({ - command: Sessions.tipCalCommands.JOG, + command: Sessions.sharedCalCommands.JOG, data: { vector: formatJogVector(axis, dir, step), }, @@ -104,8 +104,8 @@ export function MeasureNozzle(props: CalibrationPanelProps): React.Node { const proceed = () => { sendCommands( - { command: Sessions.tipCalCommands.SAVE_OFFSET }, - { command: Sessions.tipCalCommands.MOVE_TO_TIP_RACK } + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } ) } diff --git a/app/src/components/CalibrateTipLength/MeasureTip.js b/app/src/components/CalibrationPanels/MeasureTip.js similarity index 82% rename from app/src/components/CalibrateTipLength/MeasureTip.js rename to app/src/components/CalibrationPanels/MeasureTip.js index 159209a69a9..e1ba42f497a 100644 --- a/app/src/components/CalibrateTipLength/MeasureTip.js +++ b/app/src/components/CalibrationPanels/MeasureTip.js @@ -1,16 +1,21 @@ // @flow import * as React from 'react' +import { css } from 'styled-components' import { Box, Flex, - PrimaryButton, + PrimaryBtn, Text, ALIGN_CENTER, ALIGN_FLEX_START, + JUSTIFY_CENTER, BORDER_SOLID_LIGHT, DIRECTION_COLUMN, FONT_SIZE_BODY_2, POSITION_RELATIVE, + TEXT_TRANSFORM_UPPERCASE, + FONT_WEIGHT_SEMIBOLD, + FONT_SIZE_HEADER, SPACING_2, SPACING_3, SPACING_4, @@ -20,7 +25,6 @@ import * as Sessions from '../../sessions' import { JogControls } from '../JogControls' import type { JogAxis, JogDirection, JogStep } from '../../http-api-client' import type { CalibrationPanelProps } from '../CalibrationPanels/types' -import styles from './styles.css' import { formatJogVector } from '../CalibrationPanels/utils' import leftMultiBlockAsset from '../../assets/videos/tip-length-cal/Left_Multi_CalBlock_WITH_TIP_(330x260)REV1.webm' import leftMultiTrashAsset from '../../assets/videos/tip-length-cal/Left_Multi_Trash_WITH_TIP_(330x260)REV1.webm' @@ -89,7 +93,7 @@ export function MeasureTip(props: CalibrationPanelProps): React.Node { const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { sendCommands({ - command: Sessions.tipCalCommands.JOG, + command: Sessions.sharedCalCommands.JOG, data: { vector: formatJogVector(axis, dir, step), }, @@ -98,8 +102,8 @@ export function MeasureTip(props: CalibrationPanelProps): React.Node { const proceed = () => { sendCommands( - { command: Sessions.tipCalCommands.SAVE_OFFSET }, - { command: Sessions.tipCalCommands.MOVE_TO_TIP_RACK } + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } ) } @@ -112,7 +116,13 @@ export function MeasureTip(props: CalibrationPanelProps): React.Node { position={POSITION_RELATIVE} width="100%" > -

{HEADER}

+ + {HEADER} + -
+ -
+
- - + + {SAVE_NOZZLE_Z_AXIS} - + diff --git a/app/src/components/CalibrationPanels/TipConfirmation.js b/app/src/components/CalibrationPanels/TipConfirmation.js index 7f9105642e1..d186880b7d6 100644 --- a/app/src/components/CalibrationPanels/TipConfirmation.js +++ b/app/src/components/CalibrationPanels/TipConfirmation.js @@ -35,15 +35,21 @@ const contentsBySessionType: { }, [Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION]: { yesButtonText: YES_AND_MOVE_TO_MEASURE_TIP, - moveCommandString: Sessions.tipCalCommands.MOVE_TO_REFERENCE_POINT, + moveCommandString: Sessions.sharedCalCommands.MOVE_TO_REFERENCE_POINT, }, } export function TipConfirmation(props: CalibrationPanelProps): React.Node { - const { sendCommands, sessionType } = props + const { sendCommands, sessionType, hasCalibratedTipLength } = props - const { yesButtonText, moveCommandString } = contentsBySessionType[ - sessionType - ] + const isExtendedPipOffset = + sessionType === Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION && + hasCalibratedTipLength === false + + const lookupType = isExtendedPipOffset + ? Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION + : sessionType + + const { yesButtonText, moveCommandString } = contentsBySessionType[lookupType] const invalidateTip = () => { sendCommands({ command: Sessions.sharedCalCommands.INVALIDATE_TIP }) diff --git a/app/src/components/CalibrateTipLength/__tests__/MeasureNozzle.test.js b/app/src/components/CalibrationPanels/__tests__/MeasureNozzle.test.js similarity index 93% rename from app/src/components/CalibrateTipLength/__tests__/MeasureNozzle.test.js rename to app/src/components/CalibrationPanels/__tests__/MeasureNozzle.test.js index e8c522c72f5..201225b3310 100644 --- a/app/src/components/CalibrateTipLength/__tests__/MeasureNozzle.test.js +++ b/app/src/components/CalibrationPanels/__tests__/MeasureNozzle.test.js @@ -66,7 +66,7 @@ describe('MeasureNozzle', () => { wrapper.update() expect(mockSendCommands).toHaveBeenCalledWith({ - command: Sessions.tipCalCommands.JOG, + command: Sessions.sharedCalCommands.JOG, data: { vector: jogParamsByDirection[direction] }, }) }) @@ -83,8 +83,8 @@ describe('MeasureNozzle', () => { wrapper.update() expect(mockSendCommands).toHaveBeenCalledWith( - { command: Sessions.tipCalCommands.SAVE_OFFSET }, - { command: Sessions.tipCalCommands.MOVE_TO_TIP_RACK } + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } ) }) }) diff --git a/app/src/components/CalibrateTipLength/__tests__/MeasureTip.test.js b/app/src/components/CalibrationPanels/__tests__/MeasureTip.test.js similarity index 90% rename from app/src/components/CalibrateTipLength/__tests__/MeasureTip.test.js rename to app/src/components/CalibrationPanels/__tests__/MeasureTip.test.js index 99801b57324..3f4691e1175 100644 --- a/app/src/components/CalibrateTipLength/__tests__/MeasureTip.test.js +++ b/app/src/components/CalibrationPanels/__tests__/MeasureTip.test.js @@ -17,7 +17,7 @@ describe('MeasureTip', () => { const mockDeleteSession = jest.fn() const getContinueButton = wrapper => - wrapper.find('PrimaryButton[children="Save the tip length"]').find('button') + wrapper.find('button[title="saveTipLengthButton"]').find('button') const getJogButton = (wrapper, direction) => wrapper.find(`JogButton[name="${direction}"]`).find('button') @@ -66,7 +66,7 @@ describe('MeasureTip', () => { wrapper.update() expect(mockSendCommands).toHaveBeenCalledWith({ - command: Sessions.tipCalCommands.JOG, + command: Sessions.sharedCalCommands.JOG, data: { vector: jogParamsByDirection[direction] }, }) }) @@ -84,10 +84,10 @@ describe('MeasureTip', () => { expect(mockSendCommands).toHaveBeenCalledWith( { - command: Sessions.tipCalCommands.SAVE_OFFSET, + command: Sessions.sharedCalCommands.SAVE_OFFSET, }, { - command: Sessions.tipCalCommands.MOVE_TO_TIP_RACK, + command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK, } ) }) diff --git a/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js b/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js index b62ef3d7218..5293b21085e 100644 --- a/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js +++ b/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js @@ -91,7 +91,7 @@ describe('TipConfirmation', () => { wrapper.update() expect(mockSendCommands).toHaveBeenCalledWith({ - command: Sessions.tipCalCommands.MOVE_TO_REFERENCE_POINT, + command: Sessions.sharedCalCommands.MOVE_TO_REFERENCE_POINT, }) }) }) diff --git a/app/src/components/CalibrationPanels/index.js b/app/src/components/CalibrationPanels/index.js index 8ff44e7b45a..e2e634ff2e4 100644 --- a/app/src/components/CalibrationPanels/index.js +++ b/app/src/components/CalibrationPanels/index.js @@ -2,6 +2,8 @@ export { Introduction } from './Introduction' export { DeckSetup } from './DeckSetup' export { TipPickUp } from './TipPickUp' export { TipConfirmation } from './TipConfirmation' +export { MeasureNozzle } from './MeasureNozzle' +export { MeasureTip } from './MeasureTip' export { SaveZPoint } from './SaveZPoint' export { SaveXYPoint } from './SaveXYPoint' export { CompleteConfirmation } from './CompleteConfirmation' diff --git a/app/src/components/CalibrationPanels/types.js b/app/src/components/CalibrationPanels/types.js index 02bf12bbb30..5fb327cf535 100644 --- a/app/src/components/CalibrationPanels/types.js +++ b/app/src/components/CalibrationPanels/types.js @@ -20,4 +20,5 @@ export type CalibrationPanelProps = {| currentStep: CalibrationSessionStep, sessionType: SessionType, calBlock?: TipLengthCalibrationLabware | null, + hasCalibratedTipLength?: boolean | null, |} diff --git a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js index 5f4d9e830cf..1b0090e0da9 100644 --- a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js +++ b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js @@ -79,6 +79,9 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { : null )?.status === RobotApi.SUCCESS + const shouldCalibrateTipLength = true + const tipRackDefinition = null + React.useEffect(() => { if (shouldOpen) { setShowWizard(true) @@ -95,7 +98,7 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { Sessions.ensureSession( robotName, Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION, - { mount } + { mount, shouldCalibrateTipLength, tipRackDefinition } ) ) } diff --git a/app/src/sessions/__fixtures__/pipette-offset-calibration.js b/app/src/sessions/__fixtures__/pipette-offset-calibration.js index 6e72038a21c..55fb793fa89 100644 --- a/app/src/sessions/__fixtures__/pipette-offset-calibration.js +++ b/app/src/sessions/__fixtures__/pipette-offset-calibration.js @@ -25,8 +25,11 @@ export const mockPipetteOffsetCalibrationSessionDetails: PipetteOffsetCalibratio }, currentStep: 'labwareLoaded', labware: [mockPipetteOffsetTipRack], + hasCalibratedTipLength: false, } export const mockPipetteOffsetCalibrationSessionParams: PipetteOffsetCalibrationSessionParams = { mount: 'left', + shouldCalibrateTipLength: true, + tipRackDefinition: null, } diff --git a/app/src/sessions/common-calibration/constants.js b/app/src/sessions/common-calibration/constants.js index 1349d072589..6009c35c388 100644 --- a/app/src/sessions/common-calibration/constants.js +++ b/app/src/sessions/common-calibration/constants.js @@ -10,6 +10,8 @@ const MOVE_TO_TIP_RACK: 'calibration.moveToTipRack' = 'calibration.moveToTipRack' const MOVE_TO_POINT_ONE: 'calibration.moveToPointOne' = 'calibration.moveToPointOne' +const MOVE_TO_REFERENCE_POINT: 'calibration.moveToReferencePoint' = + 'calibration.moveToReferencePoint' const SAVE_OFFSET: 'calibration.saveOffset' = 'calibration.saveOffset' const EXIT: 'calibration.exitSession' = 'calibration.exitSession' @@ -22,6 +24,7 @@ export const sharedCalCommands = { MOVE_TO_DECK, MOVE_TO_TIP_RACK, MOVE_TO_POINT_ONE, + MOVE_TO_REFERENCE_POINT, SAVE_OFFSET, EXIT, } diff --git a/app/src/sessions/pipette-offset-calibration/constants.js b/app/src/sessions/pipette-offset-calibration/constants.js index e41a745b358..c4c23ac9b34 100644 --- a/app/src/sessions/pipette-offset-calibration/constants.js +++ b/app/src/sessions/pipette-offset-calibration/constants.js @@ -5,6 +5,11 @@ import { sharedCalCommands } from '../common-calibration/constants' export const PIP_OFFSET_STEP_SESSION_STARTED: 'sessionStarted' = 'sessionStarted' export const PIP_OFFSET_STEP_LABWARE_LOADED: 'labwareLoaded' = 'labwareLoaded' + +export const PIP_OFFSET_STEP_MEASURING_NOZZLE_OFFSET: 'measuringNozzleOffset' = + 'measuringNozzleOffset' +export const PIP_OFFSET_STEP_MEASURING_TIP_OFFSET: 'measuringTipOffset' = + 'measuringTipOffset' export const PIP_OFFSET_STEP_PREPARING_PIPETTE: 'preparingPipette' = 'preparingPipette' export const PIP_OFFSET_STEP_INSPECTING_TIP: 'inspectingTip' = 'inspectingTip' diff --git a/app/src/sessions/pipette-offset-calibration/types.js b/app/src/sessions/pipette-offset-calibration/types.js index 30e8211c15e..5f6ba4f9f06 100644 --- a/app/src/sessions/pipette-offset-calibration/types.js +++ b/app/src/sessions/pipette-offset-calibration/types.js @@ -42,10 +42,13 @@ export type PipetteOffsetCalibrationLabware = {| export type PipetteOffsetCalibrationSessionParams = {| mount: string, + shouldCalibrateTipLength: boolean, + tipRackDefinition: ?LabwareDefinition2, |} export type PipetteOffsetCalibrationSessionDetails = {| instrument: PipetteOffsetCalibrationInstrument, currentStep: PipetteOffsetCalibrationStep, labware: Array, + hasCalibratedTipLength: boolean, |} diff --git a/app/src/sessions/tip-length-calibration/constants.js b/app/src/sessions/tip-length-calibration/constants.js index 76083296ace..ae600465ceb 100644 --- a/app/src/sessions/tip-length-calibration/constants.js +++ b/app/src/sessions/tip-length-calibration/constants.js @@ -15,10 +15,4 @@ export const TIP_LENGTH_STEP_MEASURING_TIP_OFFSET: 'measuringTipOffset' = export const TIP_LENGTH_STEP_CALIBRATION_COMPLETE: 'calibrationComplete' = 'calibrationComplete' -const MOVE_TO_REFERENCE_POINT: 'calibration.tipLength.moveToReferencePoint' = - 'calibration.tipLength.moveToReferencePoint' - -export const tipCalCommands = { - ...sharedCalCommands, - MOVE_TO_REFERENCE_POINT, -} +export const tipCalCommands = sharedCalCommands diff --git a/robot-server/robot_server/robot/calibration/constants.py b/robot-server/robot_server/robot/calibration/constants.py index 88bd4ab315c..3267b86ebb4 100644 --- a/robot-server/robot_server/robot/calibration/constants.py +++ b/robot-server/robot_server/robot/calibration/constants.py @@ -2,7 +2,7 @@ from typing import Dict, Set, TYPE_CHECKING from dataclasses import dataclass -from opentrons.types import Point +from opentrons.types import Point, Mount if TYPE_CHECKING: from typing_extensions import Final @@ -28,6 +28,13 @@ ALLOWED_SESSIONS = {'check'} +@dataclass +class LabwareInfo: + slot: str + load_name: str + well: str + + @dataclass class LabwareLookUp: load_name: str @@ -74,3 +81,17 @@ class LabwareLookUp: MOVE_TO_TIP_RACK_SAFETY_BUFFER = Point(0, 0, 10) MOVE_TO_POINT_SAFETY_BUFFER = Point(0, 0, 5) MOVE_TO_DECK_SAFETY_BUFFER = Point(0, 10, 5) +MOVE_TO_REF_POINT_SAFETY_BUFFER = Point(0, 0, 5) + +TRASH_WELL = 'A1' +TRASH_REF_POINT_OFFSET = Point(-57.84, -55, 0) # offset from center of trash + +CAL_BLOCK_SETUP_BY_MOUNT: Dict[Mount, LabwareInfo] = { + Mount.LEFT: LabwareInfo( + load_name='opentrons_calibrationblock_short_side_right', + slot='3', + well='A1'), + Mount.RIGHT: LabwareInfo( + load_name='opentrons_calibrationblock_short_side_left', + slot='1', + well='A2')} diff --git a/robot-server/robot_server/robot/calibration/errors.py b/robot-server/robot_server/robot/calibration/errors.py index ed50f99a438..c5684293a25 100644 --- a/robot-server/robot_server/robot/calibration/errors.py +++ b/robot-server/robot_server/robot/calibration/errors.py @@ -26,6 +26,12 @@ class CalibrationError(ErrorDef): title='No State Transition', format_string='No transition available for state {state}' ) + UNMET_STATE_TRANSITION_REQ = ErrorCreateDef( + status_code=HTTPStatus.CONFLICT, + title='Unmet State Transition Requirement', + format_string='The command handler {handler} may not occur in the' + ' state {state} when "{condition}" is not true' + ) ERROR_DURING_TRANSITION = ErrorCreateDef( status_code=HTTPStatus.INTERNAL_SERVER_ERROR, title='Error During State Transition', diff --git a/robot-server/robot_server/robot/calibration/pipette_offset/constants.py b/robot-server/robot_server/robot/calibration/pipette_offset/constants.py index 677e6ac0de6..9fe2326183a 100644 --- a/robot-server/robot_server/robot/calibration/pipette_offset/constants.py +++ b/robot-server/robot_server/robot/calibration/pipette_offset/constants.py @@ -8,15 +8,39 @@ from typing_extensions import Final -class PipetteOffsetCalibrationState(str, Enum): +class GenericState(str, Enum): + def __getattr__(self, name: str): + # We need to define this method + # to ensure that mypy will + # understand the defined attributes + # on the subclasses. + return getattr(self.__class__, name) + + +class PipetteOffsetCalibrationState(GenericState): + sessionStarted = "sessionStarted" + labwareLoaded = "labwareLoaded" + preparingPipette = "preparingPipette" + inspectingTip = "inspectingTip" + joggingToDeck = "joggingToDeck" + savingPointOne = "savingPointOne" + calibrationComplete = "calibrationComplete" + sessionExited = "sessionExited" + WILDCARD = STATE_WILDCARD + + +class PipetteOffsetWithTipLengthCalibrationState(GenericState): sessionStarted = "sessionStarted" labwareLoaded = "labwareLoaded" + measuringNozzleOffset = "measuringNozzleOffset" preparingPipette = "preparingPipette" inspectingTip = "inspectingTip" + measuringTipOffset = "measuringTipOffset" joggingToDeck = "joggingToDeck" savingPointOne = "savingPointOne" calibrationComplete = "calibrationComplete" sessionExited = "sessionExited" + tipLengthComplete = "tipLengthComplete" WILDCARD = STATE_WILDCARD diff --git a/robot-server/robot_server/robot/calibration/pipette_offset/models.py b/robot-server/robot_server/robot/calibration/pipette_offset/models.py index cc67d32f2ea..d74dcc5deb9 100644 --- a/robot-server/robot_server/robot/calibration/pipette_offset/models.py +++ b/robot-server/robot_server/robot/calibration/pipette_offset/models.py @@ -12,6 +12,14 @@ class SessionCreateParams(BaseModel): ..., description='The mount on which the pipette is attached, left or right' ) + shouldCalibrateTipLength: bool = Field( + True, + description='Whether tiplength for this tiprack should be loaded' + ) + tipRackDefinition: Optional[dict] = Field( + None, + description='The full labware definition of the tip rack to calibrate.' + ) class PipetteOffsetCalibrationSessionStatus(BaseModel): @@ -20,9 +28,12 @@ class PipetteOffsetCalibrationSessionStatus(BaseModel): currentStep: str = Field( ..., description="Current step of pipette offset user flow") + labware: List[RequiredLabware] + hasCalibratedTipLength: bool =\ + Field(None, description="Does tip length calibration data exist for " + "this pipette and tip rack combination") nextSteps: Optional[NextSteps] =\ Field(None, description="Next Available Steps in Session") - labware: List[RequiredLabware] class Config: schema_extra = { @@ -50,7 +61,8 @@ class Config: "isTiprack": "true", "definition": {"ordering": "the ordering section..."} }, - ] + ], + "hasCalibratedTipLength": True, } ] } diff --git a/robot-server/robot_server/robot/calibration/pipette_offset/state_machine.py b/robot-server/robot_server/robot/calibration/pipette_offset/state_machine.py index 1fdf4089f6b..f54defa6c10 100644 --- a/robot-server/robot_server/robot/calibration/pipette_offset/state_machine.py +++ b/robot-server/robot_server/robot/calibration/pipette_offset/state_machine.py @@ -4,38 +4,89 @@ CommandDefinition, CalibrationCommand) from robot_server.robot.calibration.util import ( SimpleStateMachine, StateTransitionError) -from .constants import PipetteOffsetCalibrationState as State +from .constants import ( + PipetteOffsetCalibrationState as POCState, + PipetteOffsetWithTipLengthCalibrationState as POWTState) +PipetteOffsetTransitions =\ + Dict[POCState, Dict[CommandDefinition, POCState]] +PipetteOffsetWithTLTransitions =\ + Dict[POWTState, Dict[CommandDefinition, POWTState]] +PIP_OFFSET_CAL_TRANSITIONS: PipetteOffsetTransitions = { + POCState.sessionStarted: { + CalibrationCommand.load_labware: POCState.labwareLoaded + }, + POCState.labwareLoaded: { + CalibrationCommand.move_to_tip_rack: POCState.preparingPipette, + }, + POCState.preparingPipette: { + CalibrationCommand.jog: POCState.preparingPipette, + CalibrationCommand.pick_up_tip: POCState.inspectingTip, + }, + POCState.inspectingTip: { + CalibrationCommand.invalidate_tip: POCState.preparingPipette, + CalibrationCommand.move_to_deck: POCState.joggingToDeck, + }, + POCState.joggingToDeck: { + CalibrationCommand.jog: POCState.joggingToDeck, + CalibrationCommand.save_offset: POCState.joggingToDeck, + CalibrationCommand.move_to_point_one: POCState.savingPointOne, + }, + POCState.savingPointOne: { + CalibrationCommand.jog: POCState.savingPointOne, + CalibrationCommand.save_offset: POCState.calibrationComplete, + }, + POCState.calibrationComplete: { + CalibrationCommand.move_to_tip_rack: POCState.calibrationComplete, + }, + POCState.WILDCARD: { + CalibrationCommand.exit: POCState.sessionExited + } +} -PIP_OFFSET_CAL_TRANSITIONS: Dict[State, Dict[CommandDefinition, State]] = { - State.sessionStarted: { - CalibrationCommand.load_labware: State.labwareLoaded +PIP_OFFSET_WITH_TL_TRANSITIONS: PipetteOffsetWithTLTransitions = { + POWTState.sessionStarted: { + CalibrationCommand.load_labware: POWTState.labwareLoaded + }, + POWTState.labwareLoaded: { + CalibrationCommand.move_to_reference_point: + POWTState.measuringNozzleOffset + }, + POWTState.measuringNozzleOffset: { + CalibrationCommand.save_offset: POWTState.measuringNozzleOffset, + CalibrationCommand.jog: POWTState.measuringNozzleOffset, + CalibrationCommand.move_to_tip_rack: POWTState.preparingPipette + }, + POWTState.preparingPipette: { + CalibrationCommand.jog: POWTState.preparingPipette, + CalibrationCommand.pick_up_tip: POWTState.inspectingTip, }, - State.labwareLoaded: { - CalibrationCommand.move_to_tip_rack: State.preparingPipette + POWTState.inspectingTip: { + CalibrationCommand.invalidate_tip: POWTState.preparingPipette, + CalibrationCommand.move_to_reference_point: + POWTState.measuringTipOffset, }, - State.preparingPipette: { - CalibrationCommand.jog: State.preparingPipette, - CalibrationCommand.pick_up_tip: State.inspectingTip, + POWTState.measuringTipOffset: { + CalibrationCommand.jog: POWTState.measuringTipOffset, + CalibrationCommand.save_offset: POWTState.tipLengthComplete }, - State.inspectingTip: { - CalibrationCommand.invalidate_tip: State.preparingPipette, - CalibrationCommand.move_to_deck: State.joggingToDeck, + POWTState.tipLengthComplete: { + CalibrationCommand.move_to_deck: POWTState.joggingToDeck, }, - State.joggingToDeck: { - CalibrationCommand.jog: State.joggingToDeck, - CalibrationCommand.save_offset: State.joggingToDeck, - CalibrationCommand.move_to_point_one: State.savingPointOne, + POWTState.joggingToDeck: { + CalibrationCommand.jog: POWTState.joggingToDeck, + CalibrationCommand.save_offset: POWTState.joggingToDeck, + CalibrationCommand.move_to_point_one: POWTState.savingPointOne, }, - State.savingPointOne: { - CalibrationCommand.jog: State.savingPointOne, - CalibrationCommand.save_offset: State.calibrationComplete, + POWTState.savingPointOne: { + CalibrationCommand.jog: POWTState.savingPointOne, + CalibrationCommand.save_offset: POWTState.calibrationComplete, }, - State.calibrationComplete: { - CalibrationCommand.move_to_tip_rack: State.calibrationComplete + POWTState.calibrationComplete: { + CalibrationCommand.move_to_tip_rack: POWTState.calibrationComplete, }, - State.WILDCARD: { - CalibrationCommand.exit: State.sessionExited + POWTState.WILDCARD: { + CalibrationCommand.exit: POWTState.sessionExited } } @@ -43,11 +94,27 @@ class PipetteOffsetCalibrationStateMachine: def __init__(self): self._state_machine = SimpleStateMachine( - states=set(s for s in State), + states=set(s for s in POCState), transitions=PIP_OFFSET_CAL_TRANSITIONS ) - def get_next_state(self, from_state: State, command: CommandDefinition): + def get_next_state(self, from_state: POCState, command: CommandDefinition): + next_state = self._state_machine.get_next_state(from_state, command) + if next_state: + return next_state + else: + raise StateTransitionError(command, from_state) + + +class PipetteOffsetWithTipLengthStateMachine: + def __init__(self): + self._state_machine = SimpleStateMachine( + states=set(s for s in POWTState), + transitions=PIP_OFFSET_WITH_TL_TRANSITIONS, + ) + + def get_next_state( + self, from_state: POWTState, command: CommandDefinition): next_state = self._state_machine.get_next_state(from_state, command) if next_state: return next_state diff --git a/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py b/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py index f3bf4c48506..2b6fb4ec396 100644 --- a/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py +++ b/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py @@ -1,5 +1,7 @@ import logging -from typing import Any, Awaitable, Callable, Dict, List, Optional +from typing import ( + Any, Awaitable, Callable, Dict, + List, Optional, Union, TYPE_CHECKING) from opentrons.calibration_storage import get, modify, helpers from opentrons.calibration_storage.types import TipLengthCalNotFound @@ -9,21 +11,29 @@ from opentrons.protocols.geometry import deck from opentrons.types import Mount, Point, Location from robot_server.service.errors import RobotServerError +from robot_server.service.session.models.command import \ + CalibrationCommand +from robot_server.robot.calibration import util from robot_server.robot.calibration.constants import ( TIP_RACK_LOOKUP_BY_MAX_VOL, SHORT_TRASH_DECK, STANDARD_DECK, POINT_ONE_ID, MOVE_TO_DECK_SAFETY_BUFFER, - MOVE_TO_TIP_RACK_SAFETY_BUFFER) -import robot_server.robot.calibration.util as uf -from robot_server.service.session.models.command import \ - CalibrationCommand + MOVE_TO_TIP_RACK_SAFETY_BUFFER, + CAL_BLOCK_SETUP_BY_MOUNT) from ..errors import CalibrationError from ..helper_classes import (RequiredLabware, AttachedPipette) -from .constants import (PipetteOffsetCalibrationState as State, - TIP_RACK_SLOT, JOG_TO_DECK_SLOT) -from .state_machine import PipetteOffsetCalibrationStateMachine +from .constants import ( + PipetteOffsetCalibrationState as POCState, + PipetteOffsetWithTipLengthCalibrationState as POWTState, + GenericState, TIP_RACK_SLOT, JOG_TO_DECK_SLOT) +from .state_machine import ( + PipetteOffsetCalibrationStateMachine, + PipetteOffsetWithTipLengthStateMachine) + +if TYPE_CHECKING: + from opentrons_shared_data.labware import LabwareDefinition MODULE_LOG = logging.getLogger(__name__) @@ -38,14 +48,21 @@ COMMAND_HANDLER = Callable[..., Awaitable] COMMAND_MAP = Dict[str, COMMAND_HANDLER] +PipetteOffsetStateMachine = Union[ + PipetteOffsetCalibrationStateMachine, + PipetteOffsetWithTipLengthStateMachine] class PipetteOffsetCalibrationUserFlow: def __init__(self, hardware: ThreadManager, - mount: Mount = Mount.RIGHT): + mount: Mount = Mount.RIGHT, + load_tip_length: bool = True, + tip_rack_def: Optional['LabwareDefinition'] = None): + self._hardware = hardware self._mount = mount + self._has_calibration_block = False self._hw_pipette = self._hardware._attached_instruments[mount] if not self._hw_pipette: raise RobotServerError( @@ -55,20 +72,36 @@ def __init__(self, deck_load_name = SHORT_TRASH_DECK if ff.short_fixed_trash() \ else STANDARD_DECK self._deck = deck.Deck(load_name=deck_load_name) - self._tip_rack = self._get_tip_rack_lw() - self._deck[TIP_RACK_SLOT] = self._tip_rack - self._current_state = State.sessionStarted - self._state_machine = PipetteOffsetCalibrationStateMachine() + self._saved_offset_this_session = False point_one_pos = \ self._deck.get_calibration_position(POINT_ONE_ID).position self._cal_ref_point = Point(*point_one_pos) self._tip_origin_pt: Optional[Point] = None + self._nozzle_height_at_reference: Optional[float] = None + + self._using_default_tiprack = False + self._load_tiprack(tip_rack_def) + self._has_calibrated_tip_length: bool =\ + (self._get_stored_tip_length_cal() is not None + or self._using_default_tiprack) + + if not load_tip_length: + self._state_machine: PipetteOffsetStateMachine =\ + PipetteOffsetWithTipLengthStateMachine() + self._state: GenericState = POWTState # type: ignore + else: + self._state_machine =\ + PipetteOffsetCalibrationStateMachine() + self._state = POCState # type: ignore + self._current_state = self._state.sessionStarted self._command_map: COMMAND_MAP = { CalibrationCommand.load_labware: self.load_labware, + CalibrationCommand.move_to_reference_point: + self.move_to_reference_point, CalibrationCommand.jog: self.jog, CalibrationCommand.pick_up_tip: self.pick_up_tip, CalibrationCommand.invalidate_tip: self.invalidate_tip, @@ -84,9 +117,18 @@ def hardware(self) -> ThreadManager: return self._hardware @property - def current_state(self) -> State: + def current_state(self) -> GenericState: + # Currently, mypy can't interpret enum + # values being saved as variables. Although + # using python's built-in typing methods + # correctly reveals that this is an enum, + # mypy believes it is a string. return self._current_state + @property + def has_calibrated_tip_length(self) -> bool: + return self._has_calibrated_tip_length + def get_pipette(self) -> AttachedPipette: # TODO(mc, 2020-09-17): s/tip_length/tipLength return AttachedPipette( # type: ignore[call-arg] @@ -99,7 +141,7 @@ def get_pipette(self) -> AttachedPipette: def get_required_labware(self) -> List[RequiredLabware]: return [RequiredLabware.from_lw(self._tip_rack)] - def _set_current_state(self, to_state: State): + def _set_current_state(self, to_state: GenericState): self._current_state = to_state def _get_tip_rack_lw(self) -> labware.Labware: @@ -142,30 +184,97 @@ async def _get_current_point( async def load_labware(self): pass + async def set_has_calibration_block(self, has_calibration_block: bool): + self._has_calibration_block = has_calibration_block + if self._has_calibration_block: + self._load_calibration_block() + else: + slot = CAL_BLOCK_SETUP_BY_MOUNT[self._mount].slot + self._deck[slot] = None # type: ignore + async def jog(self, vector): await self._hardware.move_rel(mount=self._mount, delta=Point(*vector)) async def move_to_tip_rack(self): + if self._current_state == self._state.labwareLoaded and \ + not self._has_calibrated_tip_length: + self._flag_unmet_transition_req( + command_handler="move_to_tip_rack", + unmet_condition="tip length calibration data exists") point = self._tip_rack.wells()[0].top().point + \ - MOVE_TO_TIP_RACK_SAFETY_BUFFER + MOVE_TO_TIP_RACK_SAFETY_BUFFER to_loc = Location(point, None) await self._move(to_loc) - def _get_tip_length(self) -> float: + def _get_stored_tip_length_cal(self) -> Optional[float]: try: return get.load_tip_length_calibration( - self._hw_pipette.pipette_id, # type: ignore + self._hw_pipette.pipette_id, self._tip_rack._definition, '')['tipLength'] except TipLengthCalNotFound: + return None + + def _get_tip_length(self) -> float: + stored_tip_length_cal = self._get_stored_tip_length_cal() + if stored_tip_length_cal is None: tip_overlap = self._hw_pipette.config.tip_overlap.get( self._tip_rack.uri, self._hw_pipette.config.tip_overlap['default']) tip_length = self._tip_rack.tip_length return tip_length - tip_overlap + else: + return stored_tip_length_cal + + def _load_tiprack(self, + tip_rack_def: Optional['LabwareDefinition'] = None): + """ + load onto the deck the default opentrons tip rack labware for this + pipette and return the tip rack labware. If tip_rack_def is supplied, + load specific tip rack from def onto the deck and return the labware. + """ + if tip_rack_def: + tr_lw = labware.load_from_definition( + tip_rack_def, + self._deck.position_for(TIP_RACK_SLOT)) + else: + pip_vol = self._hw_pipette.config.max_volume + tr_load_name = TIP_RACK_LOOKUP_BY_MAX_VOL[str(pip_vol)].load_name + tr_lw = labware.load(tr_load_name, + self._deck.position_for(TIP_RACK_SLOT)) + self._using_default_tiprack = True + self._tip_rack = tr_lw + self._deck[TIP_RACK_SLOT] = self._tip_rack + + def _load_calibration_block(self): + cb_setup = CAL_BLOCK_SETUP_BY_MOUNT[self._mount] + self._deck[cb_setup.slot] = labware.load( + cb_setup.load_name, + self._deck.position_for(cb_setup.slot)) + + def _flag_unmet_transition_req(self, command_handler: str, + unmet_condition: str): + raise RobotServerError( + definition=CalibrationError.UNMET_STATE_TRANSITION_REQ, + handler=command_handler, + state=self._current_state, + condition=unmet_condition) async def move_to_deck(self): + if not self._has_calibrated_tip_length and \ + self._current_state == self._state.inspectingTip: + self._flag_unmet_transition_req( + command_handler="move_to_deck", + unmet_condition="tip length calibration data exists") + if self._current_state == self._state.calibrationComplete: + # recache tip length cal which has just been saved + self._has_calibrated_tip_length =\ + (self._get_stored_tip_length_cal() is not None) + if self._saved_offset_this_session: + self._flag_unmet_transition_req( + command_handler="move_to_deck", + unmet_condition="offset not saved this session") deck_pt = self._deck.get_slot_center(JOG_TO_DECK_SLOT) ydim = self._deck.get_slot_definition( JOG_TO_DECK_SLOT)['boundingBox']['yDimension'] @@ -184,9 +293,9 @@ async def move_to_point_one(self): async def save_offset(self): cur_pt = await self._get_current_point(critical_point=None) - if self.current_state == State.joggingToDeck: + if self.current_state == self._state.joggingToDeck: self._z_height_reference = cur_pt.z - elif self._current_state == State.savingPointOne: + elif self._current_state == self._state.savingPointOne: if self._hw_pipette.config.channels > 1: cur_pt = await self._get_current_point( critical_point=CriticalPoint.FRONT_NOZZLE) @@ -199,18 +308,46 @@ async def save_offset(self): pip_id=self._hw_pipette.pipette_id, tiprack_hash=tiprack_hash, tiprack_uri=self._tip_rack.uri) + self._saved_offset_this_session = True + elif isinstance(self._state, POWTState)\ + and self._current_state == self._state.measuringNozzleOffset: + self._nozzle_height_at_reference = cur_pt.z + elif isinstance(self._state, POWTState)\ + and self._current_state == self._state.measuringTipOffset: + assert self._hw_pipette.has_tip + assert self._nozzle_height_at_reference is not None + # set critical point explicitly to nozzle + noz_pt = await self._get_current_point( + critical_point=CriticalPoint.NOZZLE) + util.save_tip_length_calibration( + pipette_id=self._hw_pipette.pipette_id, + tip_length_offset=noz_pt.z - self._nozzle_height_at_reference, + tip_rack=self._tip_rack) + + async def move_to_reference_point(self): + if self._has_calibrated_tip_length and \ + self._current_state in (self._state.labwareLoaded, + self._state.inspectingTip): + self._flag_unmet_transition_req( + command_handler="move_to_reference_point", + unmet_condition="missing tip length calibration") + ref_loc = util.get_reference_location( + mount=self._mount, + deck=self._deck, + has_calibration_block=self._has_calibration_block) + await self._move(ref_loc) async def pick_up_tip(self): - await uf.pick_up_tip(self, tip_length=self._get_tip_length()) + await util.pick_up_tip(self, tip_length=self._get_tip_length()) async def invalidate_tip(self): - await uf.invalidate_tip(self) + await util.invalidate_tip(self) async def _return_tip(self): - await uf.return_tip(self, tip_length=self._get_tip_length()) + await util.return_tip(self, tip_length=self._get_tip_length()) async def _move(self, to_loc: Location): - await uf.move(self, to_loc) + await util.move(self, to_loc) async def exit_session(self): await self.move_to_tip_rack() diff --git a/robot-server/robot_server/robot/calibration/tip_length/constants.py b/robot-server/robot_server/robot/calibration/tip_length/constants.py index 87c588d420f..89840d8eb4e 100644 --- a/robot-server/robot_server/robot/calibration/tip_length/constants.py +++ b/robot-server/robot_server/robot/calibration/tip_length/constants.py @@ -1,6 +1,4 @@ from enum import Enum -from typing import Dict -from opentrons.types import Point, Mount from robot_server.robot.calibration.constants import STATE_WILDCARD @@ -16,28 +14,4 @@ class TipCalibrationState(str, Enum): WILDCARD = STATE_WILDCARD -TRASH_WELL = 'A1' TIP_RACK_SLOT = '8' -LEFT_MOUNT_CAL_BLOCK_SLOT = '3' -LEFT_MOUNT_CAL_BLOCK_LOADNAME = 'opentrons_calibrationblock_short_side_right' -LEFT_MOUNT_CAL_BLOCK_WELL = 'A1' -RIGHT_MOUNT_CAL_BLOCK_SLOT = '1' -RIGHT_MOUNT_CAL_BLOCK_LOADNAME = 'opentrons_calibrationblock_short_side_left' -RIGHT_MOUNT_CAL_BLOCK_WELL = 'A2' - -TRASH_REF_POINT_OFFSET = Point(-57.84, -55, 0) # offset from center of trash -MOVE_TO_TIP_RACK_SAFETY_BUFFER = Point(0, 0, 10) -MOVE_TO_REF_POINT_SAFETY_BUFFER = Point(0, 0, 5) - -CAL_BLOCK_SETUP_BY_MOUNT: Dict[Mount, Dict[str, str]] = { - Mount.LEFT: { - 'load_name': LEFT_MOUNT_CAL_BLOCK_LOADNAME, - 'slot': LEFT_MOUNT_CAL_BLOCK_SLOT, - 'well': LEFT_MOUNT_CAL_BLOCK_WELL, - }, - Mount.RIGHT: { - 'load_name': RIGHT_MOUNT_CAL_BLOCK_LOADNAME, - 'slot': RIGHT_MOUNT_CAL_BLOCK_SLOT, - 'well': RIGHT_MOUNT_CAL_BLOCK_WELL, - } -} diff --git a/robot-server/robot_server/robot/calibration/tip_length/state_machine.py b/robot-server/robot_server/robot/calibration/tip_length/state_machine.py index 0c85a7451af..3d55eea95aa 100644 --- a/robot-server/robot_server/robot/calibration/tip_length/state_machine.py +++ b/robot-server/robot_server/robot/calibration/tip_length/state_machine.py @@ -1,7 +1,6 @@ from typing import Dict from robot_server.service.session.models.command import ( - CommandDefinition, TipLengthCalibrationCommand as TipCalCommand, - CalibrationCommand) + CommandDefinition, CalibrationCommand) from robot_server.robot.calibration.util import ( SimpleStateMachine, StateTransitionError @@ -16,7 +15,7 @@ CalibrationCommand.load_labware: State.labwareLoaded }, State.labwareLoaded: { - TipCalCommand.move_to_reference_point: State.measuringNozzleOffset + CalibrationCommand.move_to_reference_point: State.measuringNozzleOffset }, State.measuringNozzleOffset: { CalibrationCommand.save_offset: State.measuringNozzleOffset, @@ -29,7 +28,7 @@ }, State.inspectingTip: { CalibrationCommand.invalidate_tip: State.preparingPipette, - TipCalCommand.move_to_reference_point: State.measuringTipOffset, + CalibrationCommand.move_to_reference_point: State.measuringTipOffset, }, State.measuringTipOffset: { CalibrationCommand.save_offset: State.measuringTipOffset, diff --git a/robot-server/robot_server/robot/calibration/tip_length/user_flow.py b/robot-server/robot_server/robot/calibration/tip_length/user_flow.py index 3188984b3d1..012a1022a18 100644 --- a/robot-server/robot_server/robot/calibration/tip_length/user_flow.py +++ b/robot-server/robot_server/robot/calibration/tip_length/user_flow.py @@ -4,37 +4,25 @@ TYPE_CHECKING) from opentrons.types import Mount, Point, Location from opentrons.config import feature_flags as ff -from opentrons.calibration_storage import modify from opentrons.hardware_control import ThreadManager, CriticalPoint from opentrons.protocol_api import labware from opentrons.protocols.geometry import deck -import robot_server.robot.calibration.util as uf +from robot_server.robot.calibration import util from robot_server.service.errors import RobotServerError -from robot_server.service.session.models.command import ( - CalibrationCommand, TipLengthCalibrationCommand) -from robot_server.robot.calibration.constants import ( + +from robot_server.service.session.models.command import CalibrationCommand +from ..errors import CalibrationError +from ..helper_classes import RequiredLabware, AttachedPipette +from ..constants import ( TIP_RACK_LOOKUP_BY_MAX_VOL, SHORT_TRASH_DECK, - STANDARD_DECK -) -from .state_machine import ( - TipCalibrationStateMachine -) -from .constants import ( - TipCalibrationState as State, - TRASH_WELL, - TIP_RACK_SLOT, + STANDARD_DECK, CAL_BLOCK_SETUP_BY_MOUNT, MOVE_TO_TIP_RACK_SAFETY_BUFFER, - MOVE_TO_REF_POINT_SAFETY_BUFFER, - TRASH_REF_POINT_OFFSET -) -from ..errors import CalibrationError -from ..helper_classes import ( - RequiredLabware, - AttachedPipette ) +from .constants import TipCalibrationState as State, TIP_RACK_SLOT +from .state_machine import TipCalibrationStateMachine if TYPE_CHECKING: from opentrons_shared_data.labware import LabwareDefinition @@ -86,7 +74,7 @@ def __init__(self, CalibrationCommand.pick_up_tip: self.pick_up_tip, CalibrationCommand.invalidate_tip: self.invalidate_tip, CalibrationCommand.save_offset: self.save_offset, - TipLengthCalibrationCommand.move_to_reference_point: self.move_to_reference_point, # noqa: E501 + CalibrationCommand.move_to_reference_point: self.move_to_reference_point, # noqa: E501 CalibrationCommand.move_to_tip_rack: self.move_to_tip_rack, # noqa: E501 CalibrationCommand.exit: self.exit_session, } @@ -151,24 +139,6 @@ async def move_to_tip_rack(self): else: await self._move(Location(pt_above_well, None)) - async def move_to_reference_point(self): - to_loc = self._get_reference_point() - await self._move(to_loc) - - def _get_reference_point(self) -> Location: - if self._has_calibration_block: - slot = CAL_BLOCK_SETUP_BY_MOUNT[self._mount]['slot'] - well = CAL_BLOCK_SETUP_BY_MOUNT[self._mount]['well'] - calblock: labware.Labware = self._deck[slot] # type: ignore - calblock_loc = calblock.wells_by_name()[well].top() - return calblock_loc.move(point=MOVE_TO_REF_POINT_SAFETY_BUFFER) - else: - trash = self._deck.get_fixed_trash() - assert trash - trash_loc = trash.wells_by_name()[TRASH_WELL].top() - return trash_loc.move(TRASH_REF_POINT_OFFSET + - MOVE_TO_REF_POINT_SAFETY_BUFFER) - async def save_offset(self): if self._current_state == State.measuringNozzleOffset: # critical point would default to nozzle for z height @@ -181,16 +151,11 @@ async def save_offset(self): # set critical point explicitly to nozzle cur_pt = await self._get_current_point( critical_point=CriticalPoint.NOZZLE) - tip_length_offset = cur_pt.z - self._nozzle_height_at_reference - # TODO: 07-22-2020 parent slot is not important when tracking - # tip length data, hence the empty string, we should remove it - # from create_tip_length_data in a refactor - tip_length_data = modify.create_tip_length_data( - self._tip_rack._definition, '', - tip_length_offset) - modify.save_tip_length_calibration(self._hw_pipette.pipette_id, - tip_length_data) + util.save_tip_length_calibration( + pipette_id=self._hw_pipette.pipette_id, + tip_length_offset=cur_pt.z - self._nozzle_height_at_reference, + tip_rack=self._tip_rack) def _get_default_tip_length(self) -> float: tiprack: labware.Labware = self._deck[TIP_RACK_SLOT] # type: ignore @@ -215,11 +180,18 @@ async def jog(self, vector): await self._hardware.move_rel(mount=self._mount, delta=Point(*vector)) + async def move_to_reference_point(self): + ref_loc = util.get_reference_location( + mount=self._mount, + deck=self._deck, + has_calibration_block=self._has_calibration_block) + await self._move(ref_loc) + async def pick_up_tip(self): - await uf.pick_up_tip(self, tip_length=self._get_default_tip_length()) + await util.pick_up_tip(self, tip_length=self._get_default_tip_length()) async def invalidate_tip(self): - await uf.invalidate_tip(self) + await util.invalidate_tip(self) async def exit_session(self): await self.move_to_tip_rack() @@ -243,12 +215,12 @@ def _initialize_deck(self): if self._has_calibration_block: cb_setup = CAL_BLOCK_SETUP_BY_MOUNT[self._mount] - self._deck[cb_setup['slot']] = labware.load( - cb_setup['load_name'], - self._deck.position_for(cb_setup['slot'])) + self._deck[cb_setup.slot] = labware.load( + cb_setup.load_name, + self._deck.position_for(cb_setup.slot)) async def _return_tip(self): - await uf.return_tip(self, tip_length=self._get_default_tip_length()) + await util.return_tip(self, tip_length=self._get_default_tip_length()) async def _move(self, to_loc: Location): - await uf.move(self, to_loc) + await util.move(self, to_loc) diff --git a/robot-server/robot_server/robot/calibration/util.py b/robot-server/robot_server/robot/calibration/util.py index 16f38273933..371704e4971 100644 --- a/robot-server/robot_server/robot/calibration/util.py +++ b/robot-server/robot_server/robot/calibration/util.py @@ -3,16 +3,21 @@ from opentrons.hardware_control import Pipette from opentrons.hardware_control.util import plan_arc +from opentrons.protocol_api import labware from opentrons.protocols.geometry import planning -from opentrons.types import Point, Location +from opentrons.protocols.geometry.deck import Deck +from opentrons.calibration_storage import modify +from opentrons.types import Point, Location, Mount from robot_server.service.errors import RobotServerError from robot_server.service.session.models.command import ( CommandDefinition) -from .constants import STATE_WILDCARD +from .constants import STATE_WILDCARD, CAL_BLOCK_SETUP_BY_MOUNT, \ + MOVE_TO_REF_POINT_SAFETY_BUFFER, TRASH_WELL, TRASH_REF_POINT_OFFSET from .errors import CalibrationError from .tip_length.constants import TipCalibrationState -from .pipette_offset.constants import PipetteOffsetCalibrationState +from .pipette_offset.constants import ( + PipetteOffsetCalibrationState, PipetteOffsetWithTipLengthCalibrationState) from .deck.constants import DeckCalibrationState if TYPE_CHECKING: @@ -21,7 +26,8 @@ from .pipette_offset.user_flow import PipetteOffsetCalibrationUserFlow ValidState = Union[TipCalibrationState, DeckCalibrationState, - PipetteOffsetCalibrationState] + PipetteOffsetCalibrationState, + PipetteOffsetWithTipLengthCalibrationState] class StateTransitionError(RobotServerError): @@ -147,3 +153,37 @@ async def move(user_flow: CalibrationUserFlow, to_loc: Location): await user_flow._hardware.move_to(mount=user_flow._mount, abs_position=move[0], critical_point=move[1]) + + +def get_reference_location(mount: Mount, + deck: Deck, + has_calibration_block: bool) -> Location: + """ + Get location of static z reference point. + Will be on Calibration Block if available, otherwise will be on + flat surface of fixed trash insert. + """ + if has_calibration_block: + slot = CAL_BLOCK_SETUP_BY_MOUNT[mount].slot + well = CAL_BLOCK_SETUP_BY_MOUNT[mount].well + calblock: labware.Labware = deck[slot] # type: ignore + calblock_loc = calblock.wells_by_name()[well].top() + ref_loc = calblock_loc.move(point=MOVE_TO_REF_POINT_SAFETY_BUFFER) + else: + trash = deck.get_fixed_trash() + assert trash + trash_loc = trash.wells_by_name()[TRASH_WELL].top() + ref_loc = trash_loc.move(TRASH_REF_POINT_OFFSET + + MOVE_TO_REF_POINT_SAFETY_BUFFER) + return ref_loc + + +def save_tip_length_calibration(pipette_id: str, + tip_length_offset: float, + tip_rack: labware.Labware): + # TODO: 07-22-2020 parent slot is not important when tracking + # tip length data, hence the empty string, we should remove it + # from create_tip_length_data in a refactor + tip_length_data = modify.create_tip_length_data(tip_rack._definition, '', + tip_length_offset) + modify.save_tip_length_calibration(pipette_id, tip_length_data) diff --git a/robot-server/robot_server/service/session/models/command.py b/robot-server/robot_server/service/session/models/command.py index fdf7736fcea..eb60694b964 100644 --- a/robot-server/robot_server/service/session/models/command.py +++ b/robot-server/robot_server/service/session/models/command.py @@ -168,9 +168,11 @@ class CalibrationCommand(CommandDefinition): """Shared Between Calibration Flows""" load_labware = "loadLabware" jog = ("jog", JogPosition) + set_has_calibration_block = ("setHasCalibrationBlock", bool) move_to_tip_rack = "moveToTipRack" move_to_point_one = "moveToPointOne" move_to_deck = "moveToDeck" + move_to_reference_point = "moveToReferencePoint" pick_up_tip = "pickUpTip" confirm_tip_attached = "confirmTip" invalidate_tip = "invalidateTip" @@ -195,15 +197,6 @@ def namespace(): return "calibration.check" -class TipLengthCalibrationCommand(CommandDefinition): - """Tip Length Calibration Specific""" - move_to_reference_point = "moveToReferencePoint" - - @staticmethod - def namespace(): - return "calibration.tipLength" - - class DeckCalibrationCommand(CommandDefinition): """Deck Calibration Specific""" move_to_point_two = "moveToPointTwo" @@ -234,7 +227,6 @@ def namespace(): RobotCommand, CalibrationCommand, CalibrationCheckCommand, - TipLengthCalibrationCommand, DeckCalibrationCommand, ProtocolCommand, PipetteCommand, diff --git a/robot-server/robot_server/service/session/models/session.py b/robot-server/robot_server/service/session/models/session.py index 4550f488325..8d6ac88e897 100644 --- a/robot-server/robot_server/service/session/models/session.py +++ b/robot-server/robot_server/service/session/models/session.py @@ -83,9 +83,9 @@ def model(self): """ SessionDetails = typing.Union[ CalibrationSessionStatus, + PipetteOffsetCalibrationSessionStatus, TipCalibrationSessionStatus, DeckCalibrationSessionStatus, - PipetteOffsetCalibrationSessionStatus, ProtocolSessionDetails, EmptyModel ] diff --git a/robot-server/robot_server/service/session/session_types/pipette_offset_calibration.py b/robot-server/robot_server/service/session/session_types/pipette_offset_calibration.py index eb5b8963063..a9a2c0a7ef1 100644 --- a/robot-server/robot_server/service/session/session_types/pipette_offset_calibration.py +++ b/robot-server/robot_server/service/session/session_types/pipette_offset_calibration.py @@ -1,4 +1,4 @@ -from typing import Awaitable +from typing import Awaitable, cast, TYPE_CHECKING from opentrons.types import Mount from robot_server.robot.calibration.pipette_offset.user_flow import \ PipetteOffsetCalibrationUserFlow @@ -14,6 +14,9 @@ from ..models.session import SessionType, SessionDetails from ..errors import UnsupportedFeature +if TYPE_CHECKING: + from opentrons_shared_data.labware import LabwareDefinition + class PipetteOffsetCalibrationCommandExecutor(CallableExecutor): @@ -42,6 +45,8 @@ async def create(cls, configuration: SessionConfiguration, instance_meta: SessionMetaData) -> 'BaseSession': assert isinstance(instance_meta.create_params, SessionCreateParams) mount = instance_meta.create_params.mount + load_tip_length = instance_meta.create_params.shouldCalibrateTipLength + tiprack = instance_meta.create_params.tipRackDefinition # if lights are on already it's because the user clicked the button, # so a) we don't need to turn them on now and b) we shouldn't turn them # off after @@ -50,7 +55,9 @@ async def create(cls, configuration: SessionConfiguration, try: pip_offset_cal_user_flow = PipetteOffsetCalibrationUserFlow( hardware=configuration.hardware, - mount=Mount[mount.upper()]) + mount=Mount[mount.upper()], + load_tip_length=load_tip_length, + tip_rack_def=cast('LabwareDefinition', tiprack)) except AssertionError as e: raise SessionCreationException(str(e)) @@ -78,10 +85,12 @@ def session_type(self) -> SessionType: return SessionType.pipette_offset_calibration def _get_response_details(self) -> SessionDetails: + uf = self._pip_offset_cal_user_flow return PipetteOffsetCalibrationSessionStatus( - instrument=self._pip_offset_cal_user_flow.get_pipette(), - currentStep=self._pip_offset_cal_user_flow.current_state, - labware=self._pip_offset_cal_user_flow.get_required_labware(), + instrument=uf.get_pipette(), + currentStep=uf.current_state, + labware=uf.get_required_labware(), + hasCalibratedTipLength=uf.has_calibrated_tip_length, ) async def clean_up(self): diff --git a/robot-server/tests/integration/sessions/test_tip_length_calibration.tavern.yaml b/robot-server/tests/integration/sessions/test_tip_length_calibration.tavern.yaml index 3cb7aab62b8..b29953b8817 100644 --- a/robot-server/tests/integration/sessions/test_tip_length_calibration.tavern.yaml +++ b/robot-server/tests/integration/sessions/test_tip_length_calibration.tavern.yaml @@ -42,6 +42,7 @@ stages: currentStep: sessionStarted instrument: !anydict labware: !anylist + nextSteps: null createParams: mount: left hasCalibrationBlock: true diff --git a/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py b/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py index e2349228519..d8efcef936c 100644 --- a/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py +++ b/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py @@ -1,5 +1,5 @@ import pytest -from unittest.mock import MagicMock, call +from unittest.mock import MagicMock, call, patch from typing import List, Tuple, Dict, Any from opentrons.calibration_storage import modify, helpers from opentrons.types import Mount, Point @@ -10,6 +10,8 @@ from robot_server.service.session.models.command import CalibrationCommand from robot_server.robot.calibration.pipette_offset.user_flow import \ PipetteOffsetCalibrationUserFlow +from robot_server.robot.calibration.pipette_offset.constants import\ + PipetteOffsetCalibrationState as POCState stub_jog_data = {'vector': Point(1, 1, 1)} @@ -100,6 +102,7 @@ async def gantry_pos_mock(*args, **kwargs): hardware.gantry_position = MagicMock(side_effect=gantry_pos_mock) hardware.move_to = MagicMock(side_effect=async_mock_move_to) hardware.get_instrument_max_height.return_value = 180 + return hardware @@ -107,17 +110,27 @@ async def gantry_pos_mock(*args, **kwargs): def mock_user_flow(mock_hw): mount = next(k for k, v in mock_hw._attached_instruments.items() if v) - m = PipetteOffsetCalibrationUserFlow(hardware=mock_hw, mount=mount) - yield m + mock_stored_tip_length = MagicMock(return_value=30) + with patch.object( + PipetteOffsetCalibrationUserFlow, + '_get_stored_tip_length_cal', + new=mock_stored_tip_length): + m = PipetteOffsetCalibrationUserFlow(hardware=mock_hw, mount=mount) + yield m hw_commands: List[Tuple[str, str, Dict[Any, Any], str]] = [ - (CalibrationCommand.jog, 'preparingPipette', stub_jog_data, 'move_rel'), - (CalibrationCommand.pick_up_tip, 'preparingPipette', {}, 'pick_up_tip'), - (CalibrationCommand.move_to_deck, 'inspectingTip', {}, 'move_to'), - (CalibrationCommand.move_to_point_one, 'joggingToDeck', {}, 'move_to'), - (CalibrationCommand.move_to_tip_rack, 'labwareLoaded', {}, 'move_to'), + (CalibrationCommand.jog, POCState.preparingPipette, + stub_jog_data, 'move_rel'), + (CalibrationCommand.pick_up_tip, POCState.preparingPipette, + {}, 'pick_up_tip'), + (CalibrationCommand.move_to_deck, POCState.inspectingTip, + {}, 'move_to'), + (CalibrationCommand.move_to_point_one, POCState.joggingToDeck, + {}, 'move_to'), + (CalibrationCommand.move_to_tip_rack, POCState.labwareLoaded, + {}, 'move_to'), ] diff --git a/robot-server/tests/robot/calibration/tip_length/test_state_machine.py b/robot-server/tests/robot/calibration/tip_length/test_state_machine.py index d7f4271c0f0..93c732847d8 100644 --- a/robot-server/tests/robot/calibration/tip_length/test_state_machine.py +++ b/robot-server/tests/robot/calibration/tip_length/test_state_machine.py @@ -1,14 +1,13 @@ import pytest from typing import List, Tuple -from robot_server.service.session.models.command import ( - CalibrationCommand, TipLengthCalibrationCommand) +from robot_server.service.session.models.command import CalibrationCommand from robot_server.robot.calibration.tip_length.state_machine import \ TipCalibrationStateMachine valid_commands: List[Tuple[str, str, str]] = [ (CalibrationCommand.load_labware, 'sessionStarted', 'labwareLoaded'), - (TipLengthCalibrationCommand.move_to_reference_point, 'labwareLoaded', + (CalibrationCommand.move_to_reference_point, 'labwareLoaded', 'measuringNozzleOffset'), (CalibrationCommand.jog, 'measuringNozzleOffset', 'measuringNozzleOffset'), @@ -19,7 +18,7 @@ (CalibrationCommand.jog, 'preparingPipette', 'preparingPipette'), (CalibrationCommand.pick_up_tip, 'preparingPipette', 'inspectingTip'), (CalibrationCommand.invalidate_tip, 'inspectingTip', 'preparingPipette'), - (TipLengthCalibrationCommand.move_to_reference_point, 'inspectingTip', + (CalibrationCommand.move_to_reference_point, 'inspectingTip', 'measuringTipOffset'), (CalibrationCommand.jog, 'measuringTipOffset', 'measuringTipOffset'), (CalibrationCommand.save_offset, 'measuringTipOffset', diff --git a/robot-server/tests/robot/calibration/tip_length/test_user_flow.py b/robot-server/tests/robot/calibration/tip_length/test_user_flow.py index 05264b2a20d..d8f4426cdd2 100644 --- a/robot-server/tests/robot/calibration/tip_length/test_user_flow.py +++ b/robot-server/tests/robot/calibration/tip_length/test_user_flow.py @@ -6,9 +6,9 @@ from opentrons.protocol_api.labware import get_labware_definition from opentrons.config.pipette_config import load +from robot_server.robot.calibration.util import get_reference_location from robot_server.service.errors import RobotServerError -from robot_server.service.session.models.command import ( - CalibrationCommand, TipLengthCalibrationCommand) +from robot_server.service.session.models.command import CalibrationCommand from robot_server.robot.calibration.tip_length.user_flow import \ TipCalibrationUserFlow @@ -141,9 +141,9 @@ def mock_user_flow_all_combos(mock_hw_all_combos, request): (CalibrationCommand.jog, 'measuringNozzleOffset', stub_jog_data, 'move_rel'), (CalibrationCommand.pick_up_tip, 'preparingPipette', {}, 'pick_up_tip'), - (TipLengthCalibrationCommand.move_to_reference_point, 'labwareLoaded', + (CalibrationCommand.move_to_reference_point, 'labwareLoaded', {}, 'move_to'), - (TipLengthCalibrationCommand.move_to_reference_point, 'inspectingTip', + (CalibrationCommand.move_to_reference_point, 'inspectingTip', {}, 'move_to'), (CalibrationCommand.move_to_tip_rack, 'measuringNozzleOffset', {}, 'move_to'), @@ -270,7 +270,9 @@ def test_load_cal_block(mock_user_flow_all_combos): async def test_get_reference_location(mock_user_flow_all_combos): uf = mock_user_flow_all_combos - result = uf._get_reference_point() + result = get_reference_location( + mount=uf._mount, deck=uf._deck, + has_calibration_block=uf._has_calibration_block) if uf._has_calibration_block: if uf._mount == Mount.LEFT: exp = uf._deck['3'].wells()[0].top().move(Point(0, 0, 5))