Skip to content

Commit 01e3fc2

Browse files
mjhuffsfoster1
authored andcommitted
fix(app): fix LPC error when loading a module into C2 (#18480)
Closes RESC-461 Loading a module, aka the mag block, into slot C2 causes LPC to error out when setting a default offset. Product + Design are working on a longer term solution, but in the interim, we want to include something for 8.5. Select a default offset location for LPC based on slots that we know do not contain a module. We preferentially select C2 if available.
1 parent 2418775 commit 01e3fc2

File tree

3 files changed

+146
-7
lines changed

3 files changed

+146
-7
lines changed

app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom/__tests__/getDefaultOffsetForLabware.test.ts

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { vi, it, describe, expect } from 'vitest'
2+
23
import { ANY_LOCATION } from '@opentrons/api-client'
3-
import { getLabwareDefURI } from '@opentrons/shared-data'
4+
import { C2_ADDRESSABLE_AREA, getLabwareDefURI } from '@opentrons/shared-data'
45

56
import { getDefaultOffsetDetailsForLabware } from '../getDefaultOffsetForLabware'
67
import { OFFSET_KIND_DEFAULT } from '/app/redux/protocol-runs'
@@ -70,6 +71,24 @@ describe('getDefaultOffsetDetailsForLabware', () => {
7071
definitionUri: ADAPTER_URI,
7172
},
7273
],
74+
modules: [],
75+
}
76+
77+
const MOCK_PROTOCOL_DATA_WITH_MODULES = {
78+
labware: [
79+
{
80+
id: ADAPTER_ID,
81+
definitionUri: ADAPTER_URI,
82+
},
83+
],
84+
modules: [
85+
{
86+
location: { slotName: 'C2' },
87+
},
88+
{
89+
location: { slotName: 'A1' },
90+
},
91+
],
7392
}
7493

