Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): support unversioned pipettes in JSON protocols #2605

Merged
merged 10 commits into from
Nov 7, 2018
75 changes: 42 additions & 33 deletions api/src/opentrons/config/pipette_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@


root_dir = os.path.abspath(os.path.dirname(root_file))
config_file = os.path.join(root_dir,
'shared_data', 'robot-data', 'pipette-config.json')
config_name_file = os.path.join(
root_dir, 'shared_data', 'robot-data', 'pipetteNameSpecs.json')
config_model_file = os.path.join(
root_dir, 'shared_data', 'robot-data', 'pipetteModelSpecs.json')
log = logging.getLogger(__name__)

pipette_config = namedtuple(
Expand Down Expand Up @@ -54,7 +56,7 @@
Z_OFFSET_P1000 = 20 # shortest single-channel pipette


with open(config_file) as cfg_file:
with open(config_model_file) as cfg_file:
configs = list(json.load(cfg_file).keys())


Expand All @@ -63,39 +65,46 @@ def load(pipette_model: str) -> pipette_config:
Lazily loads pipette config data from disk. This means that changes to the
configuration data should be picked up on newly instantiated objects
without requiring a restart. If :param pipette_model is not in the top-
level keys of the "pipette-config.json" file, this function will raise a
KeyError
level keys of the "pipetteModelSpecs.json" file, this function will raise
a KeyError
:param pipette_model: a pipette model string corresponding to a top-level
key in the "pipette-config.json" file
key in the "pipetteModelSpecs.json" file
:return: a `pipette_config` instance
"""
with open(config_file) as cfg_file:
cfg = json.load(cfg_file).get(pipette_model, {})

plunger_pos = cfg.get('plungerPositions', {})

res = pipette_config(
plunger_positions={
'top': plunger_pos.get('top'),
'bottom': plunger_pos.get('bottom'),
'blow_out': plunger_pos.get('blowOut'),
'drop_tip': plunger_pos.get('dropTip'),
},
pick_up_current=cfg.get('pickUpCurrent'),
pick_up_distance=cfg.get('pickUpDistance'),
aspirate_flow_rate=cfg.get('aspirateFlowRate'),
dispense_flow_rate=cfg.get('dispenseFlowRate'),
channels=cfg.get('channels'),
model_offset=cfg.get('modelOffset'),
plunger_current=cfg.get('plungerCurrent'),
drop_tip_current=cfg.get('dropTipCurrent'),
min_volume=cfg.get('minVolume'),
max_volume=cfg.get('maxVolume'),
ul_per_mm=cfg.get('ulPerMm'),
quirks=cfg.get('quirks'),
tip_length=cfg.get('tipLength'),
display_name=cfg.get('displayName')
)
with open(config_model_file) as model_cfg_file:
cfg = json.load(model_cfg_file)[pipette_model]

name_key = cfg.get('name')

# spread name keys into model-specific dict
with open(config_name_file) as name_cfg_file:
name_data = json.load(name_cfg_file)[name_key]
cfg.update(name_data)

plunger_pos = cfg.get('plungerPositions', {})

res = pipette_config(
plunger_positions={
'top': plunger_pos.get('top'),
'bottom': plunger_pos.get('bottom'),
'blow_out': plunger_pos.get('blowOut'),
'drop_tip': plunger_pos.get('dropTip'),
},
pick_up_current=cfg.get('pickUpCurrent'),
pick_up_distance=cfg.get('pickUpDistance'),
aspirate_flow_rate=cfg.get('defaultAspirateFlowRate'),
dispense_flow_rate=cfg.get('defaultDispenseFlowRate'),
channels=cfg.get('channels'),
model_offset=cfg.get('modelOffset'),
plunger_current=cfg.get('plungerCurrent'),
drop_tip_current=cfg.get('dropTipCurrent'),
min_volume=cfg.get('minVolume'),
max_volume=cfg.get('maxVolume'),
ul_per_mm=cfg.get('ulPerMm'),
quirks=cfg.get('quirks'),
tip_length=cfg.get('tipLength'),
display_name=cfg.get('displayName')
)

# Verify that stored values agree with calculations
if 'multi' in pipette_model:
Expand Down
16 changes: 8 additions & 8 deletions api/src/opentrons/legacy_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def P10_Single(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p10_single')
config = pipette_config.load(pipette_model_version)

Expand All @@ -74,7 +74,7 @@ def P10_Multi(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p10_multi')
config = pipette_config.load(pipette_model_version)

Expand All @@ -99,7 +99,7 @@ def P50_Single(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p50_single')
config = pipette_config.load(pipette_model_version)

Expand All @@ -124,7 +124,7 @@ def P50_Multi(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p50_multi')
config = pipette_config.load(pipette_model_version)

Expand All @@ -149,7 +149,7 @@ def P300_Single(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p300_single')
config = pipette_config.load(pipette_model_version)

Expand All @@ -174,7 +174,7 @@ def P300_Multi(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p300_multi')
config = pipette_config.load(pipette_model_version)

Expand All @@ -199,7 +199,7 @@ def P1000_Single(
min_volume=None,
max_volume=None):

pipette_model_version = self._retrieve_version_number(
pipette_model_version = self.retrieve_version_number(
mount, 'p1000_single')
config = pipette_config.load(pipette_model_version)

Expand Down Expand Up @@ -258,7 +258,7 @@ def _create_pipette_from_config(

return p

def _retrieve_version_number(self, mount, expected_model_substring):
def retrieve_version_number(self, mount, expected_model_substring):
attached_model = robot.get_attached_pipettes()[mount]['model']

if attached_model and expected_model_substring in attached_model:
Expand Down
17 changes: 14 additions & 3 deletions api/src/opentrons/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@ def load_pipettes(protocol_data):
for pipette_id, props in pipettes.items():
model = props.get('model')
mount = props.get('mount')
config = pipette_config.load(model)
pipettes_by_id[pipette_id] = instruments._create_pipette_from_config(

# TODO: Ian 2018-11-02 remove this HACK when backwards-compatability
# for JSON protocols with versioned pipettes is dropped
# (next JSON protocol schema major bump)
versionless_model = model.split('_v')[0]

pipette_model_version = instruments.retrieve_version_number(
mount, versionless_model)
config = pipette_config.load(pipette_model_version)

pipette = instruments._create_pipette_from_config(
config=config,
mount=mount,
name=model)
name=pipette_model_version)

pipettes_by_id[pipette_id] = pipette

return pipettes_by_id

Expand Down
2 changes: 1 addition & 1 deletion api/tests/opentrons/protocols/test_load_json_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_load_pipettes():
"pipettes": {
"leftPipetteHere": {
"mount": "left",
"model": "p10_single_v1"
"model": "p10_single"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be updated?

"leftPipetteHere": {
  "mount": "left",
  "model": "p10_single_v1",
  "name": "p10_single"
}

}
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/CalibrateDeck/AttachTip.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import * as React from 'react'
import type {PipetteConfig} from '@opentrons/shared-data'
import type {PipetteModelSpecs} from '@opentrons/shared-data'
import type {CalibrateDeckStartedProps, CalibrationStep} from './types'
import {PrimaryButton} from '@opentrons/components'

Expand All @@ -12,7 +12,7 @@ type Props = CalibrateDeckStartedProps & {

type DiagramProps = {
calibrationStep: CalibrationStep,
pipette: PipetteConfig,
pipette: PipetteModelSpecs,
}

type Channels = 'single' | 'multi'
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/CalibrateDeck/ClearDeckAlert.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react'
import {connect} from 'react-redux'
import {push} from 'react-router-redux'

import type {PipetteConfig} from '@opentrons/shared-data'
import type {PipetteNameSpecs} from '@opentrons/shared-data'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do me a favor and remove this line instead? It's unused

import type {Mount} from '../../robot'
import type {CalibrateDeckProps} from './types'

Expand All @@ -12,7 +12,7 @@ import {deckCalibrationCommand as dcCommand} from '../../http-api-client'
import ClearDeckAlertModal from '../ClearDeckAlertModal'

type OP = CalibrateDeckProps & {
pipette: PipetteConfig,
pipette: PipetteNameSpecs,
Copy link
Contributor

@mcous mcous Nov 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment: pipette and mount are not used. Not sure why they're still here but can you change this whole block to just: type OP = CalibrateDeckProps?

mount: Mount,
}

Expand Down
4 changes: 2 additions & 2 deletions app/src/components/CalibrateDeck/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Switch, Route, withRouter} from 'react-router'
import type {State, Dispatch} from '../../types'
import type {OP, SP, DP, CalibrateDeckProps, CalibrationStep} from './types'

import {getPipette} from '@opentrons/shared-data'
import {getPipetteModelSpecs} from '@opentrons/shared-data'
import {chainActions} from '../../util'
import createLogger from '../../logger'

Expand Down Expand Up @@ -134,7 +134,7 @@ function makeMapStateToProps (): (state: State, ownProps: OP) => SP {
const startRequest = getDeckCalStartState(state, robot)
const pipetteInfo = startRequest.response && startRequest.response.pipette
const pipetteProps = pipetteInfo
? {mount: pipetteInfo.mount, pipette: getPipette(pipetteInfo.model)}
? {mount: pipetteInfo.mount, pipette: getPipetteModelSpecs(pipetteInfo.model)}
: null

if (pipetteProps && !pipetteProps.pipette) {
Expand Down
6 changes: 3 additions & 3 deletions app/src/components/CalibrateDeck/types.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import type {Match} from 'react-router'
import type {PipetteConfig} from '@opentrons/shared-data'
import type {PipetteModelSpecs} from '@opentrons/shared-data'
import type {RobotService, Mount} from '../../robot'
import type {DeckCalCommandState, DeckCalStartState} from '../../http-api-client'
import type {Jog} from '../JogControls'
Expand All @@ -18,7 +18,7 @@ export type SP = {
commandRequest: DeckCalCommandState,
pipetteProps: ?{
mount: Mount,
pipette: ?PipetteConfig,
pipette: ?PipetteModelSpecs,
},
}

Expand All @@ -34,6 +34,6 @@ export type CalibrateDeckProps = OP & SP & DP
export type CalibrateDeckStartedProps = CalibrateDeckProps & {
exitUrl: string,
mount: Mount,
pipette: PipetteConfig,
pipette: PipetteModelSpecs,
calibrationStep: CalibrationStep,
}
4 changes: 2 additions & 2 deletions app/src/components/ChangePipette/ConfirmPipette.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cx from 'classnames'
import {Link} from 'react-router-dom'

import {Icon, PrimaryButton, ModalPage} from '@opentrons/components'
import {getPipetteChannelsByName} from '@opentrons/shared-data'
import {getPipetteChannelsByDisplayName} from '@opentrons/shared-data'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can and should go away once we can switch the app to checking name rather than displayName or model for pipette correctness

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added comment to function definition in shared-data/js/pipettes.js


import type {ChangePipetteProps} from './types'
import {getDiagramSrc} from './InstructionStep'
Expand Down Expand Up @@ -91,7 +91,7 @@ function StatusDetails (props: ChangePipetteProps) {
className={styles.confirm_diagram}
src={getDiagramSrc({
...props,
channels: getPipetteChannelsByName(wantedPipetteName),
channels: getPipetteChannelsByDisplayName(wantedPipetteName),
diagram: 'tab',
direction: 'attach',
})}
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/ChangePipette/Instructions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import capitalize from 'lodash/capitalize'

import type {ChangePipetteProps} from './types'

import {getPipetteChannelsByName} from '@opentrons/shared-data'
import {getPipetteChannelsByDisplayName} from '@opentrons/shared-data'
import {ModalPage, PrimaryButton, type ButtonProps} from '@opentrons/components'
import PipetteSelection from './PipetteSelection'
import InstructionStep from './InstructionStep'
Expand Down Expand Up @@ -53,7 +53,7 @@ function Steps (props: ChangePipetteProps) {
const {direction} = props
const channels = props.actualPipette
? props.actualPipette.channels
: getPipetteChannelsByName(props.wantedPipetteName)
: getPipetteChannelsByDisplayName(props.wantedPipetteName)

let stepOne
let stepTwo
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/ChangePipette/PipetteSelection.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import * as React from 'react'

import {getPipetteNames} from '@opentrons/shared-data'
import {getPipetteDisplayNames} from '@opentrons/shared-data'
import {DropdownField} from '@opentrons/components'
import styles from './styles.css'

Expand All @@ -11,7 +11,7 @@ export type PipetteSelectionProps = {
onChange: $PropertyType<React.ElementProps<typeof DropdownField>, 'onChange'>,
}

const OPTIONS = getPipetteNames().map(name => ({name, value: name}))
const OPTIONS = getPipetteDisplayNames().map(name => ({name, value: name}))

export default function PipetteSelection (props: PipetteSelectionProps) {
return (
Expand Down
10 changes: 5 additions & 5 deletions app/src/components/ChangePipette/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as React from 'react'
import {connect} from 'react-redux'
import {push, goBack} from 'react-router-redux'
import {Switch, Route, withRouter, type Match} from 'react-router'
import {getPipette, getPipetteNames} from '@opentrons/shared-data'
import {getPipetteNameSpecs, getPipetteDisplayNames} from '@opentrons/shared-data'

import type {PipetteConfig} from '@opentrons/shared-data'
import type {PipetteNameSpecs} from '@opentrons/shared-data'
import type {State, Dispatch} from '../../types'
import type {Mount} from '../../robot'
import type {Robot} from '../../discovery'
Expand Down Expand Up @@ -40,7 +40,7 @@ const TITLE = 'Pipette Setup'
// used to guarentee mount param in route is left or right
const RE_MOUNT = '(left|right)'
// used to guarentee model param in route is a pipettes model
const RE_NAME = `(${getPipetteNames().join('|')})`
const RE_NAME = `(${getPipetteDisplayNames().join('|')})`

const ConnectedChangePipetteRouter = withRouter(
connect(makeMapStateToProps, mapDispatchToProps)(ChangePipetteRouter)
Expand Down Expand Up @@ -90,7 +90,7 @@ type OP = {
type SP = {
moveRequest: RobotMove,
homeRequest: RobotHome,
actualPipette: ?PipetteConfig,
actualPipette: ?PipetteNameSpecs,
displayName: string,
direction: Direction,
success: boolean,
Expand Down Expand Up @@ -154,7 +154,7 @@ function makeMapStateToProps () {
const {mount, wantedPipetteName} = ownProps
const pipettes = getRobotPipettes(state, ownProps.robot).response
const model = pipettes && pipettes[mount] && pipettes[mount].model
const actualPipette = model ? getPipette(model) : null
const actualPipette = model ? getPipetteNameSpecs(model) : null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcous should this be getPipettenameSpecs or getPipetteModelSpecs? If it's name, that const model should become const name right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely needs to be getPipetteModelSpecs. As written this breaks change pipettes

const direction = actualPipette ? 'detach' : 'attach'

const success = (
Expand Down
Loading