Skip to content

Commit f2f0b12

Browse files
committed
feat(step-generation): check for shuttle errors and mismatched labware
1 parent 83aacbf commit f2f0b12

File tree

5 files changed

+292
-16
lines changed

5 files changed

+292
-16
lines changed

step-generation/src/__tests__/flexStackerRetrieve.test.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ import {
1313
getInitialRobotStateStandard,
1414
makeContext,
1515
} from '../fixtures'
16+
import { flexStackerStateGetter } from '../robotStateSelectors'
1617

1718
import type { LabwareDefinition2 } from '@opentrons/shared-data'
18-
import type { InvariantContext, RobotState } from '../types'
19+
import type {
20+
FlexStackerModuleState,
21+
InvariantContext,
22+
RobotState,
23+
} from '../types'
1924

2025
const mockLabwareId = 'labwareId'
2126
const mockLabwareId2 = 'labwareId2'
@@ -35,6 +40,9 @@ describe('flexStackerRetrieve', () => {
3540
model: FLEX_STACKER_MODULE_V1,
3641
pythonName: 'mock_flex_stacker_1',
3742
}
43+
vi.mocked(flexStackerStateGetter).mockReturnValue(
44+
{} as FlexStackerModuleState
45+
)
3846
})
3947
it('creates flex stacker retrieve command', () => {
4048
robotState = {
@@ -117,7 +125,7 @@ describe('flexStackerRetrieve', () => {
117125
},
118126
labware: {
119127
[mockLabwareId]: {
120-
stack: ['D2'],
128+
stack: [mockLabwareId, 'D2'],
121129
},
122130
},
123131
}
@@ -143,4 +151,52 @@ describe('flexStackerRetrieve', () => {
143151
type: 'HOPPER_EMPTY',
144152
})
145153
})
154+
it('raises an error if the shuttle is full', () => {
155+
vi.mocked(flexStackerStateGetter).mockReturnValue({
156+
labwareOnShuttle: {
157+
primaryLabwareId: mockLabwareId,
158+
adapterLabwareId: null,
159+
lidLabwareId: null,
160+
},
161+
labwareInHopper: null,
162+
maxPoolCount: 10,
163+
storedLabwareDetails: null,
164+
type: FLEX_STACKER_MODULE_TYPE,
165+
})
166+
robotState = {
167+
...robotState,
168+
modules: {
169+
[mockModuleId]: {
170+
slot: 'D3',
171+
moduleState: {} as any,
172+
},
173+
},
174+
labware: {
175+
[mockLabwareId]: {
176+
stack: [mockLabwareId, 'D3'],
177+
},
178+
},
179+
}
180+
invariantContext = {
181+
...invariantContext,
182+
labwareEntities: {
183+
[mockLabwareId]: {
184+
labwareDefURI: 'mockURI',
185+
def: fixture96Plate as LabwareDefinition2,
186+
pythonName: 'wellPlate_1',
187+
id: mockLabwareId,
188+
},
189+
},
190+
}
191+
const result = flexStackerRetrieve(
192+
{
193+
moduleId: mockModuleId,
194+
},
195+
invariantContext,
196+
robotState
197+
)
198+
expect(getErrorResult(result).errors[0]).toMatchObject({
199+
type: 'SHUTTLE_FULL',
200+
})
201+
})
146202
})

step-generation/src/__tests__/flexStackerStore.test.ts

Lines changed: 175 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest'
22

33
import {
4+
fixture96Plate,
5+
fixture384Plate,
46
FLEX_STACKER_MODULE_TYPE,
57
FLEX_STACKER_MODULE_V1,
68
} from '@opentrons/shared-data'
79

810
import { flexStackerStore } from '../commandCreators/atomic/flexStackerStore'
9-
import { getInitialRobotStateStandard, makeContext } from '../fixtures'
11+
import {
12+
getErrorResult,
13+
getInitialRobotStateStandard,
14+
makeContext,
15+
} from '../fixtures'
16+
import { flexStackerStateGetter } from '../robotStateSelectors'
1017

11-
import type { InvariantContext, RobotState } from '../types'
18+
import type { LabwareDefinition2 } from '@opentrons/shared-data'
19+
import type {
20+
FlexStackerModuleState,
21+
InvariantContext,
22+
RobotState,
23+
} from '../types'
1224

