Skip to content

Commit

Permalink
feat(protocol-designer,-step-generation): wire up absorbance reader i…
Browse files Browse the repository at this point in the history
…n step-generation (#17287)

This PR wires up absorbance reader step form field to step generation,
including transforming form to args, getNextRobotStateAndWarnings, and
error and command creators for all possible plate reader steps. I also
memoize labware presence in AbsorbanceReaderTools for efficiency.

Note that UI for creating initialization will be done in a followup PR.
Dummy data is used for now.

Closes AUTH-1268
Closes AUTH-1269
Closes AUTH-1279
  • Loading branch information
ncdiehl11 authored Jan 16, 2025
1 parent 59bcddc commit 8bee654
Show file tree
Hide file tree
Showing 28 changed files with 957 additions and 14 deletions.
20 changes: 20 additions & 0 deletions protocol-designer/src/file-data/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ export const commandCreatorFromStepArgs = (
)
case 'comment':
return StepGeneration.curryCommandCreator(StepGeneration.comment, args)
case 'absorbanceReaderOpenLid':
return StepGeneration.curryCommandCreator(
StepGeneration.absorbanceReaderOpenLid,
args
)
case 'absorbanceReaderCloseLid':
return StepGeneration.curryCommandCreator(
StepGeneration.absorbanceReaderCloseLid,
args
)
case 'absorbanceReaderRead':
return StepGeneration.curryCommandCreator(
StepGeneration.absorbanceReaderCloseRead,
args
)
case 'absorbanceReaderInitialize':
return StepGeneration.curryCommandCreator(
StepGeneration.absorbanceReaderCloseInitialize,
args
)
}
// @ts-expect-error we've exhausted all command creators, but keeping this console warn
// for when we impelement the next command creator
Expand Down
2 changes: 1 addition & 1 deletion protocol-designer/src/form-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ export type AbsorbanceReaderFormType =

