Skip to content

Commit

Permalink
feat(step-generation): error creators for absorbance reader closed li…
Browse files Browse the repository at this point in the history
…d pipetting (#17327)

This PR introduces utility to get a possible collision with absorbance
reader module if the lid is not open (closed or indeterminate). It wires
up the absorbance reader closed lid error creator in aspirate, dispense,
and moveToWell error creators (checks already used in moveLabware)

Closes AUTH-1079
  • Loading branch information
ncdiehl11 authored Jan 24, 2025
1 parent 873467e commit 10c4fab
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 2 deletions.
37 changes: 37 additions & 0 deletions step-generation/src/__tests__/aspirate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@opentrons/shared-data'

import {
absorbanceReaderCollision,
pipetteIntoHeaterShakerLatchOpen,
thermocyclerPipetteCollision,
pipetteIntoHeaterShakerWhileShaking,
Expand Down Expand Up @@ -38,6 +39,7 @@ const fixtureTiprack1000ul = tip1000 as LabwareDefinition2
const FLEX_PIPETTE = 'p1000_single_flex'
const FlexPipetteNameSpecs = getPipetteSpecsV2(FLEX_PIPETTE)

vi.mock('../utils/absorbanceReaderCollision')
vi.mock('../utils/thermocyclerPipetteCollision')
vi.mock('../utils/heaterShakerCollision')

Expand Down Expand Up @@ -282,6 +284,41 @@ describe('aspirate', () => {
type: 'THERMOCYCLER_LID_CLOSED',
})
})
it('should return an error when aspirating from absorbance with pipette collision', () => {
vi.mocked(absorbanceReaderCollision).mockImplementationOnce(
(
modules: RobotState['modules'],
labware: RobotState['labware'],
labwareId: string
) => {
expect(modules).toBe(robotStateWithTip.modules)
expect(labware).toBe(robotStateWithTip.labware)
expect(labwareId).toBe(SOURCE_LABWARE)
return true
}
)
const result = aspirate(
{
...({
...flowRateAndOffsets,
pipette: DEFAULT_PIPETTE,
volume: 50,
labware: SOURCE_LABWARE,
well: 'A1',
} as AspDispAirgapParams),
tipRack: 'tipRack',
xOffset: 0,
yOffset: 0,
nozzles: null,
},
invariantContext,
robotStateWithTip
)
expect(getErrorResult(result).errors).toHaveLength(1)
expect(getErrorResult(result).errors[0]).toMatchObject({
type: 'ABSORBANCE_READER_LID_CLOSED',
})
})
it('should return an error when aspirating from heaterShaker with latch opened', () => {
vi.mocked(pipetteIntoHeaterShakerLatchOpen).mockImplementationOnce(
(
Expand Down
4 changes: 3 additions & 1 deletion step-generation/src/__tests__/blowout.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, it, expect } from 'vitest'
import { beforeEach, describe, it, expect, vi } from 'vitest'
import { expectTimelineError } from '../__utils__/testMatchers'
import { blowout } from '../commandCreators/atomic/blowout'
import {
Expand All @@ -14,6 +14,8 @@ import {
import type { BlowoutParams } from '@opentrons/shared-data'
import type { RobotState, InvariantContext } from '../types'

vi.mock('../utils/heaterShakerCollision')

describe('blowout', () => {
let invariantContext: InvariantContext
let initialRobotState: RobotState
Expand Down
22 changes: 22 additions & 0 deletions step-generation/src/__tests__/dispense.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { when } from 'vitest-when'
import { beforeEach, describe, it, expect, vi, afterEach } from 'vitest'
import { OT2_ROBOT_TYPE, getPipetteSpecsV2 } from '@opentrons/shared-data'
import {
absorbanceReaderCollision,
thermocyclerPipetteCollision,
pipetteIntoHeaterShakerLatchOpen,
pipetteIntoHeaterShakerWhileShaking,
Expand All @@ -24,6 +25,7 @@ import { dispense } from '../commandCreators/atomic/dispense'
import type { ExtendedDispenseParams } from '../commandCreators/atomic/dispense'
import type { InvariantContext, RobotState } from '../types'

vi.mock('../utils/absorbanceReaderCollision')
vi.mock('../utils/thermocyclerPipetteCollision')
vi.mock('../utils/heaterShakerCollision')

Expand Down Expand Up @@ -160,6 +162,26 @@ describe('dispense', () => {
type: 'THERMOCYCLER_LID_CLOSED',
})
})
it('should return an error when dispensing into absorbance reader with pipette collision', () => {
vi.mocked(absorbanceReaderCollision).mockImplementationOnce(
(
modules: RobotState['modules'],
labware: RobotState['labware'],
labwareId: string
) => {
expect(modules).toBe(robotStateWithTip.modules)
expect(labware).toBe(robotStateWithTip.labware)
expect(labwareId).toBe(SOURCE_LABWARE)
return true
}
)
const result = dispense(params, invariantContext, robotStateWithTip)
const res = getErrorResult(result)
expect(res.errors).toHaveLength(1)
expect(res.errors[0]).toMatchObject({
type: 'ABSORBANCE_READER_LID_CLOSED',
})
})
it('should return an error when dispensing into heater shaker with latch open', () => {
vi.mocked(pipetteIntoHeaterShakerLatchOpen).mockImplementationOnce(
(
Expand Down
29 changes: 29 additions & 0 deletions step-generation/src/__tests__/moveToWell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OT2_ROBOT_TYPE, getPipetteSpecsV2 } from '@opentrons/shared-data'
import { expectTimelineError } from '../__utils__/testMatchers'
import { moveToWell } from '../commandCreators/atomic/moveToWell'
import {
absorbanceReaderCollision,
thermocyclerPipetteCollision,
pipetteIntoHeaterShakerLatchOpen,
pipetteIntoHeaterShakerWhileShaking,
Expand All @@ -23,6 +24,7 @@ import {
} from '../fixtures'
import type { InvariantContext, RobotState } from '../types'

vi.mock('../utils/absorbanceReaderCollision')
vi.mock('../utils/thermocyclerPipetteCollision')
vi.mock('../utils/heaterShakerCollision')

Expand Down Expand Up @@ -187,6 +189,33 @@ describe('moveToWell', () => {
type: 'THERMOCYCLER_LID_CLOSED',
})
})
it('should return an error when moving to well in a absorbance reader with pipette collision', () => {
vi.mocked(absorbanceReaderCollision).mockImplementationOnce(
(
modules: RobotState['modules'],
labware: RobotState['labware'],
labwareId: string
) => {
expect(modules).toBe(robotStateWithTip.modules)
expect(labware).toBe(robotStateWithTip.labware)
expect(labwareId).toBe(SOURCE_LABWARE)
return true
}
)
const result = moveToWell(
{
pipette: DEFAULT_PIPETTE,
labware: SOURCE_LABWARE,
well: 'A1',
},
invariantContext,
robotStateWithTip
)
expect(getErrorResult(result).errors).toHaveLength(1)
expect(getErrorResult(result).errors[0]).toMatchObject({
type: 'ABSORBANCE_READER_LID_CLOSED',
})
})

it('should return an error when moving to well in a heater-shaker with latch opened', () => {
vi.mocked(pipetteIntoHeaterShakerLatchOpen).mockImplementationOnce(
Expand Down
11 changes: 11 additions & 0 deletions step-generation/src/commandCreators/atomic/aspirate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { COLUMN, FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'
import * as errorCreators from '../../errorCreators'
import { getPipetteWithTipMaxVol } from '../../robotStateSelectors'
import {
absorbanceReaderCollision,
modulePipetteCollision,
thermocyclerPipetteCollision,
pipetteIntoHeaterShakerLatchOpen,
Expand Down Expand Up @@ -142,6 +143,16 @@ export const aspirate: CommandCreator<ExtendedAspirateParams> = (
errors.push(errorCreators.thermocyclerLidClosed())
}

if (
absorbanceReaderCollision(
prevRobotState.modules,
prevRobotState.labware,
labware
)
) {
errors.push(errorCreators.absorbanceReaderLidClosed())
}

if (
pipetteIntoHeaterShakerLatchOpen(
prevRobotState.modules,
Expand Down
115 changes: 114 additions & 1 deletion step-generation/src/commandCreators/atomic/blowout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { uuid, getLabwareSlot } from '../../utils'
import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'
import {
uuid,
getLabwareSlot,
modulePipetteCollision,
thermocyclerPipetteCollision,
absorbanceReaderCollision,
pipetteIntoHeaterShakerLatchOpen,
pipetteIntoHeaterShakerWhileShaking,
pipetteAdjacentHeaterShakerWhileShaking,
getIsHeaterShakerEastWestMultiChannelPipette,
getIsHeaterShakerEastWestWithLatchOpen,
getIsHeaterShakerNorthSouthOfNonTiprackWithMultiChannelPipette,
} from '../../utils'
import { COLUMN_4_SLOTS } from '../../constants'
import * as errorCreators from '../../errorCreators'
import type { CreateCommand, BlowoutParams } from '@opentrons/shared-data'
Expand All @@ -14,6 +27,10 @@ export const blowout: CommandCreator<BlowoutParams> = (

const actionName = 'blowout'
const errors: CommandCreatorError[] = []
const pipetteSpec = invariantContext.pipetteEntities[pipetteId]?.spec
const isFlexPipette =
(pipetteSpec?.displayCategory === 'FLEX' || pipetteSpec?.channels === 96) ??
false
const pipetteData = prevRobotState.pipettes[pipetteId]
const labwareState = prevRobotState.labware
const slotName = getLabwareSlot(
Expand All @@ -32,6 +49,14 @@ export const blowout: CommandCreator<BlowoutParams> = (
)
}

if (pipetteSpec == null) {
errors.push(
errorCreators.pipetteDoesNotExist({
pipette: pipetteId,
})
)
}

if (!prevRobotState.tipState.pipettes[pipetteId]) {
errors.push(
errorCreators.noTipOnPipette({
Expand Down Expand Up @@ -64,6 +89,94 @@ export const blowout: CommandCreator<BlowoutParams> = (
)
}
}
if (
modulePipetteCollision({
pipette: pipetteId,
labware: labwareId,
invariantContext,
prevRobotState,
})
) {
errors.push(errorCreators.modulePipetteCollisionDanger())
}

if (
thermocyclerPipetteCollision(
prevRobotState.modules,
prevRobotState.labware,
labwareId
)
) {
errors.push(errorCreators.thermocyclerLidClosed())
}

if (
absorbanceReaderCollision(
prevRobotState.modules,
prevRobotState.labware,
labwareId
)
) {
errors.push(errorCreators.absorbanceReaderLidClosed())
}

if (
pipetteIntoHeaterShakerLatchOpen(
prevRobotState.modules,
prevRobotState.labware,
labwareId
)
) {
errors.push(errorCreators.heaterShakerLatchOpen())
}

if (
pipetteIntoHeaterShakerWhileShaking(
prevRobotState.modules,
prevRobotState.labware,
labwareId
)
) {
errors.push(errorCreators.heaterShakerIsShaking())
}
if (
pipetteAdjacentHeaterShakerWhileShaking(
prevRobotState.modules,
slotName,
isFlexPipette ? FLEX_ROBOT_TYPE : OT2_ROBOT_TYPE
)
) {
errors.push(errorCreators.heaterShakerNorthSouthEastWestShaking())
}
if (!isFlexPipette && pipetteSpec != null) {
if (
getIsHeaterShakerEastWestWithLatchOpen(prevRobotState.modules, slotName)
) {
errors.push(errorCreators.heaterShakerEastWestWithLatchOpen())
}

if (
getIsHeaterShakerEastWestMultiChannelPipette(
prevRobotState.modules,
slotName,
pipetteSpec
)
) {
errors.push(errorCreators.heaterShakerEastWestOfMultiChannelPipette())
}
if (
getIsHeaterShakerNorthSouthOfNonTiprackWithMultiChannelPipette(
prevRobotState.modules,
slotName,
pipetteSpec,
invariantContext.labwareEntities[labwareId]
)
) {
errors.push(
errorCreators.heaterShakerNorthSouthOfNonTiprackWithMultiChannelPipette()
)
}
}

if (errors.length > 0) {
return {
Expand Down
11 changes: 11 additions & 0 deletions step-generation/src/commandCreators/atomic/dispense.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { COLUMN, FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'
import * as errorCreators from '../../errorCreators'
import {
absorbanceReaderCollision,
modulePipetteCollision,
thermocyclerPipetteCollision,
pipetteIntoHeaterShakerLatchOpen,
Expand Down Expand Up @@ -139,6 +140,16 @@ export const dispense: CommandCreator<ExtendedDispenseParams> = (
errors.push(errorCreators.thermocyclerLidClosed())
}

if (
absorbanceReaderCollision(
prevRobotState.modules,
prevRobotState.labware,
labware
)
) {
errors.push(errorCreators.absorbanceReaderLidClosed())
}

if (
pipetteIntoHeaterShakerLatchOpen(
prevRobotState.modules,
Expand Down
11 changes: 11 additions & 0 deletions step-generation/src/commandCreators/atomic/moveToWell.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'
import * as errorCreators from '../../errorCreators'
import {
absorbanceReaderCollision,
modulePipetteCollision,
thermocyclerPipetteCollision,
pipetteIntoHeaterShakerLatchOpen,
Expand Down Expand Up @@ -104,6 +105,16 @@ export const moveToWell: CommandCreator<v5MoveToWellParams> = (
errors.push(errorCreators.heaterShakerLatchOpen())
}

if (
absorbanceReaderCollision(
prevRobotState.modules,
prevRobotState.labware,
labware
)
) {
errors.push(errorCreators.absorbanceReaderLidClosed())
}

if (
pipetteIntoHeaterShakerWhileShaking(
prevRobotState.modules,
Expand Down
Loading

0 comments on commit 10c4fab

Please sign in to comment.