Skip to content

Commit a166bc3

Browse files
feat(api): add new speed for emulsifying pipette plunger (#16917)
1 parent 94bbb99 commit a166bc3

File tree

12 files changed

+148
-7
lines changed

12 files changed

+148
-7
lines changed

api/src/opentrons/config/defaults_ot3.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
DEFAULT_GRIPPER_MOUNT_OFFSET: Final[Offset] = (84.55, -12.75, 93.85)
7676
DEFAULT_SAFE_HOME_DISTANCE: Final = 5
7777
DEFAULT_CALIBRATION_AXIS_MAX_SPEED: Final = 30
78+
DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED: Final = 90
7879

7980
DEFAULT_MAX_SPEEDS: Final[ByGantryLoad[Dict[OT3AxisKind, float]]] = ByGantryLoad(
8081
high_throughput={

api/src/opentrons/hardware_control/backends/flex_protocol.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ def update_constraints_for_calibration_with_gantry_load(
6969
) -> None:
7070
...
7171

72+
def update_constraints_for_emulsifying_pipette(
73+
self, mount: OT3Mount, gantry_load: GantryLoad
74+
) -> None:
75+
...
76+
7277
def update_constraints_for_plunger_acceleration(
7378
self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
7479
) -> None:

api/src/opentrons/hardware_control/backends/ot3controller.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
get_system_constraints,
5151
get_system_constraints_for_calibration,
5252
get_system_constraints_for_plunger_acceleration,
53+
get_system_constraints_for_emulsifying_pipette,
5354
)
5455
from .tip_presence_manager import TipPresenceManager
5556

@@ -393,6 +394,18 @@ def update_constraints_for_calibration_with_gantry_load(
393394
f"Set system constraints for calibration: {self._move_manager.get_constraints()}"
394395
)
395396

397+
def update_constraints_for_emulsifying_pipette(
398+
self, mount: OT3Mount, gantry_load: GantryLoad
399+
) -> None:
400+
self._move_manager.update_constraints(
401+
get_system_constraints_for_emulsifying_pipette(
402+
self._configuration.motion_settings, gantry_load, mount
403+
)
404+
)
405+
log.debug(
406+
f"Set system constraints for emulsifying pipette: {self._move_manager.get_constraints()}"
407+
)
408+
396409
def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None:
397410
self._move_manager.update_constraints(
398411
get_system_constraints(self._configuration.motion_settings, gantry_load)

api/src/opentrons/hardware_control/backends/ot3simulator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ def update_constraints_for_calibration_with_gantry_load(
234234
) -> None:
235235
self._sim_gantry_load = gantry_load
236236

237+
def update_constraints_for_emulsifying_pipette(
238+
self, mount: OT3Mount, gantry_load: GantryLoad
239+
) -> None:
240+
pass
241+
237242
def update_constraints_for_plunger_acceleration(
238243
self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
239244
) -> None:

api/src/opentrons/hardware_control/backends/ot3utils.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
from typing import Dict, Iterable, List, Set, Tuple, TypeVar, cast, Sequence, Optional
33
from typing_extensions import Literal
44
from logging import getLogger
5-
from opentrons.config.defaults_ot3 import DEFAULT_CALIBRATION_AXIS_MAX_SPEED
5+
from opentrons.config.defaults_ot3 import (
6+
DEFAULT_CALIBRATION_AXIS_MAX_SPEED,
7+
DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED,
8+
)
69
from opentrons.config.types import OT3MotionSettings, OT3CurrentSettings, GantryLoad
710
from opentrons.hardware_control.types import (
811
Axis,
@@ -300,6 +303,31 @@ def get_system_constraints_for_plunger_acceleration(
300303
return new_constraints
301304

302305

306+
def get_system_constraints_for_emulsifying_pipette(
307+
config: OT3MotionSettings,
308+
gantry_load: GantryLoad,
309+
mount: OT3Mount,
310+
) -> "SystemConstraints[Axis]":
311+
old_constraints = config.by_gantry_load(gantry_load)
312+
new_constraints = {}
313+
axis_kinds = set([k for _, v in old_constraints.items() for k in v.keys()])
314+
for axis_kind in axis_kinds:
315+
for axis in Axis.of_kind(axis_kind):
316+
if axis == Axis.of_main_tool_actuator(mount):
317+
_max_speed = float(DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED)
318+
else:
319+
_max_speed = old_constraints["default_max_speed"][axis_kind]
320+
new_constraints[axis] = AxisConstraints.build(
321+
max_acceleration=old_constraints["acceleration"][axis_kind],
322+
max_speed_discont=old_constraints["max_speed_discontinuity"][axis_kind],
323+
max_direction_change_speed_discont=old_constraints[
324+
"direction_change_speed_discontinuity"
325+
][axis_kind],
326+
max_speed=_max_speed,
327+
)
328+
return new_constraints
329+
330+
303331
def _convert_to_node_id_dict(
304332
axis_pos: Coordinates[Axis, CoordinateValue],
305333
) -> NodeIdMotionValues:

api/src/opentrons/hardware_control/instruments/ot3/pipette.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
UlPerMmAction,
4242
PipetteName,
4343
PipetteModel,
44+
Quirks,
4445
)
4546
from opentrons_shared_data.pipette import (
4647
load_data as load_pipette_data,
@@ -225,6 +226,9 @@ def active_tip_settings(self) -> SupportedTipsDefinition:
225226
def push_out_volume(self) -> float:
226227
return self._active_tip_settings.default_push_out_volume
227228

229+
def is_high_speed_pipette(self) -> bool:
230+
return Quirks.highSpeed in self._config.quirks
231+
228232
def act_as(self, name: PipetteName) -> None:
229233
"""Reconfigure to act as ``name``. ``name`` must be either the
230234
actual name of the pipette, or a name in its back-compatibility

api/src/opentrons/hardware_control/ot3api.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,10 +634,21 @@ async def cache_pipette(
634634
self._feature_flags.use_old_aspiration_functions,
635635
)
636636
self._pipette_handler.hardware_instruments[mount] = p
637+
if self._pipette_handler.has_pipette(mount):
638+
self._confirm_pipette_motion_constraints(mount)
637639
# TODO (lc 12-5-2022) Properly support backwards compatibility
638640
# when applicable
639641
return skipped
640642

643+
def _confirm_pipette_motion_constraints(
644+
self,
645+
mount: OT3Mount,
646+
) -> None:
647+
if self._pipette_handler.get_pipette(mount).is_high_speed_pipette():
648+
self._backend.update_constraints_for_emulsifying_pipette(
649+
mount, self.gantry_load
650+
)
651+
641652
async def cache_gripper(self, instrument_data: AttachedGripper) -> bool:
642653
"""Set up gripper based on scanned information."""
643654
grip_cal = load_gripper_calibration_offset(instrument_data.get("id"))

api/tests/opentrons/hardware_control/backends/test_ot3_utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from opentrons_hardware.hardware_control.motion_planning import Move
44
from opentrons.hardware_control.backends import ot3utils
55
from opentrons_hardware.firmware_bindings.constants import NodeId
6-
from opentrons.hardware_control.types import Axis, OT3Mount
6+
from opentrons.hardware_control.types import Axis, OT3Mount, OT3AxisKind
77
from numpy import float64 as f64
88

99
from opentrons.config import defaults_ot3, types as conf_types
@@ -95,6 +95,22 @@ def test_get_system_contraints_for_plunger() -> None:
9595
assert updated_contraints[axis].max_acceleration == set_acceleration
9696

9797

98+
@pytest.mark.parametrize(["mount"], [[OT3Mount.LEFT], [OT3Mount.RIGHT]])
99+
def test_get_system_constraints_for_emulsifying_pipette(mount: OT3Mount) -> None:
100+
set_max_speed = 90
101+
config = defaults_ot3.build_with_defaults({})
102+
pipette_ax = Axis.of_main_tool_actuator(mount)
103+
default_pip_max_speed = config.motion_settings.default_max_speed[
104+
conf_types.GantryLoad.LOW_THROUGHPUT
105+
][OT3AxisKind.P]
106+
updated_constraints = ot3utils.get_system_constraints_for_emulsifying_pipette(
107+
config.motion_settings, conf_types.GantryLoad.LOW_THROUGHPUT, mount
108+
)
109+
other_pipette = list(set(Axis.pipette_axes()) - {pipette_ax})[0]
110+
assert updated_constraints[pipette_ax].max_speed == set_max_speed
111+
assert updated_constraints[other_pipette].max_speed == default_pip_max_speed
112+
113+
98114
@pytest.mark.parametrize(
99115
["moving", "expected"],
100116
[

hardware/tests/opentrons_hardware/hardware_control/test_motion_plan.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import numpy as np
33
from hypothesis import given, assume, strategies as st
44
from hypothesis.extra import numpy as hynp
5-
from typing import Iterator, List, Tuple
5+
from typing import Iterator, List, Tuple, Dict
66

77
from opentrons_hardware.hardware_control.motion_planning import move_manager
88
from opentrons_hardware.hardware_control.motion_planning.types import (
@@ -210,3 +210,60 @@ def test_close_move_plan(
210210
)
211211

212212
assert converged, f"Failed to converge: {blend_log}"
213+
214+
215+
def test_pipette_high_speed_motion() -> None:
216+
"""Test that updated motion constraint doesn't get overridden by motion planning."""
217+
origin: Dict[str, int] = {
218+
"X": 499,
219+
"Y": 499,
220+
"Z": 499,
221+
"A": 499,
222+
"B": 499,
223+
"C": 499,
224+
}
225+
target_list = []
226+
axis_kinds = ["X", "Y", "Z", "A", "B", "C"]
227+
constraints: SystemConstraints[str] = {}
228+
for axis_kind in axis_kinds:
229+
constraints[axis_kind] = AxisConstraints.build(
230+
max_acceleration=500,
231+
max_speed_discont=500,
232+
max_direction_change_speed_discont=500,
233+
max_speed=500,
234+
)
235+
origin_mapping: Dict[str, float] = {axis_kind: float(origin[axis_kind])}
236+
target_list.append(MoveTarget.build(origin_mapping, 500))
237+
238+
set_axis_kind = "A"
239+
dummy_em_pipette_max_speed = 90.0
240+
manager = move_manager.MoveManager(constraints=constraints)
241+
242+
new_axis_constraint = AxisConstraints.build(
243+
max_acceleration=float(constraints[set_axis_kind].max_acceleration),
244+
max_speed_discont=float(constraints[set_axis_kind].max_speed_discont),
245+
max_direction_change_speed_discont=float(
246+
constraints[set_axis_kind].max_direction_change_speed_discont
247+
),
248+
max_speed=90.0,
249+
)
250+
new_constraints = {}
251+
252+
for axis_kind in constraints.keys():
253+
if axis_kind == set_axis_kind:
254+
new_constraints[axis_kind] = new_axis_constraint
255+
else:
256+
new_constraints[axis_kind] = constraints[axis_kind]
257+
258+
manager.update_constraints(constraints=new_constraints)
259+
converged, blend_log = manager.plan_motion(
260+
origin=origin,
261+
target_list=target_list,
262+
iteration_limit=20,
263+
)
264+
for move in blend_log[0]:
265+
unit_vector = move.unit_vector
266+
for block in move.blocks:
267+
top_set_axis_speed = unit_vector[set_axis_kind] * block.final_speed
268+
if top_set_axis_speed != 0:
269+
assert abs(top_set_axis_speed) == dummy_em_pipette_max_speed

shared-data/pipette/definitions/2/general/eight_channel_em/p1000/3_0.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@
312312
"shaftDiameter": 4.5,
313313
"shaftULperMM": 15.904,
314314
"backlashDistance": 0.1,
315-
"quirks": [],
315+
"quirks": ["highSpeed"],
316316
"plungerHomingConfigurations": {
317317
"current": 1.0,
318318
"speed": 30

0 commit comments

Comments
 (0)