Skip to content

Commit

Permalink
Merge branch 'edge' into AUTH-1295-add_get_load_name
Browse files Browse the repository at this point in the history
  • Loading branch information
sanni-t authored Jan 30, 2025
2 parents 9181bc5 + b1684cb commit 06c60c9
Show file tree
Hide file tree
Showing 70 changed files with 3,915 additions and 1,577 deletions.
1 change: 1 addition & 0 deletions api-client/src/modules/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface PhysicalPort {
port: number
hub: boolean
portGroup: PortGroup
hubPort?: number
}

type ModuleOffsetSource =
Expand Down
49 changes: 26 additions & 23 deletions api/docs/v2/pipettes/characteristics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,29 +184,32 @@ These flow rates will remain in effect until you change the ``flow_rate`` attrib
Flex Pipette Flow Rates
-----------------------

Flex pipette flow rates depend on pipette volume and tip capacity. Each pipette–tip combination has a default flow rate for aspirating, dispensing, and blowing out liquid. When using a 50 µL pipette, you should only use 50 µL tips.

.. list-table::
:header-rows: 1

* - Pipette Model
- Tip Capacity (µL)
- Flow Rate (µL/s)
* - 50 µL (1- and 8-channel)
- 50
- 57
* - 1000 µL (1-, 8-, and 96-channel)
- 50
- 478
* - 1000 µL (1-, 8-, and 96-channel)
- 200
- 716
* - 1000 µL (1-, 8-, and 96-channel)
- 1000
- 716


Additionally, all Flex pipettes have a well bottom clearance of 1 mm for aspirate and dispense actions.
The following table provides data on the default aspirate, dispense, and blowout flow rates (in µL/s) for Flex pipettes. Default flow rates for each pipette-tip combination are the same across all three actions.

.. Excludes low-vol 96 channel. Not yet released.
+-----------------------------+-------------------+------------------------+
| Pipette Model | Tip Capacity (µL) | Default Flow Rate (µL) |
+=============================+===================+========================+
| 1- and 8-channel (50 µL) | 50 | 35 |
+-----------------------------+-------------------+------------------------+
| 1- and 8-channel (1000 µL) | 50 | 478 |
+ +-------------------+------------------------+
| | 200 | 716 |
+ +-------------------+------------------------+
| | 1000 | 716 |
+-----------------------------+-------------------+------------------------+
| 96-channel (5-1000 µL) | 50 | 6 |
+ +-------------------+------------------------+
| | 200 | 80 |
+ +-------------------+------------------------+
| | 1000 | 160 |
+-----------------------------+-------------------+------------------------+

Additionally:

- When using a 50 µL pipette, you should only use 50 µL tips.
- All Flex pipettes have a well bottom clearance of 1 mm for aspirate and dispense actions.

