Skip to content

Commit 15bba1a

Browse files
ryanthecodercaila-marashajjerader
authored
feat(shared-data): Add support for PEEK pipettes (#17036)
<!-- Thanks for taking the time to open a Pull Request (PR)! Please make sure you've read the "Opening Pull Requests" section of our Contributing Guide: https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests GitHub provides robust markdown to format your PR. Links, diagrams, pictures, and videos along with text formatting make it possible to create a rich and informative PR. For more information on GitHub markdown, see: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax To ensure your code is reviewed quickly and thoroughly, please fill out the sections below to the best of your ability! --> # Overview This Adds support for the new oem pipette by doing the following: - Add support for the flashing and factory testing pipettes with the new serial number prefix P1KP - Add the shared data definitions with updated max flowrates - Add ability to change the max speed when the pipette definition has the new "highSpeed" quirk - Disable support for the pressure sensor - Don't monitor for over pressure - Throw errors if trying to enable liquid presence detection on a pipette - Throw errors if trying to explicitly use LLD - Support UI differences for the new pipette name <!-- Describe your PR at a high level. State acceptance criteria and how this PR fits into other work. Link issues, PRs, and other relevant resources. --> ## Test Plan and Hands on Testing <!-- Describe your testing of the PR. Emphasize testing not reflected in the code. Attach protocols, logs, screenshots and any other assets that support your testing. --> ## Changelog <!-- List changes introduced by this PR considering future developers and the end user. Give careful thought and clear documentation to breaking changes. --> ## Review requests <!-- - What do you need from reviewers to feel confident this PR is ready to merge? - Ask questions. --> ## Risk assessment <!-- - Indicate the level of attention this PR needs. - Provide context to guide reviewers. - Discuss trade-offs, coupling, and side effects. - Look for the possibility, even if you think it's small, that your change may affect some other part of the system. - For instance, changing return tip behavior may also change the behavior of labware calibration. - How do your unit tests and on hands on testing mitigate this PR's risks and the risk of future regressions? - Especially in high risk PRs, explain how you know your testing is enough. --> --------- Co-authored-by: Caila Marashaj <[email protected]> Co-authored-by: Jethary <[email protected]>
1 parent 453cd47 commit 15bba1a

File tree

64 files changed

+2459
-187
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2459
-187
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: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ def restore_system_constraints(self) -> AsyncIterator[None]:
6060
def grab_pressure(self, channels: int, mount: OT3Mount) -> AsyncIterator[None]:
6161
...
6262

63+
def set_pressure_sensor_available(
64+
self, pipette_axis: Axis, available: bool
65+
) -> None:
66+
...
67+
68+
def get_pressure_sensor_available(self, pipette_axis: Axis) -> bool:
69+
...
70+
6371
def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None:
6472
...
6573

@@ -70,7 +78,11 @@ def update_constraints_for_calibration_with_gantry_load(
7078
...
7179

7280
def update_constraints_for_plunger_acceleration(
73-
self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
81+
self,
82+
mount: OT3Mount,
83+
acceleration: float,
84+
gantry_load: GantryLoad,
85+
high_speed_pipette: bool = False,
7486
) -> None:
7587
...
7688

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
PipetteLiquidNotFoundError,
198198
CommunicationError,
199199
PythonException,
200+
UnsupportedHardwareCommand,
200201
)
201202

202203
from .subsystem_manager import SubsystemManager
@@ -362,6 +363,7 @@ def __init__(
362363
self._configuration.motion_settings, GantryLoad.LOW_THROUGHPUT
363364
)
364365
)
366+
self._pressure_sensor_available: Dict[NodeId, bool] = {}
365367

