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(robot-server,app): add download deck calibration button #6453

Merged
merged 6 commits into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions app/src/calibration/__fixtures__/calibration-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ import type { CalibrationStatus } from '../types'
export const mockCalibrationStatus: CalibrationStatus = {
deckCalibration: {
status: DECK_CAL_STATUS_IDENTITY,
data: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
data: {
type: 'affine',
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
lastModified: null,
pipetteCalibratedWith: null,
tiprack: null,
},
},
instrumentCalibration: {
right: {
Expand Down
30 changes: 24 additions & 6 deletions app/src/calibration/api-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,33 @@ export type DeckCalibrationStatus =
| DECK_CAL_STATUS_BAD_CALIBRATION
| DECK_CAL_STATUS_SINGULARITY

export type AffineMatrix = [
[number, number, number, number],
[number, number, number, number],
[number, number, number, number],
[number, number, number, number]
]

export type AttitudeMatrix = [
[number, number, number],
[number, number, number],
[number, number, number]
]

export type DeckCalibrationInfo = {|
matrix: AffineMatrix | AttitudeMatrix,
lastModified: string | null,
pipetteCalibratedWith: string | null,
tiprack: string | null,
type: string,
|}

export type DeckCalibrationData = DeckCalibrationInfo | AffineMatrix

export type CalibrationStatus = {|
deckCalibration: {|
status: DeckCalibrationStatus,
data: [
[number, number, number, number],
[number, number, number, number],
[number, number, number, number],
[number, number, number, number]
],
data: DeckCalibrationData,
Copy link
Member

Choose a reason for hiding this comment

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

If we're going to keep the app compatible with the last version of the robot as well as the current one, we should probably handle it here. I think the general use of null coalescence makes it work, but maybe add a test based on the robot returning the old style of the affine matrix directly in data?

|},
instrumentCalibration: {|
right: {|
Expand Down
13 changes: 12 additions & 1 deletion app/src/calibration/selectors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// @flow

import type { State } from '../types'
import type { CalibrationStatus, DeckCalibrationStatus } from './types'
import type {
CalibrationStatus,
DeckCalibrationStatus,
DeckCalibrationData,
} from './types'

export const getCalibrationStatus = (
state: State,
Expand All @@ -16,3 +20,10 @@ export const getDeckCalibrationStatus = (
): DeckCalibrationStatus | null => {
return getCalibrationStatus(state, robotName)?.deckCalibration.status ?? null
}

export const getDeckCalibrationData = (
state: State,
robotName: string
): DeckCalibrationData | null => {
return getCalibrationStatus(state, robotName)?.deckCalibration.data ?? null
}
4 changes: 4 additions & 0 deletions app/src/components/RobotSettings/ControlsCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export function ControlsCard(props: Props): React.Node {
const deckCalStatus = useSelector((state: State) => {
return Calibration.getDeckCalibrationStatus(state, robotName)
})
const deckCalData = useSelector((state: State) => {
return Calibration.getDeckCalibrationData(state, robotName)
})
const notConnectable = status !== CONNECTABLE
const toggleLights = () => dispatch(updateLights(robotName, !lightsOn))
const startLegacyDeckCalibration = () => {
Expand Down Expand Up @@ -97,6 +100,7 @@ export function ControlsCard(props: Props): React.Node {
robotName={robotName}
buttonDisabled={buttonDisabled}
deckCalStatus={deckCalStatus}
deckCalData={deckCalData}
startLegacyDeckCalibration={startLegacyDeckCalibration}
/>
<LabeledButton
Expand Down
14 changes: 13 additions & 1 deletion app/src/components/RobotSettings/DeckCalibrationControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@ import * as RobotApi from '../../robot-api'
import * as Sessions from '../../sessions'

import type { State } from '../../types'
import type { DeckCalibrationStatus } from '../../calibration/types'
import type {
DeckCalibrationStatus,
DeckCalibrationData,
} from '../../calibration/types'

import { Portal } from '../portal'
import { TitledControl } from '../TitledControl'
import { CalibrateDeck } from '../CalibrateDeck'
import { DeckCalibrationWarning } from './DeckCalibrationWarning'
import { ConfirmStartDeckCalModal } from './ConfirmStartDeckCalModal'
import { DeckCalibrationDownload } from './DeckCalibrationDownload'

type Props = {|
robotName: string,
buttonDisabled: boolean,
deckCalStatus: DeckCalibrationStatus | null,
deckCalData: DeckCalibrationData | null,
startLegacyDeckCalibration: () => void,
|}

Expand All @@ -40,6 +45,7 @@ export function DeckCalibrationControl(props: Props): React.Node {
robotName,
buttonDisabled,
deckCalStatus,
deckCalData,
startLegacyDeckCalibration,
} = props

Expand Down Expand Up @@ -112,6 +118,12 @@ export function DeckCalibrationControl(props: Props): React.Node {
deckCalibrationStatus={deckCalStatus}
marginTop={SPACING_2}
/>
<DeckCalibrationDownload
deckCalibrationStatus={deckCalStatus}
deckCalibrationData={deckCalData}
robotName={robotName}
marginTop={SPACING_2}
/>
</TitledControl>
{showConfirmStart && (
<Portal>
Expand Down
81 changes: 81 additions & 0 deletions app/src/components/RobotSettings/DeckCalibrationDownload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// @flow
import * as React from 'react'
import {
Flex,
Text,
SPACING_1,
SPACING_4,
DIRECTION_COLUMN,
} from '@opentrons/components'
import { IconCta } from '../IconCta'
import { saveAs } from 'file-saver'

import type { StyleProps } from '@opentrons/components'
import type {
DeckCalibrationData,
DeckCalibrationStatus,
} from '../../calibration/types'

const LAST_CALIBRATED = 'Last calibrated:'
const DOWNLOAD = 'Download deck calibration data'

export const DOWNLOAD_NAME = 'download-deck-calibration'

export type DeckCalibrationDownloadProps = {|
deckCalibrationStatus: DeckCalibrationStatus | null,
deckCalibrationData: DeckCalibrationData | null,
robotName: string,
...StyleProps,
|}

export function DeckCalibrationDownload({
deckCalibrationData: deckCalData,
deckCalibrationStatus: deckCalStatus,
robotName: name,
...styleProps
}: DeckCalibrationDownloadProps): React.Node {
if (deckCalStatus === null || deckCalData === null) {
return null
}
const deckCalType = deckCalData?.type ? deckCalData.type : 'affine'
const deckCalMatrix = deckCalData?.matrix ? deckCalData.matrix : deckCalData
const timestamp =
typeof deckCalData?.lastModified === 'string'
? new Date(deckCalData.lastModified).toLocaleString()
: null

const handleDownloadButtonClick = () => {
const report =
deckCalType === 'attitude'
? deckCalData
: {
type: deckCalType,
matrix: deckCalMatrix,
}
const data = new Blob([JSON.stringify(report)], {
type: 'application/json',
})
saveAs(data, `${name}-deck-calibration.json`)
}

return (
<>
<Flex flexDirection={DIRECTION_COLUMN} {...styleProps}>
{deckCalType === 'attitude' && (
<Flex marginBottom={SPACING_1}>
<Text marginRight={SPACING_4}>{LAST_CALIBRATED}</Text>
<Text>{timestamp}</Text>
</Flex>
)}
<Flex>
<IconCta
iconName="download"
text={DOWNLOAD}
name={DOWNLOAD_NAME}
onClick={handleDownloadButtonClick}
/>
</Flex>
</Flex>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ describe('ControlsCard', () => {
let render

const getDeckCalButton = wrapper =>
wrapper.find('TitledControl[title="Calibrate deck"]').find('button')
wrapper
.find('TitledControl[title="Calibrate deck"]')
.find('button')
.filter({ children: 'Calibrate' })

const getCheckCalibrationControl = wrapper =>
wrapper.find(CheckCalibrationControl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ describe('ControlsCard', () => {
let render

const getDeckCalButton = wrapper =>
wrapper.find('TitledControl[title="Calibrate deck"]').find('button')
wrapper
.find('TitledControl[title="Calibrate deck"]')
.find('button')
.filter({ children: 'Calibrate' })

const getCancelDeckCalButton = wrapper =>
wrapper.find('OutlineButton[children="cancel"]').find('button')
Expand All @@ -49,13 +52,26 @@ describe('ControlsCard', () => {
robotName = 'robot-name',
buttonDisabled = false,
deckCalStatus = 'OK',
deckCalData = {
type: 'affine',
matrix: [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
],
lastModified: null,
pipetteCalibratedWith: null,
tiprack: null,
},
startLegacyDeckCalibration = () => {},
} = props
return mount(
<DeckCalibrationControl
robotName={robotName}
buttonDisabled={buttonDisabled}
deckCalStatus={deckCalStatus}
deckCalData={deckCalData}
startLegacyDeckCalibration={startLegacyDeckCalibration}
/>,
{
Expand Down
Loading