.. _ot2-flow-rates:

Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/drivers/flex_stacker/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@
max_speed=10.0,
acceleration=100.0,
max_speed_discont=40,
current=1.8,
current=1.5,
),
"move": MoveParams(
StackerAxis.Z,
max_speed=200.0,
acceleration=500.0,
max_speed_discont=40,
current=1.8,
current=1.5,
),
},
StackerAxis.L: {
Expand Down
13 changes: 9 additions & 4 deletions api/src/opentrons/hardware_control/modules/flex_stacker.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,9 @@ async def move_axis(
await self.reset_stall_detected()
motion_params = STACKER_MOTION_CONFIG[axis]["move"]
await self._driver.set_run_current(axis, current or motion_params.current or 0)
if any([speed, acceleration]):
if any([speed, acceleration, current]):
motion_params = self._reader.motion_params[axis]
motion_params.current = current or motion_params.current
motion_params.max_speed = speed or motion_params.max_speed
motion_params.acceleration = acceleration or motion_params.acceleration
distance = direction.distance(distance)
Expand Down Expand Up @@ -391,8 +393,11 @@ async def store_labware(self, labware_height: float) -> bool:

# Transfer
await self.open_latch()
await self.move_axis(StackerAxis.Z, Direction.EXTEND, (labware_height / 2))
await self.home_axis(StackerAxis.Z, Direction.EXTEND)
z_speed = (STACKER_MOTION_CONFIG[StackerAxis.Z]["move"].max_speed or 0) / 2
await self.move_axis(
StackerAxis.Z, Direction.EXTEND, (labware_height / 2), z_speed
)
await self.home_axis(StackerAxis.Z, Direction.EXTEND, z_speed)
await self.close_latch()

# Move Z then X axis
Expand Down Expand Up @@ -448,7 +453,7 @@ async def get_limit_switch_status(self) -> None:

async def get_motion_parameters(self) -> None:
"""Get the motion parameters used by the axis motors."""
self.move_params = {
self.motion_params = {
axis: await self._driver.get_motion_params(axis) for axis in StackerAxis
}

Expand Down
4 changes: 2 additions & 2 deletions app/src/assets/localization/en/quick_transfer.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@
"touch_tip": "Touch tip",
"touch_tip_after_aspirating": "Touch tip after aspirating",
"touch_tip_before_dispensing": "Touch tip before dispensing",
"touch_tip_position_mm": "Touch tip position from bottom of well (mm)",
"touch_tip_position_mm": "Touch tip position from top of well (mm)",
"touch_tip_value": "{{position}} mm from bottom",
"use_deck_slots": "<block>Quick transfers use deck slots B2-D2. These slots hold a tip rack, a source labware, and a destination labware.</block><block>Make sure that your deck configuration is up to date to avoid collisions.</block>",
"value_out_of_range": "Value must be between {{min}}-{{max}}",
"value_out_of_range": "Value must be between {{min}} to {{max}}",
"too_many_pins_body": "Remove a quick transfer in order to add more transfers to your pinned list.",
"too_many_pins_header": "You've hit your max!",
"transfer_analysis_failed": "quick transfer analysis failed",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ import {
TRASH_BIN_ADAPTER_FIXTURE,
WASTE_CHUTE_CUTOUT,
WASTE_CHUTE_FIXTURES,
FLEX_STACKER_MODULE_V1,
FLEX_STACKER_V1_FIXTURE,
FLEX_STACKER_WITH_WASTE_CHUTE_ADAPTER_COVERED_FIXTURE,
FLEX_STACKER_WTIH_WASTE_CHUTE_ADAPTER_NO_COVER_FIXTURE,
} from '@opentrons/shared-data'

import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants'
Expand Down Expand Up @@ -249,6 +253,52 @@ export function AddFixtureModal({
]
}
}
if (
cutoutId === 'cutoutD3' &&
unconfiguredMods.some(m => m.moduleModel === FLEX_STACKER_MODULE_V1)
) {
const unconfiguredFlexStackers: CutoutConfig[][] = []
unconfiguredMods
.filter(mod => mod.moduleModel === FLEX_STACKER_MODULE_V1)
.forEach(mod => {
unconfiguredFlexStackers.push([
{
cutoutId,
cutoutFixtureId: FLEX_STACKER_V1_FIXTURE,
opentronsModuleSerialNumber: mod.serialNumber,
},
])
unconfiguredFlexStackers.push([
{
cutoutId,
cutoutFixtureId: FLEX_STACKER_WITH_WASTE_CHUTE_ADAPTER_COVERED_FIXTURE,
opentronsModuleSerialNumber: mod.serialNumber,
},
])
unconfiguredFlexStackers.push([
{
cutoutId,
cutoutFixtureId: FLEX_STACKER_WTIH_WASTE_CHUTE_ADAPTER_NO_COVER_FIXTURE,
opentronsModuleSerialNumber: mod.serialNumber,
},
])
})
availableOptions.push(...unconfiguredFlexStackers)
} else if (
STAGING_AREA_CUTOUTS.includes(cutoutId) &&
unconfiguredMods.some(m => m.moduleModel === FLEX_STACKER_MODULE_V1)
) {
const unconfiguredFlexStackers = unconfiguredMods
.filter(mod => mod.moduleModel === FLEX_STACKER_MODULE_V1)
.map(mod => [
{
cutoutId,
cutoutFixtureId: FLEX_STACKER_V1_FIXTURE,
opentronsModuleSerialNumber: mod.serialNumber,
},
])
availableOptions = [...availableOptions, ...unconfiguredFlexStackers]
}
} else if (optionStage === 'wasteChuteOptions') {
availableOptions = WASTE_CHUTE_FIXTURES.map(fixture => [
{
Expand Down Expand Up @@ -315,22 +365,30 @@ export function AddFixtureModal({
closeModal()
}

const fixtureOptions = availableOptions.map(cutoutConfigs => (
<FixtureOption
key={cutoutConfigs[0].cutoutFixtureId}
optionName={getFixtureDisplayName(
cutoutConfigs[0].cutoutFixtureId,
(modulesData?.data ?? []).find(
m => m.serialNumber === cutoutConfigs[0].opentronsModuleSerialNumber
)?.usbPort.port
)}
buttonText={t('add')}
onClickHandler={() => {
handleAddFixture(cutoutConfigs)
}}
isOnDevice={isOnDevice}
/>
))
const fixtureOptions = availableOptions.map(cutoutConfigs => {
const usbPort = (modulesData?.data ?? []).find(
m => m.serialNumber === cutoutConfigs[0].opentronsModuleSerialNumber
)?.usbPort
const portDisplay =
usbPort?.hubPort != null
? `${usbPort.port}.${usbPort.hubPort}`
: usbPort?.port

return (
<FixtureOption
key={cutoutConfigs[0].cutoutFixtureId}
optionName={getFixtureDisplayName(
cutoutConfigs[0].cutoutFixtureId,
portDisplay
)}
buttonText={t('add')}
onClickHandler={() => {
handleAddFixture(cutoutConfigs)
}}
isOnDevice={isOnDevice}
/>
)
})

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ export function TouchTip(props: TouchTipProps): JSX.Element {
: state.touchTipDispense != null
)
const [currentStep, setCurrentStep] = useState<number>(1)
const [position, setPosition] = useState<number | null>(
kind === 'aspirate'
? state.touchTipAspirate ?? null
: state.touchTipDispense ?? null
const touchTipAspirate =
state.touchTipAspirate != null ? state.touchTipAspirate.toString() : null
const touchTipDispense =
state.touchTipDispense != null ? state.touchTipDispense.toString() : null
const [position, setPosition] = useState<string | null>(
kind === 'aspirate' ? touchTipAspirate : touchTipDispense
)

const touchTipAction =
Expand Down Expand Up @@ -94,7 +96,10 @@ export function TouchTip(props: TouchTipProps): JSX.Element {
setCurrentStep(2)
}
} else if (currentStep === 2) {
dispatch({ type: touchTipAction, position: position ?? undefined })
dispatch({
type: touchTipAction,
position: position != null ? parseInt(position) : undefined,
})
trackEventWithRobotSerial({
name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED,
properties: {
Expand Down Expand Up @@ -130,10 +135,13 @@ export function TouchTip(props: TouchTipProps): JSX.Element {
}

// the allowed range for touch tip is half the height of the well to 1x the height
const positionRange = { min: Math.round(wellHeight / 2), max: wellHeight }
const positionRange = { min: -Math.round(wellHeight / 2), max: 0 }
const positionError =
position !== null &&
(position < positionRange.min || position > positionRange.max)
(position === '-' ||
position.indexOf('-') !== position.lastIndexOf('-') ||
Number(position) < positionRange.min ||
Number(position) > positionRange.max)
? t(`value_out_of_range`, {
min: positionRange.min,
max: Math.floor(positionRange.max),
Expand Down Expand Up @@ -197,8 +205,8 @@ export function TouchTip(props: TouchTipProps): JSX.Element {
marginTop={SPACING.spacing68}
>
<InputField
type="number"
value={position}
type="text"
value={String(position ?? '')}
title={t('touch_tip_position_mm')}
error={positionError}
readOnly
Expand All @@ -211,10 +219,11 @@ export function TouchTip(props: TouchTipProps): JSX.Element {
borderRadius="0"
>
<NumericalKeyboard
hasHyphen
keyboardRef={keyboardRef}
initialValue={String(position ?? '')}
onChange={e => {
setPosition(Number(e))
setPosition(e)
}}
/>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ describe('AirGap', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Air gap volume (µL)',
error: 'Value must be between 1-180',
error: 'Value must be between 1 to 180',
readOnly: true,
type: 'number',
value: 0,
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('AirGap', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Air gap volume (µL)',
error: 'Value must be between 1-80',
error: 'Value must be between 1 to 80',
readOnly: true,
type: 'number',
value: 0,
Expand All @@ -179,7 +179,7 @@ describe('AirGap', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Air gap volume (µL)',
error: 'Value must be between 1-140',
error: 'Value must be between 1 to 140',
readOnly: true,
type: 'number',
value: 0,
Expand All @@ -204,7 +204,7 @@ describe('AirGap', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Air gap volume (µL)',
error: 'Value must be between 1-200',
error: 'Value must be between 1 to 200',
readOnly: true,
type: 'number',
value: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('Delay', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Delay duration (seconds)',
error: 'Value must be between 1-9999999999',
error: 'Value must be between 1 to 9999999999',
readOnly: true,
type: 'number',
value: 0,
Expand All @@ -158,7 +158,7 @@ describe('Delay', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Delay position from bottom of well (mm)',
error: 'Value must be between 1-100',
error: 'Value must be between 1 to 100',
readOnly: true,
type: 'number',
value: 0,
Expand Down Expand Up @@ -188,7 +188,7 @@ describe('Delay', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Delay position from bottom of well (mm)',
error: 'Value must be between 1-400',
error: 'Value must be between 1 to 400',
readOnly: true,
type: 'number',
value: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ describe('FlowRate', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Aspirate flow rate (µL/s)',
error: 'Value must be between 1-92',
error: 'Value must be between 1 to 92',
readOnly: true,
type: 'number',
value: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('Mix', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Mix volume (µL)',
error: 'Value must be between 1-200',
error: 'Value must be between 1 to 200',
readOnly: true,
type: 'number',
value: 0,
Expand All @@ -158,7 +158,7 @@ describe('Mix', () => {
expect(vi.mocked(InputField)).toHaveBeenCalledWith(
{
title: 'Mix repetitions',
error: 'Value must be between 1-999',
error: 'Value must be between 1 to 999',
readOnly: true,
type: 'number',
value: 0,
Expand Down
Loading

0 comments on commit 06c60c9

Please sign in to comment.