opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.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/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 +19 -3
- opentrons/legacy_commands/helpers.py +15 -0
- opentrons/legacy_commands/types.py +3 -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 +1233 -65
- 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/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
- opentrons/protocol_api/core/engine/well.py +60 -5
- opentrons/protocol_api/core/instrument.py +65 -19
- 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 +69 -21
- 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 +25 -1
- 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 +67 -21
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +21 -1
- opentrons/protocol_api/instrument_context.py +246 -123
- opentrons/protocol_api/labware.py +75 -11
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +156 -16
- 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 +30 -0
- 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 +237 -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 +69 -0
- 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 +240 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
- 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 +288 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
- opentrons/protocol_engine/commands/liquid_probe.py +21 -12
- 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/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +154 -7
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- 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/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +233 -26
- opentrons/protocol_engine/execution/tip_handler.py +14 -5
- opentrons/protocol_engine/labware_offset_standardization.py +173 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -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 +26 -10
- opentrons/protocol_engine/state/geometry.py +683 -100
- 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 +178 -52
- 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 +54 -8
- opentrons/protocol_engine/types/__init__.py +292 -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 +110 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +108 -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 +137 -0
- opentrons/protocol_engine/types/location.py +193 -0
- opentrons/protocol_engine/types/module.py +269 -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 +107 -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 +5 -6
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +15 -6
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
- 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.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Protocol engine types to do with labware."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from .location import LabwareLocation
|
|
12
|
+
from .labware_offset_location import (
|
|
13
|
+
LegacyLabwareOffsetLocation,
|
|
14
|
+
LabwareOffsetLocationSequence,
|
|
15
|
+
)
|
|
16
|
+
from .labware_offset_vector import LabwareOffsetVector
|
|
17
|
+
from .util import Vec3f
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OverlapOffset(Vec3f):
|
|
21
|
+
"""Offset representing overlap space of one labware on top of another labware or module."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LabwareOffset(BaseModel):
|
|
25
|
+
"""An offset that the robot adds to a pipette's position when it moves to a labware.
|
|
26
|
+
|
|
27
|
+
During the run, if a labware is loaded whose definition URI and location
|
|
28
|
+
both match what's found here, the given offset will be added to all
|
|
29
|
+
pipette movements that use that labware as a reference point.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
id: str = Field(..., description="Unique labware offset record identifier.")
|
|
33
|
+
createdAt: datetime = Field(..., description="When this labware offset was added.")
|
|
34
|
+
definitionUri: str = Field(..., description="The URI for the labware's definition.")
|
|
35
|
+
location: LegacyLabwareOffsetLocation = Field(
|
|
36
|
+
...,
|
|
37
|
+
description="Where the labware is located on the robot. Deprecated and present only for backwards compatibility; cannot represent certain locations. Use locationSequence instead.",
|
|
38
|
+
)
|
|
39
|
+
locationSequence: Optional[LabwareOffsetLocationSequence] = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Where the labware is located on the robot. Can represent all locations, but may not be present for older runs.",
|
|
42
|
+
)
|
|
43
|
+
vector: LabwareOffsetVector = Field(
|
|
44
|
+
...,
|
|
45
|
+
description="The offset applied to matching labware.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LegacyLabwareOffsetCreate(BaseModel):
|
|
50
|
+
"""Create request data for a labware offset with a legacy location field."""
|
|
51
|
+
|
|
52
|
+
definitionUri: str = Field(..., description="The URI for the labware's definition.")
|
|
53
|
+
location: LegacyLabwareOffsetLocation = Field(
|
|
54
|
+
...,
|
|
55
|
+
description="Where the labware is located on the robot.",
|
|
56
|
+
)
|
|
57
|
+
vector: LabwareOffsetVector = Field(
|
|
58
|
+
...,
|
|
59
|
+
description="The offset applied to matching labware.",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class LabwareOffsetCreate(BaseModel):
|
|
64
|
+
"""Create request data for a labware offset with a modern location sequence."""
|
|
65
|
+
|
|
66
|
+
definitionUri: str = Field(..., description="The URI for the labware's definition.")
|
|
67
|
+
locationSequence: LabwareOffsetLocationSequence = Field(
|
|
68
|
+
..., description="Where the labware is located on the robot."
|
|
69
|
+
)
|
|
70
|
+
vector: LabwareOffsetVector = Field(
|
|
71
|
+
..., description="The offset applied to matching labware."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class LabwareOffsetCreateInternal:
|
|
77
|
+
"""An internal-only labware offset creator that captures both old and new location arguments."""
|
|
78
|
+
|
|
79
|
+
definitionUri: str
|
|
80
|
+
locationSequence: LabwareOffsetLocationSequence
|
|
81
|
+
legacyLocation: LegacyLabwareOffsetLocation
|
|
82
|
+
vector: LabwareOffsetVector
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class LoadedLabware(BaseModel):
|
|
86
|
+
"""A labware that has been loaded."""
|
|
87
|
+
|
|
88
|
+
id: str
|
|
89
|
+
loadName: str
|
|
90
|
+
definitionUri: str
|
|
91
|
+
location: LabwareLocation = Field(
|
|
92
|
+
..., description="The labware's current location."
|
|
93
|
+
)
|
|
94
|
+
lid_id: Optional[str] = Field(
|
|
95
|
+
None,
|
|
96
|
+
description=("Labware ID of a Lid currently loaded on top of the labware."),
|
|
97
|
+
)
|
|
98
|
+
offsetId: Optional[str] = Field(
|
|
99
|
+
None,
|
|
100
|
+
description=(
|
|
101
|
+
"An ID referencing the labware offset"
|
|
102
|
+
" that applies to this labware placement."
|
|
103
|
+
" Null or undefined means no offset was provided for this load,"
|
|
104
|
+
" so the default of (0, 0, 0) will be used."
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
displayName: Optional[str] = Field(
|
|
108
|
+
None,
|
|
109
|
+
description="A user-specified display name for this labware, if provided.",
|
|
110
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Protocol Engine types to do with moving labware."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from .labware_offset_vector import LabwareOffsetVector
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LabwareMovementStrategy(str, Enum):
|
|
11
|
+
"""Strategy to use for labware movement."""
|
|
12
|
+
|
|
13
|
+
USING_GRIPPER = "usingGripper"
|
|
14
|
+
MANUAL_MOVE_WITH_PAUSE = "manualMoveWithPause"
|
|
15
|
+
MANUAL_MOVE_WITHOUT_PAUSE = "manualMoveWithoutPause"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LabwareMovementOffsetData(BaseModel):
|
|
19
|
+
"""Offsets to be used during labware movement."""
|
|
20
|
+
|
|
21
|
+
pickUpOffset: LabwareOffsetVector
|
|
22
|
+
dropOffset: LabwareOffsetVector
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Protocol engine types for legacy labware offset locations.
|
|
2
|
+
|
|
3
|
+
This is its own module to fix circular imports.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional, Literal, Annotated
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from opentrons.types import DeckSlotName
|
|
11
|
+
|
|
12
|
+
from .module import ModuleModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OnLabwareOffsetLocationSequenceComponent(BaseModel):
|
|
16
|
+
"""Offset location sequence component for a labware on another labware."""
|
|
17
|
+
|
|
18
|
+
kind: Literal["onLabware"] = "onLabware"
|
|
19
|
+
labwareUri: str = Field(
|
|
20
|
+
...,
|
|
21
|
+
description="The definition URI of a labware that a labware can be loaded onto.",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OnModuleOffsetLocationSequenceComponent(BaseModel):
|
|
26
|
+
"""Offset location sequence component for a labware on a module."""
|
|
27
|
+
|
|
28
|
+
kind: Literal["onModule"] = "onModule"
|
|
29
|
+
moduleModel: ModuleModel = Field(
|
|
30
|
+
..., description="The model of a module that a labware can be loaded on to."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OnAddressableAreaOffsetLocationSequenceComponent(BaseModel):
|
|
35
|
+
"""Offset location sequence component for a labware on an addressable area."""
|
|
36
|
+
|
|
37
|
+
kind: Literal["onAddressableArea"] = "onAddressableArea"
|
|
38
|
+
addressableAreaName: str = Field(
|
|
39
|
+
...,
|
|
40
|
+
description=(
|
|
41
|
+
'The ID of an addressable area that a labware or module can be loaded onto, such as (on the OT-2) "2" '
|
|
42
|
+
'or (on the Flex) "C1". '
|
|
43
|
+
"\n\n"
|
|
44
|
+
"On the Flex, this field must be correct for the kind of entity it hosts. For instance, if the prior entity "
|
|
45
|
+
"in the location sequence is an `OnModuleOffsetLocationSequenceComponent(moduleModel=temperatureModuleV2)`, "
|
|
46
|
+
"this entity must be temperatureModuleV2NN where NN is the slot name in which the module resides. "
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
LabwareOffsetLocationSequenceComponentsUnion = (
|
|
52
|
+
OnLabwareOffsetLocationSequenceComponent
|
|
53
|
+
| OnModuleOffsetLocationSequenceComponent
|
|
54
|
+
| OnAddressableAreaOffsetLocationSequenceComponent
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
LabwareOffsetLocationSequenceComponents = Annotated[
|
|
58
|
+
LabwareOffsetLocationSequenceComponentsUnion, Field(discriminator="kind")
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
LabwareOffsetLocationSequence = list[LabwareOffsetLocationSequenceComponents]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class LegacyLabwareOffsetLocation(BaseModel):
|
|
65
|
+
"""Parameters describing when a given offset may apply to a given labware load."""
|
|
66
|
+
|
|
67
|
+
slotName: DeckSlotName = Field(
|
|
68
|
+
...,
|
|
69
|
+
description=(
|
|
70
|
+
"The deck slot where the protocol will load the labware."
|
|
71
|
+
" Or, if the protocol will load the labware on a module,"
|
|
72
|
+
" the deck slot where the protocol will load that module."
|
|
73
|
+
"\n\n"
|
|
74
|
+
# This description should be kept in sync with DeckSlotLocation.slotName.
|
|
75
|
+
'The plain numbers like `"5"` are for the OT-2,'
|
|
76
|
+
' and the coordinates like `"C2"` are for the Flex.'
|
|
77
|
+
"\n\n"
|
|
78
|
+
"When you provide one of these values, you can use either style."
|
|
79
|
+
" It will automatically be converted to match the robot."
|
|
80
|
+
"\n\n"
|
|
81
|
+
"When one of these values is returned, it will always match the robot."
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
moduleModel: Optional[ModuleModel] = Field(
|
|
85
|
+
None,
|
|
86
|
+
description=(
|
|
87
|
+
"The model of the module that the labware will be loaded onto,"
|
|
88
|
+
" if applicable."
|
|
89
|
+
"\n\n"
|
|
90
|
+
"Because of module compatibility, the model that the protocol requests"
|
|
91
|
+
" may not be exactly the same"
|
|
92
|
+
" as what it will find physically connected during execution."
|
|
93
|
+
" For this labware offset to apply,"
|
|
94
|
+
" this field must be the *requested* model, not the connected one."
|
|
95
|
+
" You can retrieve this from a `loadModule` command's `params.model`"
|
|
96
|
+
" in the protocol's analysis."
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
definitionUri: Optional[str] = Field(
|
|
100
|
+
None,
|
|
101
|
+
description=(
|
|
102
|
+
"The definition URI of a labware that a labware can be loaded onto,"
|
|
103
|
+
" if applicable."
|
|
104
|
+
"\n\n"
|
|
105
|
+
"This can be combined with moduleModel if the labware is loaded on top of"
|
|
106
|
+
" an adapter that is loaded on a module."
|
|
107
|
+
),
|
|
108
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Protocol engine types for labware offset vectors.
|
|
2
|
+
|
|
3
|
+
This is a separate module to avoid circular imports.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# TODO(mm, 2022-11-07): Deduplicate with Vec3f.
|
|
12
|
+
class LabwareOffsetVector(BaseModel):
|
|
13
|
+
"""Offset, in deck coordinates from nominal to actual position."""
|
|
14
|
+
|
|
15
|
+
x: float
|
|
16
|
+
y: float
|
|
17
|
+
z: float
|
|
18
|
+
|
|
19
|
+
def __add__(self, other: Any) -> LabwareOffsetVector:
|
|
20
|
+
"""Adds two vectors together."""
|
|
21
|
+
if not isinstance(other, LabwareOffsetVector):
|
|
22
|
+
return NotImplemented
|
|
23
|
+
return LabwareOffsetVector(
|
|
24
|
+
x=self.x + other.x, y=self.y + other.y, z=self.z + other.z
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def __sub__(self, other: Any) -> LabwareOffsetVector:
|
|
28
|
+
"""Subtracts two vectors."""
|
|
29
|
+
if not isinstance(other, LabwareOffsetVector):
|
|
30
|
+
return NotImplemented
|
|
31
|
+
return LabwareOffsetVector(
|
|
32
|
+
x=self.x - other.x, y=self.y - other.y, z=self.z - other.z
|
|
33
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Protocol engine types to do with liquids."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Literal, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import RootModel, BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HexColor(RootModel[str]):
|
|
10
|
+
"""Hex color representation."""
|
|
11
|
+
|
|
12
|
+
root: str = Field(pattern=r"^#(?:[0-9a-fA-F]{3,4}){1,2}$")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
EmptyLiquidId = Literal["EMPTY"]
|
|
16
|
+
LiquidId = str | EmptyLiquidId
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Liquid(BaseModel):
|
|
20
|
+
"""Payload required to create a liquid."""
|
|
21
|
+
|
|
22
|
+
id: str
|
|
23
|
+
displayName: str
|
|
24
|
+
description: str
|
|
25
|
+
displayColor: Optional[HexColor] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FluidKind(str, Enum):
|
|
29
|
+
"""A kind of fluid that can be inside a pipette."""
|
|
30
|
+
|
|
31
|
+
LIQUID = "LIQUID"
|
|
32
|
+
AIR = "AIR"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class AspiratedFluid:
|
|
37
|
+
"""Fluid inside a pipette."""
|
|
38
|
+
|
|
39
|
+
kind: FluidKind
|
|
40
|
+
volume: float
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Protocol engine types to do with liquid classes."""
|
|
2
|
+
from typing import Any
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from opentrons_shared_data.liquid_classes.liquid_class_definition import (
|
|
6
|
+
ByTipTypeSetting,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LiquidClassRecord(ByTipTypeSetting, frozen=True):
|
|
11
|
+
"""LiquidClassRecord is our internal representation of an (immutable) liquid class.
|
|
12
|
+
|
|
13
|
+
Conceptually, a liquid class record is the tuple (name, pipette, tip, transfer properties).
|
|
14
|
+
We consider two liquid classes to be the same if every entry in that tuple is the same; and liquid
|
|
15
|
+
classes are different if any entry in the tuple is different.
|
|
16
|
+
|
|
17
|
+
This class defines the tuple via inheritance so that we can reuse the definitions from shared_data.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
liquidClassName: str = Field(
|
|
21
|
+
...,
|
|
22
|
+
description="Identifier for the liquid of this liquid class, e.g. glycerol50.",
|
|
23
|
+
)
|
|
24
|
+
pipetteModel: str = Field(
|
|
25
|
+
...,
|
|
26
|
+
description="Identifier for the pipette of this liquid class.",
|
|
27
|
+
)
|
|
28
|
+
# The other fields like tiprack ID, aspirate properties, etc. are pulled in from ByTipTypeSetting.
|
|
29
|
+
|
|
30
|
+
def __hash__(self) -> int:
|
|
31
|
+
"""Hash function for LiquidClassRecord."""
|
|
32
|
+
# Within the Protocol Engine, LiquidClassRecords are immutable, and we'd like to be able to
|
|
33
|
+
# look up LiquidClassRecords by value, which involves hashing. However, Pydantic does not
|
|
34
|
+
# generate a usable hash function if any of the subfields (like Coordinate) are not frozen.
|
|
35
|
+
# So we have to implement the hash function ourselves.
|
|
36
|
+
# Our strategy is to recursively convert this object into a list of (key, value) tuples.
|
|
37
|
+
def dict_to_tuple(d: dict[str, Any]) -> tuple[tuple[str, Any], ...]:
|
|
38
|
+
return tuple(
|
|
39
|
+
(
|
|
40
|
+
field_name,
|
|
41
|
+
dict_to_tuple(value)
|
|
42
|
+
if isinstance(value, dict)
|
|
43
|
+
else tuple(value)
|
|
44
|
+
if isinstance(value, list)
|
|
45
|
+
else value,
|
|
46
|
+
)
|
|
47
|
+
for field_name, value in d.items()
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return hash(dict_to_tuple(self.model_dump()))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LiquidClassRecordWithId(LiquidClassRecord, frozen=True):
|
|
54
|
+
"""A LiquidClassRecord with its ID, for use in summary lists."""
|
|
55
|
+
|
|
56
|
+
liquidClassId: str = Field(
|
|
57
|
+
...,
|
|
58
|
+
description="Unique identifier for this liquid class.",
|
|
59
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Protocol engine types to do with liquid handling."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FlowRates:
|
|
9
|
+
"""Default and current flow rates for a pipette."""
|
|
10
|
+
|
|
11
|
+
default_blow_out: Dict[str, float]
|
|
12
|
+
default_aspirate: Dict[str, float]
|
|
13
|
+
default_dispense: Dict[str, float]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Protocol Engine types to do with liquid level detection."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
from pydantic import BaseModel, model_serializer, field_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SimulatedProbeResult(BaseModel):
|
|
10
|
+
"""A sentinel value to substitute for the resulting volume/height of a liquid probe during simulation."""
|
|
11
|
+
|
|
12
|
+
operations_after_probe: List[float] = []
|
|
13
|
+
net_liquid_exchanged_after_probe: float = 0.0
|
|
14
|
+
|
|
15
|
+
@model_serializer
|
|
16
|
+
def serialize_model(self) -> str:
|
|
17
|
+
"""Serialize instances of this class as a string."""
|
|
18
|
+
return "SimulatedProbeResult"
|
|
19
|
+
|
|
20
|
+
def __add__(
|
|
21
|
+
self, other: float | SimulatedProbeResult
|
|
22
|
+
) -> float | SimulatedProbeResult:
|
|
23
|
+
"""Bypass addition and just return self."""
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def __sub__(
|
|
27
|
+
self, other: float | SimulatedProbeResult
|
|
28
|
+
) -> float | SimulatedProbeResult:
|
|
29
|
+
"""Bypass subtraction and just return self."""
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def __radd__(
|
|
33
|
+
self, other: float | SimulatedProbeResult
|
|
34
|
+
) -> float | SimulatedProbeResult:
|
|
35
|
+
"""Bypass addition and just return self."""
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def __rsub__(
|
|
39
|
+
self, other: float | SimulatedProbeResult
|
|
40
|
+
) -> float | SimulatedProbeResult:
|
|
41
|
+
"""Bypass subtraction and just return self."""
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def __gt__(self, other: float | SimulatedProbeResult) -> bool:
|
|
45
|
+
"""Bypass 'greater than' and just return self."""
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
def __lt__(self, other: float | SimulatedProbeResult) -> bool:
|
|
49
|
+
"""Bypass 'less than' and just return self."""
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def __ge__(self, other: float | SimulatedProbeResult) -> bool:
|
|
53
|
+
"""Bypass 'greater than or eaqual to' and just return self."""
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
def __le__(self, other: float | SimulatedProbeResult) -> bool:
|
|
57
|
+
"""Bypass 'less than or equal to' and just return self."""
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
def __eq__(self, other: object) -> bool:
|
|
61
|
+
"""A SimulatedProbeResult should only be equal to the same instance of its class."""
|
|
62
|
+
if not isinstance(other, SimulatedProbeResult):
|
|
63
|
+
return False
|
|
64
|
+
return self is other
|
|
65
|
+
|
|
66
|
+
def __neq__(self, other: object) -> bool:
|
|
67
|
+
"""A SimulatedProbeResult should only be equal to the same instance of its class."""
|
|
68
|
+
if not isinstance(other, SimulatedProbeResult):
|
|
69
|
+
return True
|
|
70
|
+
return self is not other
|
|
71
|
+
|
|
72
|
+
def simulate_probed_aspirate_dispense(self, volume: float) -> None:
|
|
73
|
+
"""Record the current state of aspirate/dispense calls."""
|
|
74
|
+
self.net_liquid_exchanged_after_probe += volume
|
|
75
|
+
self.operations_after_probe.append(volume)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
LiquidTrackingType = SimulatedProbeResult | float
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class LoadedVolumeInfo(BaseModel):
|
|
82
|
+
"""A well's liquid volume, initialized by a LoadLiquid, updated by Aspirate and Dispense."""
|
|
83
|
+
|
|
84
|
+
volume: LiquidTrackingType | None = None
|
|
85
|
+
last_loaded: datetime
|
|
86
|
+
operations_since_load: int
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ProbedHeightInfo(BaseModel):
|
|
90
|
+
"""A well's liquid height, initialized by a LiquidProbe, cleared by Aspirate and Dispense."""
|
|
91
|
+
|
|
92
|
+
height: LiquidTrackingType | None = None
|
|
93
|
+
last_probed: datetime
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ProbedVolumeInfo(BaseModel):
|
|
97
|
+
"""A well's liquid volume, initialized by a LiquidProbe, updated by Aspirate and Dispense."""
|
|
98
|
+
|
|
99
|
+
volume: LiquidTrackingType | None = None
|
|
100
|
+
last_probed: datetime
|
|
101
|
+
operations_since_probe: int
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class WellInfoSummary(BaseModel):
|
|
105
|
+
"""Payload for a well's liquid info in StateSummary."""
|
|
106
|
+
|
|
107
|
+
# TODO(cm): 3/21/25: refactor SimulatedLiquidProbe in a way that
|
|
108
|
+
# doesn't require models like this one that are just using it to
|
|
109
|
+
# need a custom validator
|
|
110
|
+
@field_validator("probed_height", "probed_volume", mode="before")
|
|
111
|
+
@classmethod
|
|
112
|
+
def validate_simulated_probe_result(
|
|
113
|
+
cls, input_val: object
|
|
114
|
+
) -> LiquidTrackingType | None:
|
|
115
|
+
"""Return the appropriate input to WellInfoSummary from json data."""
|
|
116
|
+
if input_val is None:
|
|
117
|
+
return None
|
|
118
|
+
if isinstance(input_val, LiquidTrackingType):
|
|
119
|
+
return input_val
|
|
120
|
+
if isinstance(input_val, str) and input_val == "SimulatedProbeResult":
|
|
121
|
+
return SimulatedProbeResult()
|
|
122
|
+
raise ValueError(f"Invalid input value {input_val} to WellInfoSummary")
|
|
123
|
+
|
|
124
|
+
labware_id: str
|
|
125
|
+
well_name: str
|
|
126
|
+
loaded_volume: Optional[float] = None
|
|
127
|
+
probed_height: LiquidTrackingType | None = None
|
|
128
|
+
probed_volume: LiquidTrackingType | None = None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class WellLiquidInfo:
|
|
133
|
+
"""Tracked and sensed information about liquid in a well."""
|
|
134
|
+
|
|
135
|
+
probed_height: Optional[ProbedHeightInfo]
|
|
136
|
+
loaded_volume: Optional[LoadedVolumeInfo]
|
|
137
|
+
probed_volume: Optional[ProbedVolumeInfo]
|