From 643921a7af2dc3a03d2b595ad024742b4ef2f114 Mon Sep 17 00:00:00 2001 From: ahiuchingau <20424172+ahiuchingau@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:40:26 -0400 Subject: [PATCH 1/6] feat(robot-server,app): add download deck calibration button closes #6055 --- .../__fixtures__/calibration-status.js | 18 ++- app/src/calibration/api-types.js | 28 ++++- app/src/calibration/selectors.js | 13 +- .../components/RobotSettings/ControlsCard.js | 4 + .../RobotSettings/DeckCalibrationControl.js | 14 ++- .../RobotSettings/DeckCalibrationDownload.js | 78 ++++++++++++ .../__tests__/ControlsCard.test.js | 5 +- .../__tests__/DeckCalibrationControl.test.js | 13 +- .../__tests__/DeckCalibrationDownload.test.js | 111 ++++++++++++++++++ .../__snapshots__/icons.test.js.snap | 24 ++++ components/src/icons/icon-data.js | 4 + .../service/legacy/models/deck_calibration.py | 31 ++++- .../legacy/routers/deck_calibration.py | 21 +++- .../test_deck_calibration.tavern.yaml | 21 ++-- 14 files changed, 356 insertions(+), 29 deletions(-) create mode 100644 app/src/components/RobotSettings/DeckCalibrationDownload.js create mode 100644 app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js diff --git a/app/src/calibration/__fixtures__/calibration-status.js b/app/src/calibration/__fixtures__/calibration-status.js index dc755452de1..7ce1d4f363c 100644 --- a/app/src/calibration/__fixtures__/calibration-status.js +++ b/app/src/calibration/__fixtures__/calibration-status.js @@ -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: { diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index 8f741f61466..ea3a55f044b 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -13,15 +13,31 @@ 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, number, number, number] +] + +export type DeckCalibrationData = {| + matrix: AffineMatrix | AttitudeMatrix, + lastModified: string | null, + pipetteCalibratedWith: string | null, + tiprack: string | null, + type: string, +|} + 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, |}, instrumentCalibration: {| right: {| diff --git a/app/src/calibration/selectors.js b/app/src/calibration/selectors.js index bd7bb25fa19..e936c0dbd04 100644 --- a/app/src/calibration/selectors.js +++ b/app/src/calibration/selectors.js @@ -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, @@ -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 +} diff --git a/app/src/components/RobotSettings/ControlsCard.js b/app/src/components/RobotSettings/ControlsCard.js index 306aa643653..ae1da0941ab 100644 --- a/app/src/components/RobotSettings/ControlsCard.js +++ b/app/src/components/RobotSettings/ControlsCard.js @@ -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 = () => { @@ -97,6 +100,7 @@ export function ControlsCard(props: Props): React.Node { robotName={robotName} buttonDisabled={buttonDisabled} deckCalStatus={deckCalStatus} + deckCalData={deckCalData} startLegacyDeckCalibration={startLegacyDeckCalibration} /> void, |} @@ -40,6 +45,7 @@ export function DeckCalibrationControl(props: Props): React.Node { robotName, buttonDisabled, deckCalStatus, + deckCalData, startLegacyDeckCalibration, } = props @@ -112,6 +118,12 @@ export function DeckCalibrationControl(props: Props): React.Node { deckCalibrationStatus={deckCalStatus} marginTop={SPACING_2} /> + {showConfirmStart && ( diff --git a/app/src/components/RobotSettings/DeckCalibrationDownload.js b/app/src/components/RobotSettings/DeckCalibrationDownload.js new file mode 100644 index 00000000000..65d4152a260 --- /dev/null +++ b/app/src/components/RobotSettings/DeckCalibrationDownload.js @@ -0,0 +1,78 @@ +// @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 { + 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) { + return null + } + + const isAttitude = deckCalData?.type === 'attitude' + const timestamp = deckCalData?.lastModified + ? new Date(deckCalData.lastModified).toLocaleString() + : null + + const handleDownloadButtonClick = () => { + const report = isAttitude + ? deckCalData + : { + type: deckCalData?.type, + matrix: deckCalData?.matrix, + } + const data = new Blob([JSON.stringify(report)], { + type: 'application/json', + }) + saveAs(data, `${name}-deck-calibration.json`) + } + + return ( + <> + + {isAttitude && ( + + {LAST_CALIBRATED} + {timestamp} + + )} + + + + + + ) +} diff --git a/app/src/components/RobotSettings/__tests__/ControlsCard.test.js b/app/src/components/RobotSettings/__tests__/ControlsCard.test.js index b2825d897f7..5b3222c5447 100644 --- a/app/src/components/RobotSettings/__tests__/ControlsCard.test.js +++ b/app/src/components/RobotSettings/__tests__/ControlsCard.test.js @@ -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) diff --git a/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js b/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js index e54c6d50e99..cee4c74b838 100644 --- a/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js +++ b/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js @@ -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') @@ -49,6 +52,13 @@ 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]], + lastModified: null, + pipetteCalibratedWith: null, + tiprack: null, + }, startLegacyDeckCalibration = () => {}, } = props return mount( @@ -56,6 +66,7 @@ describe('ControlsCard', () => { robotName={robotName} buttonDisabled={buttonDisabled} deckCalStatus={deckCalStatus} + deckCalData={deckCalData} startLegacyDeckCalibration={startLegacyDeckCalibration} />, { diff --git a/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js b/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js new file mode 100644 index 00000000000..4b7491c5d48 --- /dev/null +++ b/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js @@ -0,0 +1,111 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' +import { act } from 'react-dom/test-utils' +import { saveAs } from 'file-saver' + +import * as Calibration from '../../../calibration' +import { Text } from '@opentrons/components' +import { DeckCalibrationDownload } from '../DeckCalibrationDownload' + +jest.mock('file-saver') + +const mockSaveAs: JestMockFn< + [Blob, string], + $Call +> = saveAs + +describe('Calibration Download Component', () => { + let render + + const getDownloadButton = wrapper => wrapper.find('button') + + const getLastModifedText = wrapper => + wrapper.find(Text).filter({ children: 'Last calibrated:' }) + + beforeEach(() => { + render = (props = {}) => { + const { + calData = { + type: 'attitude', + matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + lastModified: '2020-08-25T14:14:30.422070+00:00', + pipetteCalibratedWith: 'P20MV202020042206', + tiprack: 'somehash', + }, + calStatus = Calibration.DECK_CAL_STATUS_OK, + name = 'opentrons', + } = props + return mount( + + ) + } + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + it('renders when deck calibration status is ok', () => { + const wrapper = render() + expect(wrapper.exists()).toEqual(true) + }) + + it('renders when deck calibration status is IDENTITY', () => { + const wrapper = render({ + deckCalibrationStatus: Calibration.DECK_CAL_STATUS_IDENTITY, + }) + expect(wrapper.exists()).toEqual(true) + }) + + it('renders when deck calibration status is SINGULARITY', () => { + const wrapper = render({ + deckCalibrationStatus: Calibration.DECK_CAL_STATUS_SINGULARITY, + }) + expect(wrapper.exists()).toEqual(true) + }) + + it('renders when deck calibration status is BAD_CALIBRATION', () => { + const wrapper = render({ + deckCalibrationStatus: Calibration.DECK_CAL_STATUS_BAD_CALIBRATION, + }) + expect(wrapper.exists()).toEqual(true) + }) + + it('renders nothing when deck calibration status is unknown', () => { + const wrapper = render({ deckCalibrationStatus: null }) + expect(wrapper).toEqual({}) + }) + + it('saves the deck calibration data when the button is clicked', () => { + const wrapper = render() + + act(() => getDownloadButton(wrapper).invoke('onClick')()) + wrapper.update() + expect(mockSaveAs).toHaveBeenCalled() + }) + + it('renders last modified when deck calibration type is attitude', () => { + const wrapper = render() + + expect(getLastModifedText(wrapper).exists()).toEqual(true) + }) + + it('should not render last modified when deck calibration type is affine', () => { + const wrapper = render({ + deckCalibrationData: { + type: 'random', + matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], + lastModified: null, + pipetteCalibratedWith: null, + tiprack: null, + }, + }) + + expect(getLastModifedText(wrapper)).toEqual({}) + }) +}) diff --git a/components/src/__tests__/__snapshots__/icons.test.js.snap b/components/src/__tests__/__snapshots__/icons.test.js.snap index 041e19fc29d..d8cd4b5e42f 100644 --- a/components/src/__tests__/__snapshots__/icons.test.js.snap +++ b/components/src/__tests__/__snapshots__/icons.test.js.snap @@ -557,6 +557,30 @@ exports[`icons dots-horizontal renders correctly 1`] = ` `; +exports[`icons download renders correctly 1`] = ` +.c0.spin { + -webkit-animation: GLFYz 0.8s steps(8) infinite; + animation: GLFYz 0.8s steps(8) infinite; + -webkit-transform-origin: center; + -ms-transform-origin: center; + transform-origin: center; +} + + +`; + exports[`icons flask-outline renders correctly 1`] = ` .c0.spin { -webkit-animation: GLFYz 0.8s steps(8) infinite; diff --git a/components/src/icons/icon-data.js b/components/src/icons/icon-data.js index f4cc48ac3cc..71d2e2fdda7 100644 --- a/components/src/icons/icon-data.js +++ b/components/src/icons/icon-data.js @@ -363,4 +363,8 @@ export const ICON_DATA_BY_NAME = { path: 'M26.6 13.5c0 3.8-2.9 6.9-6.6 6.9H6.9c-3.6 0-6.6-3.1-6.6-6.9s2.9-6.9 6.6-6.9H20c3.6 0 6.6 3.1 6.6 6.9zm-2.7 0c0-2.3-1.8-4.1-3.9-4.1-2.2 0-3.9 1.8-3.9 4.1s1.8 4.1 3.9 4.1 3.9-1.8 3.9-4.1z', }, + download: { + viewBox: '0 0 24 24', + path: 'M5 20H19V18H5M19 9H15V3H9V9H5L12 16L19 9Z', + }, } diff --git a/robot-server/robot_server/service/legacy/models/deck_calibration.py b/robot-server/robot_server/service/legacy/models/deck_calibration.py index 208ebc63a02..cbc77da1f82 100644 --- a/robot-server/robot_server/service/legacy/models/deck_calibration.py +++ b/robot-server/robot_server/service/legacy/models/deck_calibration.py @@ -1,4 +1,5 @@ import typing +from datetime import datetime from enum import Enum from uuid import UUID @@ -161,15 +162,41 @@ class InstrumentCalibrationStatus(BaseModel): left: InstrumentOffset +class MatrixType(str, Enum): + """The deck calibration matrix type""" + affine = 'affine' + attitude = 'attitude' + + +class DeckCalibrationData(BaseModel): + type: MatrixType = \ + Field(..., + description="The type of deck calibration matrix:" + "affine or attitude") + matrix: typing.List[typing.List[float]] = \ + Field(..., + description="The deck calibration transform matrix") + lastModified: typing.Optional[datetime] = \ + Field(None, + description="When this calibration was last modified") + pipetteCalibratedWith: typing.Optional[str] = \ + Field(None, + description="The ID of the pipette used in this calibration") + tiprack: typing.Optional[str] = \ + Field(None, + description="The sha256 hash of the tiprack used in this" + "calibration") + + class DeckCalibrationStatus(BaseModel): status: DeckTransformState = \ Field(..., description="An enum stating whether a user has a valid robot" "deck calibration. See DeckTransformState" "class for more information.") - data: typing.List[typing.List[float]] = \ + data: DeckCalibrationData = \ Field(..., - description="The deck calibration transform matrix") + description="Deck calibration data") class CalibrationStatus(BaseModel): diff --git a/robot-server/robot_server/service/legacy/routers/deck_calibration.py b/robot-server/robot_server/service/legacy/routers/deck_calibration.py index 4a0f35631dd..cbc84681644 100644 --- a/robot-server/robot_server/service/legacy/routers/deck_calibration.py +++ b/robot-server/robot_server/service/legacy/routers/deck_calibration.py @@ -3,7 +3,9 @@ from opentrons.config import robot_configs from starlette import status from fastapi import APIRouter, Depends -from opentrons.hardware_control import ThreadManager +from opentrons.config import feature_flags as ff +from opentrons.hardware_control import ThreadManager, \ + robot_calibration as robot_cal import opentrons.deck_calibration.endpoints as dc from robot_server.service.dependencies import get_hardware @@ -11,7 +13,7 @@ from robot_server.service.legacy.models import V1BasicResponse from robot_server.service.legacy.models.deck_calibration import DeckStart, \ DeckStartResponse, DeckCalibrationDispatch, PipetteDeckCalibration, \ - CalibrationStatus, DeckCalibrationStatus + CalibrationStatus, DeckCalibrationStatus, DeckCalibrationData, MatrixType router = APIRouter() @@ -83,8 +85,21 @@ async def post_calibration_deck(operation: DeckCalibrationDispatch) \ async def get_calibration_status( hardware: ThreadManager = Depends(get_hardware)) -> CalibrationStatus: robot_conf = robot_configs.load() + if ff.enable_calibration_overhaul(): + deck_cal = robot_cal.load().deck_calibration + deck_cal_data = DeckCalibrationData( + type=MatrixType.attitude, + matrix=deck_cal.attitude, + lastModified=deck_cal.last_modified, + pipetteCalibratedWith=deck_cal.pipette_calibrated_with, + tiprack=deck_cal.tiprack) + else: + deck_cal_data = DeckCalibrationData( + type=MatrixType.affine, + matrix=robot_conf.gantry_calibration) + return CalibrationStatus( deckCalibration=DeckCalibrationStatus( status=hardware.validate_calibration(), - data=robot_conf.gantry_calibration), + data=deck_cal_data), instrumentCalibration=robot_conf.instrument_offset) diff --git a/robot-server/tests/integration/test_deck_calibration.tavern.yaml b/robot-server/tests/integration/test_deck_calibration.tavern.yaml index 86ac7cdc149..f03cffd92c6 100644 --- a/robot-server/tests/integration/test_deck_calibration.tavern.yaml +++ b/robot-server/tests/integration/test_deck_calibration.tavern.yaml @@ -214,14 +214,19 @@ stages: deckCalibration: status: OK data: - - &matrix_row - - !anyfloat - - !anyfloat - - !anyfloat - - !anyfloat - - *matrix_row - - *matrix_row - - *matrix_row + type: affine + matrix: + - &matrix_row + - !anyfloat + - !anyfloat + - !anyfloat + - !anyfloat + - *matrix_row + - *matrix_row + - *matrix_row + lastModified: null + pipetteCalibratedWith: null + tiprack: null instrumentCalibration: right: &inst single: &vector From 98b4464b3a93364fcbde5c5bfe7ab00f1e1b67e0 Mon Sep 17 00:00:00 2001 From: ahiuchingau <20424172+ahiuchingau@users.noreply.github.com> Date: Thu, 27 Aug 2020 16:58:27 -0400 Subject: [PATCH 2/6] fix check errors --- app/src/calibration/api-types.js | 6 +++--- .../components/RobotSettings/DeckCalibrationDownload.js | 3 ++- .../RobotSettings/__tests__/DeckCalibrationControl.test.js | 7 ++++++- .../__tests__/DeckCalibrationDownload.test.js | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index ea3a55f044b..7510868241c 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -21,9 +21,9 @@ export type AffineMatrix = [ ] export type AttitudeMatrix = [ - [number, number, number, number], - [number, number, number, number], - [number, number, number, number] + [number, number, number], + [number, number, number], + [number, number, number] ] export type DeckCalibrationData = {| diff --git a/app/src/components/RobotSettings/DeckCalibrationDownload.js b/app/src/components/RobotSettings/DeckCalibrationDownload.js index 65d4152a260..ffce7e2555d 100644 --- a/app/src/components/RobotSettings/DeckCalibrationDownload.js +++ b/app/src/components/RobotSettings/DeckCalibrationDownload.js @@ -10,6 +10,7 @@ import { import { IconCta } from '../IconCta' import { saveAs } from 'file-saver' +import type { StyleProps } from '@opentrons/components' import type { DeckCalibrationData, DeckCalibrationStatus, @@ -24,7 +25,7 @@ export type DeckCalibrationDownloadProps = {| deckCalibrationStatus: DeckCalibrationStatus | null, deckCalibrationData: DeckCalibrationData | null, robotName: string, - ...styleProps, + ...StyleProps, |} export function DeckCalibrationDownload({ diff --git a/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js b/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js index cee4c74b838..2b427e2faab 100644 --- a/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js +++ b/app/src/components/RobotSettings/__tests__/DeckCalibrationControl.test.js @@ -54,7 +54,12 @@ describe('ControlsCard', () => { deckCalStatus = 'OK', deckCalData = { type: 'affine', - matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], + matrix: [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ], lastModified: null, pipetteCalibratedWith: null, tiprack: null, diff --git a/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js b/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js index 4b7491c5d48..d060ab4eac1 100644 --- a/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js +++ b/app/src/components/RobotSettings/__tests__/DeckCalibrationDownload.test.js @@ -98,8 +98,8 @@ describe('Calibration Download Component', () => { it('should not render last modified when deck calibration type is affine', () => { const wrapper = render({ deckCalibrationData: { - type: 'random', - matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], + 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, From 1a69c51942c344793da2b50689bef5dff52f44f1 Mon Sep 17 00:00:00 2001 From: ahiuchingau <20424172+ahiuchingau@users.noreply.github.com> Date: Tue, 1 Sep 2020 23:42:52 -0400 Subject: [PATCH 3/6] made requested changes --- app/src/calibration/api-types.js | 4 +- .../RobotSettings/DeckCalibrationDownload.js | 10 +-- .../__tests__/DeckCalibrationDownload.test.js | 65 ++++++++++++++++--- .../service/legacy/models/deck_calibration.py | 13 +++- .../legacy/routers/deck_calibration.py | 5 +- 5 files changed, 79 insertions(+), 18 deletions(-) diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index 7510868241c..52634d3eee5 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -26,7 +26,7 @@ export type AttitudeMatrix = [ [number, number, number] ] -export type DeckCalibrationData = {| +export type DeckCalibrationInfo = {| matrix: AffineMatrix | AttitudeMatrix, lastModified: string | null, pipetteCalibratedWith: string | null, @@ -34,6 +34,8 @@ export type DeckCalibrationData = {| type: string, |} +export type DeckCalibrationData = DeckCalibrationInfo | AffineMatrix + export type CalibrationStatus = {| deckCalibration: {| status: DeckCalibrationStatus, diff --git a/app/src/components/RobotSettings/DeckCalibrationDownload.js b/app/src/components/RobotSettings/DeckCalibrationDownload.js index ffce7e2555d..908d668c927 100644 --- a/app/src/components/RobotSettings/DeckCalibrationDownload.js +++ b/app/src/components/RobotSettings/DeckCalibrationDownload.js @@ -37,8 +37,9 @@ export function DeckCalibrationDownload({ if (deckCalStatus === null) { return null } - - const isAttitude = deckCalData?.type === 'attitude' + const deckCalType = deckCalData?.type ?? 'affine' + const deckCalMatrix = deckCalData?.matrix ?? deckCalData + const isAttitude = deckCalType === 'attitude' const timestamp = deckCalData?.lastModified ? new Date(deckCalData.lastModified).toLocaleString() : null @@ -47,8 +48,8 @@ export function DeckCalibrationDownload({ const report = isAttitude ? deckCalData : { - type: deckCalData?.type, - matrix: deckCalData?.matrix, + type: deckCalType, + matrix: deckCalMatrix, } const data = new Blob([JSON.stringify(report)], { type: 'application/json', @@ -66,6 +67,7 @@ export function DeckCalibrationDownload({ )} + {} -> = saveAs +global.Blob = function(content, options) { + return { content, options } +} describe('Calibration Download Component', () => { let render @@ -57,21 +56,21 @@ describe('Calibration Download Component', () => { it('renders when deck calibration status is IDENTITY', () => { const wrapper = render({ - deckCalibrationStatus: Calibration.DECK_CAL_STATUS_IDENTITY, + calStatus: Calibration.DECK_CAL_STATUS_IDENTITY, }) expect(wrapper.exists()).toEqual(true) }) it('renders when deck calibration status is SINGULARITY', () => { const wrapper = render({ - deckCalibrationStatus: Calibration.DECK_CAL_STATUS_SINGULARITY, + calStatus: Calibration.DECK_CAL_STATUS_SINGULARITY, }) expect(wrapper.exists()).toEqual(true) }) it('renders when deck calibration status is BAD_CALIBRATION', () => { const wrapper = render({ - deckCalibrationStatus: Calibration.DECK_CAL_STATUS_BAD_CALIBRATION, + calStatus: Calibration.DECK_CAL_STATUS_BAD_CALIBRATION, }) expect(wrapper.exists()).toEqual(true) }) @@ -83,10 +82,19 @@ describe('Calibration Download Component', () => { it('saves the deck calibration data when the button is clicked', () => { const wrapper = render() - + const report = { + type: 'attitude', + matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + lastModified: '2020-08-25T14:14:30.422070+00:00', + pipetteCalibratedWith: 'P20MV202020042206', + tiprack: 'somehash', + } + const blob = new Blob([JSON.stringify(report)], { + type: 'application/json', + }) act(() => getDownloadButton(wrapper).invoke('onClick')()) wrapper.update() - expect(mockSaveAs).toHaveBeenCalled() + expect(saveAs).toHaveBeenCalledWith(blob, 'opentrons-deck-calibration.json') }) it('renders last modified when deck calibration type is attitude', () => { @@ -95,6 +103,28 @@ describe('Calibration Download Component', () => { expect(getLastModifedText(wrapper).exists()).toEqual(true) }) + it('saves affine deck calibration data correctly', () => { + const wrapper = render({ + calData: { + 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, + }, + }) + const report = { + type: 'affine', + matrix: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], + } + const blob = new Blob([JSON.stringify(report)], { + type: 'application/json', + }) + act(() => getDownloadButton(wrapper).invoke('onClick')()) + wrapper.update() + expect(saveAs).toHaveBeenCalledWith(blob, 'opentrons-deck-calibration.json') + }) + it('should not render last modified when deck calibration type is affine', () => { const wrapper = render({ deckCalibrationData: { @@ -108,4 +138,21 @@ describe('Calibration Download Component', () => { expect(getLastModifedText(wrapper)).toEqual({}) }) + + it('old robot calibration data format should still download correctly', () => { + const wrapper = render({ + calData: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], + }) + const report = { + type: 'affine', + matrix: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], + } + const blob = new Blob([JSON.stringify(report)], { + type: 'application/json', + }) + + act(() => getDownloadButton(wrapper).invoke('onClick')()) + wrapper.update() + expect(saveAs).toHaveBeenCalledWith(blob, 'opentrons-deck-calibration.json') + }) }) diff --git a/robot-server/robot_server/service/legacy/models/deck_calibration.py b/robot-server/robot_server/service/legacy/models/deck_calibration.py index cbc77da1f82..069c325fd41 100644 --- a/robot-server/robot_server/service/legacy/models/deck_calibration.py +++ b/robot-server/robot_server/service/legacy/models/deck_calibration.py @@ -151,6 +151,17 @@ class Config: Offset = typing.Tuple[float, float, float] +AffineMatrix = typing.Tuple[ + typing.Tuple[float, float, float, float], + typing.Tuple[float, float, float, float], + typing.Tuple[float, float, float, float], + typing.Tuple[float, float, float, float]] + +AttitudeMatrix = typing.Tuple[ + typing.Tuple[float, float, float], + typing.Tuple[float, float, float], + typing.Tuple[float, float, float]] + class InstrumentOffset(BaseModel): single: Offset @@ -173,7 +184,7 @@ class DeckCalibrationData(BaseModel): Field(..., description="The type of deck calibration matrix:" "affine or attitude") - matrix: typing.List[typing.List[float]] = \ + matrix: typing.Union[AffineMatrix, AttitudeMatrix] = \ Field(..., description="The deck calibration transform matrix") lastModified: typing.Optional[datetime] = \ diff --git a/robot-server/robot_server/service/legacy/routers/deck_calibration.py b/robot-server/robot_server/service/legacy/routers/deck_calibration.py index cbc84681644..81fab8e3118 100644 --- a/robot-server/robot_server/service/legacy/routers/deck_calibration.py +++ b/robot-server/robot_server/service/legacy/routers/deck_calibration.py @@ -4,8 +4,7 @@ from starlette import status from fastapi import APIRouter, Depends from opentrons.config import feature_flags as ff -from opentrons.hardware_control import ThreadManager, \ - robot_calibration as robot_cal +from opentrons.hardware_control import ThreadManager import opentrons.deck_calibration.endpoints as dc from robot_server.service.dependencies import get_hardware @@ -86,7 +85,7 @@ async def get_calibration_status( hardware: ThreadManager = Depends(get_hardware)) -> CalibrationStatus: robot_conf = robot_configs.load() if ff.enable_calibration_overhaul(): - deck_cal = robot_cal.load().deck_calibration + deck_cal = hardware.robot_calibration.deck_calibration deck_cal_data = DeckCalibrationData( type=MatrixType.attitude, matrix=deck_cal.attitude, From 19b1f384d5c3615f54319db3a582ad0116c15426 Mon Sep 17 00:00:00 2001 From: ahiuchingau <20424172+ahiuchingau@users.noreply.github.com> Date: Wed, 2 Sep 2020 14:51:35 -0400 Subject: [PATCH 4/6] fix type checks --- .../RobotSettings/DeckCalibrationDownload.js | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/components/RobotSettings/DeckCalibrationDownload.js b/app/src/components/RobotSettings/DeckCalibrationDownload.js index 908d668c927..029fd69aa96 100644 --- a/app/src/components/RobotSettings/DeckCalibrationDownload.js +++ b/app/src/components/RobotSettings/DeckCalibrationDownload.js @@ -14,6 +14,7 @@ import type { StyleProps } from '@opentrons/components' import type { DeckCalibrationData, DeckCalibrationStatus, + DeckCalibrationInfo, } from '../../calibration/types' const LAST_CALIBRATED = 'Last calibrated:' @@ -34,23 +35,24 @@ export function DeckCalibrationDownload({ robotName: name, ...styleProps }: DeckCalibrationDownloadProps): React.Node { - if (deckCalStatus === null) { + if (deckCalStatus === null || deckCalData === null) { return null } - const deckCalType = deckCalData?.type ?? 'affine' - const deckCalMatrix = deckCalData?.matrix ?? deckCalData - const isAttitude = deckCalType === 'attitude' - const timestamp = deckCalData?.lastModified - ? new Date(deckCalData.lastModified).toLocaleString() - : 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 = isAttitude - ? deckCalData - : { - type: deckCalType, - matrix: deckCalMatrix, - } + const report = + deckCalType === 'attitude' + ? deckCalData + : { + type: deckCalType, + matrix: deckCalMatrix, + } const data = new Blob([JSON.stringify(report)], { type: 'application/json', }) @@ -60,14 +62,13 @@ export function DeckCalibrationDownload({ return ( <> - {isAttitude && ( + {deckCalType === 'attitude' && ( {LAST_CALIBRATED} {timestamp} )} - {} Date: Thu, 3 Sep 2020 10:34:52 -0400 Subject: [PATCH 5/6] fix type error --- app/src/components/RobotSettings/DeckCalibrationDownload.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/components/RobotSettings/DeckCalibrationDownload.js b/app/src/components/RobotSettings/DeckCalibrationDownload.js index 029fd69aa96..21d7347a013 100644 --- a/app/src/components/RobotSettings/DeckCalibrationDownload.js +++ b/app/src/components/RobotSettings/DeckCalibrationDownload.js @@ -38,10 +38,10 @@ export function DeckCalibrationDownload({ if (deckCalStatus === null || deckCalData === null) { return null } - const deckCalType = deckCalData.type ? deckCalData.type : 'affine' - const deckCalMatrix = deckCalData.matrix ? deckCalData.matrix : deckCalData + const deckCalType = deckCalData?.type ? deckCalData.type : 'affine' + const deckCalMatrix = deckCalData?.matrix ? deckCalData.matrix : deckCalData const timestamp = - typeof deckCalData.lastModified === 'string' + typeof deckCalData?.lastModified === 'string' ? new Date(deckCalData.lastModified).toLocaleString() : null From 121d53a48f62042b1ff8ed9404c0f80d95d9f5ae Mon Sep 17 00:00:00 2001 From: ahiuchingau <20424172+ahiuchingau@users.noreply.github.com> Date: Thu, 3 Sep 2020 11:16:32 -0400 Subject: [PATCH 6/6] fix lint error --- app/src/components/RobotSettings/DeckCalibrationDownload.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/components/RobotSettings/DeckCalibrationDownload.js b/app/src/components/RobotSettings/DeckCalibrationDownload.js index 21d7347a013..8ebdf0436c0 100644 --- a/app/src/components/RobotSettings/DeckCalibrationDownload.js +++ b/app/src/components/RobotSettings/DeckCalibrationDownload.js @@ -14,7 +14,6 @@ import type { StyleProps } from '@opentrons/components' import type { DeckCalibrationData, DeckCalibrationStatus, - DeckCalibrationInfo, } from '../../calibration/types' const LAST_CALIBRATED = 'Last calibrated:'