1325
const moduleId = 'flexStackerId'
1426
vi.mock('../robotStateSelectors')
@@ -25,13 +37,60 @@ describe('flexStackerStore', () => {
2537
model: FLEX_STACKER_MODULE_V1,
2638
pythonName: 'mock_flex_stacker_1',
2739
}
40+
invariantContext.labwareEntities = {
41+
mockLabwareId: {
42+
id: 'mockLabwareId',
43+
labwareDefURI: 'mockURI',
44+
def: fixture96Plate as LabwareDefinition2,
45+
pythonName: 'wellPlate_1',
46+
},
47+
}
48+
vi.mocked(flexStackerStateGetter).mockReturnValue({
49+
labwareOnShuttle: {
50+
primaryLabwareId: 'mockLabwareId',
51+
adapterLabwareId: null,
52+
lidLabwareId: null,
53+
},
54+
labwareInHopper: [
55+
{
56+
primaryLabwareId: 'mockLabwareId',
57+
adapterLabwareId: null,
58+
lidLabwareId: null,
59+
},
60+
],
61+
maxPoolCount: 10,
62+
storedLabwareDetails: {
63+
moduleId,
64+
initialCount: 5,
65+
primaryLabware: {
66+
loadName: 'fixture_96_plate',
67+
namespace: 'opentrons',
68+
version: 1,
69+
},
70+
lidLabware: null,
71+
adapterLabware: null,
72+
},
73+
type: FLEX_STACKER_MODULE_TYPE,
74+
} as FlexStackerModuleState)
75+
robotState = {
76+
...robotState,
77+
modules: {
78+
[moduleId]: {
79+
slot: 'D3',
80+
moduleState: {} as FlexStackerModuleState,
81+
},
82+
},
83+
labware: {
84+
mockLabwareId: {
85+
stack: ['mockLabwareId', 'D3'],
86+
},
87+
},
88+
}
2889
})
90+
2991
it('creates flex stacker store command', () => {
3092
const result = flexStackerStore(
31-
{
32-
moduleId,
33-
strategy: 'automatic',
34-
},
93+
{ moduleId, strategy: 'automatic' },
3594
invariantContext,
3695
robotState
3796
)
@@ -49,4 +108,114 @@ describe('flexStackerStore', () => {
49108
python: 'mock_flex_stacker_1.store()',
50109
})
51110
})
111+
112+
it('raises an error if the shuttle is empty', () => {
113+
vi.mocked(flexStackerStateGetter).mockReturnValue({
114+
labwareOnShuttle: null,
115+
labwareInHopper: null,
116+
maxPoolCount: 10,
117+
storedLabwareDetails: null,
118+
type: FLEX_STACKER_MODULE_TYPE,
119+
})
120+
121+
const emptyShuttleRobotState: RobotState = {
122+
...robotState,
123+
modules: {
124+
[moduleId]: {
125+
slot: 'D3',
126+
moduleState: {} as FlexStackerModuleState,
127+
},
128+
},
129+
}
130+
131+
const emptyShuttleInvariantContext: InvariantContext = {
132+
...invariantContext,
133+
labwareEntities: {
134+
[moduleId]: {
135+
id: moduleId,
136+
labwareDefURI: 'mockURI',
137+
def: fixture96Plate as LabwareDefinition2,
138+
pythonName: 'wellPlate_1',
139+
},
140+
},
141+
}
142+
143+
const result = flexStackerStore(
144+
{ moduleId, strategy: 'automatic' },
145+
emptyShuttleInvariantContext,
146+
emptyShuttleRobotState
147+
)
148+
149+
expect(getErrorResult(result).errors[0]).toMatchObject({
150+
type: 'SHUTTLE_EMPTY',
151+
})
152+
})
153+
154+
it('raises an error if the labware to be stored does not match the current labware in the hopper', () => {
155+
vi.mocked(flexStackerStateGetter).mockReturnValue({
156+
labwareOnShuttle: {
157+
primaryLabwareId: 'labware-in-shuttle',
158+
adapterLabwareId: null,
159+
lidLabwareId: null,
160+
},
161+
labwareInHopper: [
162+
{
163+
primaryLabwareId: 'labware-in-hopper',
164+
adapterLabwareId: null,
165+
lidLabwareId: null,
166+
},
167+
],
168+
maxPoolCount: 10,
169+
storedLabwareDetails: {
170+
moduleId,
171+
initialCount: 5,
172+
primaryLabware: {
173+
loadName: 'wellPlate_1',
174+
namespace: 'opentrons',
175+
version: 1,
176+
},
177+
lidLabware: null,
178+
adapterLabware: null,
179+
},
180+
type: FLEX_STACKER_MODULE_TYPE,
181+
})
182+
const mismatchRobotState: RobotState = {
183+
...robotState,
184+
modules: {
185+
[moduleId]: {
186+
slot: 'D3',
187+
moduleState: {} as FlexStackerModuleState,
188+
},
189+
},
190+
labware: {
191+
'labware-in-hopper': { stack: ['labware-in-hopper', 'D3'] },
192+
'labware-in-shuttle': { stack: ['labware-in-shuttle', 'D3'] },
193+
},
194+
}
195+
const mismatchInvariantContext: InvariantContext = {
196+
...invariantContext,
197+
labwareEntities: {
198+
'labware-in-hopper': {
199+
id: 'labware-in-hopper',
200+
labwareDefURI: 'mockURI',
201+
def: fixture96Plate as LabwareDefinition2,
202+
pythonName: 'wellPlate_1',
203+
},
204+
'labware-in-shuttle': {
205+
id: 'labware-in-shuttle',
206+
labwareDefURI: 'differentMockURI',
207+
def: fixture384Plate as LabwareDefinition2,
208+
pythonName: 'wellPlate_2',
209+
},
210+
},
211+
}
212+
const result = flexStackerStore(
213+
{ moduleId, strategy: 'automatic' },
214+
mismatchInvariantContext,
215+
mismatchRobotState
216+
)
217+
expect(getErrorResult(result).errors[0]).toMatchObject({
218+
type: 'MISMATCHED_STACKER_LABWARE_TYPE',
219+
})
220+
})
52221
})

