Skip to content

Commit

Permalink
feat(protocol-designer): hook up wait for temp in PD (#4926)
Browse files Browse the repository at this point in the history
* feat(protocol-designer): hook up wait for temp in PD

Adds new command creator for await temp, adds warnings as specified in ticket

fix #4732

* refactor await temperature test

* add comment explaining unreachable temp

* clean up imports

* refactor step generation tests to use same robot factory function

* plug in forAwaitTemperature state updater

* adds in missing temp step error text into localization

* removed comments

* Use temperature instead of temp to avoid confusion

* move mock object creator into existing fixture file

* rename test name to be more understandable

* refactor test to reuse mock factory function

* import status constants into test fixture file

* update test names to reflect action and outcome

* remove leftover console.log
  • Loading branch information
shlokamin authored Feb 7, 2020
1 parent 49d81c9 commit 710cffa
Show file tree
Hide file tree
Showing 16 changed files with 528 additions and 69 deletions.
5 changes: 5 additions & 0 deletions protocol-designer/src/file-data/selectors/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ const commandCreatorFromStepArgs = (
StepGeneration.deactivateTemperature,
args
)
case 'awaitTemperature':
return StepGeneration.curryCommandCreator(
StepGeneration.awaitTemperature,
args
)
}
console.warn(`unhandled commandCreatorFnName: ${args.commandCreatorFnName}`)
return null
Expand Down
4 changes: 4 additions & 0 deletions protocol-designer/src/localization/en/alert.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
"MISSING_MODULE": {
"title": "Missing module for step",
"body": "A step requires a module that does not exist"
},
"MISSING_TEMPERATURE_STEP": {
"title": "Missing Temperature step",
"body": "Add a Temperature step prior to this Pause step. The module is not currently changing temperature because it has either been deactivated or is holding a temperature"
}
},
"warning": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// @flow
import {
TEMPDECK,
TEMPERATURE_AT_TARGET,
TEMPERATURE_DEACTIVATED,
} from '../../../constants'
import * as errorCreators from '../../errorCreators'
import type { CommandCreator, AwaitTemperatureArgs } from '../../types'
import { getModuleState } from '../../utils/misc'

