opentrons 8.3.2__py2.py3-none-any.whl → 8.4.0__py2.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/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +102 -5
- opentrons/legacy_commands/helpers.py +74 -1
- opentrons/legacy_commands/types.py +33 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1356 -107
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +6 -14
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
- opentrons/protocol_api/core/engine/well.py +73 -5
- opentrons/protocol_api/core/instrument.py +71 -21
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +23 -2
- opentrons/protocol_api/instrument_context.py +454 -150
- opentrons/protocol_api/labware.py +98 -50
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +163 -19
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +66 -36
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +102 -33
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +27 -13
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/move_to_well.py +5 -11
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +159 -8
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
- opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
- opentrons/protocol_engine/errors/__init__.py +10 -0
- opentrons/protocol_engine/errors/exceptions.py +62 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +235 -25
- opentrons/protocol_engine/execution/tip_handler.py +82 -32
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +36 -14
- opentrons/protocol_engine/state/geometry.py +892 -227
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +210 -67
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +55 -9
- opentrons/protocol_engine/types/__init__.py +300 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +111 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +301 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +27 -23
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +20 -7
- opentrons/util/logging_config.py +94 -25
- opentrons/util/logging_queue_handler.py +61 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Pipetting command handling."""
|
|
2
|
-
from typing import Optional, Iterator
|
|
2
|
+
from typing import Optional, Iterator, Tuple
|
|
3
3
|
from typing_extensions import Protocol as TypingProtocol
|
|
4
4
|
from contextlib import contextmanager
|
|
5
5
|
|
|
@@ -13,8 +13,13 @@ from ..errors.exceptions import (
|
|
|
13
13
|
InvalidAspirateVolumeError,
|
|
14
14
|
InvalidPushOutVolumeError,
|
|
15
15
|
InvalidDispenseVolumeError,
|
|
16
|
+
InvalidLiquidHeightFound,
|
|
16
17
|
)
|
|
17
18
|
from opentrons.protocol_engine.types import WellLocation
|
|
19
|
+
from opentrons.protocol_engine.types.liquid_level_detection import (
|
|
20
|
+
SimulatedProbeResult,
|
|
21
|
+
LiquidTrackingType,
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
# 1e-9 µL (1 femtoliter!) is a good value because:
|
|
20
25
|
# * It's large relative to rounding errors that occur in practice in protocols. For
|
|
@@ -30,6 +35,9 @@ _VOLUME_ROUNDING_ERROR_TOLERANCE = 1e-9
|
|
|
30
35
|
class PipettingHandler(TypingProtocol):
|
|
31
36
|
"""Liquid handling commands."""
|
|
32
37
|
|
|
38
|
+
def get_state_view(self) -> StateView:
|
|
39
|
+
"""Get the stateview associated with this handler."""
|
|
40
|
+
|
|
33
41
|
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
34
42
|
"""Get whether a pipette is ready to aspirate."""
|
|
35
43
|
|
|
@@ -42,15 +50,41 @@ class PipettingHandler(TypingProtocol):
|
|
|
42
50
|
volume: float,
|
|
43
51
|
flow_rate: float,
|
|
44
52
|
command_note_adder: CommandNoteAdder,
|
|
53
|
+
correction_volume: float = 0.0,
|
|
45
54
|
) -> float:
|
|
46
55
|
"""Set flow-rate and aspirate."""
|
|
47
56
|
|
|
57
|
+
async def aspirate_while_tracking(
|
|
58
|
+
self,
|
|
59
|
+
pipette_id: str,
|
|
60
|
+
labware_id: str,
|
|
61
|
+
well_name: str,
|
|
62
|
+
volume: float,
|
|
63
|
+
flow_rate: float,
|
|
64
|
+
command_note_adder: CommandNoteAdder,
|
|
65
|
+
) -> float:
|
|
66
|
+
"""Set flow-rate and aspirate while tracking."""
|
|
67
|
+
|
|
68
|
+
async def dispense_while_tracking(
|
|
69
|
+
self,
|
|
70
|
+
pipette_id: str,
|
|
71
|
+
labware_id: str,
|
|
72
|
+
well_name: str,
|
|
73
|
+
volume: float,
|
|
74
|
+
flow_rate: float,
|
|
75
|
+
push_out: Optional[float],
|
|
76
|
+
is_full_dispense: bool = False,
|
|
77
|
+
) -> float:
|
|
78
|
+
"""Set flow-rate and dispense while tracking."""
|
|
79
|
+
|
|
48
80
|
async def dispense_in_place(
|
|
49
81
|
self,
|
|
50
82
|
pipette_id: str,
|
|
51
83
|
volume: float,
|
|
52
84
|
flow_rate: float,
|
|
53
85
|
push_out: Optional[float],
|
|
86
|
+
is_full_dispense: bool,
|
|
87
|
+
correction_volume: float = 0.0,
|
|
54
88
|
) -> float:
|
|
55
89
|
"""Set flow-rate and dispense."""
|
|
56
90
|
|
|
@@ -67,18 +101,25 @@ class PipettingHandler(TypingProtocol):
|
|
|
67
101
|
labware_id: str,
|
|
68
102
|
well_name: str,
|
|
69
103
|
well_location: WellLocation,
|
|
70
|
-
) ->
|
|
104
|
+
) -> LiquidTrackingType:
|
|
71
105
|
"""Detect liquid level."""
|
|
72
106
|
|
|
107
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
108
|
+
"""Increase evo tip dispense action count."""
|
|
109
|
+
|
|
73
110
|
|
|
74
111
|
class HardwarePipettingHandler(PipettingHandler):
|
|
75
|
-
"""Liquid handling, using the Hardware API."""
|
|
112
|
+
"""Liquid handling, using the Hardware API."""
|
|
76
113
|
|
|
77
114
|
def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> None:
|
|
78
115
|
"""Initialize a PipettingHandler instance."""
|
|
79
116
|
self._state_view = state_view
|
|
80
117
|
self._hardware_api = hardware_api
|
|
81
118
|
|
|
119
|
+
def get_state_view(self) -> StateView:
|
|
120
|
+
"""Get the stateview associated with this handler."""
|
|
121
|
+
return self._state_view
|
|
122
|
+
|
|
82
123
|
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
83
124
|
"""Get whether a pipette is ready to aspirate."""
|
|
84
125
|
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
@@ -88,6 +129,7 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
88
129
|
return (
|
|
89
130
|
self._state_view.pipettes.get_aspirated_volume(pipette_id) is not None
|
|
90
131
|
and hw_pipette.config["ready_to_aspirate"]
|
|
132
|
+
and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
|
|
91
133
|
)
|
|
92
134
|
|
|
93
135
|
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
@@ -99,10 +141,48 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
99
141
|
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
|
|
100
142
|
await self._hardware_api.prepare_for_aspirate(mount=hw_mount)
|
|
101
143
|
|
|
102
|
-
|
|
144
|
+
def get_hw_aspirate_params(
|
|
145
|
+
self,
|
|
146
|
+
pipette_id: str,
|
|
147
|
+
volume: float,
|
|
148
|
+
command_note_adder: CommandNoteAdder,
|
|
149
|
+
) -> Tuple[HardwarePipette, float]:
|
|
150
|
+
"""Get params for hardware aspirate."""
|
|
151
|
+
_adjusted_volume = _validate_aspirate_volume(
|
|
152
|
+
state_view=self._state_view,
|
|
153
|
+
pipette_id=pipette_id,
|
|
154
|
+
aspirate_volume=volume,
|
|
155
|
+
command_note_adder=command_note_adder,
|
|
156
|
+
)
|
|
157
|
+
_hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
158
|
+
pipette_id=pipette_id,
|
|
159
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
160
|
+
)
|
|
161
|
+
return _hw_pipette, _adjusted_volume
|
|
162
|
+
|
|
163
|
+
def get_hw_dispense_params(
|
|
103
164
|
self,
|
|
104
165
|
pipette_id: str,
|
|
105
166
|
volume: float,
|
|
167
|
+
) -> Tuple[HardwarePipette, float]:
|
|
168
|
+
"""Get params for hardware dispense."""
|
|
169
|
+
_adjusted_volume = _validate_dispense_volume(
|
|
170
|
+
state_view=self._state_view,
|
|
171
|
+
pipette_id=pipette_id,
|
|
172
|
+
dispense_volume=volume,
|
|
173
|
+
)
|
|
174
|
+
_hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
175
|
+
pipette_id=pipette_id,
|
|
176
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
177
|
+
)
|
|
178
|
+
return _hw_pipette, _adjusted_volume
|
|
179
|
+
|
|
180
|
+
async def aspirate_while_tracking(
|
|
181
|
+
self,
|
|
182
|
+
pipette_id: str,
|
|
183
|
+
labware_id: str,
|
|
184
|
+
well_name: str,
|
|
185
|
+
volume: float,
|
|
106
186
|
flow_rate: float,
|
|
107
187
|
command_note_adder: CommandNoteAdder,
|
|
108
188
|
) -> float:
|
|
@@ -112,19 +192,88 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
112
192
|
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
113
193
|
"""
|
|
114
194
|
# get mount and config data from state and hardware controller
|
|
115
|
-
adjusted_volume =
|
|
116
|
-
|
|
195
|
+
hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
|
|
196
|
+
pipette_id, volume, command_note_adder
|
|
197
|
+
)
|
|
198
|
+
aspirate_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
|
|
199
|
+
labware_id=labware_id,
|
|
200
|
+
well_name=well_name,
|
|
201
|
+
operation_volume=volume * -1,
|
|
117
202
|
pipette_id=pipette_id,
|
|
118
|
-
aspirate_volume=volume,
|
|
119
|
-
command_note_adder=command_note_adder,
|
|
120
203
|
)
|
|
121
|
-
|
|
204
|
+
if isinstance(aspirate_z_distance, SimulatedProbeResult):
|
|
205
|
+
raise InvalidLiquidHeightFound(
|
|
206
|
+
"Aspirate distance must be a float in Hardware pipetting handler."
|
|
207
|
+
)
|
|
208
|
+
with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
|
|
209
|
+
await self._hardware_api.aspirate_while_tracking(
|
|
210
|
+
mount=hw_pipette.mount,
|
|
211
|
+
z_distance=aspirate_z_distance,
|
|
212
|
+
flow_rate=flow_rate,
|
|
213
|
+
volume=adjusted_volume,
|
|
214
|
+
)
|
|
215
|
+
return adjusted_volume
|
|
216
|
+
|
|
217
|
+
async def dispense_while_tracking(
|
|
218
|
+
self,
|
|
219
|
+
pipette_id: str,
|
|
220
|
+
labware_id: str,
|
|
221
|
+
well_name: str,
|
|
222
|
+
volume: float,
|
|
223
|
+
flow_rate: float,
|
|
224
|
+
push_out: Optional[float],
|
|
225
|
+
is_full_dispense: bool = False,
|
|
226
|
+
) -> float:
|
|
227
|
+
"""Set flow-rate and dispense.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
231
|
+
"""
|
|
232
|
+
# get mount and config data from state and hardware controller
|
|
233
|
+
hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
|
|
234
|
+
dispense_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
|
|
235
|
+
labware_id=labware_id,
|
|
236
|
+
well_name=well_name,
|
|
237
|
+
operation_volume=volume,
|
|
122
238
|
pipette_id=pipette_id,
|
|
123
|
-
|
|
239
|
+
)
|
|
240
|
+
if isinstance(dispense_z_distance, SimulatedProbeResult):
|
|
241
|
+
raise InvalidLiquidHeightFound(
|
|
242
|
+
"Dispense distance must be a float in Hardware pipetting handler."
|
|
243
|
+
)
|
|
244
|
+
with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
|
|
245
|
+
await self._hardware_api.dispense_while_tracking(
|
|
246
|
+
mount=hw_pipette.mount,
|
|
247
|
+
z_distance=dispense_z_distance,
|
|
248
|
+
flow_rate=flow_rate,
|
|
249
|
+
volume=adjusted_volume,
|
|
250
|
+
push_out=push_out,
|
|
251
|
+
is_full_dispense=is_full_dispense,
|
|
252
|
+
)
|
|
253
|
+
return adjusted_volume
|
|
254
|
+
|
|
255
|
+
async def aspirate_in_place(
|
|
256
|
+
self,
|
|
257
|
+
pipette_id: str,
|
|
258
|
+
volume: float,
|
|
259
|
+
flow_rate: float,
|
|
260
|
+
command_note_adder: CommandNoteAdder,
|
|
261
|
+
correction_volume: float = 0.0,
|
|
262
|
+
) -> float:
|
|
263
|
+
"""Set flow-rate and aspirate.
|
|
264
|
+
|
|
265
|
+
Raises:
|
|
266
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
267
|
+
"""
|
|
268
|
+
# get mount and config data from state and hardware controller
|
|
269
|
+
hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
|
|
270
|
+
pipette_id, volume, command_note_adder
|
|
124
271
|
)
|
|
125
272
|
with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
|
|
126
273
|
await self._hardware_api.aspirate(
|
|
127
|
-
mount=hw_pipette.mount,
|
|
274
|
+
mount=hw_pipette.mount,
|
|
275
|
+
volume=adjusted_volume,
|
|
276
|
+
correction_volume=correction_volume,
|
|
128
277
|
)
|
|
129
278
|
|
|
130
279
|
return adjusted_volume
|
|
@@ -135,15 +284,11 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
135
284
|
volume: float,
|
|
136
285
|
flow_rate: float,
|
|
137
286
|
push_out: Optional[float],
|
|
287
|
+
is_full_dispense: bool,
|
|
288
|
+
correction_volume: float = 0.0,
|
|
138
289
|
) -> float:
|
|
139
290
|
"""Dispense liquid without moving the pipette."""
|
|
140
|
-
adjusted_volume =
|
|
141
|
-
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
|
|
142
|
-
)
|
|
143
|
-
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
144
|
-
pipette_id=pipette_id,
|
|
145
|
-
attached_pipettes=self._hardware_api.attached_instruments,
|
|
146
|
-
)
|
|
291
|
+
hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
|
|
147
292
|
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
148
293
|
if push_out and push_out < 0:
|
|
149
294
|
raise InvalidPushOutVolumeError(
|
|
@@ -151,7 +296,11 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
151
296
|
)
|
|
152
297
|
with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
|
|
153
298
|
await self._hardware_api.dispense(
|
|
154
|
-
mount=hw_pipette.mount,
|
|
299
|
+
mount=hw_pipette.mount,
|
|
300
|
+
volume=adjusted_volume,
|
|
301
|
+
push_out=push_out,
|
|
302
|
+
correction_volume=correction_volume,
|
|
303
|
+
is_full_dispense=is_full_dispense,
|
|
155
304
|
)
|
|
156
305
|
|
|
157
306
|
return adjusted_volume
|
|
@@ -176,8 +325,8 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
176
325
|
labware_id: str,
|
|
177
326
|
well_name: str,
|
|
178
327
|
well_location: WellLocation,
|
|
179
|
-
) ->
|
|
180
|
-
"""
|
|
328
|
+
) -> LiquidTrackingType:
|
|
329
|
+
"""Return liquid level relative to the bottom of the well."""
|
|
181
330
|
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
182
331
|
pipette_id=pipette_id,
|
|
183
332
|
attached_pipettes=self._hardware_api.attached_instruments,
|
|
@@ -223,6 +372,14 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
223
372
|
blow_out=original_blow_out_rate,
|
|
224
373
|
)
|
|
225
374
|
|
|
375
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
376
|
+
"""Increase evo tip dispense action count."""
|
|
377
|
+
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
378
|
+
pipette_id=pipette_id,
|
|
379
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
380
|
+
)
|
|
381
|
+
await self._hardware_api.increase_evo_disp_count(mount=hw_pipette.mount)
|
|
382
|
+
|
|
226
383
|
|
|
227
384
|
class VirtualPipettingHandler(PipettingHandler):
|
|
228
385
|
"""Liquid handling, using the virtual pipettes.""" ""
|
|
@@ -236,12 +393,19 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
236
393
|
"""Initialize a PipettingHandler instance."""
|
|
237
394
|
self._state_view = state_view
|
|
238
395
|
|
|
396
|
+
def get_state_view(self) -> StateView:
|
|
397
|
+
"""Get the stateview associated with this handler."""
|
|
398
|
+
return self._state_view
|
|
399
|
+
|
|
239
400
|
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
240
401
|
"""Get whether a pipette is ready to aspirate."""
|
|
241
|
-
return self._state_view.pipettes.get_aspirated_volume(
|
|
402
|
+
return self._state_view.pipettes.get_aspirated_volume(
|
|
403
|
+
pipette_id
|
|
404
|
+
) is not None and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
|
|
242
405
|
|
|
243
406
|
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
244
407
|
"""Virtually prepare to aspirate (no-op)."""
|
|
408
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
245
409
|
|
|
246
410
|
async def aspirate_in_place(
|
|
247
411
|
self,
|
|
@@ -249,6 +413,7 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
249
413
|
volume: float,
|
|
250
414
|
flow_rate: float,
|
|
251
415
|
command_note_adder: CommandNoteAdder,
|
|
416
|
+
correction_volume: float = 0.0,
|
|
252
417
|
) -> float:
|
|
253
418
|
"""Virtually aspirate (no-op)."""
|
|
254
419
|
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
@@ -265,6 +430,8 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
265
430
|
volume: float,
|
|
266
431
|
flow_rate: float,
|
|
267
432
|
push_out: Optional[float],
|
|
433
|
+
is_full_dispense: bool,
|
|
434
|
+
correction_volume: float = 0.0,
|
|
268
435
|
) -> float:
|
|
269
436
|
"""Virtually dispense (no-op)."""
|
|
270
437
|
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
@@ -290,10 +457,9 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
290
457
|
labware_id: str,
|
|
291
458
|
well_name: str,
|
|
292
459
|
well_location: WellLocation,
|
|
293
|
-
) ->
|
|
460
|
+
) -> LiquidTrackingType:
|
|
294
461
|
"""Detect liquid level."""
|
|
295
|
-
|
|
296
|
-
return well_def.depth
|
|
462
|
+
return SimulatedProbeResult()
|
|
297
463
|
|
|
298
464
|
def _validate_tip_attached(self, pipette_id: str, command_name: str) -> None:
|
|
299
465
|
"""Validate if there is a tip attached."""
|
|
@@ -303,6 +469,50 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
303
469
|
f"Cannot perform {command_name} without a tip attached"
|
|
304
470
|
)
|
|
305
471
|
|
|
472
|
+
async def aspirate_while_tracking(
|
|
473
|
+
self,
|
|
474
|
+
pipette_id: str,
|
|
475
|
+
labware_id: str,
|
|
476
|
+
well_name: str,
|
|
477
|
+
volume: float,
|
|
478
|
+
flow_rate: float,
|
|
479
|
+
command_note_adder: CommandNoteAdder,
|
|
480
|
+
) -> float:
|
|
481
|
+
"""Virtually aspirate (no-op)."""
|
|
482
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
483
|
+
|
|
484
|
+
return _validate_aspirate_volume(
|
|
485
|
+
state_view=self._state_view,
|
|
486
|
+
pipette_id=pipette_id,
|
|
487
|
+
aspirate_volume=volume,
|
|
488
|
+
command_note_adder=command_note_adder,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
async def dispense_while_tracking(
|
|
492
|
+
self,
|
|
493
|
+
pipette_id: str,
|
|
494
|
+
labware_id: str,
|
|
495
|
+
well_name: str,
|
|
496
|
+
volume: float,
|
|
497
|
+
flow_rate: float,
|
|
498
|
+
push_out: Optional[float],
|
|
499
|
+
is_full_dispense: bool = False,
|
|
500
|
+
) -> float:
|
|
501
|
+
"""Virtually dispense (no-op)."""
|
|
502
|
+
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
503
|
+
if push_out and push_out < 0:
|
|
504
|
+
raise InvalidPushOutVolumeError(
|
|
505
|
+
"push out value cannot have a negative value."
|
|
506
|
+
)
|
|
507
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="dispense")
|
|
508
|
+
return _validate_dispense_volume(
|
|
509
|
+
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
513
|
+
"""Increase evo tip dispense action count."""
|
|
514
|
+
pass
|
|
515
|
+
|
|
306
516
|
|
|
307
517
|
def create_pipetting_handler(
|
|
308
518
|
state_view: StateView, hardware_api: HardwareControlAPI
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""Tip pickup and drop procedures."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional, Dict
|
|
3
|
+
from typing import Optional, Dict, Tuple
|
|
4
4
|
from typing_extensions import Protocol as TypingProtocol
|
|
5
5
|
|
|
6
6
|
from opentrons.hardware_control import HardwareControlAPI
|
|
7
|
-
from opentrons.hardware_control.types import
|
|
7
|
+
from opentrons.hardware_control.types import (
|
|
8
|
+
FailedTipStateCheck,
|
|
9
|
+
InstrumentProbeType,
|
|
10
|
+
TipScrapeType,
|
|
11
|
+
)
|
|
8
12
|
from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError
|
|
9
13
|
from opentrons.types import Mount, NozzleConfigurationType
|
|
10
14
|
|
|
@@ -82,6 +86,7 @@ class TipHandler(TypingProtocol):
|
|
|
82
86
|
home_after: Optional[bool],
|
|
83
87
|
do_not_ignore_tip_presence: bool = True,
|
|
84
88
|
ignore_plunger: bool = False,
|
|
89
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
85
90
|
) -> None:
|
|
86
91
|
"""Drop the attached tip into the current location.
|
|
87
92
|
|
|
@@ -196,6 +201,18 @@ async def _available_for_nozzle_layout( # noqa: C901
|
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
|
|
204
|
+
def tip_on_left_side_96(back_left_nozzle: str) -> bool:
|
|
205
|
+
"""Return if there is a tip on the left edge of the 96 channel."""
|
|
206
|
+
left_most_column = int(back_left_nozzle[1:])
|
|
207
|
+
return left_most_column == 1
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def tip_on_right_side_96(front_right_nozzle: str) -> bool:
|
|
211
|
+
"""Return if there is a tip on the left edge of the 96 channel."""
|
|
212
|
+
right_most_column = int(front_right_nozzle[1:])
|
|
213
|
+
return right_most_column == 12
|
|
214
|
+
|
|
215
|
+
|
|
199
216
|
class HardwareTipHandler(TipHandler):
|
|
200
217
|
"""Pick up and drop tips, using the Hardware API."""
|
|
201
218
|
|
|
@@ -232,6 +249,50 @@ class HardwareTipHandler(TipHandler):
|
|
|
232
249
|
channels, style, primary_nozzle, front_right_nozzle, back_left_nozzle
|
|
233
250
|
)
|
|
234
251
|
|
|
252
|
+
def get_tip_presence_config(
|
|
253
|
+
self, pipette_id: str
|
|
254
|
+
) -> Tuple[bool, Optional[InstrumentProbeType]]:
|
|
255
|
+
"""Return the supported settings for tip presence on a given pipette depending on it's current nozzle map."""
|
|
256
|
+
follow_singular_sensor = None
|
|
257
|
+
|
|
258
|
+
unsupported_layout_types_96 = [NozzleConfigurationType.SINGLE]
|
|
259
|
+
# NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor
|
|
260
|
+
supported_partial_nozzle_minimum = 4
|
|
261
|
+
|
|
262
|
+
nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration(
|
|
263
|
+
pipette_id=pipette_id
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
match self._state_view.pipettes.get_channels(pipette_id):
|
|
267
|
+
case 1:
|
|
268
|
+
tip_presence_supported = True
|
|
269
|
+
case 8:
|
|
270
|
+
tip_presence_supported = (
|
|
271
|
+
nozzle_configuration.tip_count >= supported_partial_nozzle_minimum
|
|
272
|
+
)
|
|
273
|
+
case 96:
|
|
274
|
+
tip_presence_supported = (
|
|
275
|
+
nozzle_configuration.configuration
|
|
276
|
+
not in unsupported_layout_types_96
|
|
277
|
+
and nozzle_configuration.tip_count
|
|
278
|
+
>= supported_partial_nozzle_minimum
|
|
279
|
+
)
|
|
280
|
+
if (
|
|
281
|
+
nozzle_configuration.configuration != NozzleConfigurationType.FULL
|
|
282
|
+
and tip_presence_supported
|
|
283
|
+
):
|
|
284
|
+
use_left = tip_on_left_side_96(nozzle_configuration.back_left)
|
|
285
|
+
use_right = tip_on_right_side_96(nozzle_configuration.front_right)
|
|
286
|
+
if not (use_left and use_right):
|
|
287
|
+
if use_left:
|
|
288
|
+
follow_singular_sensor = InstrumentProbeType.PRIMARY
|
|
289
|
+
else:
|
|
290
|
+
follow_singular_sensor = InstrumentProbeType.SECONDARY
|
|
291
|
+
case _:
|
|
292
|
+
raise ValueError("Unknown pipette type.")
|
|
293
|
+
|
|
294
|
+
return (tip_presence_supported, follow_singular_sensor)
|
|
295
|
+
|
|
235
296
|
async def pick_up_tip(
|
|
236
297
|
self,
|
|
237
298
|
pipette_id: str,
|
|
@@ -261,9 +322,18 @@ class HardwareTipHandler(TipHandler):
|
|
|
261
322
|
await self._hardware_api.tip_pickup_moves(
|
|
262
323
|
mount=hw_mount, presses=None, increment=None
|
|
263
324
|
)
|
|
264
|
-
|
|
325
|
+
|
|
326
|
+
tip_presence_supported, follow_singular_sensor = self.get_tip_presence_config(
|
|
327
|
+
pipette_id
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
if do_not_ignore_tip_presence and tip_presence_supported:
|
|
265
331
|
try:
|
|
266
|
-
await self.verify_tip_presence(
|
|
332
|
+
await self.verify_tip_presence(
|
|
333
|
+
pipette_id,
|
|
334
|
+
TipPresenceStatus.PRESENT,
|
|
335
|
+
follow_singular_sensor=follow_singular_sensor,
|
|
336
|
+
)
|
|
267
337
|
except TipNotAttachedError as e:
|
|
268
338
|
raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
|
|
269
339
|
|
|
@@ -279,19 +349,22 @@ class HardwareTipHandler(TipHandler):
|
|
|
279
349
|
home_after: Optional[bool],
|
|
280
350
|
do_not_ignore_tip_presence: bool = True,
|
|
281
351
|
ignore_plunger: bool = False,
|
|
352
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
282
353
|
) -> None:
|
|
283
354
|
"""See documentation on abstract base class."""
|
|
284
355
|
hw_mount = self._get_hw_mount(pipette_id)
|
|
285
356
|
|
|
286
357
|
# Let the hardware controller handle defaulting home_after since its behavior
|
|
287
358
|
# differs between machines
|
|
359
|
+
kwargs = {}
|
|
288
360
|
if home_after is not None:
|
|
289
|
-
kwargs
|
|
290
|
-
else:
|
|
291
|
-
kwargs = {}
|
|
361
|
+
kwargs["home_after"] = home_after
|
|
292
362
|
|
|
293
363
|
await self._hardware_api.tip_drop_moves(
|
|
294
|
-
mount=hw_mount,
|
|
364
|
+
mount=hw_mount,
|
|
365
|
+
ignore_plunger=ignore_plunger,
|
|
366
|
+
scrape_type=scrape_type,
|
|
367
|
+
**kwargs,
|
|
295
368
|
)
|
|
296
369
|
|
|
297
370
|
if do_not_ignore_tip_presence:
|
|
@@ -342,30 +415,6 @@ class HardwareTipHandler(TipHandler):
|
|
|
342
415
|
follow_singular_sensor: Optional[InstrumentProbeType] = None,
|
|
343
416
|
) -> None:
|
|
344
417
|
"""See documentation on abstract base class."""
|
|
345
|
-
nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration(
|
|
346
|
-
pipette_id=pipette_id
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
# Configuration metrics by which tip presence checking is ignored
|
|
350
|
-
unsupported_pipette_types = [8, 96]
|
|
351
|
-
unsupported_layout_types = [
|
|
352
|
-
NozzleConfigurationType.SINGLE,
|
|
353
|
-
NozzleConfigurationType.COLUMN,
|
|
354
|
-
]
|
|
355
|
-
# NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor
|
|
356
|
-
supported_partial_nozzle_minimum = 4
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
nozzle_configuration is not None
|
|
360
|
-
and self._state_view.pipettes.get_channels(pipette_id)
|
|
361
|
-
in unsupported_pipette_types
|
|
362
|
-
and nozzle_configuration.configuration in unsupported_layout_types
|
|
363
|
-
and len(nozzle_configuration.map_store) < supported_partial_nozzle_minimum
|
|
364
|
-
):
|
|
365
|
-
# Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette.
|
|
366
|
-
# This is due in part to a press distance tolerance which creates a risk case for false positives. In the case of single tip, the mechanical tolerance
|
|
367
|
-
# for presses with 100% success is below the minimum average achieved press distance for a given multi channel pipette in that configuration.
|
|
368
|
-
return
|
|
369
418
|
try:
|
|
370
419
|
ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)
|
|
371
420
|
hw_mount = self._get_hw_mount(pipette_id)
|
|
@@ -445,6 +494,7 @@ class VirtualTipHandler(TipHandler):
|
|
|
445
494
|
home_after: Optional[bool],
|
|
446
495
|
do_not_ignore_tip_presence: bool = True,
|
|
447
496
|
ignore_plunger: bool = False,
|
|
497
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
448
498
|
) -> None:
|
|
449
499
|
"""Pick up a tip at the current location using a virtual pipette.
|
|
450
500
|
|