Skip to content

Commit

Permalink
feat(ot3): add shared data support for ot3 pipettes (#11704)
Browse files Browse the repository at this point in the history
  • Loading branch information
Laura-Danielle committed Nov 16, 2022
1 parent 934c322 commit 355ee9c
Show file tree
Hide file tree
Showing 33 changed files with 1,724 additions and 13 deletions.
2 changes: 1 addition & 1 deletion api/tests/opentrons/config/test_pipette_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from opentrons_shared_data import load_shared_data
from opentrons_shared_data.pipette.dev_types import PipetteModel

defs = json.loads(load_shared_data("pipette/definitions/pipetteModelSpecs.json"))
defs = json.loads(load_shared_data("pipette/definitions/1/pipetteModelSpecs.json"))


def check_sequences_close(
Expand Down
113 changes: 113 additions & 0 deletions shared-data/js/__tests__/pipetteSchemaV2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Ajv from 'ajv'
import glob from 'glob'
import path from 'path'

import liquidSpecsSchema from '../../pipette/schemas/2/pipetteLiquidPropertiesSchema.json'
import geometrySpecsSchema from '../../pipette/schemas/2/pipetteGeometrySchema.json'
import generalSpecsSchema from '../../pipette/schemas/2/pipettePropertiesSchema.json'

const allGeometryDefinitions = path.join(
__dirname,
'../../pipette/definitions/2/geometry/**/**/*.json'
)

const allGeneralDefinitions = path.join(
__dirname,
'../../labware/definitions/2/general/**/**/*.json'
)

const allLiquidDefinitions = path.join(
__dirname,
'../../labware/definitions/2/liquid/**/**/*.json'
)

const ajv = new Ajv({ allErrors: true, jsonPointers: true })

const validateLiquidSpecs = ajv.compile(liquidSpecsSchema)
const validateGeometrySpecs = ajv.compile(geometrySpecsSchema)
const validateGeneralSpecs = ajv.compile(generalSpecsSchema)

describe('test schema against all liquid specs definitions', () => {
const liquidPaths = glob.sync(allLiquidDefinitions)

beforeAll(() => {
// Make sure definitions path didn't break, which would give you false positives
expect(liquidPaths.length).toBeGreaterThan(0)
})

liquidPaths.forEach(liquidPath => {
const liquidDef = require(liquidPath)

it(`${liquidPath} validates against schema`, () => {
const valid = validateLiquidSpecs(liquidDef)
const validationErrors = validateLiquidSpecs.errors
expect(validationErrors).toBe(null)
expect(valid).toBe(true)
})

it(`parent dir matches pipette model: ${liquidPath}`, () => {
expect(['p50', 'p1000']).toContain(
path.basename(path.dirname(liquidPath))
)
})
})
})

describe('test schema against all geometry specs definitions', () => {
const geometryPaths = glob.sync(allGeometryDefinitions)

beforeAll(() => {
// Make sure definitions path didn't break, which would give you false positives
expect(geometryPaths.length).toBeGreaterThan(0)
})

geometryPaths.forEach(geometryPath => {
const geometryDef = require(geometryPath)
const geometryParentDir = path.dirname(geometryPath)

it(`${geometryPath} validates against schema`, () => {
const valid = validateGeometrySpecs(geometryDef)
const validationErrors = validateGeometrySpecs.errors
expect(validationErrors).toBe(null)
expect(valid).toBe(true)
})

it(`parent dir matches pipette model: ${geometryPath}`, () => {
expect(['p50', 'p1000']).toContain(
path.basename(path.dirname(geometryPath))
)
})

it(`parent directory contains a gltf file: ${geometryPath}`, () => {
const gltf_file = glob.sync(path.join(geometryParentDir, '*.gltf'))
expect(gltf_file.length).toBeGreaterThan(0)
expect(gltf_file).toBeDefined()
})
})
})

describe('test schema against all general specs definitions', () => {
const generalPaths = glob.sync(allGeneralDefinitions)

beforeAll(() => {
// Make sure definitions path didn't break, which would give you false positives
expect(generalPaths.length).toBeGreaterThan(0)
})

generalPaths.forEach(generalPath => {
const generalDef = require(generalPath)

it(`${generalPath} validates against schema`, () => {
const valid = validateGeneralSpecs(generalDef)
const validationErrors = validateGeneralSpecs.errors
expect(validationErrors).toBe(null)
expect(valid).toBe(true)
})

it(`parent dir matches pipette model: ${generalPath}`, () => {
expect(['p50', 'p1000']).toContain(
path.basename(path.dirname(generalPath))
)
})
})
})
8 changes: 4 additions & 4 deletions shared-data/js/__tests__/pipetteSpecSchemas.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Ajv from 'ajv'
import nameSpecsSchema from '../../pipette/schemas/pipetteNameSpecsSchema.json'
import modelSpecsSchema from '../../pipette/schemas/pipetteModelSpecsSchema.json'
import pipetteNameSpecs from '../../pipette/definitions/pipetteNameSpecs.json'
import pipetteModelSpecs from '../../pipette/definitions/pipetteModelSpecs.json'
import nameSpecsSchema from '../../pipette/schemas/1/pipetteNameSpecsSchema.json'
import modelSpecsSchema from '../../pipette/schemas/1/pipetteModelSpecsSchema.json'
import pipetteNameSpecs from '../../pipette/definitions/1/pipetteNameSpecs.json'
import pipetteModelSpecs from '../../pipette/definitions/1/pipetteModelSpecs.json'

const ajv = new Ajv({ allErrors: true, jsonPointers: true })

Expand Down
4 changes: 2 additions & 2 deletions shared-data/js/pipettes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pipetteNameSpecs from '../pipette/definitions/pipetteNameSpecs.json'
import pipetteModelSpecs from '../pipette/definitions/pipetteModelSpecs.json'
import pipetteNameSpecs from '../pipette/definitions/1/pipetteNameSpecs.json'
import pipetteModelSpecs from '../pipette/definitions/1/pipetteModelSpecs.json'
import { OT3_PIPETTES } from './constants'

import type { PipetteNameSpecs, PipetteModelSpecs } from './types'
Expand Down
37 changes: 33 additions & 4 deletions shared-data/pipette/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,50 @@
# Pipette Spec Data
# Pipette Configurations

## Schema Version 2

Information about our pipettes is now split into 3 different categories of data. Each data file is organized into `<configuration_type>/<pipette_type>/<pipette_model>/<pipette_version>`.

- `configuration_type` is the top level category of data (i.e. `geometry` or `liquid`)
- `pipette_type` is the type of pipette generally referred to by the channel size (i.e. `single_channel` or `eight_channel`)
- `pipette_model` is the max volume of the pipette (i.e. `p50` or `p1000`)
- `pipette_version` is the version number flashed to the pipette (i.e. `v1` or `v1.2`)

This organization is subject to change based on the model name changes that product might make.

### Geometry Configurations: `shared-data/pipette/schemas/2/pipetteGeometrySchema.json`

Pipette geometry configurations includes physical properties that map the pipette end effector in space. In this section of data, we would also like to store 3D model descriptor files that are compatible with typescript and other 3D modeling visualization software for future applications.

We are planning to use [gltf](https://www.khronos.org/gltf/) formatted files as you can choose your 3D model anchors inside solidworks and export the file.

### Liquid Configurations: `shared-data/pipette/schemas/2/pipetteLiquidPropertiesSchema.json`

Pipette liquid configurations include all pipette properties that may affect liquid handling. This includes pipetting function and default flow rates based on tip size.

We have now added in the ability to categorize liquid handling properties by tip type (which can also vary by brand). Eventually, we may need this to be more complex than just a look up dictionary of `tip_type` : `brand+liquid` but we can decide to make that change at a different time.

### General Properties Configurations: `shared-data/pipette/schemas/2/pipettePropertiesSchema.json`

Pipette general properties should be similar to schema version 1 name specs that are shared across pipette type + model.

## Schema Version 1

Information about our pipettes is split into 2 different files.

## Name Level: `shared-data/pipette/definitions/pipetteNameSpecs.json`
### Name Level: `shared-data/pipette/schemas/1/pipetteNameSpecs.json`

A pipette name is what is communicated with customers, what is listed in the store, etc. Name-level information does not vary across pipettes with the same "name", it includes: min and max volume, display name, number of channels, and default aspirate/dispense flow rates.

The "name" is all that is communicated to the average user about a pipette. Both JSON and Python protocols specify pipettes by name; they never specify the pipette model/version.

`"p10_single"` is an example of a name.

## Model Level: `shared-data/pipette/definitions/pipetteModelSpecs.json`
### Model Level: `shared-data/pipette/schemas/1/pipetteModelSpecs.json`

A "model" is synonymous with a part number. Our models / part numbers look like `"p10_single_v1.3"`. Although the name is a substring of the model string, it isn't a good idea to infer name by parsing it out of the model.

The model level contains information specific to particular pipette models. The model can be read off of a pipette's EEPROM at runtime. This information is required for protocol execution on the robot, but is not used directly in the code of JSON or Python protocols.

# JSON Schemas
## JSON Schemas

In `shared-data/pipette/schemas/` there are JSON schemas for these files, which ensure data integrity. Further descriptions about the individual fields are written into the schemas.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json",
"displayName": "P1000 Eight Channel GEN3",
"model": "eightChannel",
"displayCategory": "GEN3",
"pickUpTipConfigurations": {
"current": 0.5,
"presses": 1,
"speed": 10,
"increment": 0.0,
"distance": 13.0
},
"dropTipConfigurations": {
"current": 1.0,
"speed": 10
},
"plungerMotorConfigurations": {
"idle": 0.3,
"run": 1.0
},
"plungerPositionsConfigurations": {
"top": 0.5,
"bottom": 71.5,
"blowout": 76.5,
"drop": 92.5
},
"availableSensors": {
"sensors": ["pressure", "capacitive", "environment"],
"pressure": { "count": 2 },
"capacitive": { "count": 2 },
"environment": { "count": 1 }
},
"partialTipConfigurations": {
"partialTipSupported": true,
"availableConfigurations": [1, 2, 3, 4, 5, 6, 7, 8]
},
"channels": 8
}
38 changes: 38 additions & 0 deletions shared-data/pipette/definitions/2/general/eight_channel/p50/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json",
"displayName": "P50 Eight Channel GEN3",
"model": "eightChannel",
"displayCategory": "GEN3",
"pickUpTipConfigurations": {
"current": 0.5,
"presses": 1,
"speed": 10,
"increment": 0.0,
"distance": 13.0
},
"dropTipConfigurations": {
"current": 1.0,
"speed": 10
},
"plungerMotorConfigurations": {
"idle": 0.3,
"run": 1.0
},
"plungerPositionsConfigurations": {
"top": 0.5,
"bottom": 71.5,
"blowout": 76.5,
"drop": 92.5
},
"availableSensors": {
"sensors": ["pressure", "capacitive", "environment"],
"pressure": { "count": 2 },
"capacitive": { "count": 2 },
"environment": { "count": 1 }
},
"partialTipConfigurations": {
"partialTipSupported": true,
"availableConfigurations": [1, 2, 3, 4, 5, 6, 7, 8]
},
"channels": 8
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json",
"displayName": "P1000 96 Channel GEN3",
"model": "ninetySixChannel",
"displayCategory": "GEN3",
"pickUpTipConfigurations": {
"current": 0.4,
"presses": 1,
"speed": 10,
"increment": 0.5,
"distance": 1.0
},
"dropTipConfigurations": {
"current": 1.0,
"speed": 10
},
"plungerMotorConfigurations": {
"idle": 0.3,
"run": 1.0
},
"plungerPositionsConfigurations": {
"top": 0,
"bottom": 66,
"blowout": 71,
"drop": 80
},
"availableSensors": {
"sensors": ["pressure", "capacitive", "environment"],
"pressure": { "count": 2 },
"capacitive": { "count": 2 },
"environment": { "count": 1 }
},
"partialTipConfigurations": {
"partialTipSupported": true,
"availableConfigurations": [1, 8, 12, 96]
},
"channels": 96
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json",
"displayName": "P1000 One Channel GEN3",
"model": "oneChannel",
"displayCategory": "GEN3",
"pickUpTipConfigurations": {
"current": 0.15,
"presses": 1,
"speed": 10,
"increment": 0.0,
"distance": 13.0
},
"dropTipConfigurations": {
"current": 1.0,
"speed": 10
},
"plungerMotorConfigurations": {
"idle": 0.3,
"run": 1.0
},
"plungerPositionsConfigurations": {
"top": 0.5,
"bottom": 71.5,
"blowout": 76.5,
"drop": 90.5
},
"availableSensors": {
"sensors": ["pressure", "capacitive", "environment"],
"pressure": { "count": 1 },
"capacitive": { "count": 1 },
"environment": { "count": 1 }
},
"partialTipConfigurations": {
"partialTipSupported": false
},
"channels": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json",
"displayName": "P50 One Channel GEN3",
"model": "oneChannel",
"displayCategory": "GEN3",
"pickUpTipConfigurations": {
"current": 0.15,
"presses": 1,
"speed": 10,
"increment": 0.0,
"distance": 13.0
},
"dropTipConfigurations": {
"current": 1.0,
"speed": 10
},
"plungerMotorConfigurations": {
"idle": 0.3,
"run": 1.0
},
"plungerPositionsConfigurations": {
"top": 0.5,
"bottom": 71.5,
"blowout": 76.5,
"drop": 90.5
},
"availableSensors": {
"sensors": ["pressure", "capacitive", "environment"],
"pressure": { "count": 1 },
"capacitive": { "count": 1 },
"environment": { "count": 1 }
},
"partialTipConfigurations": {
"partialTipSupported": false
},
"channels": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$otSharedSchema": "#/pipette/schemas/2/pipetteGeometrySchema.json",
"pathTo3D": "pipette/definitions/2/eight_channel/p50/placeholder.gltf",
"nozzleOffset": [-0.5, -16.0, -259.15]
}
Empty file.
Loading

0 comments on commit 355ee9c

Please sign in to comment.