Skip to content

Commit bfe3055

Browse files
authored
refactor(step-generation): create airGapInWell compound command (#17786)
This is the 3rd (and final, i think) Pr in a series of PRs that refactors any utils that emit several commands in step-generation into a compound command for py interop.
1 parent 6ffd517 commit bfe3055

File tree

4 files changed

+209
-72
lines changed

4 files changed

+209
-72
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { beforeEach, describe, it, expect } from 'vitest'
2+
import {
3+
makeContext,
4+
getRobotStateWithTipStandard,
5+
getSuccessResult,
6+
SOURCE_LABWARE,
7+
DEST_LABWARE,
8+
} from '../fixtures'
9+
import { airGapInWell } from '../commandCreators/compound'
10+
import type { RobotState, InvariantContext } from '../types'
11+
12+
describe('airGapInWell', () => {
13+
let invariantContext: InvariantContext
14+
let robotStateWithTip: RobotState
15+
16+
beforeEach(() => {
17+
invariantContext = makeContext()
18+
robotStateWithTip = getRobotStateWithTipStandard(invariantContext)
19+
})
20+
it('air gap in 1 well for transfer', () => {
21+
const args = {
22+
flowRate: 10,
23+
offsetFromBottomMm: 1,
24+
pipetteId: 'p10SingleId',
25+
volume: 10,
26+
labwareId: SOURCE_LABWARE,
27+
wellName: 'B1',
28+
}
29+
30+
const result = airGapInWell(args, invariantContext, robotStateWithTip)
31+
const res = getSuccessResult(result)
32+
expect(res.commands).toEqual([
33+
{
34+
commandType: 'moveToWell',
35+
key: expect.any(String),
36+
params: {
37+
labwareId: 'sourcePlateId',
38+
pipetteId: 'p10SingleId',
39+
wellLocation: {
40+
offset: {
41+
x: 0,
42+
y: 0,
43+
z: 1,
44+
},
45+
origin: 'bottom',
46+
},
47+
wellName: 'B1',
48+
},
49+
},
50+
{
51+
commandType: 'prepareToAspirate',
52+
key: expect.any(String),
53+
params: {
54+
pipetteId: 'p10SingleId',
55+
},
56+
},
57+
{
58+
commandType: 'airGapInPlace',
59+
key: expect.any(String),
60+
params: {
61+
flowRate: 10,
62+
pipetteId: 'p10SingleId',
63+
volume: 10,
64+
},
65+
},
66+
])
67+
})
68+
it('air gap for multi wells for consolidate', () => {
69+
const args = {
70+
labwareId: DEST_LABWARE,
71+
wellName: 'A1',
72+
flowRate: 10,
73+
offsetFromBottomMm: 1,
74+
pipetteId: 'p10SingleId',
75+
volume: 10,
76+
}
77+
78+
const result = airGapInWell(args, invariantContext, robotStateWithTip)
79+
const res = getSuccessResult(result)
80+
expect(res.commands).toEqual([
81+
{
82+
commandType: 'moveToWell',
83+
key: expect.any(String),
84+
params: {
85+
labwareId: 'destPlateId',
86+
pipetteId: 'p10SingleId',
87+
wellLocation: {
88+
offset: {
89+
x: 0,
90+
y: 0,
91+
z: 1,
92+
},
93+
origin: 'bottom',
94+
},
95+
wellName: 'A1',
96+
},
97+
},
98+
{
99+
commandType: 'prepareToAspirate',
100+
key: expect.any(String),
101+
params: {
102+
pipetteId: 'p10SingleId',
103+
},
104+
},
105+
{
106+
commandType: 'airGapInPlace',
107+
key: expect.any(String),
108+
params: {
109+
flowRate: 10,
110+
pipetteId: 'p10SingleId',
111+
volume: 10,
112+
},
113+
},
114+
])
115+
})
116+
})
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { curryCommandCreator, reduceCommandCreators } from '../../utils'
2+
import { airGapInPlace, moveToWell, prepareToAspirate } from '../atomic'
3+
import type { CommandCreator, CurriedCommandCreator } from '../../types'
4+
5+
interface AirGapInWellArgs {
6+
flowRate: number
7+
offsetFromBottomMm: number
8+
pipetteId: string
9+
volume: number
10+
labwareId: string
11+
wellName: string
12+
}
13+
14+
export const airGapInWell: CommandCreator<AirGapInWellArgs> = (
15+
args,
16+
invariantContext,
17+
prevRobotState
18+
) => {
19+
const {
20+
labwareId,
21+
wellName,
22+
flowRate,
23+
offsetFromBottomMm,
24+
pipetteId,
25+
volume,
26+
} = args
27+
28+
const commandCreators: CurriedCommandCreator[] = [
29+
curryCommandCreator(moveToWell, {
30+
pipetteId,
31+
labwareId,
32+
wellName,
33+
wellLocation: {
34+
origin: 'bottom',
35+
offset: {
36+
z: offsetFromBottomMm,
37+
x: 0,
38+
y: 0,
39+
},
40+
},
41+
}),
42+
curryCommandCreator(prepareToAspirate, {
43+
pipetteId,
44+
}),
45+
curryCommandCreator(airGapInPlace, {
46+
pipetteId,
47+
volume,
48+
flowRate,
49+
}),
50+
]
51+
52+
return reduceCommandCreators(
53+
commandCreators,
54+
invariantContext,
55+
prevRobotState
56+
)
57+
}

step-generation/src/commandCreators/compound/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { absorbanceReaderCloseInitialize } from './absorbanceReaderCloseInitiali
22
export { absorbanceReaderCloseRead } from './absorbanceReaderCloseRead'
33
export { airGapInTrash } from './airGapInTrash'
44
export { airGapInWasteChute } from './airGapInWasteChute'
5+
export { airGapInWell } from './airGapInWell'
56
export { blowOutInTrash } from './blowOutInTrash'
67
export { blowOutInWasteChute } from './blowOutInWasteChute'
78
export { consolidate } from './consolidate'

step-generation/src/utils/misc.ts

Lines changed: 35 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ import {
1414
OT2_ROBOT_TYPE,
1515
FLEX_ROBOT_TYPE,
1616
} from '@opentrons/shared-data'
17+
import { ZERO_OFFSET } from '../constants'
1718
import { reduceCommandCreators } from './index'
1819
import {
19-
airGapInPlace,
2020
dispense,
2121
moveToAddressableArea,
2222
moveToWell,
23-
prepareToAspirate,
2423
} from '../commandCreators/atomic'
2524
import {
26-
airGapInWasteChute,
27-
blowOutInWasteChute,
28-
dispenseInWasteChute,
2925
airGapInTrash,
3026
blowOutInTrash,
3127
dispenseInTrash,
28+
airGapInWell,
29+
airGapInWasteChute,
30+
blowOutInWasteChute,
31+
dispenseInWasteChute,
3232
} from '../commandCreators/compound'
3333
import { blowout } from '../commandCreators/atomic/blowout'
3434
import { curryCommandCreator } from './curryCommandCreator'
@@ -55,7 +55,6 @@ import type {
5555
RobotState,
5656
SourceAndDest,
5757
} from '../types'
58-
import { ZERO_OFFSET } from '../constants'
5958
export const AIR: '__air__' = '__air__'
6059
export const SOURCE_WELL_BLOWOUT_DESTINATION: 'source_well' = 'source_well'
6160
export const DEST_WELL_BLOWOUT_DESTINATION: 'dest_well' = 'dest_well'
@@ -450,11 +449,11 @@ export function createTipLiquidState<T>(
450449
}
451450
// always return destination unless the blowout location is the source
452451
export const getDispenseAirGapLocation = (args: {
453-
blowoutLocation: string | null | undefined
454-
sourceLabware: string
455452
destLabware: string
456-
sourceWell: string
457453
destWell: string
454+
sourceWell?: string
455+
sourceLabware?: string
456+
blowoutLocation?: string | null
458457
}): {
459458
dispenseAirGapLabware: string
460459
dispenseAirGapWell: string
@@ -466,12 +465,18 @@ export const getDispenseAirGapLocation = (args: {
466465
sourceWell,
467466
destWell,
468467
} = args
469-
return blowoutLocation === SOURCE_WELL_BLOWOUT_DESTINATION
468+
return blowoutLocation === SOURCE_WELL_BLOWOUT_DESTINATION &&
469+
// note: sourceLabware & sourceWell != null for air gap in a transfer only
470+
// since transfer allows you to specify the blowout location as source well
471+
sourceLabware != null &&
472+
sourceWell != null
470473
? {
471474
dispenseAirGapLabware: sourceLabware,
472475
dispenseAirGapWell: sourceWell,
473476
}
474477
: {
478+
// this case is for transfer and consolidate when blowout location is NOT
479+
// in a source well
475480
dispenseAirGapLabware: destLabware,
476481
dispenseAirGapWell: destWell,
477482
}
@@ -730,68 +735,26 @@ export const airGapHelper: CommandCreator<AirGapArgs> = (
730735

731736
let commands: CurriedCommandCreator[] = []
732737
if (trashOrLabware === 'labware' && destWell != null) {
733-
// when aspirating out of 1 well for transfer
734-
if (sourceId != null && sourceWell != null) {
735-
const {
736-
dispenseAirGapLabware,
737-
dispenseAirGapWell,
738-
} = getDispenseAirGapLocation({
739-
blowoutLocation: blowOutLocation,
740-
sourceLabware: sourceId,
741-
destLabware: destinationId,
742-
sourceWell,
743-
destWell: destWell,
744-
})
745-
746-
commands = [
747-
curryCommandCreator(moveToWell, {
748-
pipetteId,
749-
labwareId: dispenseAirGapLabware,
750-
wellName: dispenseAirGapWell,
751-
wellLocation: {
752-
origin: 'bottom',
753-
offset: {
754-
z: offsetFromBottomMm,
755-
x: 0,
756-
y: 0,
757-
},
758-
},
759-
}),
760-
curryCommandCreator(prepareToAspirate, {
761-
pipetteId,
762-
}),
763-
curryCommandCreator(airGapInPlace, {
764-
pipetteId,
765-
volume,
766-
flowRate,
767-
}),
768-
]
769-
// when aspirating out of multi wells for consolidate
770-
} else {
771-
commands = [
772-
curryCommandCreator(moveToWell, {
773-
pipetteId,
774-
labwareId: destinationId,
775-
wellName: destWell,
776-
wellLocation: {
777-
origin: 'bottom',
778-
offset: {
779-
z: offsetFromBottomMm,
780-
x: 0,
781-
y: 0,
782-
},
783-
},
784-
}),
785-
curryCommandCreator(prepareToAspirate, {
786-
pipetteId,
787-
}),
788-
curryCommandCreator(airGapInPlace, {
789-
pipetteId,
790-
volume,
791-
flowRate,
792-
}),
793-
]
794-
}
738+
const {
739+
dispenseAirGapLabware,
740+
dispenseAirGapWell,
741+
} = getDispenseAirGapLocation({
742+
blowoutLocation: blowOutLocation,
743+
sourceLabware: sourceId,
744+
destLabware: destinationId,
745+
sourceWell,
746+
destWell: destWell,
747+
})
748+
commands = [
749+
curryCommandCreator(airGapInWell, {
750+
flowRate,
751+
offsetFromBottomMm,
752+
pipetteId,
753+
labwareId: dispenseAirGapLabware,
754+
wellName: dispenseAirGapWell,
755+
volume,
756+
}),
757+
]
795758
} else if (trashOrLabware === 'wasteChute') {
796759
commands = [
797760
curryCommandCreator(airGapInWasteChute, {

0 commit comments

Comments
 (0)