366368
@asynccontextmanager
367369
async def restore_system_constraints(self) -> AsyncIterator[None]:
@@ -380,6 +382,16 @@ async def grab_pressure(
380382
async with grab_pressure(channels, tool, self._messenger):
381383
yield
382384

385+
def set_pressure_sensor_available(
386+
self, pipette_axis: Axis, available: bool
387+
) -> None:
388+
pip_node = axis_to_node(pipette_axis)
389+
self._pressure_sensor_available[pip_node] = available
390+
391+
def get_pressure_sensor_available(self, pipette_axis: Axis) -> bool:
392+
pip_node = axis_to_node(pipette_axis)
393+
return self._pressure_sensor_available[pip_node]
394+
383395
def update_constraints_for_calibration_with_gantry_load(
384396
self,
385397
gantry_load: GantryLoad,
@@ -399,10 +411,18 @@ def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None:
399411
)
400412

401413
def update_constraints_for_plunger_acceleration(
402-
self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
414+
self,
415+
mount: OT3Mount,
416+
acceleration: float,
417+
gantry_load: GantryLoad,
418+
high_speed_pipette: bool = False,
403419
) -> None:
404420
new_constraints = get_system_constraints_for_plunger_acceleration(
405-
self._configuration.motion_settings, gantry_load, mount, acceleration
421+
self._configuration.motion_settings,
422+
gantry_load,
423+
mount,
424+
acceleration,
425+
high_speed_pipette,
406426
)
407427
self._move_manager.update_constraints(new_constraints)
408428

@@ -679,7 +699,8 @@ async def move(
679699

680700
pipettes_moving = moving_pipettes_in_move_group(move_group)
681701

682-
async with self._monitor_overpressure(pipettes_moving):
702+
checked_moving_pipettes = self._pipettes_to_monitor_pressure(pipettes_moving)
703+
async with self._monitor_overpressure(checked_moving_pipettes):
683704
positions = await runner.run(can_messenger=self._messenger)
684705
self._handle_motor_status_response(positions)
685706

@@ -786,7 +807,8 @@ async def home(
786807
moving_pipettes = [
787808
axis_to_node(ax) for ax in checked_axes if ax in Axis.pipette_axes()
788809
]
789-
async with self._monitor_overpressure(moving_pipettes):
810+
checked_moving_pipettes = self._pipettes_to_monitor_pressure(moving_pipettes)
811+
async with self._monitor_overpressure(checked_moving_pipettes):
790812
positions = await asyncio.gather(*coros)
791813
# TODO(CM): default gear motor homing routine to have some acceleration
792814
if Axis.Q in checked_axes:
@@ -800,6 +822,9 @@ async def home(
800822
self._handle_motor_status_response(position)
801823
return axis_convert(self._position, 0.0)
802824

825+
def _pipettes_to_monitor_pressure(self, pipettes: List[NodeId]) -> List[NodeId]:
826+
return [pip for pip in pipettes if self._pressure_sensor_available[pip]]
827+
803828
def _filter_move_group(self, move_group: MoveGroup) -> MoveGroup:
804829
new_group: MoveGroup = []
805830
for step in move_group:
@@ -915,6 +940,7 @@ def _lookup_serial_key(pipette_name: FirmwarePipetteName) -> str:
915940
lookup_name = {
916941
FirmwarePipetteName.p1000_single: "P1KS",
917942
FirmwarePipetteName.p1000_multi: "P1KM",
943+
FirmwarePipetteName.p1000_multi_em: "P1KP",
918944
FirmwarePipetteName.p50_single: "P50S",
919945
FirmwarePipetteName.p50_multi: "P50M",
920946
FirmwarePipetteName.p1000_96: "P1KH",
@@ -949,6 +975,7 @@ def _build_attached_pip(
949975
converted_name.pipette_type,
950976
converted_name.pipette_channels,
951977
converted_name.pipette_version,
978+
converted_name.oem_type,
952979
),
953980
"id": OT3Controller._combine_serial_number(attached),
954981
}
@@ -1378,6 +1405,11 @@ async def liquid_probe(
13781405
) -> float:
13791406
head_node = axis_to_node(Axis.by_mount(mount))
13801407
tool = sensor_node_for_pipette(OT3Mount(mount.value))
1408+
if tool not in self._pipettes_to_monitor_pressure([tool]):
1409+
raise UnsupportedHardwareCommand(
1410+
"Liquid Presence Detection not available on this pipette."
1411+
)
1412+
13811413
positions = await liquid_probe(
13821414
messenger=self._messenger,
13831415
tool=tool,

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,11 @@ def update_constraints_for_calibration_with_gantry_load(
235235
self._sim_gantry_load = gantry_load
236236

237237
def update_constraints_for_plunger_acceleration(
238-
self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad
238+
self,
239+
mount: OT3Mount,
240+
acceleration: float,
241+
gantry_load: GantryLoad,
242+
high_speed_pipette: bool = False,
239243
) -> None:
240244
self._sim_gantry_load = gantry_load
241245

@@ -505,6 +509,7 @@ def _attached_pipette_to_mount(
505509
converted_name.pipette_type,
506510
converted_name.pipette_channels,
507511
converted_name.pipette_version,
512+
converted_name.oem_type,
508513
),
509514
"id": None,
510515
}
@@ -527,6 +532,7 @@ def _attached_pipette_to_mount(
527532
converted_name.pipette_type,
528533
converted_name.pipette_channels,
529534
converted_name.pipette_version,
535+
converted_name.oem_type,
530536
),
531537
"id": init_instr["id"],
532538
}
@@ -538,6 +544,7 @@ def _attached_pipette_to_mount(
538544
converted_name.pipette_type,
539545
converted_name.pipette_channels,
540546
converted_name.pipette_version,
547+
converted_name.oem_type,
541548
),
542549
"id": None,
543550
}

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

Lines changed: 40 additions & 2 deletions
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,
@@ -281,12 +284,22 @@ def get_system_constraints_for_plunger_acceleration(
281284
gantry_load: GantryLoad,
282285
mount: OT3Mount,
283286
acceleration: float,
287+
high_speed_pipette: bool = False,
284288
) -> "SystemConstraints[Axis]":
285289
old_constraints = config.by_gantry_load(gantry_load)
286290
new_constraints = {}
287291
axis_kinds = set([k for _, v in old_constraints.items() for k in v.keys()])
292+
293+
def _get_axis_max_speed(ax: Axis) -> float:
294+
if ax == Axis.of_main_tool_actuator(mount) and high_speed_pipette:
295+
_max_speed = float(DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED)
296+
else:
297+
_max_speed = old_constraints["default_max_speed"][axis_kind]
298+
return _max_speed
299+
288300
for axis_kind in axis_kinds:
289301
for axis in Axis.of_kind(axis_kind):
302+
_default_max_speed = _get_axis_max_speed(axis)
290303
if axis == Axis.of_main_tool_actuator(mount):
291304
_accel = acceleration
292305
else:
@@ -295,7 +308,32 @@ def get_system_constraints_for_plunger_acceleration(
295308
_accel,
296309
old_constraints["max_speed_discontinuity"][axis_kind],
297310
old_constraints["direction_change_speed_discontinuity"][axis_kind],
298-
old_constraints["default_max_speed"][axis_kind],
311+
_default_max_speed,
312+
)
313+
return new_constraints
314+
315+
316+
def get_system_constraints_for_emulsifying_pipette(
317+
config: OT3MotionSettings,
318+
gantry_load: GantryLoad,
319+
mount: OT3Mount,
320+
) -> "SystemConstraints[Axis]":
321+
old_constraints = config.by_gantry_load(gantry_load)
322+
new_constraints = {}
323+
axis_kinds = set([k for _, v in old_constraints.items() for k in v.keys()])
324+
for axis_kind in axis_kinds:
325+
for axis in Axis.of_kind(axis_kind):
326+
if axis == Axis.of_main_tool_actuator(mount):
327+
_max_speed = float(DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED)
328+
else:
329+
_max_speed = old_constraints["default_max_speed"][axis_kind]
330+
new_constraints[axis] = AxisConstraints.build(
331+
max_acceleration=old_constraints["acceleration"][axis_kind],
332+
max_speed_discont=old_constraints["max_speed_discontinuity"][axis_kind],
333+
max_direction_change_speed_discont=old_constraints[
334+
"direction_change_speed_discontinuity"
335+
][axis_kind],
336+
max_speed=_max_speed,
299337
)
300338
return new_constraints
301339

api/src/opentrons/hardware_control/dev_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
PipetteConfigurations,
2121
SupportedTipsDefinition,
2222
PipetteBoundingBoxOffsetDefinition,
23+
AvailableSensorDefinition,
2324
)
2425
from opentrons_shared_data.gripper import (
2526
GripperModel,
@@ -100,6 +101,7 @@ class PipetteDict(InstrumentDict):
100101
pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition
101102
current_nozzle_map: NozzleMap
102103
lld_settings: Optional[Dict[str, Dict[str, float]]]
104+
available_sensors: AvailableSensorDefinition
103105

104106

105107
class PipetteStateDict(TypedDict):

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
UlPerMmAction,
5757
PipetteName,
5858
PipetteModel,
59+
PipetteOEMType,
5960
)
6061
from opentrons.hardware_control.dev_types import InstrumentHardwareConfigs
6162

@@ -112,17 +113,20 @@ def __init__(
112113
pipette_type=config.pipette_type,
113114
pipette_channels=config.channels,
114115
pipette_generation=config.display_category,
116+
oem_type=PipetteOEMType.OT,
115117
)
116118
self._acting_as = self._pipette_name
117119
self._pipette_model = PipetteModelVersionType(
118120
pipette_type=config.pipette_type,
119121
pipette_channels=config.channels,
120122
pipette_version=config.version,
123+
oem_type=PipetteOEMType.OT,
121124
)
122125
self._valid_nozzle_maps = load_pipette_data.load_valid_nozzle_maps(
123126
self._pipette_model.pipette_type,
124127
self._pipette_model.pipette_channels,
125128
self._pipette_model.pipette_version,
129+
PipetteOEMType.OT,
126130
)
127131
self._nozzle_offset = self._config.nozzle_offset
128132
self._nozzle_manager = (
@@ -189,7 +193,7 @@ def act_as(self, name: PipetteNameType) -> None:
189193
], f"{self.name} is not back-compatible with {name}"
190194

191195
liquid_model = load_pipette_data.load_liquid_model(
192-
name.pipette_type, name.pipette_channels, name.get_version()
196+
name.pipette_type, name.pipette_channels, name.get_version(), name.oem_type
193197
)
194198
# TODO need to grab name config here to deal with act as test
195199
self._liquid_class.max_volume = liquid_model["default"].max_volume
@@ -280,6 +284,7 @@ def reload_configurations(self) -> None:
280284
self._pipette_model.pipette_type,
281285
self._pipette_model.pipette_channels,
282286
self._pipette_model.pipette_version,
287+
self._pipette_model.oem_type,
283288
)
284289
self._config_as_dict = self._config.dict()
285290

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
UlPerMmAction,
4242
PipetteName,
4343
PipetteModel,
44+
Quirks,
45+
PipetteOEMType,
4446
)
4547
from opentrons_shared_data.pipette import (
4648
load_data as load_pipette_data,
@@ -92,22 +94,26 @@ def __init__(
9294
self._liquid_class_name = pip_types.LiquidClasses.default
9395
self._liquid_class = self._config.liquid_properties[self._liquid_class_name]
9496

97+
oem = PipetteOEMType.get_oem_from_quirks(config.quirks)
9598
# TODO (lc 12-05-2022) figure out how we can safely deprecate "name" and "model"
9699
self._pipette_name = PipetteNameType(
97100
pipette_type=config.pipette_type,
98101
pipette_channels=config.channels,
99102
pipette_generation=config.display_category,
103+
oem_type=oem,
100104
)
101105
self._acting_as = self._pipette_name
102106
self._pipette_model = PipetteModelVersionType(
103107
pipette_type=config.pipette_type,
104108
pipette_channels=config.channels,
105109
pipette_version=config.version,
110+
oem_type=oem,
106111
)
107112
self._valid_nozzle_maps = load_pipette_data.load_valid_nozzle_maps(
108113
self._pipette_model.pipette_type,
109114
self._pipette_model.pipette_channels,
110115
self._pipette_model.pipette_version,
116+
self._pipette_model.oem_type,
111117
)
112118
self._nozzle_offset = self._config.nozzle_offset
113119
self._nozzle_manager = (
@@ -225,6 +231,9 @@ def active_tip_settings(self) -> SupportedTipsDefinition:
225231
def push_out_volume(self) -> float:
226232
return self._active_tip_settings.default_push_out_volume
227233

234+
def is_high_speed_pipette(self) -> bool:
235+
return Quirks.highSpeed in self._config.quirks
236+
228237
def act_as(self, name: PipetteName) -> None:
229238
"""Reconfigure to act as ``name``. ``name`` must be either the
230239
actual name of the pipette, or a name in its back-compatibility
@@ -246,6 +255,7 @@ def reload_configurations(self) -> None:
246255
self._pipette_model.pipette_type,
247256
self._pipette_model.pipette_channels,
248257
self._pipette_model.pipette_version,
258+
self._pipette_model.oem_type,
249259
)
250260
self._config_as_dict = self._config.dict()
251261

0 commit comments

Comments
 (0)