7594
it('should return default offset details with minimal params', () => {
@@ -89,7 +108,7 @@ describe('getDefaultOffsetDetailsForLabware', () => {
89108
labwareId: LABWARE_ID,
90109
definitionUri: LABWARE_URI,
91110
kind: OFFSET_KIND_DEFAULT,
92-
addressableAreaName: 'C2',
111+
addressableAreaName: C2_ADDRESSABLE_AREA,
93112
lwOffsetLocSeq: ANY_LOCATION,
94113
closestBeneathAdapterId: undefined,
95114
lwModOnlyStackupDetails: [
@@ -202,4 +221,75 @@ describe('getDefaultOffsetDetailsForLabware', () => {
202221

203222
expect(result.locationDetails.closestBeneathAdapterId).toBeUndefined()
204223
})
224+
225+
it('should use C2 when available and no modules are present', () => {
226+
const result = getDefaultOffsetDetailsForLabware({
227+
uri: LABWARE_URI,
228+
lwLocInfo: MOCK_LW_LOC_COMBOS,
229+
currentOffsets: [],
230+
labwareDefs: [MOCK_LABWARE_DEF],
231+
locationSpecificOffsetDetails: [],
232+
protocolData: MOCK_PROTOCOL_DATA,
233+
} as any)
234+
235+
expect(result.locationDetails.addressableAreaName).toBe(C2_ADDRESSABLE_AREA)
236+
})
237+
238+
it('should use alternative slot when C2 is occupied by a module', () => {
239+
const result = getDefaultOffsetDetailsForLabware({
240+
uri: LABWARE_URI,
241+
lwLocInfo: MOCK_LW_LOC_COMBOS,
242+
currentOffsets: [],
243+
labwareDefs: [MOCK_LABWARE_DEF],
244+
locationSpecificOffsetDetails: [],
245+
protocolData: MOCK_PROTOCOL_DATA_WITH_MODULES,
246+
} as any)
247+
248+
expect(result.locationDetails.addressableAreaName).not.toBe(
249+
C2_ADDRESSABLE_AREA
250+
)
251+
expect([
252+
'A2',
253+
'A3',
254+
'B1',
255+
'B2',
256+
'B3',
257+
'C1',
258+
'C3',
259+
'D1',
260+
'D2',
261+
'D3',
262+
]).toContain(result.locationDetails.addressableAreaName)
263+
})
264+
265+
it('should fallback to C2 when all slots are occupied', () => {
266+
const protocolDataWithAllModules = {
267+
labware: [],
268+
modules: [
269+
{ location: { slotName: 'A1' } },
270+
{ location: { slotName: 'A2' } },
271+
{ location: { slotName: 'A3' } },
272+
{ location: { slotName: 'B1' } },
273+
{ location: { slotName: 'B2' } },
274+
{ location: { slotName: 'B3' } },
275+
{ location: { slotName: 'C1' } },
276+
{ location: { slotName: 'C2' } },
277+
{ location: { slotName: 'C3' } },
278+
{ location: { slotName: 'D1' } },
279+
{ location: { slotName: 'D2' } },
280+
{ location: { slotName: 'D3' } },
281+
],
282+
}
283+
284+
const result = getDefaultOffsetDetailsForLabware({
285+
uri: LABWARE_URI,
286+
lwLocInfo: MOCK_LW_LOC_COMBOS,
287+
currentOffsets: [],
288+
labwareDefs: [MOCK_LABWARE_DEF],
289+
locationSpecificOffsetDetails: [],
290+
protocolData: protocolDataWithAllModules,
291+
} as any)
292+
293+
expect(result.locationDetails.addressableAreaName).toBe(C2_ADDRESSABLE_AREA)
294+
})
205295
})

app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom/getDefaultOffsetForLabware.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1+
import head from 'lodash/head'
2+
13
import { ANY_LOCATION } from '@opentrons/api-client'
2-
import { getLabwareDefURI } from '@opentrons/shared-data'
4+
import {
5+
A1_ADDRESSABLE_AREA,
6+
B1_ADDRESSABLE_AREA,
7+
C2_ADDRESSABLE_AREA,
8+
getLabwareDefURI,
9+
STANDARD_FLEX_SLOTS,
10+
THERMOCYCLER_MODULE_V1,
11+
THERMOCYCLER_MODULE_V2,
12+
} from '@opentrons/shared-data'
313

414
import { OFFSET_KIND_DEFAULT } from '/app/redux/protocol-runs'
515

6-
import type { LabwareDefinition2 } from '@opentrons/shared-data'
16+
import type {
17+
FlexAddressableAreaName,
18+
LabwareDefinition2,
19+
} from '@opentrons/shared-data'
720
import type {
821
DefaultOffsetDetails,
9-
LocationSpecificOffsetDetails,
1022
LabwareModuleStackupDetails,
23+
LocationSpecificOffsetDetails,
1124
} from '/app/redux/protocol-runs'
1225
import type { GetLPCLabwareInfoForURI } from '.'
1326

@@ -42,7 +55,7 @@ export function getDefaultOffsetDetailsForLabware(
4255
definitionUri: uri,
4356
kind: OFFSET_KIND_DEFAULT,
4457
// We always do default offset LPCing in this slot.
45-
addressableAreaName: 'C2',
58+
addressableAreaName: getValidDefaultOffsetLocation(params.protocolData),
4659
lwOffsetLocSeq: ANY_LOCATION,
4760
closestBeneathAdapterId,
4861
// The only labware present on deck when configuring the default offset is the top-most labware itself.
@@ -128,3 +141,39 @@ function getFirstAdapterIdFrom(
128141
lsOffset => lsOffset.locationDetails.closestBeneathAdapterId != null
129142
)?.locationDetails.closestBeneathAdapterId
130143
}
144+
145+
// Find a valid location for setting a default offset slot.
146+
// A slot is valid if it does not contain a module.
147+
// This util works under the assumption that modules cannot be added or removed
148+
// from the deck mid-protocol run and that currently there is at least one
149+
// slot on the deck without a module (true as of 8.4.0, since launching LPC requires
150+
// the presence of a tiprack, which must occupy a non-module deck slot).
151+
// NOTE: This util is meant to be temporary until product/design devise
152+
// an alternative method for default offset location selection.
153+
function getValidDefaultOffsetLocation(
154+
protocolData: GetStackingInfoParams['protocolData']
155+
): FlexAddressableAreaName {
156+
const validLocations = new Set<FlexAddressableAreaName>(
157+
STANDARD_FLEX_SLOTS as FlexAddressableAreaName[]
158+
)
159+
160+
protocolData?.modules.forEach(mod => {
161+
if (
162+
mod.model === THERMOCYCLER_MODULE_V2 ||
163+
mod.model === THERMOCYCLER_MODULE_V1
164+
) {
165+
validLocations.delete(A1_ADDRESSABLE_AREA)
166+
validLocations.delete(B1_ADDRESSABLE_AREA)
167+
} else {
168+
const slotName = mod.location.slotName as FlexAddressableAreaName
169+
validLocations.delete(slotName)
170+
}
171+
})
172+
173+
if (validLocations.has(C2_ADDRESSABLE_AREA)) {
174+
return C2_ADDRESSABLE_AREA
175+
} else {
176+
// The fallback should never occur in practice.
177+
return head(Array.from(validLocations)) ?? C2_ADDRESSABLE_AREA
178+
}
179+
}

app/src/redux/protocol-runs/types/lpc/offsets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export type OffsetLocationDetails =
7373

7474
export interface DefaultOffsetLocationDetails
7575
extends BaseOffsetLocationDetails {
76-
addressableAreaName: 'C2'
76+
addressableAreaName: FlexAddressableAreaName
7777
kind: 'default'
7878
lwOffsetLocSeq: typeof ANY_LOCATION
7979
hardCodedOffsetId?: undefined

0 commit comments

Comments
 (0)