opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.0a1__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.
- opentrons/calibration_storage/deck_configuration.py +3 -3
- opentrons/calibration_storage/file_operators.py +3 -3
- opentrons/calibration_storage/helpers.py +3 -1
- opentrons/calibration_storage/ot2/models/v1.py +16 -29
- opentrons/calibration_storage/ot2/tip_length.py +7 -4
- opentrons/calibration_storage/ot3/models/v1.py +14 -23
- opentrons/cli/analyze.py +18 -6
- opentrons/config/defaults_ot3.py +1 -0
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/errors.py +16 -3
- opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
- opentrons/drivers/command_builder.py +2 -2
- opentrons/drivers/flex_stacker/__init__.py +9 -0
- opentrons/drivers/flex_stacker/abstract.py +89 -0
- opentrons/drivers/flex_stacker/driver.py +260 -0
- opentrons/drivers/flex_stacker/simulator.py +109 -0
- opentrons/drivers/flex_stacker/types.py +138 -0
- opentrons/drivers/heater_shaker/driver.py +18 -3
- opentrons/drivers/temp_deck/driver.py +13 -3
- opentrons/drivers/thermocycler/driver.py +17 -3
- opentrons/execute.py +3 -1
- opentrons/hardware_control/__init__.py +1 -2
- opentrons/hardware_control/api.py +28 -20
- opentrons/hardware_control/backends/flex_protocol.py +17 -7
- opentrons/hardware_control/backends/ot3controller.py +213 -63
- opentrons/hardware_control/backends/ot3simulator.py +18 -9
- opentrons/hardware_control/backends/ot3utils.py +43 -15
- opentrons/hardware_control/dev_types.py +4 -0
- opentrons/hardware_control/emulation/heater_shaker.py +4 -0
- opentrons/hardware_control/emulation/module_server/client.py +1 -1
- opentrons/hardware_control/emulation/module_server/server.py +5 -3
- opentrons/hardware_control/emulation/settings.py +3 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
- opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
- opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
- opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
- opentrons/hardware_control/modules/mod_abc.py +2 -2
- opentrons/hardware_control/motion_utilities.py +68 -0
- opentrons/hardware_control/nozzle_manager.py +39 -41
- opentrons/hardware_control/ot3_calibration.py +1 -1
- opentrons/hardware_control/ot3api.py +60 -23
- opentrons/hardware_control/protocols/gripper_controller.py +3 -0
- opentrons/hardware_control/protocols/hardware_manager.py +5 -1
- opentrons/hardware_control/protocols/liquid_handler.py +18 -0
- opentrons/hardware_control/protocols/motion_controller.py +6 -0
- opentrons/hardware_control/robot_calibration.py +1 -1
- opentrons/hardware_control/types.py +61 -0
- opentrons/protocol_api/__init__.py +20 -1
- opentrons/protocol_api/_liquid.py +24 -49
- opentrons/protocol_api/_liquid_properties.py +754 -0
- opentrons/protocol_api/_types.py +24 -0
- opentrons/protocol_api/core/common.py +2 -0
- opentrons/protocol_api/core/engine/instrument.py +82 -10
- opentrons/protocol_api/core/engine/labware.py +29 -7
- opentrons/protocol_api/core/engine/protocol.py +130 -5
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/well.py +4 -1
- opentrons/protocol_api/core/instrument.py +46 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +158 -44
- opentrons/protocol_api/labware.py +231 -7
- opentrons/protocol_api/module_contexts.py +21 -17
- opentrons/protocol_api/protocol_context.py +125 -4
- opentrons/protocol_api/robot_context.py +204 -32
- opentrons/protocol_api/validation.py +262 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/actions.py +2 -3
- opentrons/protocol_engine/clients/sync_client.py +18 -0
- opentrons/protocol_engine/commands/__init__.py +81 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
- opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
- opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
- opentrons/protocol_engine/commands/aspirate.py +103 -53
- opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
- opentrons/protocol_engine/commands/blow_out.py +44 -39
- opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
- opentrons/protocol_engine/commands/command.py +73 -66
- opentrons/protocol_engine/commands/command_unions.py +101 -1
- opentrons/protocol_engine/commands/comment.py +1 -1
- opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
- opentrons/protocol_engine/commands/custom.py +6 -12
- opentrons/protocol_engine/commands/dispense.py +82 -48
- opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
- opentrons/protocol_engine/commands/drop_tip.py +52 -31
- opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
- opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/home.py +13 -4
- opentrons/protocol_engine/commands/liquid_probe.py +67 -24
- opentrons/protocol_engine/commands/load_labware.py +29 -7
- opentrons/protocol_engine/commands/load_lid.py +146 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
- opentrons/protocol_engine/commands/load_liquid.py +12 -4
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +31 -10
- opentrons/protocol_engine/commands/load_pipette.py +19 -8
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
- opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +19 -6
- opentrons/protocol_engine/commands/move_relative.py +35 -25
- opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
- opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
- opentrons/protocol_engine/commands/move_to_well.py +40 -24
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
- opentrons/protocol_engine/commands/pipetting_common.py +169 -87
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
- opentrons/protocol_engine/commands/reload_labware.py +1 -1
- opentrons/protocol_engine/commands/retract_axis.py +1 -1
- opentrons/protocol_engine/commands/robot/__init__.py +69 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
- opentrons/protocol_engine/commands/save_position.py +14 -5
- opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
- opentrons/protocol_engine/commands/set_status_bar.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/touch_tip.py +65 -16
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
- opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
- opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/error_occurrence.py +19 -20
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +73 -5
- opentrons/protocol_engine/execution/gantry_mover.py +364 -8
- opentrons/protocol_engine/execution/movement.py +27 -0
- opentrons/protocol_engine/execution/pipetting.py +5 -1
- opentrons/protocol_engine/execution/tip_handler.py +4 -6
- opentrons/protocol_engine/notes/notes.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +7 -6
- opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_validation.py +5 -0
- opentrons/protocol_engine/resources/module_data_provider.py +1 -1
- opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
- opentrons/protocol_engine/slot_standardization.py +9 -9
- opentrons/protocol_engine/state/_move_types.py +9 -5
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +25 -61
- opentrons/protocol_engine/state/command_history.py +12 -0
- opentrons/protocol_engine/state/commands.py +17 -13
- opentrons/protocol_engine/state/files.py +10 -12
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/frustum_helpers.py +57 -32
- opentrons/protocol_engine/state/geometry.py +47 -1
- opentrons/protocol_engine/state/labware.py +79 -25
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +16 -4
- opentrons/protocol_engine/state/modules.py +52 -70
- opentrons/protocol_engine/state/motion.py +6 -1
- opentrons/protocol_engine/state/pipettes.py +144 -58
- opentrons/protocol_engine/state/state.py +21 -2
- opentrons/protocol_engine/state/state_summary.py +4 -2
- opentrons/protocol_engine/state/tips.py +11 -44
- opentrons/protocol_engine/state/update_types.py +343 -48
- opentrons/protocol_engine/state/wells.py +19 -11
- opentrons/protocol_engine/types.py +176 -28
- opentrons/protocol_reader/extract_labware_definitions.py +5 -2
- opentrons/protocol_reader/file_format_validator.py +5 -5
- opentrons/protocol_runner/json_file_reader.py +9 -3
- opentrons/protocol_runner/json_translator.py +51 -25
- opentrons/protocol_runner/legacy_command_mapper.py +66 -64
- opentrons/protocol_runner/protocol_runner.py +35 -4
- opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
- opentrons/protocol_runner/run_orchestrator.py +13 -3
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +1 -1
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +56 -0
- opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +1 -1
- opentrons/protocols/api_support/util.py +10 -0
- opentrons/protocols/labware.py +39 -6
- opentrons/protocols/models/json_protocol.py +5 -9
- opentrons/simulate.py +3 -1
- opentrons/types.py +162 -2
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
"""Basic pipette data state and store."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import dataclasses
|
|
6
|
+
from logging import getLogger
|
|
5
7
|
from typing import (
|
|
6
8
|
Dict,
|
|
7
9
|
List,
|
|
8
10
|
Mapping,
|
|
9
11
|
Optional,
|
|
10
12
|
Tuple,
|
|
11
|
-
|
|
13
|
+
cast,
|
|
12
14
|
)
|
|
13
15
|
|
|
16
|
+
from typing_extensions import assert_never
|
|
17
|
+
|
|
14
18
|
from opentrons_shared_data.pipette import pipette_definition
|
|
19
|
+
from opentrons_shared_data.pipette.ul_per_mm import calculate_ul_per_mm
|
|
20
|
+
from opentrons_shared_data.pipette.types import UlPerMmAction
|
|
21
|
+
|
|
15
22
|
from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE
|
|
16
23
|
from opentrons.hardware_control.dev_types import PipetteDict
|
|
17
24
|
from opentrons.hardware_control import CriticalPoint
|
|
18
25
|
from opentrons.hardware_control.nozzle_manager import (
|
|
19
|
-
NozzleConfigurationType,
|
|
20
26
|
NozzleMap,
|
|
21
27
|
)
|
|
22
|
-
from opentrons.types import MountType, Mount as HwMount, Point
|
|
28
|
+
from opentrons.types import MountType, Mount as HwMount, Point, NozzleConfigurationType
|
|
23
29
|
|
|
24
|
-
from . import update_types
|
|
25
|
-
from .. import commands
|
|
30
|
+
from . import update_types, fluid_stack
|
|
26
31
|
from .. import errors
|
|
27
32
|
from ..types import (
|
|
28
33
|
LoadedPipette,
|
|
@@ -36,13 +41,13 @@ from ..types import (
|
|
|
36
41
|
)
|
|
37
42
|
from ..actions import (
|
|
38
43
|
Action,
|
|
39
|
-
FailCommandAction,
|
|
40
44
|
SetPipetteMovementSpeedAction,
|
|
41
|
-
SucceedCommandAction,
|
|
42
45
|
get_state_updates,
|
|
43
46
|
)
|
|
44
47
|
from ._abstract_store import HasState, HandlesActions
|
|
45
48
|
|
|
49
|
+
LOG = getLogger(__name__)
|
|
50
|
+
|
|
46
51
|
|
|
47
52
|
@dataclasses.dataclass(frozen=True)
|
|
48
53
|
class HardwarePipette:
|
|
@@ -98,6 +103,9 @@ class StaticPipetteConfig:
|
|
|
98
103
|
bounding_nozzle_offsets: BoundingNozzlesOffsets
|
|
99
104
|
default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove?
|
|
100
105
|
lld_settings: Optional[Dict[str, Dict[str, float]]]
|
|
106
|
+
plunger_positions: Dict[str, float]
|
|
107
|
+
shaft_ul_per_mm: float
|
|
108
|
+
available_sensors: pipette_definition.AvailableSensorDefinition
|
|
101
109
|
|
|
102
110
|
|
|
103
111
|
@dataclasses.dataclass
|
|
@@ -108,7 +116,7 @@ class PipetteState:
|
|
|
108
116
|
# attributes are populated at the appropriate times. Refactor to a
|
|
109
117
|
# single dict-of-many-things instead of many dicts-of-single-things.
|
|
110
118
|
pipettes_by_id: Dict[str, LoadedPipette]
|
|
111
|
-
|
|
119
|
+
pipette_contents_by_id: Dict[str, Optional[fluid_stack.FluidStack]]
|
|
112
120
|
current_location: Optional[CurrentPipetteLocation]
|
|
113
121
|
current_deck_point: CurrentDeckPoint
|
|
114
122
|
attached_tip_by_id: Dict[str, Optional[TipGeometry]]
|
|
@@ -128,7 +136,7 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
128
136
|
"""Initialize a PipetteStore and its state."""
|
|
129
137
|
self._state = PipetteState(
|
|
130
138
|
pipettes_by_id={},
|
|
131
|
-
|
|
139
|
+
pipette_contents_by_id={},
|
|
132
140
|
attached_tip_by_id={},
|
|
133
141
|
current_location=None,
|
|
134
142
|
current_deck_point=CurrentDeckPoint(mount=None, deck_point=None),
|
|
@@ -147,11 +155,9 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
147
155
|
self._update_pipette_config(state_update)
|
|
148
156
|
self._update_pipette_nozzle_map(state_update)
|
|
149
157
|
self._update_tip_state(state_update)
|
|
158
|
+
self._update_volumes(state_update)
|
|
150
159
|
|
|
151
|
-
if isinstance(action,
|
|
152
|
-
self._update_volumes(action)
|
|
153
|
-
|
|
154
|
-
elif isinstance(action, SetPipetteMovementSpeedAction):
|
|
160
|
+
if isinstance(action, SetPipetteMovementSpeedAction):
|
|
155
161
|
self._state.movement_speed_by_id[action.pipette_id] = action.speed
|
|
156
162
|
|
|
157
163
|
def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
|
|
@@ -166,7 +172,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
166
172
|
self._state.liquid_presence_detection_by_id[pipette_id] = (
|
|
167
173
|
state_update.loaded_pipette.liquid_presence_detection or False
|
|
168
174
|
)
|
|
169
|
-
self._state.aspirated_volume_by_id[pipette_id] = None
|
|
170
175
|
self._state.movement_speed_by_id[pipette_id] = None
|
|
171
176
|
self._state.attached_tip_by_id[pipette_id] = None
|
|
172
177
|
|
|
@@ -177,7 +182,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
177
182
|
attached_tip = state_update.pipette_tip_state.tip_geometry
|
|
178
183
|
|
|
179
184
|
self._state.attached_tip_by_id[pipette_id] = attached_tip
|
|
180
|
-
self._state.aspirated_volume_by_id[pipette_id] = 0
|
|
181
185
|
|
|
182
186
|
static_config = self._state.static_config_by_id.get(pipette_id)
|
|
183
187
|
if static_config:
|
|
@@ -204,7 +208,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
204
208
|
|
|
205
209
|
else:
|
|
206
210
|
pipette_id = state_update.pipette_tip_state.pipette_id
|
|
207
|
-
self._state.aspirated_volume_by_id[pipette_id] = None
|
|
208
211
|
self._state.attached_tip_by_id[pipette_id] = None
|
|
209
212
|
|
|
210
213
|
static_config = self._state.static_config_by_id.get(pipette_id)
|
|
@@ -292,6 +295,9 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
292
295
|
),
|
|
293
296
|
default_nozzle_map=config.nozzle_map,
|
|
294
297
|
lld_settings=config.pipette_lld_settings,
|
|
298
|
+
plunger_positions=config.plunger_positions,
|
|
299
|
+
shaft_ul_per_mm=config.shaft_ul_per_mm,
|
|
300
|
+
available_sensors=config.available_sensors,
|
|
295
301
|
)
|
|
296
302
|
self._state.flow_rates_by_id[
|
|
297
303
|
state_update.pipette_config.pipette_id
|
|
@@ -308,54 +314,43 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
308
314
|
state_update.pipette_nozzle_map.pipette_id
|
|
309
315
|
] = state_update.pipette_nozzle_map.nozzle_map
|
|
310
316
|
|
|
311
|
-
def _update_volumes(
|
|
312
|
-
|
|
317
|
+
def _update_volumes(self, state_update: update_types.StateUpdate) -> None:
|
|
318
|
+
if state_update.pipette_aspirated_fluid == update_types.NO_CHANGE:
|
|
319
|
+
return
|
|
320
|
+
if state_update.pipette_aspirated_fluid.type == "aspirated":
|
|
321
|
+
self._update_aspirated(state_update.pipette_aspirated_fluid)
|
|
322
|
+
elif state_update.pipette_aspirated_fluid.type == "ejected":
|
|
323
|
+
self._update_ejected(state_update.pipette_aspirated_fluid)
|
|
324
|
+
elif state_update.pipette_aspirated_fluid.type == "empty":
|
|
325
|
+
self._update_empty(state_update.pipette_aspirated_fluid)
|
|
326
|
+
elif state_update.pipette_aspirated_fluid.type == "unknown":
|
|
327
|
+
self._update_unknown(state_update.pipette_aspirated_fluid)
|
|
328
|
+
else:
|
|
329
|
+
assert_never(state_update.pipette_aspirated_fluid.type)
|
|
330
|
+
|
|
331
|
+
def _update_aspirated(
|
|
332
|
+
self, update: update_types.PipetteAspiratedFluidUpdate
|
|
313
333
|
) -> None:
|
|
314
|
-
|
|
315
|
-
# https://opentrons.atlassian.net/browse/EXEC-754
|
|
334
|
+
self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid)
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
(commands.AspirateResult, commands.AspirateInPlaceResult),
|
|
320
|
-
):
|
|
321
|
-
pipette_id = action.command.params.pipetteId
|
|
322
|
-
previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0
|
|
323
|
-
# PipetteHandler will have clamped action.command.result.volume for us, so
|
|
324
|
-
# next_volume should always be in bounds.
|
|
325
|
-
next_volume = previous_volume + action.command.result.volume
|
|
336
|
+
def _update_ejected(self, update: update_types.PipetteEjectedFluidUpdate) -> None:
|
|
337
|
+
self._fluid_stack_log_if_empty(update.pipette_id).remove_fluid(update.volume)
|
|
326
338
|
|
|
327
|
-
|
|
339
|
+
def _update_empty(self, update: update_types.PipetteEmptyFluidUpdate) -> None:
|
|
340
|
+
self._state.pipette_contents_by_id[update.pipette_id] = fluid_stack.FluidStack()
|
|
328
341
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
(commands.DispenseResult, commands.DispenseInPlaceResult),
|
|
332
|
-
):
|
|
333
|
-
pipette_id = action.command.params.pipetteId
|
|
334
|
-
previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0
|
|
335
|
-
# PipetteHandler will have clamped action.command.result.volume for us, so
|
|
336
|
-
# next_volume should always be in bounds.
|
|
337
|
-
next_volume = previous_volume - action.command.result.volume
|
|
338
|
-
self._state.aspirated_volume_by_id[pipette_id] = next_volume
|
|
339
|
-
|
|
340
|
-
elif isinstance(action, SucceedCommandAction) and isinstance(
|
|
341
|
-
action.command.result,
|
|
342
|
-
(
|
|
343
|
-
commands.BlowOutResult,
|
|
344
|
-
commands.BlowOutInPlaceResult,
|
|
345
|
-
commands.unsafe.UnsafeBlowOutInPlaceResult,
|
|
346
|
-
),
|
|
347
|
-
):
|
|
348
|
-
pipette_id = action.command.params.pipetteId
|
|
349
|
-
self._state.aspirated_volume_by_id[pipette_id] = None
|
|
342
|
+
def _update_unknown(self, update: update_types.PipetteUnknownFluidUpdate) -> None:
|
|
343
|
+
self._state.pipette_contents_by_id[update.pipette_id] = None
|
|
350
344
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
def _fluid_stack_log_if_empty(self, pipette_id: str) -> fluid_stack.FluidStack:
|
|
346
|
+
stack = self._state.pipette_contents_by_id[pipette_id]
|
|
347
|
+
if stack is None:
|
|
348
|
+
LOG.error("Pipette state tried to alter an unknown-contents pipette")
|
|
349
|
+
return fluid_stack.FluidStack()
|
|
350
|
+
return stack
|
|
356
351
|
|
|
357
352
|
|
|
358
|
-
class PipetteView
|
|
353
|
+
class PipetteView:
|
|
359
354
|
"""Read-only view of computed pipettes state."""
|
|
360
355
|
|
|
361
356
|
_state: PipetteState
|
|
@@ -457,6 +452,10 @@ class PipetteView(HasState[PipetteState]):
|
|
|
457
452
|
def get_aspirated_volume(self, pipette_id: str) -> Optional[float]:
|
|
458
453
|
"""Get the currently aspirated volume of a pipette by ID.
|
|
459
454
|
|
|
455
|
+
This is the volume currently displaced by the plunger relative to its bottom position,
|
|
456
|
+
regardless of whether that volume likely contains liquid or air. This makes it the right
|
|
457
|
+
function to call to know how much more volume the plunger may displace.
|
|
458
|
+
|
|
460
459
|
Returns:
|
|
461
460
|
The volume the pipette has aspirated.
|
|
462
461
|
None, after blow-out and the plunger is in an unsafe position.
|
|
@@ -468,13 +467,50 @@ class PipetteView(HasState[PipetteState]):
|
|
|
468
467
|
self.validate_tip_state(pipette_id, True)
|
|
469
468
|
|
|
470
469
|
try:
|
|
471
|
-
|
|
470
|
+
stack = self._state.pipette_contents_by_id[pipette_id]
|
|
471
|
+
if stack is None:
|
|
472
|
+
return None
|
|
473
|
+
return stack.aspirated_volume()
|
|
472
474
|
|
|
473
475
|
except KeyError as e:
|
|
474
476
|
raise errors.PipetteNotLoadedError(
|
|
475
477
|
f"Pipette {pipette_id} not found; unable to get current volume."
|
|
476
478
|
) from e
|
|
477
479
|
|
|
480
|
+
def get_liquid_dispensed_by_ejecting_volume(
|
|
481
|
+
self, pipette_id: str, volume: float
|
|
482
|
+
) -> Optional[float]:
|
|
483
|
+
"""Get the amount of liquid (not air) that will be dispensed if the pipette ejects a specified volume.
|
|
484
|
+
|
|
485
|
+
For instance, if the pipette contains, in vertical order,
|
|
486
|
+
10 ul air
|
|
487
|
+
80 ul liquid
|
|
488
|
+
5 ul air
|
|
489
|
+
|
|
490
|
+
then dispensing 10ul would result in 5ul of liquid; dispensing 85 ul would result in 80ul liquid; dispensing
|
|
491
|
+
95ul would result in 80ul liquid.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
The volume of liquid that would be dispensed by the requested volume.
|
|
495
|
+
None, after blow-out or when the plunger is in an unsafe position.
|
|
496
|
+
|
|
497
|
+
Raises:
|
|
498
|
+
PipetteNotLoadedError: pipette ID does not exist.
|
|
499
|
+
TipnotAttachedError: No tip is attached to the pipette.
|
|
500
|
+
"""
|
|
501
|
+
self.validate_tip_state(pipette_id, True)
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
stack = self._state.pipette_contents_by_id[pipette_id]
|
|
505
|
+
if stack is None:
|
|
506
|
+
return None
|
|
507
|
+
return stack.liquid_part_of_dispense_volume(volume)
|
|
508
|
+
|
|
509
|
+
except KeyError as e:
|
|
510
|
+
raise errors.PipetteNotLoadedError(
|
|
511
|
+
f"Pipette {pipette_id} not found; unable to get current liquid volume."
|
|
512
|
+
) from e
|
|
513
|
+
|
|
478
514
|
def get_working_volume(self, pipette_id: str) -> float:
|
|
479
515
|
"""Get the working maximum volume of a pipette by ID.
|
|
480
516
|
|
|
@@ -641,6 +677,10 @@ class PipetteView(HasState[PipetteState]):
|
|
|
641
677
|
nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
|
|
642
678
|
return nozzle_map.starting_nozzle
|
|
643
679
|
|
|
680
|
+
def get_nozzle_configuration(self, pipette_id: str) -> NozzleMap:
|
|
681
|
+
"""Get the nozzle map of the pipette."""
|
|
682
|
+
return self._state.nozzle_configuration_by_id[pipette_id]
|
|
683
|
+
|
|
644
684
|
def _get_critical_point_offset_without_tip(
|
|
645
685
|
self, pipette_id: str, critical_point: Optional[CriticalPoint]
|
|
646
686
|
) -> Point:
|
|
@@ -723,6 +763,13 @@ class PipetteView(HasState[PipetteState]):
|
|
|
723
763
|
pip_front_left_bound,
|
|
724
764
|
)
|
|
725
765
|
|
|
766
|
+
def get_pipette_supports_pressure(self, pipette_id: str) -> bool:
|
|
767
|
+
"""Return if this pipette supports a pressure sensor."""
|
|
768
|
+
return (
|
|
769
|
+
"pressure"
|
|
770
|
+
in self._state.static_config_by_id[pipette_id].available_sensors.sensors
|
|
771
|
+
)
|
|
772
|
+
|
|
726
773
|
def get_liquid_presence_detection(self, pipette_id: str) -> bool:
|
|
727
774
|
"""Determine if liquid presence detection is enabled for this pipette."""
|
|
728
775
|
try:
|
|
@@ -731,3 +778,42 @@ class PipetteView(HasState[PipetteState]):
|
|
|
731
778
|
raise errors.PipetteNotLoadedError(
|
|
732
779
|
f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled."
|
|
733
780
|
) from e
|
|
781
|
+
|
|
782
|
+
def get_nozzle_configuration_supports_lld(self, pipette_id: str) -> bool:
|
|
783
|
+
"""Determine if the current partial tip configuration supports LLD."""
|
|
784
|
+
nozzle_map = self.get_nozzle_configuration(pipette_id)
|
|
785
|
+
if (
|
|
786
|
+
nozzle_map.physical_nozzle_count == 96
|
|
787
|
+
and nozzle_map.back_left != nozzle_map.full_instrument_back_left
|
|
788
|
+
and nozzle_map.front_right != nozzle_map.full_instrument_front_right
|
|
789
|
+
):
|
|
790
|
+
return False
|
|
791
|
+
return True
|
|
792
|
+
|
|
793
|
+
def lookup_volume_to_mm_conversion(
|
|
794
|
+
self, pipette_id: str, volume: float, action: str
|
|
795
|
+
) -> float:
|
|
796
|
+
"""Get the volumn to mm conversion for a pipette."""
|
|
797
|
+
try:
|
|
798
|
+
lookup_volume = self.get_working_volume(pipette_id)
|
|
799
|
+
except errors.TipNotAttachedError:
|
|
800
|
+
lookup_volume = self.get_maximum_volume(pipette_id)
|
|
801
|
+
|
|
802
|
+
pipette_config = self.get_config(pipette_id)
|
|
803
|
+
lookup_table_from_config = pipette_config.tip_configuration_lookup_table
|
|
804
|
+
try:
|
|
805
|
+
tip_settings = lookup_table_from_config[lookup_volume]
|
|
806
|
+
except KeyError:
|
|
807
|
+
tip_settings = list(lookup_table_from_config.values())[0]
|
|
808
|
+
return calculate_ul_per_mm(
|
|
809
|
+
volume,
|
|
810
|
+
cast(UlPerMmAction, action),
|
|
811
|
+
tip_settings,
|
|
812
|
+
shaft_ul_per_mm=pipette_config.shaft_ul_per_mm,
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
def lookup_plunger_position_name(
|
|
816
|
+
self, pipette_id: str, position_name: str
|
|
817
|
+
) -> float:
|
|
818
|
+
"""Get the plunger position provided for the given pipette id."""
|
|
819
|
+
return self.get_config(pipette_id).plunger_positions[position_name]
|
|
@@ -9,7 +9,7 @@ from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
|
9
9
|
from opentrons_shared_data.robot.types import RobotDefinition
|
|
10
10
|
|
|
11
11
|
from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy
|
|
12
|
-
from opentrons.protocol_engine.types import ModuleOffsetData
|
|
12
|
+
from opentrons.protocol_engine.types import LiquidClassRecordWithId, ModuleOffsetData
|
|
13
13
|
from opentrons.util.change_notifier import ChangeNotifier
|
|
14
14
|
|
|
15
15
|
from ..resources import DeckFixedLabware
|
|
@@ -25,6 +25,7 @@ from .labware import LabwareState, LabwareStore, LabwareView
|
|
|
25
25
|
from .pipettes import PipetteState, PipetteStore, PipetteView
|
|
26
26
|
from .modules import ModuleState, ModuleStore, ModuleView
|
|
27
27
|
from .liquids import LiquidState, LiquidView, LiquidStore
|
|
28
|
+
from .liquid_classes import LiquidClassState, LiquidClassStore, LiquidClassView
|
|
28
29
|
from .tips import TipState, TipView, TipStore
|
|
29
30
|
from .wells import WellState, WellView, WellStore
|
|
30
31
|
from .geometry import GeometryView
|
|
@@ -49,6 +50,7 @@ class State:
|
|
|
49
50
|
pipettes: PipetteState
|
|
50
51
|
modules: ModuleState
|
|
51
52
|
liquids: LiquidState
|
|
53
|
+
liquid_classes: LiquidClassState
|
|
52
54
|
tips: TipState
|
|
53
55
|
wells: WellState
|
|
54
56
|
files: FileState
|
|
@@ -64,6 +66,7 @@ class StateView(HasState[State]):
|
|
|
64
66
|
_pipettes: PipetteView
|
|
65
67
|
_modules: ModuleView
|
|
66
68
|
_liquid: LiquidView
|
|
69
|
+
_liquid_classes: LiquidClassView
|
|
67
70
|
_tips: TipView
|
|
68
71
|
_wells: WellView
|
|
69
72
|
_geometry: GeometryView
|
|
@@ -101,6 +104,11 @@ class StateView(HasState[State]):
|
|
|
101
104
|
"""Get state view selectors for liquid state."""
|
|
102
105
|
return self._liquid
|
|
103
106
|
|
|
107
|
+
@property
|
|
108
|
+
def liquid_classes(self) -> LiquidClassView:
|
|
109
|
+
"""Get state view selectors for liquid class state."""
|
|
110
|
+
return self._liquid_classes
|
|
111
|
+
|
|
104
112
|
@property
|
|
105
113
|
def tips(self) -> TipView:
|
|
106
114
|
"""Get state view selectors for tip state."""
|
|
@@ -135,7 +143,7 @@ class StateView(HasState[State]):
|
|
|
135
143
|
"""Get protocol run data."""
|
|
136
144
|
error = self._commands.get_error()
|
|
137
145
|
# TODO maybe add summary here for AA
|
|
138
|
-
return StateSummary.
|
|
146
|
+
return StateSummary.model_construct(
|
|
139
147
|
status=self._commands.get_status(),
|
|
140
148
|
errors=[] if error is None else [error],
|
|
141
149
|
pipettes=self._pipettes.get_all(),
|
|
@@ -148,6 +156,12 @@ class StateView(HasState[State]):
|
|
|
148
156
|
wells=self._wells.get_all(),
|
|
149
157
|
hasEverEnteredErrorRecovery=self._commands.get_has_entered_recovery_mode(),
|
|
150
158
|
files=self._state.files.file_ids,
|
|
159
|
+
liquidClasses=[
|
|
160
|
+
LiquidClassRecordWithId(
|
|
161
|
+
liquidClassId=liquid_class_id, **dict(liquid_class_record)
|
|
162
|
+
)
|
|
163
|
+
for liquid_class_id, liquid_class_record in self._liquid_classes.get_all().items()
|
|
164
|
+
],
|
|
151
165
|
)
|
|
152
166
|
|
|
153
167
|
|
|
@@ -213,6 +227,7 @@ class StateStore(StateView, ActionHandler):
|
|
|
213
227
|
module_calibration_offsets=module_calibration_offsets,
|
|
214
228
|
)
|
|
215
229
|
self._liquid_store = LiquidStore()
|
|
230
|
+
self._liquid_class_store = LiquidClassStore()
|
|
216
231
|
self._tip_store = TipStore()
|
|
217
232
|
self._well_store = WellStore()
|
|
218
233
|
self._file_store = FileStore()
|
|
@@ -224,6 +239,7 @@ class StateStore(StateView, ActionHandler):
|
|
|
224
239
|
self._labware_store,
|
|
225
240
|
self._module_store,
|
|
226
241
|
self._liquid_store,
|
|
242
|
+
self._liquid_class_store,
|
|
227
243
|
self._tip_store,
|
|
228
244
|
self._well_store,
|
|
229
245
|
self._file_store,
|
|
@@ -342,6 +358,7 @@ class StateStore(StateView, ActionHandler):
|
|
|
342
358
|
pipettes=self._pipette_store.state,
|
|
343
359
|
modules=self._module_store.state,
|
|
344
360
|
liquids=self._liquid_store.state,
|
|
361
|
+
liquid_classes=self._liquid_class_store.state,
|
|
345
362
|
tips=self._tip_store.state,
|
|
346
363
|
wells=self._well_store.state,
|
|
347
364
|
files=self._file_store.state,
|
|
@@ -359,6 +376,7 @@ class StateStore(StateView, ActionHandler):
|
|
|
359
376
|
self._pipettes = PipetteView(state.pipettes)
|
|
360
377
|
self._modules = ModuleView(state.modules)
|
|
361
378
|
self._liquid = LiquidView(state.liquids)
|
|
379
|
+
self._liquid_classes = LiquidClassView(state.liquid_classes)
|
|
362
380
|
self._tips = TipView(state.tips)
|
|
363
381
|
self._wells = WellView(state.wells)
|
|
364
382
|
self._files = FileView(state.files)
|
|
@@ -391,6 +409,7 @@ class StateStore(StateView, ActionHandler):
|
|
|
391
409
|
self._pipettes._state = next_state.pipettes
|
|
392
410
|
self._modules._state = next_state.modules
|
|
393
411
|
self._liquid._state = next_state.liquids
|
|
412
|
+
self._liquid_classes._state = next_state.liquid_classes
|
|
394
413
|
self._tips._state = next_state.tips
|
|
395
414
|
self._wells._state = next_state.wells
|
|
396
415
|
self._change_notifier.notify()
|
|
@@ -11,6 +11,7 @@ from ..types import (
|
|
|
11
11
|
LoadedModule,
|
|
12
12
|
LoadedPipette,
|
|
13
13
|
Liquid,
|
|
14
|
+
LiquidClassRecordWithId,
|
|
14
15
|
WellInfoSummary,
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -27,8 +28,9 @@ class StateSummary(BaseModel):
|
|
|
27
28
|
pipettes: List[LoadedPipette]
|
|
28
29
|
modules: List[LoadedModule]
|
|
29
30
|
labwareOffsets: List[LabwareOffset]
|
|
30
|
-
startedAt: Optional[datetime]
|
|
31
|
-
completedAt: Optional[datetime]
|
|
31
|
+
startedAt: Optional[datetime] = None
|
|
32
|
+
completedAt: Optional[datetime] = None
|
|
32
33
|
liquids: List[Liquid] = Field(default_factory=list)
|
|
33
34
|
wells: List[WellInfoSummary] = Field(default_factory=list)
|
|
34
35
|
files: List[str] = Field(default_factory=list)
|
|
36
|
+
liquidClasses: List[LiquidClassRecordWithId] = Field(default_factory=list)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Tip state tracking."""
|
|
2
|
+
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Dict, Optional, List, Union
|
|
5
6
|
|
|
7
|
+
from opentrons.types import NozzleMapInterface
|
|
6
8
|
from opentrons.protocol_engine.state import update_types
|
|
7
9
|
|
|
8
10
|
from ._abstract_store import HasState, HandlesActions
|
|
11
|
+
from ._well_math import wells_covered_dense
|
|
9
12
|
from ..actions import Action, ResetTipsAction, get_state_updates
|
|
10
13
|
|
|
11
14
|
from opentrons.hardware_control.nozzle_manager import NozzleMap
|
|
@@ -108,49 +111,15 @@ class TipStore(HasState[TipState], HandlesActions):
|
|
|
108
111
|
column for column in definition.ordering
|
|
109
112
|
]
|
|
110
113
|
|
|
111
|
-
def _set_used_tips(
|
|
112
|
-
self, pipette_id: str, well_name: str, labware_id: str
|
|
113
|
-
) -> None:
|
|
114
|
+
def _set_used_tips(self, pipette_id: str, well_name: str, labware_id: str) -> None:
|
|
114
115
|
columns = self._state.column_by_labware_id.get(labware_id, [])
|
|
115
116
|
wells = self._state.tips_by_labware_id.get(labware_id, {})
|
|
116
117
|
nozzle_map = self._state.pipette_info_by_pipette_id[pipette_id].nozzle_map
|
|
118
|
+
for well in wells_covered_dense(nozzle_map, well_name, columns):
|
|
119
|
+
wells[well] = TipRackWellState.USED
|
|
117
120
|
|
|
118
|
-
# TODO (cb, 02-28-2024): Transition from using partial nozzle map to full instrument map for the set used logic
|
|
119
|
-
num_nozzle_cols = len(nozzle_map.columns)
|
|
120
|
-
num_nozzle_rows = len(nozzle_map.rows)
|
|
121
|
-
|
|
122
|
-
critical_column = 0
|
|
123
|
-
critical_row = 0
|
|
124
|
-
for column in columns:
|
|
125
|
-
if well_name in column:
|
|
126
|
-
critical_row = column.index(well_name)
|
|
127
|
-
critical_column = columns.index(column)
|
|
128
121
|
|
|
129
|
-
|
|
130
|
-
for j in range(num_nozzle_rows):
|
|
131
|
-
if nozzle_map.starting_nozzle == "A1":
|
|
132
|
-
if (critical_column + i < len(columns)) and (
|
|
133
|
-
critical_row + j < len(columns[critical_column])
|
|
134
|
-
):
|
|
135
|
-
well = columns[critical_column + i][critical_row + j]
|
|
136
|
-
wells[well] = TipRackWellState.USED
|
|
137
|
-
elif nozzle_map.starting_nozzle == "A12":
|
|
138
|
-
if (critical_column - i >= 0) and (
|
|
139
|
-
critical_row + j < len(columns[critical_column])
|
|
140
|
-
):
|
|
141
|
-
well = columns[critical_column - i][critical_row + j]
|
|
142
|
-
wells[well] = TipRackWellState.USED
|
|
143
|
-
elif nozzle_map.starting_nozzle == "H1":
|
|
144
|
-
if (critical_column + i < len(columns)) and (critical_row - j >= 0):
|
|
145
|
-
well = columns[critical_column + i][critical_row - j]
|
|
146
|
-
wells[well] = TipRackWellState.USED
|
|
147
|
-
elif nozzle_map.starting_nozzle == "H12":
|
|
148
|
-
if (critical_column - i >= 0) and (critical_row - j >= 0):
|
|
149
|
-
well = columns[critical_column - i][critical_row - j]
|
|
150
|
-
wells[well] = TipRackWellState.USED
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class TipView(HasState[TipState]):
|
|
122
|
+
class TipView:
|
|
154
123
|
"""Read-only tip state view."""
|
|
155
124
|
|
|
156
125
|
_state: TipState
|
|
@@ -168,12 +137,13 @@ class TipView(HasState[TipState]):
|
|
|
168
137
|
labware_id: str,
|
|
169
138
|
num_tips: int,
|
|
170
139
|
starting_tip_name: Optional[str],
|
|
171
|
-
nozzle_map: Optional[
|
|
140
|
+
nozzle_map: Optional[NozzleMapInterface],
|
|
172
141
|
) -> Optional[str]:
|
|
173
142
|
"""Get the next available clean tip. Does not support use of a starting tip if the pipette used is in a partial configuration."""
|
|
174
143
|
wells = self._state.tips_by_labware_id.get(labware_id, {})
|
|
175
144
|
columns = self._state.column_by_labware_id.get(labware_id, [])
|
|
176
145
|
|
|
146
|
+
# TODO(sf): I'm pretty sure this can be replaced with wells_covered_96 but I'm not quite sure how
|
|
177
147
|
def _identify_tip_cluster(
|
|
178
148
|
active_columns: int,
|
|
179
149
|
active_rows: int,
|
|
@@ -224,10 +194,7 @@ class TipView(HasState[TipState]):
|
|
|
224
194
|
return None
|
|
225
195
|
else:
|
|
226
196
|
# In the case of an 8ch pipette where a column has mixed state tips we may simply progress to the next column in our search
|
|
227
|
-
if
|
|
228
|
-
nozzle_map is not None
|
|
229
|
-
and len(nozzle_map.full_instrument_map_store) == 8
|
|
230
|
-
):
|
|
197
|
+
if nozzle_map is not None and nozzle_map.physical_nozzle_count == 8:
|
|
231
198
|
return None
|
|
232
199
|
|
|
233
200
|
# In the case of a 96ch we can attempt to index in by singular rows and columns assuming that indexed direction is safe
|
|
@@ -357,7 +324,7 @@ class TipView(HasState[TipState]):
|
|
|
357
324
|
return None
|
|
358
325
|
|
|
359
326
|
if starting_tip_name is None and nozzle_map is not None and columns:
|
|
360
|
-
num_channels =
|
|
327
|
+
num_channels = nozzle_map.physical_nozzle_count
|
|
361
328
|
num_nozzle_cols = len(nozzle_map.columns)
|
|
362
329
|
num_nozzle_rows = len(nozzle_map.rows)
|
|
363
330
|
# Each pipette's cluster search is determined by the point of entry for a given pipette/configuration:
|