opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a2__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/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 +4 -6
- opentrons/hardware_control/backends/ot3controller.py +177 -59
- opentrons/hardware_control/backends/ot3simulator.py +10 -8
- opentrons/hardware_control/backends/ot3utils.py +3 -13
- opentrons/hardware_control/dev_types.py +2 -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 +9 -21
- 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 +13 -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 +34 -22
- 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 +67 -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 +42 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -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 +34 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +145 -43
- 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 +261 -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 +60 -25
- 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 +12 -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 +135 -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 +70 -8
- opentrons/protocols/models/json_protocol.py +5 -9
- opentrons/simulate.py +3 -1
- opentrons/types.py +162 -2
- opentrons/util/entrypoint_util.py +2 -5
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/METADATA +16 -15
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/RECORD +229 -202
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/WHEEL +1 -1
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/LICENSE +0 -0
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""A data store of liquid classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
from typing import Dict
|
|
7
|
+
from typing_extensions import Optional
|
|
8
|
+
|
|
9
|
+
from .. import errors
|
|
10
|
+
from ..actions import Action, get_state_updates
|
|
11
|
+
from ..types import LiquidClassRecord
|
|
12
|
+
from . import update_types
|
|
13
|
+
from ._abstract_store import HasState, HandlesActions
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclasses.dataclass
|
|
17
|
+
class LiquidClassState:
|
|
18
|
+
"""Our state is a bidirectional mapping between IDs <-> LiquidClassRecords."""
|
|
19
|
+
|
|
20
|
+
# We use the bidirectional map to see if we've already assigned an ID to a liquid class when the
|
|
21
|
+
# engine is asked to store a new liquid class.
|
|
22
|
+
liquid_class_record_by_id: Dict[str, LiquidClassRecord]
|
|
23
|
+
liquid_class_record_to_id: Dict[LiquidClassRecord, str]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LiquidClassStore(HasState[LiquidClassState], HandlesActions):
|
|
27
|
+
"""Container for LiquidClassState."""
|
|
28
|
+
|
|
29
|
+
_state: LiquidClassState
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self._state = LiquidClassState(
|
|
33
|
+
liquid_class_record_by_id={},
|
|
34
|
+
liquid_class_record_to_id={},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def handle_action(self, action: Action) -> None:
|
|
38
|
+
"""Update the state in response to the action."""
|
|
39
|
+
for state_update in get_state_updates(action):
|
|
40
|
+
if state_update.liquid_class_loaded != update_types.NO_CHANGE:
|
|
41
|
+
self._handle_liquid_class_loaded_update(
|
|
42
|
+
state_update.liquid_class_loaded
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def _handle_liquid_class_loaded_update(
|
|
46
|
+
self, state_update: update_types.LiquidClassLoadedUpdate
|
|
47
|
+
) -> None:
|
|
48
|
+
# We're just a data store. All the validation and ID generation happens in the command implementation.
|
|
49
|
+
self._state.liquid_class_record_by_id[
|
|
50
|
+
state_update.liquid_class_id
|
|
51
|
+
] = state_update.liquid_class_record
|
|
52
|
+
self._state.liquid_class_record_to_id[
|
|
53
|
+
state_update.liquid_class_record
|
|
54
|
+
] = state_update.liquid_class_id
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class LiquidClassView:
|
|
58
|
+
"""Read-only view of the LiquidClassState."""
|
|
59
|
+
|
|
60
|
+
_state: LiquidClassState
|
|
61
|
+
|
|
62
|
+
def __init__(self, state: LiquidClassState) -> None:
|
|
63
|
+
self._state = state
|
|
64
|
+
|
|
65
|
+
def get(self, liquid_class_id: str) -> LiquidClassRecord:
|
|
66
|
+
"""Get the LiquidClassRecord with the given identifier."""
|
|
67
|
+
try:
|
|
68
|
+
return self._state.liquid_class_record_by_id[liquid_class_id]
|
|
69
|
+
except KeyError as e:
|
|
70
|
+
raise errors.LiquidClassDoesNotExistError(
|
|
71
|
+
f"Liquid class ID {liquid_class_id} not found."
|
|
72
|
+
) from e
|
|
73
|
+
|
|
74
|
+
def get_id_for_liquid_class_record(
|
|
75
|
+
self, liquid_class_record: LiquidClassRecord
|
|
76
|
+
) -> Optional[str]:
|
|
77
|
+
"""See if the given LiquidClassRecord if already in the store, and if so, return its identifier."""
|
|
78
|
+
return self._state.liquid_class_record_to_id.get(liquid_class_record)
|
|
79
|
+
|
|
80
|
+
def get_all(self) -> Dict[str, LiquidClassRecord]:
|
|
81
|
+
"""Get all the LiquidClassRecords in the store."""
|
|
82
|
+
return self._state.liquid_class_record_by_id.copy()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Basic liquid data state and store."""
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import Dict, List
|
|
4
|
-
from opentrons.protocol_engine.types import Liquid
|
|
4
|
+
from opentrons.protocol_engine.types import Liquid, LiquidId
|
|
5
5
|
|
|
6
6
|
from ._abstract_store import HasState, HandlesActions
|
|
7
7
|
from ..actions import Action, AddLiquidAction
|
|
8
|
-
from ..errors import LiquidDoesNotExistError
|
|
8
|
+
from ..errors import LiquidDoesNotExistError, InvalidLiquidError
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
@@ -34,7 +34,7 @@ class LiquidStore(HasState[LiquidState], HandlesActions):
|
|
|
34
34
|
self._state.liquids_by_id[action.liquid.id] = action.liquid
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
class LiquidView
|
|
37
|
+
class LiquidView:
|
|
38
38
|
"""Read-only liquid state view."""
|
|
39
39
|
|
|
40
40
|
_state: LiquidState
|
|
@@ -51,11 +51,23 @@ class LiquidView(HasState[LiquidState]):
|
|
|
51
51
|
"""Get all protocol liquids."""
|
|
52
52
|
return list(self._state.liquids_by_id.values())
|
|
53
53
|
|
|
54
|
-
def validate_liquid_id(self, liquid_id:
|
|
54
|
+
def validate_liquid_id(self, liquid_id: LiquidId) -> LiquidId:
|
|
55
55
|
"""Check if liquid_id exists in liquids."""
|
|
56
|
+
is_empty = liquid_id == "EMPTY"
|
|
57
|
+
if is_empty:
|
|
58
|
+
return liquid_id
|
|
56
59
|
has_liquid = liquid_id in self._state.liquids_by_id
|
|
57
60
|
if not has_liquid:
|
|
58
61
|
raise LiquidDoesNotExistError(
|
|
59
62
|
f"Supplied liquidId: {liquid_id} does not exist in the loaded liquids."
|
|
60
63
|
)
|
|
61
64
|
return liquid_id
|
|
65
|
+
|
|
66
|
+
def validate_liquid_allowed(self, liquid: Liquid) -> Liquid:
|
|
67
|
+
"""Validate that a liquid is legal to load."""
|
|
68
|
+
is_empty = liquid.id == "EMPTY"
|
|
69
|
+
if is_empty:
|
|
70
|
+
raise InvalidLiquidError(
|
|
71
|
+
message='Protocols may not define a liquid with the special id "EMPTY".'
|
|
72
|
+
)
|
|
73
|
+
return liquid
|
|
@@ -35,6 +35,7 @@ from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate
|
|
|
35
35
|
AbsorbanceReaderMeasureMode,
|
|
36
36
|
)
|
|
37
37
|
from opentrons.types import DeckSlotName, MountType, StagingSlotName
|
|
38
|
+
from .update_types import AbsorbanceReaderStateUpdate
|
|
38
39
|
from ..errors import ModuleNotConnectedError
|
|
39
40
|
|
|
40
41
|
from ..types import (
|
|
@@ -63,7 +64,6 @@ from ..commands import (
|
|
|
63
64
|
heater_shaker,
|
|
64
65
|
temperature_module,
|
|
65
66
|
thermocycler,
|
|
66
|
-
absorbance_reader,
|
|
67
67
|
)
|
|
68
68
|
from ..actions import (
|
|
69
69
|
Action,
|
|
@@ -296,40 +296,10 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
|
|
|
296
296
|
):
|
|
297
297
|
self._handle_thermocycler_module_commands(command)
|
|
298
298
|
|
|
299
|
-
if isinstance(
|
|
300
|
-
command.result,
|
|
301
|
-
(
|
|
302
|
-
absorbance_reader.InitializeResult,
|
|
303
|
-
absorbance_reader.ReadAbsorbanceResult,
|
|
304
|
-
),
|
|
305
|
-
):
|
|
306
|
-
self._handle_absorbance_reader_commands(command)
|
|
307
|
-
|
|
308
299
|
def _handle_state_update(self, state_update: update_types.StateUpdate) -> None:
|
|
309
|
-
if state_update.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
# Get current values:
|
|
314
|
-
absorbance_reader_substate = self._state.substate_by_module_id[module_id]
|
|
315
|
-
assert isinstance(
|
|
316
|
-
absorbance_reader_substate, AbsorbanceReaderSubState
|
|
317
|
-
), f"{module_id} is not an absorbance plate reader."
|
|
318
|
-
configured = absorbance_reader_substate.configured
|
|
319
|
-
measure_mode = absorbance_reader_substate.measure_mode
|
|
320
|
-
configured_wavelengths = absorbance_reader_substate.configured_wavelengths
|
|
321
|
-
reference_wavelength = absorbance_reader_substate.reference_wavelength
|
|
322
|
-
data = absorbance_reader_substate.data
|
|
323
|
-
|
|
324
|
-
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
|
|
325
|
-
module_id=AbsorbanceReaderId(module_id),
|
|
326
|
-
configured=configured,
|
|
327
|
-
measured=True,
|
|
328
|
-
is_lid_on=is_lid_on,
|
|
329
|
-
measure_mode=measure_mode,
|
|
330
|
-
configured_wavelengths=configured_wavelengths,
|
|
331
|
-
reference_wavelength=reference_wavelength,
|
|
332
|
-
data=data,
|
|
300
|
+
if state_update.absorbance_reader_state_update != update_types.NO_CHANGE:
|
|
301
|
+
self._handle_absorbance_reader_commands(
|
|
302
|
+
state_update.absorbance_reader_state_update
|
|
333
303
|
)
|
|
334
304
|
|
|
335
305
|
def _add_module_substate(
|
|
@@ -589,50 +559,61 @@ class ModuleStore(HasState[ModuleState], HandlesActions):
|
|
|
589
559
|
)
|
|
590
560
|
|
|
591
561
|
def _handle_absorbance_reader_commands(
|
|
592
|
-
self,
|
|
593
|
-
command: Union[
|
|
594
|
-
absorbance_reader.Initialize,
|
|
595
|
-
absorbance_reader.ReadAbsorbance,
|
|
596
|
-
],
|
|
562
|
+
self, absorbance_reader_state_update: AbsorbanceReaderStateUpdate
|
|
597
563
|
) -> None:
|
|
598
|
-
|
|
564
|
+
# Get current values:
|
|
565
|
+
module_id = absorbance_reader_state_update.module_id
|
|
599
566
|
absorbance_reader_substate = self._state.substate_by_module_id[module_id]
|
|
600
567
|
assert isinstance(
|
|
601
568
|
absorbance_reader_substate, AbsorbanceReaderSubState
|
|
602
569
|
), f"{module_id} is not an absorbance plate reader."
|
|
603
|
-
|
|
604
|
-
|
|
570
|
+
is_lid_on = absorbance_reader_substate.is_lid_on
|
|
571
|
+
measured = True
|
|
605
572
|
configured = absorbance_reader_substate.configured
|
|
606
573
|
measure_mode = absorbance_reader_substate.measure_mode
|
|
607
574
|
configured_wavelengths = absorbance_reader_substate.configured_wavelengths
|
|
608
575
|
reference_wavelength = absorbance_reader_substate.reference_wavelength
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
576
|
+
data = absorbance_reader_substate.data
|
|
577
|
+
if (
|
|
578
|
+
absorbance_reader_state_update.absorbance_reader_lid
|
|
579
|
+
!= update_types.NO_CHANGE
|
|
580
|
+
):
|
|
581
|
+
is_lid_on = absorbance_reader_state_update.absorbance_reader_lid.is_lid_on
|
|
582
|
+
elif (
|
|
583
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update
|
|
584
|
+
!= update_types.NO_CHANGE
|
|
585
|
+
):
|
|
586
|
+
configured = True
|
|
587
|
+
measured = False
|
|
588
|
+
is_lid_on = is_lid_on
|
|
589
|
+
measure_mode = AbsorbanceReaderMeasureMode(
|
|
590
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update.measure_mode
|
|
621
591
|
)
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
is_lid_on=is_lid_on,
|
|
628
|
-
measure_mode=measure_mode,
|
|
629
|
-
configured_wavelengths=configured_wavelengths,
|
|
630
|
-
reference_wavelength=reference_wavelength,
|
|
631
|
-
data=command.result.data,
|
|
592
|
+
configured_wavelengths = (
|
|
593
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update.sample_wave_lengths
|
|
594
|
+
)
|
|
595
|
+
reference_wavelength = (
|
|
596
|
+
absorbance_reader_state_update.initialize_absorbance_reader_update.reference_wave_length
|
|
632
597
|
)
|
|
598
|
+
data = None
|
|
599
|
+
elif (
|
|
600
|
+
absorbance_reader_state_update.absorbance_reader_data
|
|
601
|
+
!= update_types.NO_CHANGE
|
|
602
|
+
):
|
|
603
|
+
data = absorbance_reader_state_update.absorbance_reader_data.read_result
|
|
604
|
+
self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState(
|
|
605
|
+
module_id=AbsorbanceReaderId(module_id),
|
|
606
|
+
configured=configured,
|
|
607
|
+
measured=measured,
|
|
608
|
+
is_lid_on=is_lid_on,
|
|
609
|
+
measure_mode=measure_mode,
|
|
610
|
+
configured_wavelengths=configured_wavelengths,
|
|
611
|
+
reference_wavelength=reference_wavelength,
|
|
612
|
+
data=data,
|
|
613
|
+
)
|
|
633
614
|
|
|
634
615
|
|
|
635
|
-
class ModuleView
|
|
616
|
+
class ModuleView:
|
|
636
617
|
"""Read-only view of computed module state."""
|
|
637
618
|
|
|
638
619
|
_state: ModuleState
|
|
@@ -654,7 +635,7 @@ class ModuleView(HasState[ModuleState]):
|
|
|
654
635
|
DeckSlotLocation(slotName=slot_name) if slot_name is not None else None
|
|
655
636
|
)
|
|
656
637
|
|
|
657
|
-
return LoadedModule.
|
|
638
|
+
return LoadedModule.model_construct(
|
|
658
639
|
id=module_id,
|
|
659
640
|
location=location,
|
|
660
641
|
model=attached_module.definition.model,
|
|
@@ -860,8 +841,8 @@ class ModuleView(HasState[ModuleState]):
|
|
|
860
841
|
Labware Position Check offset.
|
|
861
842
|
"""
|
|
862
843
|
if (
|
|
863
|
-
self.
|
|
864
|
-
or self.
|
|
844
|
+
self._state.deck_type == DeckType.OT2_STANDARD
|
|
845
|
+
or self._state.deck_type == DeckType.OT2_SHORT_TRASH
|
|
865
846
|
):
|
|
866
847
|
definition = self.get_definition(module_id)
|
|
867
848
|
slot = self.get_location(module_id).slotName.id
|
|
@@ -908,7 +889,7 @@ class ModuleView(HasState[ModuleState]):
|
|
|
908
889
|
"Module location invalid for nominal module offset calculation."
|
|
909
890
|
)
|
|
910
891
|
module_addressable_area = self.ensure_and_convert_module_fixture_location(
|
|
911
|
-
location,
|
|
892
|
+
location, module.model
|
|
912
893
|
)
|
|
913
894
|
module_addressable_area_position = (
|
|
914
895
|
addressable_areas.get_addressable_area_offsets_from_cutout(
|
|
@@ -1281,13 +1262,14 @@ class ModuleView(HasState[ModuleState]):
|
|
|
1281
1262
|
def ensure_and_convert_module_fixture_location(
|
|
1282
1263
|
self,
|
|
1283
1264
|
deck_slot: DeckSlotName,
|
|
1284
|
-
deck_type: DeckType,
|
|
1285
1265
|
model: ModuleModel,
|
|
1286
1266
|
) -> str:
|
|
1287
1267
|
"""Ensure module fixture load location is valid.
|
|
1288
1268
|
|
|
1289
1269
|
Also, convert the deck slot to a valid module fixture addressable area.
|
|
1290
1270
|
"""
|
|
1271
|
+
deck_type = self._state.deck_type
|
|
1272
|
+
|
|
1291
1273
|
if deck_type == DeckType.OT2_STANDARD or deck_type == DeckType.OT2_SHORT_TRASH:
|
|
1292
1274
|
raise ValueError(
|
|
1293
1275
|
f"Invalid Deck Type: {deck_type.name} - Does not support modules as fixtures."
|
|
@@ -327,6 +327,7 @@ class MotionView:
|
|
|
327
327
|
labware_id: str,
|
|
328
328
|
well_name: str,
|
|
329
329
|
center_point: Point,
|
|
330
|
+
mm_from_edge: float = 0,
|
|
330
331
|
radius: float = 1.0,
|
|
331
332
|
) -> List[motion_planning.Waypoint]:
|
|
332
333
|
"""Get a list of touch points for a touch tip operation."""
|
|
@@ -346,7 +347,11 @@ class MotionView:
|
|
|
346
347
|
)
|
|
347
348
|
|
|
348
349
|
positions = _move_types.get_edge_point_list(
|
|
349
|
-
center_point,
|
|
350
|
+
center=center_point,
|
|
351
|
+
x_radius=x_offset,
|
|
352
|
+
y_radius=y_offset,
|
|
353
|
+
mm_from_edge=mm_from_edge,
|
|
354
|
+
edge_path_type=edge_path_type,
|
|
350
355
|
)
|
|
351
356
|
critical_point: Optional[CriticalPoint] = None
|
|
352
357
|
|
|
@@ -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,8 @@ 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
|
|
101
108
|
available_sensors: pipette_definition.AvailableSensorDefinition
|
|
102
109
|
|
|
103
110
|
|
|
@@ -109,7 +116,7 @@ class PipetteState:
|
|
|
109
116
|
# attributes are populated at the appropriate times. Refactor to a
|
|
110
117
|
# single dict-of-many-things instead of many dicts-of-single-things.
|
|
111
118
|
pipettes_by_id: Dict[str, LoadedPipette]
|
|
112
|
-
|
|
119
|
+
pipette_contents_by_id: Dict[str, Optional[fluid_stack.FluidStack]]
|
|
113
120
|
current_location: Optional[CurrentPipetteLocation]
|
|
114
121
|
current_deck_point: CurrentDeckPoint
|
|
115
122
|
attached_tip_by_id: Dict[str, Optional[TipGeometry]]
|
|
@@ -129,7 +136,7 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
129
136
|
"""Initialize a PipetteStore and its state."""
|
|
130
137
|
self._state = PipetteState(
|
|
131
138
|
pipettes_by_id={},
|
|
132
|
-
|
|
139
|
+
pipette_contents_by_id={},
|
|
133
140
|
attached_tip_by_id={},
|
|
134
141
|
current_location=None,
|
|
135
142
|
current_deck_point=CurrentDeckPoint(mount=None, deck_point=None),
|
|
@@ -148,11 +155,9 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
148
155
|
self._update_pipette_config(state_update)
|
|
149
156
|
self._update_pipette_nozzle_map(state_update)
|
|
150
157
|
self._update_tip_state(state_update)
|
|
158
|
+
self._update_volumes(state_update)
|
|
151
159
|
|
|
152
|
-
if isinstance(action,
|
|
153
|
-
self._update_volumes(action)
|
|
154
|
-
|
|
155
|
-
elif isinstance(action, SetPipetteMovementSpeedAction):
|
|
160
|
+
if isinstance(action, SetPipetteMovementSpeedAction):
|
|
156
161
|
self._state.movement_speed_by_id[action.pipette_id] = action.speed
|
|
157
162
|
|
|
158
163
|
def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
|
|
@@ -167,7 +172,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
167
172
|
self._state.liquid_presence_detection_by_id[pipette_id] = (
|
|
168
173
|
state_update.loaded_pipette.liquid_presence_detection or False
|
|
169
174
|
)
|
|
170
|
-
self._state.aspirated_volume_by_id[pipette_id] = None
|
|
171
175
|
self._state.movement_speed_by_id[pipette_id] = None
|
|
172
176
|
self._state.attached_tip_by_id[pipette_id] = None
|
|
173
177
|
|
|
@@ -178,7 +182,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
178
182
|
attached_tip = state_update.pipette_tip_state.tip_geometry
|
|
179
183
|
|
|
180
184
|
self._state.attached_tip_by_id[pipette_id] = attached_tip
|
|
181
|
-
self._state.aspirated_volume_by_id[pipette_id] = 0
|
|
182
185
|
|
|
183
186
|
static_config = self._state.static_config_by_id.get(pipette_id)
|
|
184
187
|
if static_config:
|
|
@@ -205,7 +208,6 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
205
208
|
|
|
206
209
|
else:
|
|
207
210
|
pipette_id = state_update.pipette_tip_state.pipette_id
|
|
208
|
-
self._state.aspirated_volume_by_id[pipette_id] = None
|
|
209
211
|
self._state.attached_tip_by_id[pipette_id] = None
|
|
210
212
|
|
|
211
213
|
static_config = self._state.static_config_by_id.get(pipette_id)
|
|
@@ -293,6 +295,8 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
293
295
|
),
|
|
294
296
|
default_nozzle_map=config.nozzle_map,
|
|
295
297
|
lld_settings=config.pipette_lld_settings,
|
|
298
|
+
plunger_positions=config.plunger_positions,
|
|
299
|
+
shaft_ul_per_mm=config.shaft_ul_per_mm,
|
|
296
300
|
available_sensors=config.available_sensors,
|
|
297
301
|
)
|
|
298
302
|
self._state.flow_rates_by_id[
|
|
@@ -310,54 +314,43 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
310
314
|
state_update.pipette_nozzle_map.pipette_id
|
|
311
315
|
] = state_update.pipette_nozzle_map.nozzle_map
|
|
312
316
|
|
|
313
|
-
def _update_volumes(
|
|
314
|
-
|
|
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
|
|
315
333
|
) -> None:
|
|
316
|
-
|
|
317
|
-
# https://opentrons.atlassian.net/browse/EXEC-754
|
|
334
|
+
self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid)
|
|
318
335
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
(commands.AspirateResult, commands.AspirateInPlaceResult),
|
|
322
|
-
):
|
|
323
|
-
pipette_id = action.command.params.pipetteId
|
|
324
|
-
previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0
|
|
325
|
-
# PipetteHandler will have clamped action.command.result.volume for us, so
|
|
326
|
-
# next_volume should always be in bounds.
|
|
327
|
-
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)
|
|
328
338
|
|
|
329
|
-
|
|
339
|
+
def _update_empty(self, update: update_types.PipetteEmptyFluidUpdate) -> None:
|
|
340
|
+
self._state.pipette_contents_by_id[update.pipette_id] = fluid_stack.FluidStack()
|
|
330
341
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
(commands.DispenseResult, commands.DispenseInPlaceResult),
|
|
334
|
-
):
|
|
335
|
-
pipette_id = action.command.params.pipetteId
|
|
336
|
-
previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0
|
|
337
|
-
# PipetteHandler will have clamped action.command.result.volume for us, so
|
|
338
|
-
# next_volume should always be in bounds.
|
|
339
|
-
next_volume = previous_volume - action.command.result.volume
|
|
340
|
-
self._state.aspirated_volume_by_id[pipette_id] = next_volume
|
|
341
|
-
|
|
342
|
-
elif isinstance(action, SucceedCommandAction) and isinstance(
|
|
343
|
-
action.command.result,
|
|
344
|
-
(
|
|
345
|
-
commands.BlowOutResult,
|
|
346
|
-
commands.BlowOutInPlaceResult,
|
|
347
|
-
commands.unsafe.UnsafeBlowOutInPlaceResult,
|
|
348
|
-
),
|
|
349
|
-
):
|
|
350
|
-
pipette_id = action.command.params.pipetteId
|
|
351
|
-
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
|
|
352
344
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
358
351
|
|
|
359
352
|
|
|
360
|
-
class PipetteView
|
|
353
|
+
class PipetteView:
|
|
361
354
|
"""Read-only view of computed pipettes state."""
|
|
362
355
|
|
|
363
356
|
_state: PipetteState
|
|
@@ -459,6 +452,10 @@ class PipetteView(HasState[PipetteState]):
|
|
|
459
452
|
def get_aspirated_volume(self, pipette_id: str) -> Optional[float]:
|
|
460
453
|
"""Get the currently aspirated volume of a pipette by ID.
|
|
461
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
|
+
|
|
462
459
|
Returns:
|
|
463
460
|
The volume the pipette has aspirated.
|
|
464
461
|
None, after blow-out and the plunger is in an unsafe position.
|
|
@@ -470,13 +467,50 @@ class PipetteView(HasState[PipetteState]):
|
|
|
470
467
|
self.validate_tip_state(pipette_id, True)
|
|
471
468
|
|
|
472
469
|
try:
|
|
473
|
-
|
|
470
|
+
stack = self._state.pipette_contents_by_id[pipette_id]
|
|
471
|
+
if stack is None:
|
|
472
|
+
return None
|
|
473
|
+
return stack.aspirated_volume()
|
|
474
474
|
|
|
475
475
|
except KeyError as e:
|
|
476
476
|
raise errors.PipetteNotLoadedError(
|
|
477
477
|
f"Pipette {pipette_id} not found; unable to get current volume."
|
|
478
478
|
) from e
|
|
479
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
|
+
|
|
480
514
|
def get_working_volume(self, pipette_id: str) -> float:
|
|
481
515
|
"""Get the working maximum volume of a pipette by ID.
|
|
482
516
|
|
|
@@ -643,6 +677,10 @@ class PipetteView(HasState[PipetteState]):
|
|
|
643
677
|
nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
|
|
644
678
|
return nozzle_map.starting_nozzle
|
|
645
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
|
+
|
|
646
684
|
def _get_critical_point_offset_without_tip(
|
|
647
685
|
self, pipette_id: str, critical_point: Optional[CriticalPoint]
|
|
648
686
|
) -> Point:
|
|
@@ -740,3 +778,42 @@ class PipetteView(HasState[PipetteState]):
|
|
|
740
778
|
raise errors.PipetteNotLoadedError(
|
|
741
779
|
f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled."
|
|
742
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]
|