export interface HydratedAbsorbanceReaderFormData {
absorbanceReaderFormType: AbsorbanceReaderFormType | null
filePath: string | null
fileName: string | null
lidOpen: boolean | null
mode:
| typeof ABSORBANCE_READER_INITIALIZE_MODE_MULTI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function ReadSettings(props: ReadSettingsProps): JSX.Element {
</Flex>
<InputStepFormField
padding="0"
{...propsForFields.filePath}
{...propsForFields.fileName}
title={t('exported_file_name')}
/>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import {
Expand Down Expand Up @@ -35,8 +35,9 @@ export function AbsorbanceReaderTools(props: StepFormProps): JSX.Element {
const robotState = useSelector(getRobotStateAtActiveItem)
const absorbanceReaderOptions = useSelector(getAbsorbanceReaderLabwareOptions)
const { labware = {}, modules = {} } = robotState ?? {}
const isLabwareOnAbsorbanceReader = Object.values(labware).some(
lw => lw.slot === propsForFields.moduleId.value
const isLabwareOnAbsorbanceReader = useMemo(
() => Object.values(labware).some(lw => lw.slot === moduleId),
[moduleId]
)
const absorbanceReaderFormType = formData.absorbanceReaderFormType as AbsorbanceReaderFormType
const absorbanceReaderState = modules[moduleId]
Expand Down Expand Up @@ -86,7 +87,11 @@ export function AbsorbanceReaderTools(props: StepFormProps): JSX.Element {
)

const page1Content = (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing12}>
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing12}
width="100%"
>
<DropdownStepFormField
options={absorbanceReaderOptions}
title={t('module')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export function getDefaultsForStepType(
case 'absorbanceReader':
return {
absorbanceReaderFormType: null,
filePath: null,
fileName: null,
lidOpen: null,
mode: null,
moduleId: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const updatePatchOnAbsorbanceReaderFormType = (
'referenceWavelength',
'lidOpen',
'mode',
'filePath'
'fileName'
),
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
ABSORBANCE_READER_INITIALIZE,
ABSORBANCE_READER_LID,
ABSORBANCE_READER_READ,
} from '../../../constants'
import type { AbsorbanceReaderArgs } from '@opentrons/step-generation'
import type { HydratedAbsorbanceReaderFormData } from '../../../form-types'

// TODO (nd: 1/15/2025) replace with actual form data once UI is
const DUMMY_INITIALIZATION = {
wavelengths: [420, 600],
referenceWavelength: 200,
}

export const absorbanceReaderFormToArgs = (
hydratedFormData: HydratedAbsorbanceReaderFormData
): AbsorbanceReaderArgs | null => {
const {
absorbanceReaderFormType,
fileName,
lidOpen,
moduleId,
// mode,
// referenceWavelength,
// wavelengths,
} = hydratedFormData
const lidAction = lidOpen
? 'absorbanceReaderOpenLid'
: 'absorbanceReaderCloseLid'
switch (absorbanceReaderFormType) {
case ABSORBANCE_READER_INITIALIZE:
return {
module: moduleId,
mode: 'multi', // TODO (nd: 1/16/2025): reflect actual `mode` form field
commandCreatorFnName: 'absorbanceReaderInitialize',
...DUMMY_INITIALIZATION,
}
case ABSORBANCE_READER_READ:
return {
module: moduleId,
commandCreatorFnName: 'absorbanceReaderRead',
fileName,
}
case ABSORBANCE_READER_LID:
return {
module: moduleId,
commandCreatorFnName: lidAction,
}
default:
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { heaterShakerFormToArgs } from './heaterShakerFormToArgs'
import { moveLiquidFormToArgs } from './moveLiquidFormToArgs'
import { moveLabwareFormToArgs } from './moveLabwareFormToArgs'
import { commentFormToArgs } from './commentFormToArgs'
import { absorbanceReaderFormToArgs } from './absorbanceReaderFormToArgs'
import type { CommandCreatorArgs } from '@opentrons/step-generation'
import type {
FormData,
HydratedAbsorbanceReaderFormData,
HydratedCommentFormData,
HydratedHeaterShakerFormData,
HydratedMagnetFormData,
Expand Down Expand Up @@ -74,6 +76,12 @@ export const stepFormToArgs = (hydratedForm: FormData): StepArgs => {
return commentFormToArgs(commentFormData)
}

case 'absorbanceReader': {
return absorbanceReaderFormToArgs(
castForm as HydratedAbsorbanceReaderFormData
)
}

default:
console.warn(`stepFormToArgs not implemented for ${castForm.stepType}`)
return null
Expand Down
1 change: 0 additions & 1 deletion protocol-designer/src/steplist/generateSubstepItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ export function generateSubstepItem(

console.warn(
"generateSubsteps doesn't support commandCreatorFnName: ",
// @ts-expect-error(sa, 2021-6-14): I don't think this case can ever happen, so stepArgs.commandCreatorFnName gets never typed
stepArgs.commandCreatorFnName,
stepId
)
Expand Down
142 changes: 142 additions & 0 deletions step-generation/src/__tests__/absorbanceReaderCloseInitialize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { beforeEach, describe, it, expect, vi, afterEach } from 'vitest'
import { absorbanceReaderCloseInitialize } from '../commandCreators'
import {
absorbanceReaderStateGetter,
getModuleState,
} from '../robotStateSelectors'
import { getInitialRobotStateStandard, makeContext } from '../fixtures'
import { getErrorResult, getSuccessResult } from '../fixtures/commandFixtures'

import type {
AbsorbanceReaderInitializeArgs,
AbsorbanceReaderState,
InvariantContext,
RobotState,
} from '../types'
import {
ABSORBANCE_READER_TYPE,
ABSORBANCE_READER_V1,
} from '@opentrons/shared-data'

vi.mock('../robotStateSelectors')

describe('absorbanceReaderCloseInitialize compound command creator', () => {
let absorbanceReaderCloseInitializeArgs: AbsorbanceReaderInitializeArgs
const ABSORBANCE_READER_MODULE_ID = 'absorbanceReaderModuleId'
const ABSORBANCE_READER_MODULE_SLOT = 'D3'
let robotState: RobotState
let invariantContext: InvariantContext
beforeEach(() => {
absorbanceReaderCloseInitializeArgs = {
commandCreatorFnName: 'absorbanceReaderInitialize',
module: ABSORBANCE_READER_MODULE_ID,
mode: 'single',
wavelengths: [450],
}
invariantContext = {
...makeContext(),
moduleEntities: {
[ABSORBANCE_READER_MODULE_ID]: {
id: ABSORBANCE_READER_MODULE_ID,
type: ABSORBANCE_READER_TYPE,
model: ABSORBANCE_READER_V1,
},
},
}
const state = getInitialRobotStateStandard(invariantContext)

robotState = {
...state,
modules: {
...state.modules,
[ABSORBANCE_READER_MODULE_ID]: {
slot: ABSORBANCE_READER_MODULE_SLOT,
} as any,
},
}
vi.mocked(getModuleState).mockReturnValue({
type: ABSORBANCE_READER_TYPE,
} as any)
})
afterEach(() => {
vi.restoreAllMocks()
})
it('should return an error when module is not found', () => {
const result = absorbanceReaderCloseInitialize(
absorbanceReaderCloseInitializeArgs,
invariantContext,
robotState
)
vi.mocked(absorbanceReaderStateGetter).mockReturnValue(null)

expect(getErrorResult(result).errors).toHaveLength(1)
expect(getErrorResult(result).errors[0]).toMatchObject({
type: 'MISSING_MODULE',
})
})
it('should emit close and intalize commands if single mode', () => {
vi.mocked(absorbanceReaderStateGetter).mockReturnValue(
{} as AbsorbanceReaderState
)

const result = absorbanceReaderCloseInitialize(
absorbanceReaderCloseInitializeArgs,
invariantContext,
robotState
)

expect(getSuccessResult(result).commands).toEqual([
{
commandType: 'absorbanceReader/closeLid',
key: expect.any(String),
params: {
moduleId: 'absorbanceReaderModuleId',
},
},
{
commandType: 'absorbanceReader/initialize',
key: expect.any(String),
params: {
moduleId: 'absorbanceReaderModuleId',
sampleWavelengths: [450],
measureMode: 'single',
},
},
])
})
it('should emit close and intalize commands if multi mode', () => {
absorbanceReaderCloseInitializeArgs = {
...absorbanceReaderCloseInitializeArgs,
mode: 'multi',
wavelengths: [450, 600],
}
vi.mocked(absorbanceReaderStateGetter).mockReturnValue(
{} as AbsorbanceReaderState
)

const result = absorbanceReaderCloseInitialize(
absorbanceReaderCloseInitializeArgs,
invariantContext,
robotState
)

expect(getSuccessResult(result).commands).toEqual([
{
commandType: 'absorbanceReader/closeLid',
key: expect.any(String),
params: {
moduleId: 'absorbanceReaderModuleId',
},
},
{
commandType: 'absorbanceReader/initialize',
key: expect.any(String),
params: {
moduleId: 'absorbanceReaderModuleId',
sampleWavelengths: [450, 600],
measureMode: 'multi',
},
},
])
})
})
Loading

0 comments on commit 8bee654

Please sign in to comment.