/** Set temperature target for specified module. */
export const awaitTemperature: CommandCreator<AwaitTemperatureArgs> = (
args,
invariantContext,
prevRobotState
) => {
const { module, temperature } = args
const tempModState = module ? getModuleState(prevRobotState, module) : null

if (module === null || !tempModState) {
return { errors: [errorCreators.missingModuleError()] }
}

if (tempModState.type !== TEMPDECK) {
console.error(
`expected module to be ${TEMPDECK} but got ${tempModState.type}`
)
return { errors: [errorCreators.missingModuleError()] }
}

// if the temp mod is already at the target temp
// AND the newly awaited temperature is different than the target temp
// this means the temp mod will not change its temp, since it is already
// at the target temp, so the new await temp will never be reached
const unreachableTemp =
tempModState.status === TEMPERATURE_AT_TARGET &&
tempModState.targetTemperature !== temperature

if (unreachableTemp || tempModState.status === TEMPERATURE_DEACTIVATED) {
return { errors: [errorCreators.missingTemperatureStep()] }
}

const moduleType = invariantContext.moduleEntities[module]?.type
const params = { module, temperature }
switch (moduleType) {
case TEMPDECK:
return {
commands: [
{
command: 'temperatureModule/awaitTemperature',
params,
},
],
}

default:
console.error(
`awaitTemperature expected module ${module} to be ${TEMPDECK}, got ${moduleType}`
)
return { errors: [errorCreators.missingModuleError()] }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import aspirate from './aspirate'
import { awaitTemperature } from './awaitTemperature'
import blowout from './blowout'
import { deactivateTemperature } from './deactivateTemperature'
import delay from './delay'
Expand All @@ -14,6 +15,7 @@ import touchTip from './touchTip'

export {
aspirate,
awaitTemperature,
blowout,
deactivateTemperature,
delay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { transfer, mix, consolidate, distribute } from './compound'

export {
aspirate,
awaitTemperature,
blowout,
deactivateTemperature,
delay,
Expand Down
8 changes: 8 additions & 0 deletions protocol-designer/src/step-generation/errorCreators.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ export function missingModuleError(): CommandCreatorError {
}
}

export function missingTemperatureStep(): CommandCreatorError {
return {
message:
'This module is not changing temperature because it has either been deactivated or is already holding a temperature. In order to pause the protocol and wait for your module to reach a temperature, you must first use a Temperature step to tell the module to start changing to a new temperature',
type: 'MISSING_TEMPERATURE_STEP',
}
}

export function tipVolumeExceeded(args: {|
actionName: string,
volume: string | number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { forDropTip } from './forDropTip'
import { forPickUpTip } from './forPickUpTip'
import { forEngageMagnet, forDisengageMagnet } from './magnetUpdates'
import {
forAwaitTemperature,
forSetTemperature,
forDeactivateTemperature,
} from './temperatureUpdates'
Expand Down Expand Up @@ -70,6 +71,12 @@ function _getNextRobotStateAndWarningsSingleCommand(
)
break
case 'temperatureModule/awaitTemperature':
forAwaitTemperature(
command.params,
invariantContext,
robotStateAndWarnings
)
break
case 'thermocycler/setTargetBlockTemperature':
case 'thermocycler/setTargetLidTemperature':
case 'thermocycler/awaitBlockTemperature':
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// @flow
import assert from 'assert'
import { getModuleState } from '../utils/misc'
import {
TEMPDECK,
TEMPERATURE_APPROACHING_TARGET,
TEMPERATURE_DEACTIVATED,
TEMPERATURE_AT_TARGET,
} from '../../constants'
import { getModuleState } from '../utils/misc'
import type {
TemperatureParams,
ModuleOnlyParams,
Expand Down Expand Up @@ -40,6 +41,27 @@ export function forSetTemperature(
)
}

export function forAwaitTemperature(
params: TemperatureParams,
invariantContext: InvariantContext,
robotStateAndWarnings: RobotStateAndWarnings
): void {
const { robotState } = robotStateAndWarnings
const { module, temperature } = params
const moduleState = getModuleState(robotState, module)

assert(
module in robotState.modules,
`forSetTemperature expected module id "${module}"`
)

if (moduleState.type === TEMPDECK) {
if (temperature === moduleState.targetTemperature) {
moduleState.status = TEMPERATURE_AT_TARGET
}
}
}

export function forDeactivateTemperature(
params: ModuleOnlyParams,
invariantContext: InvariantContext,
Expand Down
1 change: 1 addition & 0 deletions protocol-designer/src/step-generation/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
export {
aspirate,
awaitTemperature,
blowout,
consolidate,
distribute,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// @flow
import {
TEMPERATURE_AT_TARGET,
TEMPERATURE_APPROACHING_TARGET,
TEMPERATURE_DEACTIVATED,
} from '../../constants'
import { awaitTemperature } from '../commandCreators/atomic/awaitTemperature'
import {
getStateAndContextTempMagModules,
robotWithStatusAndTemp,
} from './fixtures'

describe('awaitTemperature', () => {
const temperatureModuleId = 'temperatureModuleId'
const thermocyclerId = 'thermocyclerId'
const commandCreatorFnName = 'awaitTemperature'
const prevRobotTemp = 42

const missingModuleError = {
errors: [{ message: expect.any(String), type: 'MISSING_MODULE' }],
}
const missingTemperatureStep = {
errors: [{ message: expect.any(String), type: 'MISSING_TEMPERATURE_STEP' }],
}

let invariantContext
let robotState

beforeEach(() => {
const stateAndContext = getStateAndContextTempMagModules({
temperatureModuleId,
thermocyclerId,
})
invariantContext = stateAndContext.invariantContext
robotState = stateAndContext.robotState
})

test('temperature module id exists and temp status is approaching temp', () => {
const temperature = 20
const args = {
module: temperatureModuleId,
temperature,
commandCreatorFnName,
}
const previousRobotState = robotWithStatusAndTemp(
robotState,
temperatureModuleId,
TEMPERATURE_APPROACHING_TARGET,
prevRobotTemp
)

const expected = {
commands: [
{
command: 'temperatureModule/awaitTemperature',
params: {
module: temperatureModuleId,
temperature: 20,
},
},
],
}
const result = awaitTemperature(args, invariantContext, previousRobotState)
expect(result).toEqual(expected)
})
test('returns missing module error when module id does not exist', () => {
const temperature = 42
const args = {
module: 'someNonexistentModuleId',
temperature,
commandCreatorFnName,
}

const result = awaitTemperature(args, invariantContext, robotState)
expect(result).toEqual(missingModuleError)
})
test('returns missing module error when module id is null', () => {
const temperature = 42
const args = {
module: null,
temperature,
commandCreatorFnName,
}

const result = awaitTemperature(args, invariantContext, robotState)
expect(result).toEqual(missingModuleError)
})
test('returns awaitTemperature command creator when temperature module already at target temp and awaiting that same temp', () => {
const temperature = 42
const args = {
module: temperatureModuleId,
temperature,
commandCreatorFnName,
}
const previousRobotState = robotWithStatusAndTemp(
robotState,
temperatureModuleId,
TEMPERATURE_AT_TARGET,
prevRobotTemp
)
const expected = {
commands: [
{
command: 'temperatureModule/awaitTemperature',
params: {
module: temperatureModuleId,
temperature: 42,
},
},
],
}
const result = awaitTemperature(args, invariantContext, previousRobotState)
expect(result).toEqual(expected)
})
test('returns missing temperature step error when temperature module already at target temp and awaiting different temp', () => {
const temperature = 80
const args = {
module: temperatureModuleId,
temperature,
commandCreatorFnName,
}

const previousRobotState = robotWithStatusAndTemp(
robotState,
temperatureModuleId,
TEMPERATURE_AT_TARGET,
prevRobotTemp
)

const result = awaitTemperature(args, invariantContext, previousRobotState)
expect(result).toEqual(missingTemperatureStep)
})
test('returns missing temperature step error when prev temp state is DEACTIVATED', () => {
const temperature = 80
const args = {
module: temperatureModuleId,
temperature,
commandCreatorFnName,
}
const previousRobotState = robotWithStatusAndTemp(
robotState,
temperatureModuleId,
TEMPERATURE_DEACTIVATED,
prevRobotTemp
)

const result = awaitTemperature(args, invariantContext, previousRobotState)
expect(result).toEqual(missingTemperatureStep)
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import cloneDeep from 'lodash/cloneDeep'
import mapValues from 'lodash/mapValues'
import { getLabwareDefURI } from '@opentrons/shared-data'
import {
Expand All @@ -17,6 +18,8 @@ import {
SPAN7_8_10_11_SLOT,
TEMPDECK,
THERMOCYCLER,
TEMPERATURE_APPROACHING_TARGET,
TEMPERATURE_AT_TARGET,
TEMPERATURE_DEACTIVATED,
} from '../../../constants'
import {
Expand Down Expand Up @@ -274,3 +277,21 @@ export const getStateAndContextTempMagModules = ({
}
return { invariantContext, robotState }
}

export const robotWithStatusAndTemp = (
robotState: RobotState,
temperatureModuleId: string,
status:
| typeof TEMPERATURE_AT_TARGET
| typeof TEMPERATURE_APPROACHING_TARGET
| typeof TEMPERATURE_DEACTIVATED,
targetTemperature: number | null
): RobotState => {
const robot = cloneDeep(robotState)
robot.modules[temperatureModuleId].moduleState = {
type: TEMPDECK,
targetTemperature,
status,
}
return robot
}
Loading

0 comments on commit 710cffa

Please sign in to comment.