step-generation/src/commandCreators/atomic/flexStackerRetrieve.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as errorCreators from '../../errorCreators'
2-
import { getLabwareIdOnHopper, uuid } from '../../utils'
2+
import { flexStackerStateGetter } from '../../robotStateSelectors'
3+
import { getLabwareIdOnHopper, getLabwareIdOnShuttle, uuid } from '../../utils'
34

45
import type { FlexStackerRetrieveCreateCommand } from '@opentrons/shared-data'
56
import type { CommandCreator } from '../../types'
@@ -12,15 +13,20 @@ export const flexStackerRetrieve: CommandCreator<
1213
const { moduleEntities, labwareEntities } = invariantContext
1314
const modulePythonName = moduleEntities[moduleId].pythonName
1415
const moduleLocation = modules[moduleId].slot
15-
const labwareIdOnModule = getLabwareIdOnHopper(labware, moduleLocation)
16-
const labwarePythonName = labwareEntities[labwareIdOnModule]?.pythonName
17-
// TODO: add error for if there is labware on the shuttle
18-
if (!labwareIdOnModule) {
16+
const flexStackerState = flexStackerStateGetter(robotState, moduleId)
17+
const labwareIdOnHopper = getLabwareIdOnHopper(labware, moduleLocation)
18+
const labwarePythonName = labwareEntities[labwareIdOnHopper]?.pythonName
19+
20+
if (flexStackerState !== null && getLabwareIdOnShuttle(flexStackerState)) {
21+
return {
22+
errors: [errorCreators.flexStackerShuttleFull()],
23+
}
24+
}
25+
if (!labwareIdOnHopper) {
1926
return {
2027
errors: [errorCreators.flexStackerHopperEmpty()],
2128
}
2229
}
23-
2430
return {
2531
commands: [
2632
{

step-generation/src/commandCreators/atomic/flexStackerStore.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,39 @@
1-
import { uuid } from '../../utils'
1+
import * as errorCreators from '../../errorCreators'
2+
import { flexStackerStateGetter } from '../../robotStateSelectors'
3+
import {
4+
getLabwareIdOnShuttle,
5+
labwareMatchesLabwareInHopper,
6+
uuid,
7+
} from '../../utils'
28

39
import type { FlexStackerStoreCreateCommand } from '@opentrons/shared-data'
410
import type { CommandCreator } from '../../types'
511

612
export const flexStackerStore: CommandCreator<
713
FlexStackerStoreCreateCommand['params']
8-
> = (args, invariantContext) => {
14+
> = (args, invariantContext, robotState) => {
915
const { moduleId } = args
1016
const pythonName = invariantContext.moduleEntities[moduleId].pythonName
17+
const flexStackerState = flexStackerStateGetter(robotState, moduleId)
18+
const labwareId = flexStackerState?.labwareOnShuttle?.primaryLabwareId ?? null
19+
if (flexStackerState !== null && !getLabwareIdOnShuttle(flexStackerState)) {
20+
return {
21+
errors: [errorCreators.flexStackerShuttleEmpty()],
22+
}
23+
}
24+
25+
if (
26+
labwareId !== null &&
27+
!labwareMatchesLabwareInHopper(
28+
labwareId,
29+
invariantContext,
30+
flexStackerState
31+
)
32+
) {
33+
return {
34+
errors: [errorCreators.flexStackerLabwareTypeMismatch()],
35+
}
36+
}
1137
return {
1238
commands: [
1339
{

0 commit comments

Comments
 (0)