Skip to content

Commit

Permalink
feat(app): use metal probe for LPC on Opentrons Flex (#13706)
Browse files Browse the repository at this point in the history
In order to support the 96 channel pipette in Labware Position Check, in addition to increasing the
accuracy of labware offsets, performing Labware Position Check on an Opentrons Flex will walk the
user through attaching the calibration probe to the back left most nozzle and in order to use it as
a visual indicator within each position check. Note: the OT2 will still "pick up a tip" to use as a
visual indicator when performing LPC.

Closes RAUT-711
  • Loading branch information
b-cooper authored Oct 6, 2023
1 parent 470a84c commit 1a301eb
Show file tree
Hide file tree
Showing 18 changed files with 88 additions and 188 deletions.
2 changes: 0 additions & 2 deletions app/src/assets/localization/en/app_settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"__dev_internal__enableDeckConfiguration": "Enable Deck Configuration",
"__dev_internal__enableExtendedHardware": "Enable Extended Hardware",
"__dev_internal__lpcWithProbe": "Golden Tip LPC",
"add_folder_button": "Add labware source folder",
"add_ip_button": "Add",
"add_ip_error": "Enter an IP Address or Hostname",
Expand Down
3 changes: 3 additions & 0 deletions app/src/assets/localization/en/labware_position_check.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"applied_offset_data": "Applied Labware Offset data",
"apply_offset_data": "Apply labware offset data",
"apply_offsets": "apply offsets",
"attach_probe": "Attach probe to pipette",
"check_item_in_location": "Check {{item}} in {{location}}",
"check_labware_in_slot_title": "Check Labware {{labware_display_name}} in slot {{slot}}",
"check_remaining_labware_with_primary_pipette_section": "Check remaining labware with {{primary_mount}} Pipette and tip",
Expand All @@ -28,6 +29,8 @@
"exit_screen_subtitle": "If you exit now, all labware offsets will be discarded. This cannot be undone.",
"exit_screen_title": "Exit before completing Labware Position Check?",
"get_labware_offset_data": "Get Labware Offset Data",
"install_probe_8_channel": "<block>Take the calibration probe from its storage location. Ensure its collar is unlocked. Push the pipette ejector up and press the probe firmly onto the <strong>backmost</strong> pipette nozzle. Twist the collar to lock the probe. Test that the probe is secure by gently pulling it back and forth.</block>",
"install_probe_96_channel": "<block>Take the calibration probe from its storage location. Ensure its collar is unlocked. Push the pipette ejector up and press the probe firmly onto the <strong>A1 (back left corner)</strong> pipette nozzle. Twist the collar to lock the probe. Test that the probe is secure by gently pulling it back and forth.</block>",
"jog_controls_adjustment": "Need to make an adjustment?",
"jupyter_notebook": "Jupyter Notebook",
"labware_display_location_text": "Deck Slot {{slot}}",
Expand Down
4 changes: 2 additions & 2 deletions app/src/assets/localization/en/protocol_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"reanalyze_or_view_error": "<analysisLink>Reanalyze</analysisLink> protocol or view <errorLink>error details</errorLink>",
"right_mount": "right mount",
"robot": "robot",
"send_to_ot3_overflow": "Send to {{robot_display_name}}",
"send_to_ot3": "Send protocol to {{robot_display_name}}",
"send_to_robot_overflow": "Send to {{robot_display_name}}",
"send_to_robot": "Send protocol to {{robot_display_name}}",
"should_delete_this_protocol": "Delete this protocol?",
"show_in_folder": "Show in folder",
"start_setup": "Start setup",
Expand Down
14 changes: 9 additions & 5 deletions app/src/organisms/LabwarePositionCheck/CheckItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PrepareSpace } from './PrepareSpace'
import { JogToWell } from './JogToWell'
import {
CreateCommand,
FLEX_ROBOT_TYPE,
getIsTiprack,
getLabwareDefURI,
getLabwareDisplayName,
Expand All @@ -17,6 +18,7 @@ import {
IDENTITY_VECTOR,
LabwareLocation,
MoveLabwareCreateCommand,
RobotType,
THERMOCYCLER_MODULE_TYPE,
} from '@opentrons/shared-data'
import { useSelector } from 'react-redux'
Expand All @@ -27,7 +29,7 @@ import {
import { UnorderedList } from '../../molecules/UnorderedList'
import { getCurrentOffsetForLabwareInLocation } from '../Devices/ProtocolRun/utils/getCurrentOffsetForLabwareInLocation'
import { useChainRunCommands } from '../../resources/runs/hooks'
import { useFeatureFlag, getIsOnDevice } from '../../redux/config'
import { getIsOnDevice } from '../../redux/config'
import { getDisplayLocation } from './utils/getDisplayLocation'

import type { LabwareOffset } from '@opentrons/api-client'
Expand All @@ -52,6 +54,7 @@ interface CheckItemProps extends Omit<CheckLabwareStep, 'section'> {
existingOffsets: LabwareOffset[]
handleJog: Jog
isRobotMoving: boolean
robotType: RobotType
}
export const CheckItem = (props: CheckItemProps): JSX.Element | null => {
const {
Expand All @@ -69,8 +72,8 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => {
isRobotMoving,
existingOffsets,
setFatalError,
robotType,
} = props
const goldenLPC = useFeatureFlag('lpcWithProbe')
const { t, i18n } = useTranslation(['labware_position_check', 'shared'])
const isOnDevice = useSelector(getIsOnDevice)
const labwareDef = getLabwareDef(labwareId, protocolData)
Expand Down Expand Up @@ -259,9 +262,10 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => {
wellName: 'A1',
wellLocation: {
origin: 'top' as const,
offset: goldenLPC
? { x: 0, y: 0, z: PROBE_LENGTH_MM }
: IDENTITY_VECTOR,
offset:
robotType === FLEX_ROBOT_TYPE
? { x: 0, y: 0, z: PROBE_LENGTH_MM }
: IDENTITY_VECTOR,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
CompletedProtocolAnalysis,
Coordinates,
FIXED_TRASH_ID,
FLEX_ROBOT_TYPE,
OT2_ROBOT_TYPE,
} from '@opentrons/shared-data'
import { Portal } from '../../App/portal'
// import { useTrackEvent } from '../../redux/analytics'
Expand All @@ -21,7 +23,7 @@ import { ExitConfirmation } from './ExitConfirmation'
import { CheckItem } from './CheckItem'
import { LegacyModalShell } from '../../molecules/LegacyModal'
import { WizardHeader } from '../../molecules/WizardHeader'
import { getIsOnDevice, useFeatureFlag } from '../../redux/config'
import { getIsOnDevice } from '../../redux/config'
import { AttachProbe } from './AttachProbe'
import { DetachProbe } from './DetachProbe'
import { PickUpTip } from './PickUpTip'
Expand All @@ -36,36 +38,36 @@ import type { DropTipCreateCommand } from '@opentrons/shared-data/protocol/types
import type { CreateCommand } from '@opentrons/shared-data'
import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types'
import type { RegisterPositionAction, WorkingOffset } from './types'
import { getGoldenCheckSteps } from './utils/getGoldenCheckSteps'

const RUN_REFETCH_INTERVAL = 5000
const JOG_COMMAND_TIMEOUT = 10000 // 10 seconds
interface LabwarePositionCheckModalProps {
onCloseClick: () => unknown
runId: string
maintenanceRunId: string
mostRecentAnalysis: CompletedProtocolAnalysis | null
existingOffsets: LabwareOffset[]
onCloseClick: () => unknown
protocolName: string
caughtError?: Error
setMaintenanceRunId: (id: string | null) => void
caughtError?: Error
}

export const LabwarePositionCheckComponent = (
props: LabwarePositionCheckModalProps
): JSX.Element | null => {
const {
mostRecentAnalysis,
onCloseClick,
existingOffsets,
runId,
maintenanceRunId,
onCloseClick,
setMaintenanceRunId,
protocolName,
} = props
const { t } = useTranslation(['labware_position_check', 'shared'])
const isOnDevice = useSelector(getIsOnDevice)
const protocolData = mostRecentAnalysis
const robotType = mostRecentAnalysis?.robotType ?? OT2_ROBOT_TYPE

// we should start checking for run deletion only after the maintenance run is created
// and the useCurrentRun poll has returned that created id
Expand Down Expand Up @@ -182,22 +184,22 @@ export const LabwarePositionCheckComponent = (
isCommandMutationLoading: isCommandChainLoading,
} = useChainMaintenanceCommands()

const goldenLPC = useFeatureFlag('lpcWithProbe')
const { createLabwareOffset } = useCreateLabwareOffsetMutation()
const [currentStepIndex, setCurrentStepIndex] = React.useState<number>(0)
const handleCleanUpAndClose = (): void => {
setIsExiting(true)
const dropTipToBeSafeCommands: DropTipCreateCommand[] = goldenLPC
? []
: (protocolData?.pipettes ?? []).map(pip => ({
commandType: 'dropTip' as const,
params: {
pipetteId: pip.id,
labwareId: FIXED_TRASH_ID,
wellName: 'A1',
wellLocation: { origin: 'default' as const },
},
}))
const dropTipToBeSafeCommands: DropTipCreateCommand[] =
robotType === FLEX_ROBOT_TYPE
? []
: (protocolData?.pipettes ?? []).map(pip => ({
commandType: 'dropTip' as const,
params: {
pipetteId: pip.id,
labwareId: FIXED_TRASH_ID,
wellName: 'A1',
wellLocation: { origin: 'default' as const },
},
}))
chainRunCommands(
maintenanceRunId,
[
Expand Down Expand Up @@ -243,9 +245,7 @@ export const LabwarePositionCheckComponent = (
)
}
if (protocolData == null) return null
const LPCSteps = goldenLPC
? getGoldenCheckSteps(protocolData)
: getLabwarePositionCheckSteps(protocolData)
const LPCSteps = getLabwarePositionCheckSteps(protocolData, robotType)
const totalStepCount = LPCSteps.length - 1
const currentStep = LPCSteps?.[currentStepIndex]
if (currentStep == null) return null
Expand Down Expand Up @@ -336,7 +336,9 @@ export const LabwarePositionCheckComponent = (
currentStep.section === 'CHECK_TIP_RACKS' ||
currentStep.section === 'CHECK_LABWARE'
) {
modalContent = <CheckItem {...currentStep} {...movementStepProps} />
modalContent = (
<CheckItem {...currentStep} {...movementStepProps} {...{ robotType }} />
)
} else if (currentStep.section === 'ATTACH_PROBE') {
modalContent = <AttachProbe {...currentStep} {...movementStepProps} />
} else if (currentStep.section === 'DETACH_PROBE') {
Expand Down
12 changes: 6 additions & 6 deletions app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ import {
LabwareDefinition2,
THERMOCYCLER_MODULE_TYPE,
getModuleType,
FLEX_ROBOT_TYPE,
} from '@opentrons/shared-data'
import ot2DeckDef from '@opentrons/shared-data/deck/definitions/3/ot2_standard.json'
import ot3DeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json'
import flexDeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json'

import { getIsOnDevice } from '../../redux/config'
import { useProtocolMetadata } from '../Devices/hooks'

import type { CheckLabwareStep } from './types'
import { SmallButton } from '../../atoms/buttons'
import { NeedHelpLink } from '../CalibrationPanels'

import type { CheckLabwareStep } from './types'

const LPC_HELP_LINK_URL =
'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2'

Expand Down Expand Up @@ -83,11 +83,11 @@ export const PrepareSpace = (props: PrepareSpaceProps): JSX.Element | null => {
const { i18n, t } = useTranslation(['labware_position_check', 'shared'])
const { location, moduleId, labwareDef, protocolData, header, body } = props

const { robotType } = useProtocolMetadata()
const robotType = protocolData.robotType
const isOnDevice = useSelector(getIsOnDevice)

if (protocolData == null || robotType == null) return null
const deckDef = robotType === 'OT-3 Standard' ? ot3DeckDef : ot2DeckDef
const deckDef = robotType === FLEX_ROBOT_TYPE ? flexDeckDef : ot2DeckDef
return (
<Flex css={TILE_CONTAINER_STYLE}>
<Flex flexDirection={DIRECTION_ROW} gridGap={SPACING.spacing40}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getLabwareDefURI } from '@opentrons/shared-data'
import { FLEX_ROBOT_TYPE, getLabwareDefURI } from '@opentrons/shared-data'
import { mockTipRackDef } from './mockTipRackDef'
import { mockLabwareDef } from './mockLabwareDef'

Expand Down Expand Up @@ -32,6 +32,7 @@ export const mockCompletedAnalysis: CompletedProtocolAnalysis = {
],
modules: [],
liquids: [],
robotType: FLEX_ROBOT_TYPE,
commands: [
{
commandType: 'loadLabware',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import * as React from 'react'
import { resetAllWhenMocks, when } from 'jest-when'
import { renderWithProviders, nestedTextMatcher } from '@opentrons/components'
import {
FLEX_ROBOT_TYPE,
HEATERSHAKER_MODULE_V1,
OT2_ROBOT_TYPE,
THERMOCYCLER_MODULE_V2,
} from '@opentrons/shared-data'
import { i18n } from '../../../i18n'
import { useFeatureFlag } from '../../../redux/config'
import { useProtocolMetadata } from '../../Devices/hooks'
import { CheckItem } from '../CheckItem'
import { SECTIONS } from '../constants'
Expand All @@ -22,9 +23,6 @@ const mockEndPosition = { x: 9, y: 19, z: 29 }
const mockUseProtocolMetaData = useProtocolMetadata as jest.MockedFunction<
typeof useProtocolMetadata
>
const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction<
typeof useFeatureFlag
>

const matchTextWithSpans: (text: string) => MatcherFunction = (
text: string
Expand Down Expand Up @@ -65,9 +63,9 @@ describe('CheckItem', () => {
workingOffsets: [],
existingOffsets: mockExistingOffsets,
isRobotMoving: false,
robotType: OT2_ROBOT_TYPE,
}
mockUseProtocolMetaData.mockReturnValue({ robotType: 'OT-3 Standard' })
when(mockUseFeatureFlag).calledWith('lpcWithProbe').mockReturnValue(false)
mockUseProtocolMetaData.mockReturnValue({ robotType: OT2_ROBOT_TYPE })
})
afterEach(() => {
jest.resetAllMocks()
Expand Down Expand Up @@ -530,7 +528,11 @@ describe('CheckItem', () => {
)
})
it('executes correct chained commands when confirm placement CTA is clicked when using probe for LPC', async () => {
when(mockUseFeatureFlag).calledWith('lpcWithProbe').mockReturnValue(true)
props = {
...props,
robotType: FLEX_ROBOT_TYPE,
}
mockUseProtocolMetaData.mockReturnValue({ robotType: FLEX_ROBOT_TYPE })
when(mockChainRunCommands)
.calledWith(
[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { getPrimaryPipetteId } from './utils/getPrimaryPipetteId'
import { getCheckSteps } from './utils/getCheckSteps'
import type { CompletedProtocolAnalysis } from '@opentrons/shared-data'
import {
CompletedProtocolAnalysis,
FLEX_ROBOT_TYPE,
RobotType,
} from '@opentrons/shared-data'
import { getTipBasedLPCSteps } from './utils/getTipBasedLPCSteps'
import { getProbeBasedLPCSteps } from './utils/getProbeBasedLPCSteps'
import type { LabwarePositionCheckStep } from './types'
import { getGoldenCheckSteps } from './utils/getGoldenCheckSteps'

export const getLabwarePositionCheckSteps = (
protocolData: CompletedProtocolAnalysis
protocolData: CompletedProtocolAnalysis,
robotType: RobotType
): LabwarePositionCheckStep[] => {
if (protocolData != null && 'pipettes' in protocolData) {
if (protocolData.pipettes.length === 0) {
throw new Error(
'no pipettes loaded within protocol, labware position check cannot be performed'
)
}
if (robotType === FLEX_ROBOT_TYPE)
return getProbeBasedLPCSteps(protocolData)

// filter out any pipettes that are not being used in the protocol
const pipettesUsedInProtocol: CompletedProtocolAnalysis['pipettes'] = protocolData.pipettes.filter(
({ id }) =>
Expand All @@ -31,7 +44,7 @@ export const getLabwarePositionCheckSteps = (
const secondaryPipetteId =
pipettesUsedInProtocol.find(({ id }) => id !== primaryPipetteId)?.id ??
null
return getCheckSteps({
return getTipBasedLPCSteps({
primaryPipetteId,
secondaryPipetteId,
labware,
Expand All @@ -42,19 +55,3 @@ export const getLabwarePositionCheckSteps = (
console.error('expected pipettes to be in protocol data')
return []
}

export const getGoldenLabwarePositionCheckSteps = (
protocolData: CompletedProtocolAnalysis
): LabwarePositionCheckStep[] => {
if (protocolData != null && 'pipettes' in protocolData) {
if (protocolData.pipettes.length === 0) {
throw new Error(
'no pipettes loaded within protocol, labware position check cannot be performed'
)
}
return getGoldenCheckSteps(protocolData)
}
throw new Error(
'no pipettes found in protocol, labware position check cannot be performed'
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function getPrimaryPipetteId(pipettes: LoadedPipette[]): string {
}, pipettes[0]).id
}

export const getGoldenCheckSteps = (
export const getProbeBasedLPCSteps = (
protocolData: CompletedProtocolAnalysis
): LabwarePositionCheckStep[] => {
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ interface LPCArgs {
commands: RunTimeCommand[]
}

export const getCheckSteps = (args: LPCArgs): LabwarePositionCheckStep[] => {
export const getTipBasedLPCSteps = (
args: LPCArgs
): LabwarePositionCheckStep[] => {
const checkTipRacksSectionSteps = getCheckTipRackSectionSteps(args)
if (checkTipRacksSectionSteps.length < 1) return []
const allButLastTiprackCheckSteps = checkTipRacksSectionSteps.slice(
Expand Down
Loading

0 comments on commit 1a301eb

Please sign in to comment.