opentrons 8.6.0a12__py3-none-any.whl → 8.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/_version.py +2 -2
- opentrons/drivers/asyncio/communication/serial_connection.py +8 -5
- opentrons/drivers/flex_stacker/driver.py +6 -1
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +25 -13
- opentrons/hardware_control/backends/ot3simulator.py +2 -1
- opentrons/hardware_control/dev_types.py +3 -1
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +1 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1 -0
- opentrons/hardware_control/ot3api.py +3 -1
- opentrons/hardware_control/protocols/gripper_controller.py +1 -0
- opentrons/protocol_api/core/engine/_default_liquid_class_versions.py +56 -0
- opentrons/protocol_api/core/engine/instrument.py +143 -18
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +77 -17
- opentrons/protocol_api/core/engine/protocol.py +53 -7
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -1
- opentrons/protocol_api/core/protocol.py +1 -1
- opentrons/protocol_api/labware.py +36 -2
- opentrons/protocol_api/module_contexts.py +146 -14
- opentrons/protocol_api/protocol_context.py +162 -12
- opentrons/protocol_api/validation.py +4 -0
- opentrons/protocol_engine/commands/command_unions.py +2 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +13 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +20 -2
- opentrons/protocol_engine/execution/labware_movement.py +14 -12
- opentrons/protocol_engine/resources/pipette_data_provider.py +3 -0
- opentrons/protocol_engine/state/geometry.py +33 -5
- opentrons/protocol_engine/state/labware.py +66 -0
- opentrons/protocol_engine/state/modules.py +6 -0
- opentrons/protocol_engine/state/pipettes.py +12 -3
- opentrons/protocol_engine/types/__init__.py +2 -0
- opentrons/protocol_engine/types/labware.py +9 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/METADATA +4 -4
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/RECORD +38 -37
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/WHEEL +0 -0
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/licenses/LICENSE +0 -0
opentrons/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '8.
|
|
32
|
-
__version_tuple__ = version_tuple = (8,
|
|
31
|
+
__version__ = version = '8.7.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (8, 7, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -462,7 +462,10 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
462
462
|
self._async_error_ack = async_error_ack.lower()
|
|
463
463
|
|
|
464
464
|
async def send_command(
|
|
465
|
-
self,
|
|
465
|
+
self,
|
|
466
|
+
command: CommandBuilder,
|
|
467
|
+
retries: int | None = None,
|
|
468
|
+
timeout: float | None = None,
|
|
466
469
|
) -> str:
|
|
467
470
|
"""
|
|
468
471
|
Send a command and return the response.
|
|
@@ -478,12 +481,12 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
478
481
|
"""
|
|
479
482
|
return await self.send_data(
|
|
480
483
|
data=command.build(),
|
|
481
|
-
retries=retries
|
|
484
|
+
retries=retries if retries is not None else self._number_of_retries,
|
|
482
485
|
timeout=timeout,
|
|
483
486
|
)
|
|
484
487
|
|
|
485
488
|
async def send_data(
|
|
486
|
-
self, data: str, retries: int =
|
|
489
|
+
self, data: str, retries: int | None = None, timeout: float | None = None
|
|
487
490
|
) -> str:
|
|
488
491
|
"""
|
|
489
492
|
Send data and return the response.
|
|
@@ -501,7 +504,8 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
501
504
|
"timeout", timeout
|
|
502
505
|
):
|
|
503
506
|
return await self._send_data(
|
|
504
|
-
data=data,
|
|
507
|
+
data=data,
|
|
508
|
+
retries=retries if retries is not None else self._number_of_retries,
|
|
505
509
|
)
|
|
506
510
|
|
|
507
511
|
async def _send_data(self, data: str, retries: int = 0) -> str:
|
|
@@ -517,7 +521,6 @@ class AsyncResponseSerialConnection(SerialConnection):
|
|
|
517
521
|
Raises: SerialException
|
|
518
522
|
"""
|
|
519
523
|
data_encode = data.encode()
|
|
520
|
-
retries = retries or self._number_of_retries
|
|
521
524
|
|
|
522
525
|
for retry in range(retries + 1):
|
|
523
526
|
log.debug(f"{self._name}: Write -> {data_encode!r}")
|
|
@@ -461,7 +461,12 @@ class FlexStackerDriver(AbstractFlexStackerDriver):
|
|
|
461
461
|
command = GCODE.GET_TOF_MEASUREMENT.build_command().add_element(sensor.name)
|
|
462
462
|
if resend:
|
|
463
463
|
command.add_element("R")
|
|
464
|
-
|
|
464
|
+
|
|
465
|
+
# Note: We DONT want to auto resend the request if it fails, because the
|
|
466
|
+
# firmware will send the next frame id instead of the current one missed.
|
|
467
|
+
# So lets set `retries=0` so we only send the frame once and we can
|
|
468
|
+
# use the retry mechanism of the `get_tof_histogram` method instead.
|
|
469
|
+
resp = await self._connection.send_command(command, retries=0)
|
|
465
470
|
return self.parse_get_tof_measurement(resp)
|
|
466
471
|
|
|
467
472
|
async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:
|
|
@@ -686,9 +686,9 @@ class OT3Controller(FlexBackend):
|
|
|
686
686
|
return (
|
|
687
687
|
MoveGroupRunner(
|
|
688
688
|
move_groups=[move_group],
|
|
689
|
-
ignore_stalls=
|
|
690
|
-
|
|
691
|
-
|
|
689
|
+
ignore_stalls=(
|
|
690
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
691
|
+
),
|
|
692
692
|
),
|
|
693
693
|
False,
|
|
694
694
|
)
|
|
@@ -712,9 +712,9 @@ class OT3Controller(FlexBackend):
|
|
|
712
712
|
return (
|
|
713
713
|
MoveGroupRunner(
|
|
714
714
|
move_groups=[tip_motor_move_group],
|
|
715
|
-
ignore_stalls=
|
|
716
|
-
|
|
717
|
-
|
|
715
|
+
ignore_stalls=(
|
|
716
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
717
|
+
),
|
|
718
718
|
),
|
|
719
719
|
True,
|
|
720
720
|
)
|
|
@@ -939,9 +939,9 @@ class OT3Controller(FlexBackend):
|
|
|
939
939
|
|
|
940
940
|
runner = MoveGroupRunner(
|
|
941
941
|
move_groups=[move_group],
|
|
942
|
-
ignore_stalls=
|
|
943
|
-
|
|
944
|
-
|
|
942
|
+
ignore_stalls=(
|
|
943
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
944
|
+
),
|
|
945
945
|
)
|
|
946
946
|
try:
|
|
947
947
|
positions = await runner.run(can_messenger=self._messenger)
|
|
@@ -976,9 +976,9 @@ class OT3Controller(FlexBackend):
|
|
|
976
976
|
move_group = self._build_tip_action_group(origin, targets)
|
|
977
977
|
runner = MoveGroupRunner(
|
|
978
978
|
move_groups=[move_group],
|
|
979
|
-
ignore_stalls=
|
|
980
|
-
|
|
981
|
-
|
|
979
|
+
ignore_stalls=(
|
|
980
|
+
True if not self._feature_flags.stall_detection_enabled else False
|
|
981
|
+
),
|
|
982
982
|
)
|
|
983
983
|
try:
|
|
984
984
|
positions = await runner.run(can_messenger=self._messenger)
|
|
@@ -1763,6 +1763,7 @@ class OT3Controller(FlexBackend):
|
|
|
1763
1763
|
max_allowed_grip_error: float,
|
|
1764
1764
|
hard_limit_lower: float,
|
|
1765
1765
|
hard_limit_upper: float,
|
|
1766
|
+
disable_geometry_grip_check: bool = False,
|
|
1766
1767
|
) -> None:
|
|
1767
1768
|
"""
|
|
1768
1769
|
Check if the gripper is at the expected location.
|
|
@@ -1777,7 +1778,16 @@ class OT3Controller(FlexBackend):
|
|
|
1777
1778
|
expected_grip_width + grip_width_uncertainty_wider
|
|
1778
1779
|
)
|
|
1779
1780
|
current_gripper_position = jaw_width
|
|
1780
|
-
|
|
1781
|
+
log.info(
|
|
1782
|
+
f"Checking gripper position: current {jaw_width}; max error {max_allowed_grip_error}; hard limits {hard_limit_lower}, {hard_limit_upper}; expected {expected_gripper_position_min}, {expected_grip_width}, {expected_gripper_position_max}; uncertainty {grip_width_uncertainty_narrower}, {grip_width_uncertainty_wider}"
|
|
1783
|
+
)
|
|
1784
|
+
if (
|
|
1785
|
+
isclose(current_gripper_position, hard_limit_lower)
|
|
1786
|
+
# this odd check handles internal backlash that can lead the position to read as if
|
|
1787
|
+
# the gripper has overshot its lower bound; this is physically impossible and an
|
|
1788
|
+
# artifact of the gearing, so it always indicates a hard stop
|
|
1789
|
+
or current_gripper_position < hard_limit_lower
|
|
1790
|
+
):
|
|
1781
1791
|
raise FailedGripperPickupError(
|
|
1782
1792
|
message="Failed to grip: jaws all the way closed",
|
|
1783
1793
|
details={
|
|
@@ -1796,6 +1806,7 @@ class OT3Controller(FlexBackend):
|
|
|
1796
1806
|
if (
|
|
1797
1807
|
current_gripper_position - expected_gripper_position_min
|
|
1798
1808
|
< -max_allowed_grip_error
|
|
1809
|
+
and not disable_geometry_grip_check
|
|
1799
1810
|
):
|
|
1800
1811
|
raise FailedGripperPickupError(
|
|
1801
1812
|
message="Failed to grip: jaws closed too far",
|
|
@@ -1809,6 +1820,7 @@ class OT3Controller(FlexBackend):
|
|
|
1809
1820
|
if (
|
|
1810
1821
|
current_gripper_position - expected_gripper_position_max
|
|
1811
1822
|
> max_allowed_grip_error
|
|
1823
|
+
and not disable_geometry_grip_check
|
|
1812
1824
|
):
|
|
1813
1825
|
raise FailedGripperPickupError(
|
|
1814
1826
|
message="Failed to grip: jaws could not close far enough",
|
|
@@ -781,7 +781,7 @@ class OT3Simulator(FlexBackend):
|
|
|
781
781
|
next_fw_version=1,
|
|
782
782
|
fw_update_needed=False,
|
|
783
783
|
current_fw_sha="simulated",
|
|
784
|
-
pcba_revision="A1",
|
|
784
|
+
pcba_revision="A1.0",
|
|
785
785
|
update_state=None,
|
|
786
786
|
)
|
|
787
787
|
for axis in self._present_axes
|
|
@@ -848,6 +848,7 @@ class OT3Simulator(FlexBackend):
|
|
|
848
848
|
max_allowed_grip_error: float,
|
|
849
849
|
hard_limit_lower: float,
|
|
850
850
|
hard_limit_upper: float,
|
|
851
|
+
disable_geometry_grip_check: bool = False,
|
|
851
852
|
) -> None:
|
|
852
853
|
# This is a (pretty bad) simulation of the gripper actually gripping something,
|
|
853
854
|
# but it should work.
|
|
@@ -14,8 +14,9 @@ from opentrons_shared_data.pipette.types import (
|
|
|
14
14
|
PipetteModel,
|
|
15
15
|
PipetteName,
|
|
16
16
|
ChannelCount,
|
|
17
|
+
PipetteTipType,
|
|
18
|
+
LiquidClasses,
|
|
17
19
|
)
|
|
18
|
-
from opentrons_shared_data.pipette.types import PipetteTipType
|
|
19
20
|
from opentrons_shared_data.pipette.pipette_definition import (
|
|
20
21
|
PipetteConfigurations,
|
|
21
22
|
SupportedTipsDefinition,
|
|
@@ -104,6 +105,7 @@ class PipetteDict(InstrumentDict):
|
|
|
104
105
|
plunger_positions: Dict[str, float]
|
|
105
106
|
shaft_ul_per_mm: float
|
|
106
107
|
available_sensors: AvailableSensorDefinition
|
|
108
|
+
volume_mode: LiquidClasses # LiquidClasses refer to volume mode in this context
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
class PipetteStateDict(TypedDict):
|
|
@@ -267,6 +267,7 @@ class PipetteHandlerProvider(Generic[MountType]):
|
|
|
267
267
|
"drop_tip": instr.plunger_positions.drop_tip,
|
|
268
268
|
}
|
|
269
269
|
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
|
|
270
|
+
result["volume_mode"] = instr.liquid_class_name
|
|
270
271
|
return cast(PipetteDict, result)
|
|
271
272
|
|
|
272
273
|
@property
|
|
@@ -294,6 +294,7 @@ class OT3PipetteHandler:
|
|
|
294
294
|
}
|
|
295
295
|
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
|
|
296
296
|
result["available_sensors"] = instr.config.available_sensors
|
|
297
|
+
result["volume_mode"] = instr.liquid_class_name
|
|
297
298
|
return cast(PipetteDict, result)
|
|
298
299
|
|
|
299
300
|
@property
|
|
@@ -1462,6 +1462,7 @@ class OT3API(
|
|
|
1462
1462
|
expected_grip_width: float,
|
|
1463
1463
|
grip_width_uncertainty_wider: float,
|
|
1464
1464
|
grip_width_uncertainty_narrower: float,
|
|
1465
|
+
disable_geometry_grip_check: bool = False,
|
|
1465
1466
|
) -> None:
|
|
1466
1467
|
"""Ensure that a gripper pickup succeeded.
|
|
1467
1468
|
|
|
@@ -1480,8 +1481,9 @@ class OT3API(
|
|
|
1480
1481
|
grip_width_uncertainty_narrower,
|
|
1481
1482
|
gripper.jaw_width,
|
|
1482
1483
|
gripper.max_allowed_grip_error,
|
|
1483
|
-
gripper.max_jaw_width,
|
|
1484
1484
|
gripper.min_jaw_width,
|
|
1485
|
+
gripper.max_jaw_width,
|
|
1486
|
+
disable_geometry_grip_check,
|
|
1485
1487
|
)
|
|
1486
1488
|
|
|
1487
1489
|
def gripper_jaw_can_home(self) -> bool:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""The versions of standard liquid classes that the Protocol API should load by default."""
|
|
2
|
+
|
|
3
|
+
from typing import TypeAlias
|
|
4
|
+
from opentrons.protocols.api_support.types import APIVersion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DefaultLiquidClassVersions: TypeAlias = dict[APIVersion, dict[str, int]]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# This:
|
|
11
|
+
#
|
|
12
|
+
# {
|
|
13
|
+
# APIVersion(2, 100): {
|
|
14
|
+
# "foo_liquid": 3,
|
|
15
|
+
# },
|
|
16
|
+
# APIVersion(2, 105): {
|
|
17
|
+
# "foo_liquid": 7
|
|
18
|
+
# }
|
|
19
|
+
# }
|
|
20
|
+
#
|
|
21
|
+
# Means this:
|
|
22
|
+
#
|
|
23
|
+
# apiLevels name Default liquid class version
|
|
24
|
+
# ---------------------------------------------------------------
|
|
25
|
+
# <2.100 foo_liquid 1
|
|
26
|
+
# >=2.100,<2.105 foo_liquid 3
|
|
27
|
+
# >=2.105 foo_liquid 7
|
|
28
|
+
# [any] [anything else] 1
|
|
29
|
+
DEFAULT_LIQUID_CLASS_VERSIONS: DefaultLiquidClassVersions = {
|
|
30
|
+
APIVersion(2, 26): {
|
|
31
|
+
"ethanol_80": 2,
|
|
32
|
+
"glycerol_50": 2,
|
|
33
|
+
"water": 2,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_liquid_class_version(
|
|
39
|
+
api_version: APIVersion,
|
|
40
|
+
liquid_class_name: str,
|
|
41
|
+
) -> int:
|
|
42
|
+
"""Return what version of a liquid class the Protocol API should load by default."""
|
|
43
|
+
default_lc_versions_newest_to_oldest = sorted(
|
|
44
|
+
DEFAULT_LIQUID_CLASS_VERSIONS.items(), key=lambda kv: kv[0], reverse=True
|
|
45
|
+
)
|
|
46
|
+
for (
|
|
47
|
+
breakpoint_api_version,
|
|
48
|
+
breakpoint_liquid_class_versions,
|
|
49
|
+
) in default_lc_versions_newest_to_oldest:
|
|
50
|
+
if (
|
|
51
|
+
api_version >= breakpoint_api_version
|
|
52
|
+
and liquid_class_name in breakpoint_liquid_class_versions
|
|
53
|
+
):
|
|
54
|
+
return breakpoint_liquid_class_versions[liquid_class_name]
|
|
55
|
+
|
|
56
|
+
return 1
|
|
@@ -66,7 +66,6 @@ from opentrons.protocol_engine.types.automatic_tip_selection import (
|
|
|
66
66
|
)
|
|
67
67
|
from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
|
|
68
68
|
from opentrons.protocol_engine.clients import SyncClient as EngineClient
|
|
69
|
-
from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
|
|
70
69
|
from opentrons_shared_data.pipette.types import (
|
|
71
70
|
PIPETTE_API_NAMES_MAP,
|
|
72
71
|
LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP,
|
|
@@ -101,6 +100,9 @@ _RESIN_TIP_DEFAULT_FLOW_RATE = 10.0
|
|
|
101
100
|
_FLEX_PIPETTE_NAMES_FIXED_IN = APIVersion(2, 23)
|
|
102
101
|
"""The version after which InstrumentContext.name returns the correct API-specific names of Flex pipettes."""
|
|
103
102
|
|
|
103
|
+
_DEFAULT_FLOW_RATE_BUG_FIXED_IN = APIVersion(2, 26)
|
|
104
|
+
"""The version after which default flow rates correctly update when pipette tip or volume changes."""
|
|
105
|
+
|
|
104
106
|
|
|
105
107
|
class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
106
108
|
"""Instrument API core using a ProtocolEngine.
|
|
@@ -122,18 +124,27 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
122
124
|
self._sync_hardware_api = sync_hardware_api
|
|
123
125
|
self._protocol_core = protocol_core
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
flow_rates = self._engine_client.state.pipettes.get_flow_rates(pipette_id)
|
|
128
|
-
self._aspirate_flow_rate = find_value_for_api_version(
|
|
129
|
-
MAX_SUPPORTED_VERSION, flow_rates.default_aspirate
|
|
130
|
-
)
|
|
131
|
-
self._dispense_flow_rate = find_value_for_api_version(
|
|
132
|
-
MAX_SUPPORTED_VERSION, flow_rates.default_dispense
|
|
133
|
-
)
|
|
134
|
-
self._blow_out_flow_rate = find_value_for_api_version(
|
|
135
|
-
MAX_SUPPORTED_VERSION, flow_rates.default_blow_out
|
|
127
|
+
self._initial_default_flow_rates = (
|
|
128
|
+
self._engine_client.state.pipettes.get_flow_rates(pipette_id)
|
|
136
129
|
)
|
|
130
|
+
self._user_aspirate_flow_rate: Optional[float] = None
|
|
131
|
+
self._user_dispense_flow_rate: Optional[float] = None
|
|
132
|
+
self._user_blow_out_flow_rate: Optional[float] = None
|
|
133
|
+
|
|
134
|
+
if self._protocol_core.api_version < _DEFAULT_FLOW_RATE_BUG_FIXED_IN:
|
|
135
|
+
# Set to the initial defaults to preserve buggy behavior where the default was not correctly updated
|
|
136
|
+
self._user_aspirate_flow_rate = find_value_for_api_version(
|
|
137
|
+
self._protocol_core.api_version,
|
|
138
|
+
self._initial_default_flow_rates.default_aspirate,
|
|
139
|
+
)
|
|
140
|
+
self._user_dispense_flow_rate = find_value_for_api_version(
|
|
141
|
+
self._protocol_core.api_version,
|
|
142
|
+
self._initial_default_flow_rates.default_dispense,
|
|
143
|
+
)
|
|
144
|
+
self._user_blow_out_flow_rate = find_value_for_api_version(
|
|
145
|
+
self._protocol_core.api_version,
|
|
146
|
+
self._initial_default_flow_rates.default_blow_out,
|
|
147
|
+
)
|
|
137
148
|
self._flow_rates = FlowRates(self)
|
|
138
149
|
|
|
139
150
|
self.set_default_speed(speed=default_movement_speed)
|
|
@@ -1031,13 +1042,64 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1031
1042
|
return self._flow_rates
|
|
1032
1043
|
|
|
1033
1044
|
def get_aspirate_flow_rate(self, rate: float = 1.0) -> float:
|
|
1034
|
-
|
|
1045
|
+
"""Returns the user-set aspirate flow rate if that's been modified, otherwise return the default.
|
|
1046
|
+
|
|
1047
|
+
Note that in API versions 2.25 and below `_user_aspirate_flow_rate` will automatically be set to the initial
|
|
1048
|
+
default flow rate when the pipette is loaded (which is the same as the max tip capacity). This is to preserve
|
|
1049
|
+
buggy behavior in which the default was never correctly updated when the pipette picked up or dropped a tip or
|
|
1050
|
+
had its volume configuration changed.
|
|
1051
|
+
"""
|
|
1052
|
+
aspirate_flow_rate = (
|
|
1053
|
+
self._user_aspirate_flow_rate
|
|
1054
|
+
or find_value_for_api_version(
|
|
1055
|
+
self._protocol_core.api_version,
|
|
1056
|
+
self._engine_client.state.pipettes.get_flow_rates(
|
|
1057
|
+
self._pipette_id
|
|
1058
|
+
).default_aspirate,
|
|
1059
|
+
)
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
return aspirate_flow_rate * rate
|
|
1035
1063
|
|
|
1036
1064
|
def get_dispense_flow_rate(self, rate: float = 1.0) -> float:
|
|
1037
|
-
|
|
1065
|
+
"""Returns the user-set dispense flow rate if that's been modified, otherwise return the default.
|
|
1066
|
+
|
|
1067
|
+
Note that in API versions 2.25 and below `_user_dispense_flow_rate` will automatically be set to the initial
|
|
1068
|
+
default flow rate when the pipette is loaded (which is the same as the max tip capacity). This is to preserve
|
|
1069
|
+
buggy behavior in which the default was never correctly updated when the pipette picked up or dropped a tip or
|
|
1070
|
+
had its volume configuration changed.
|
|
1071
|
+
"""
|
|
1072
|
+
dispense_flow_rate = (
|
|
1073
|
+
self._user_dispense_flow_rate
|
|
1074
|
+
or find_value_for_api_version(
|
|
1075
|
+
self._protocol_core.api_version,
|
|
1076
|
+
self._engine_client.state.pipettes.get_flow_rates(
|
|
1077
|
+
self._pipette_id
|
|
1078
|
+
).default_dispense,
|
|
1079
|
+
)
|
|
1080
|
+
)
|
|
1081
|
+
|
|
1082
|
+
return dispense_flow_rate * rate
|
|
1038
1083
|
|
|
1039
1084
|
def get_blow_out_flow_rate(self, rate: float = 1.0) -> float:
|
|
1040
|
-
|
|
1085
|
+
"""Returns the user-set blow-out flow rate if that's been modified, otherwise return the default.
|
|
1086
|
+
|
|
1087
|
+
Note that in API versions 2.25 and below `_user_dispense_flow_rate` will automatically be set to the initial
|
|
1088
|
+
default flow rate when the pipette is loaded (which is the same as the max tip capacity). This is to preserve
|
|
1089
|
+
buggy behavior in which the default was never correctly updated when the pipette picked up or dropped a tip or
|
|
1090
|
+
had its volume configuration changed.
|
|
1091
|
+
"""
|
|
1092
|
+
blow_out_flow_rate = (
|
|
1093
|
+
self._user_blow_out_flow_rate
|
|
1094
|
+
or find_value_for_api_version(
|
|
1095
|
+
self._protocol_core.api_version,
|
|
1096
|
+
self._engine_client.state.pipettes.get_flow_rates(
|
|
1097
|
+
self._pipette_id
|
|
1098
|
+
).default_blow_out,
|
|
1099
|
+
)
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
return blow_out_flow_rate * rate
|
|
1041
1103
|
|
|
1042
1104
|
def get_nozzle_configuration(self) -> NozzleConfigurationType:
|
|
1043
1105
|
return self._engine_client.state.pipettes.get_nozzle_layout_type(
|
|
@@ -1084,13 +1146,13 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1084
1146
|
) -> None:
|
|
1085
1147
|
if aspirate is not None:
|
|
1086
1148
|
assert aspirate > 0
|
|
1087
|
-
self.
|
|
1149
|
+
self._user_aspirate_flow_rate = aspirate
|
|
1088
1150
|
if dispense is not None:
|
|
1089
1151
|
assert dispense > 0
|
|
1090
|
-
self.
|
|
1152
|
+
self._user_dispense_flow_rate = dispense
|
|
1091
1153
|
if blow_out is not None:
|
|
1092
1154
|
assert blow_out > 0
|
|
1093
|
-
self.
|
|
1155
|
+
self._user_blow_out_flow_rate = blow_out
|
|
1094
1156
|
|
|
1095
1157
|
def set_liquid_presence_detection(self, enable: bool) -> None:
|
|
1096
1158
|
self._liquid_presence_detection = enable
|
|
@@ -1105,6 +1167,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1105
1167
|
),
|
|
1106
1168
|
)
|
|
1107
1169
|
)
|
|
1170
|
+
if self._protocol_core.api_version >= _DEFAULT_FLOW_RATE_BUG_FIXED_IN:
|
|
1171
|
+
self._user_aspirate_flow_rate = None
|
|
1172
|
+
self._user_dispense_flow_rate = None
|
|
1173
|
+
self._user_blow_out_flow_rate = None
|
|
1108
1174
|
|
|
1109
1175
|
def prepare_to_aspirate(self) -> None:
|
|
1110
1176
|
self._engine_client.execute_command(
|
|
@@ -1273,6 +1339,13 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1273
1339
|
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1274
1340
|
)
|
|
1275
1341
|
|
|
1342
|
+
original_aspirate_flow_rate = self._user_aspirate_flow_rate
|
|
1343
|
+
original_dispense_flow_rate = self._user_dispense_flow_rate
|
|
1344
|
+
original_blow_out_flow_rate = self._user_blow_out_flow_rate
|
|
1345
|
+
in_low_volume_mode = self._engine_client.state.pipettes.get_is_low_volume_mode(
|
|
1346
|
+
self._pipette_id
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1276
1349
|
target_destinations: Sequence[
|
|
1277
1350
|
Union[Tuple[Location, WellCore], TrashBin, WasteChute]
|
|
1278
1351
|
]
|
|
@@ -1367,6 +1440,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1367
1440
|
if not keep_last_tip:
|
|
1368
1441
|
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1369
1442
|
|
|
1443
|
+
if self._protocol_core.api_version >= _DEFAULT_FLOW_RATE_BUG_FIXED_IN:
|
|
1444
|
+
self._restore_pipette_flow_rates_and_volume_mode(
|
|
1445
|
+
aspirate_flow_rate=original_aspirate_flow_rate,
|
|
1446
|
+
dispense_flow_rate=original_dispense_flow_rate,
|
|
1447
|
+
blow_out_flow_rate=original_blow_out_flow_rate,
|
|
1448
|
+
is_low_volume=in_low_volume_mode,
|
|
1449
|
+
)
|
|
1450
|
+
|
|
1370
1451
|
def distribute_with_liquid_class( # noqa: C901
|
|
1371
1452
|
self,
|
|
1372
1453
|
liquid_class: LiquidClass,
|
|
@@ -1474,6 +1555,13 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1474
1555
|
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1475
1556
|
)
|
|
1476
1557
|
|
|
1558
|
+
original_aspirate_flow_rate = self._user_aspirate_flow_rate
|
|
1559
|
+
original_dispense_flow_rate = self._user_dispense_flow_rate
|
|
1560
|
+
original_blow_out_flow_rate = self._user_blow_out_flow_rate
|
|
1561
|
+
in_low_volume_mode = self._engine_client.state.pipettes.get_is_low_volume_mode(
|
|
1562
|
+
self._pipette_id
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1477
1565
|
# This will return a generator that provides pairs of destination well and
|
|
1478
1566
|
# the volume to dispense into it
|
|
1479
1567
|
dest_per_volume_step = (
|
|
@@ -1617,6 +1705,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1617
1705
|
if not keep_last_tip:
|
|
1618
1706
|
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1619
1707
|
|
|
1708
|
+
if self._protocol_core.api_version >= _DEFAULT_FLOW_RATE_BUG_FIXED_IN:
|
|
1709
|
+
self._restore_pipette_flow_rates_and_volume_mode(
|
|
1710
|
+
aspirate_flow_rate=original_aspirate_flow_rate,
|
|
1711
|
+
dispense_flow_rate=original_dispense_flow_rate,
|
|
1712
|
+
blow_out_flow_rate=original_blow_out_flow_rate,
|
|
1713
|
+
is_low_volume=in_low_volume_mode,
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1620
1716
|
def _tip_can_hold_volume_for_multi_dispensing(
|
|
1621
1717
|
self,
|
|
1622
1718
|
transfer_volume: float,
|
|
@@ -1712,6 +1808,13 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1712
1808
|
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1713
1809
|
)
|
|
1714
1810
|
|
|
1811
|
+
original_aspirate_flow_rate = self._user_aspirate_flow_rate
|
|
1812
|
+
original_dispense_flow_rate = self._user_dispense_flow_rate
|
|
1813
|
+
original_blow_out_flow_rate = self._user_blow_out_flow_rate
|
|
1814
|
+
in_low_volume_mode = self._engine_client.state.pipettes.get_is_low_volume_mode(
|
|
1815
|
+
self._pipette_id
|
|
1816
|
+
)
|
|
1817
|
+
|
|
1715
1818
|
working_volume = self.get_working_volume_for_tip_rack(tip_racks[0][1])
|
|
1716
1819
|
|
|
1717
1820
|
source_per_volume_step = (
|
|
@@ -1796,6 +1899,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1796
1899
|
if not keep_last_tip:
|
|
1797
1900
|
self._drop_tip_for_liquid_class(trash_location, return_tip)
|
|
1798
1901
|
|
|
1902
|
+
if self._protocol_core.api_version >= _DEFAULT_FLOW_RATE_BUG_FIXED_IN:
|
|
1903
|
+
self._restore_pipette_flow_rates_and_volume_mode(
|
|
1904
|
+
aspirate_flow_rate=original_aspirate_flow_rate,
|
|
1905
|
+
dispense_flow_rate=original_dispense_flow_rate,
|
|
1906
|
+
blow_out_flow_rate=original_blow_out_flow_rate,
|
|
1907
|
+
is_low_volume=in_low_volume_mode,
|
|
1908
|
+
)
|
|
1909
|
+
|
|
1799
1910
|
def _get_location_and_well_core_from_next_tip_info(
|
|
1800
1911
|
self,
|
|
1801
1912
|
tip_info: NextTipInfo,
|
|
@@ -1904,6 +2015,20 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1904
2015
|
alternate_drop_location=True,
|
|
1905
2016
|
)
|
|
1906
2017
|
|
|
2018
|
+
def _restore_pipette_flow_rates_and_volume_mode(
|
|
2019
|
+
self,
|
|
2020
|
+
aspirate_flow_rate: Optional[float],
|
|
2021
|
+
dispense_flow_rate: Optional[float],
|
|
2022
|
+
blow_out_flow_rate: Optional[float],
|
|
2023
|
+
is_low_volume: bool,
|
|
2024
|
+
) -> None:
|
|
2025
|
+
# TODO(jbl 2025-09-17) this works for p50 low volume mode but is not guaranteed to work for future low volume
|
|
2026
|
+
# modes, this should be replaced with something less flaky
|
|
2027
|
+
self.configure_for_volume(self.get_max_volume() if not is_low_volume else 1)
|
|
2028
|
+
self._user_aspirate_flow_rate = aspirate_flow_rate
|
|
2029
|
+
self._user_dispense_flow_rate = dispense_flow_rate
|
|
2030
|
+
self._user_blow_out_flow_rate = blow_out_flow_rate
|
|
2031
|
+
|
|
1907
2032
|
def aspirate_liquid_class(
|
|
1908
2033
|
self,
|
|
1909
2034
|
volume: float,
|