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.
Files changed (192) hide show
  1. opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
  2. opentrons/calibration_storage/ot2/tip_length.py +6 -6
  3. opentrons/config/advanced_settings.py +9 -11
  4. opentrons/config/feature_flags.py +0 -4
  5. opentrons/config/reset.py +7 -2
  6. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  7. opentrons/drivers/asyncio/communication/async_serial.py +4 -0
  8. opentrons/drivers/asyncio/communication/errors.py +41 -8
  9. opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
  10. opentrons/drivers/flex_stacker/__init__.py +9 -3
  11. opentrons/drivers/flex_stacker/abstract.py +140 -15
  12. opentrons/drivers/flex_stacker/driver.py +593 -47
  13. opentrons/drivers/flex_stacker/errors.py +64 -0
  14. opentrons/drivers/flex_stacker/simulator.py +222 -24
  15. opentrons/drivers/flex_stacker/types.py +211 -15
  16. opentrons/drivers/flex_stacker/utils.py +19 -0
  17. opentrons/execute.py +4 -2
  18. opentrons/hardware_control/api.py +5 -0
  19. opentrons/hardware_control/backends/flex_protocol.py +4 -0
  20. opentrons/hardware_control/backends/ot3controller.py +12 -1
  21. opentrons/hardware_control/backends/ot3simulator.py +3 -0
  22. opentrons/hardware_control/backends/subsystem_manager.py +8 -4
  23. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
  24. opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
  25. opentrons/hardware_control/modules/__init__.py +12 -1
  26. opentrons/hardware_control/modules/absorbance_reader.py +11 -9
  27. opentrons/hardware_control/modules/flex_stacker.py +498 -0
  28. opentrons/hardware_control/modules/heater_shaker.py +12 -10
  29. opentrons/hardware_control/modules/magdeck.py +5 -1
  30. opentrons/hardware_control/modules/tempdeck.py +5 -1
  31. opentrons/hardware_control/modules/thermocycler.py +15 -14
  32. opentrons/hardware_control/modules/types.py +191 -1
  33. opentrons/hardware_control/modules/utils.py +3 -0
  34. opentrons/hardware_control/motion_utilities.py +20 -0
  35. opentrons/hardware_control/ot3api.py +145 -15
  36. opentrons/hardware_control/protocols/liquid_handler.py +47 -1
  37. opentrons/hardware_control/types.py +6 -0
  38. opentrons/legacy_commands/commands.py +19 -3
  39. opentrons/legacy_commands/helpers.py +15 -0
  40. opentrons/legacy_commands/types.py +3 -2
  41. opentrons/protocol_api/__init__.py +2 -0
  42. opentrons/protocol_api/_liquid.py +39 -8
  43. opentrons/protocol_api/_liquid_properties.py +20 -19
  44. opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
  45. opentrons/protocol_api/core/common.py +3 -1
  46. opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
  47. opentrons/protocol_api/core/engine/instrument.py +1233 -65
  48. opentrons/protocol_api/core/engine/labware.py +8 -4
  49. opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
  50. opentrons/protocol_api/core/engine/module_core.py +118 -2
  51. opentrons/protocol_api/core/engine/protocol.py +253 -11
  52. opentrons/protocol_api/core/engine/stringify.py +19 -8
  53. opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
  54. opentrons/protocol_api/core/engine/well.py +60 -5
  55. opentrons/protocol_api/core/instrument.py +65 -19
  56. opentrons/protocol_api/core/labware.py +6 -2
  57. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  58. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
  59. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  60. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  61. opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
  62. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  63. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  64. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
  66. opentrons/protocol_api/core/module.py +43 -0
  67. opentrons/protocol_api/core/protocol.py +33 -0
  68. opentrons/protocol_api/core/well.py +21 -1
  69. opentrons/protocol_api/instrument_context.py +246 -123
  70. opentrons/protocol_api/labware.py +75 -11
  71. opentrons/protocol_api/module_contexts.py +140 -0
  72. opentrons/protocol_api/protocol_context.py +156 -16
  73. opentrons/protocol_api/validation.py +51 -41
  74. opentrons/protocol_engine/__init__.py +21 -2
  75. opentrons/protocol_engine/actions/actions.py +5 -5
  76. opentrons/protocol_engine/clients/sync_client.py +6 -0
  77. opentrons/protocol_engine/commands/__init__.py +30 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  79. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  80. opentrons/protocol_engine/commands/aspirate.py +6 -2
  81. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  82. opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
  83. opentrons/protocol_engine/commands/blow_out.py +2 -0
  84. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  85. opentrons/protocol_engine/commands/command_unions.py +69 -0
  86. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  87. opentrons/protocol_engine/commands/dispense.py +3 -1
  88. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  89. opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
  90. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  91. opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
  92. opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
  93. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
  94. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  95. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  96. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  97. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  98. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  99. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  100. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  101. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  102. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  103. opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
  104. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  105. opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
  106. opentrons/protocol_engine/commands/liquid_probe.py +21 -12
  107. opentrons/protocol_engine/commands/load_labware.py +42 -39
  108. opentrons/protocol_engine/commands/load_lid.py +21 -13
  109. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  110. opentrons/protocol_engine/commands/load_module.py +18 -17
  111. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  112. opentrons/protocol_engine/commands/move_labware.py +139 -20
  113. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  114. opentrons/protocol_engine/commands/pipetting_common.py +154 -7
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  118. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  119. opentrons/protocol_engine/errors/__init__.py +8 -0
  120. opentrons/protocol_engine/errors/exceptions.py +50 -0
  121. opentrons/protocol_engine/execution/equipment.py +123 -106
  122. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  123. opentrons/protocol_engine/execution/pipetting.py +233 -26
  124. opentrons/protocol_engine/execution/tip_handler.py +14 -5
  125. opentrons/protocol_engine/labware_offset_standardization.py +173 -0
  126. opentrons/protocol_engine/protocol_engine.py +22 -13
  127. opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
  128. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  129. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  130. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  131. opentrons/protocol_engine/slot_standardization.py +11 -23
  132. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  133. opentrons/protocol_engine/state/frustum_helpers.py +26 -10
  134. opentrons/protocol_engine/state/geometry.py +683 -100
  135. opentrons/protocol_engine/state/labware.py +252 -55
  136. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  137. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  138. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  139. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  140. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  141. opentrons/protocol_engine/state/modules.py +178 -52
  142. opentrons/protocol_engine/state/pipettes.py +54 -0
  143. opentrons/protocol_engine/state/state.py +1 -1
  144. opentrons/protocol_engine/state/tips.py +14 -0
  145. opentrons/protocol_engine/state/update_types.py +180 -25
  146. opentrons/protocol_engine/state/wells.py +54 -8
  147. opentrons/protocol_engine/types/__init__.py +292 -0
  148. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  149. opentrons/protocol_engine/types/command_annotations.py +53 -0
  150. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  151. opentrons/protocol_engine/types/execution.py +96 -0
  152. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  153. opentrons/protocol_engine/types/instrument.py +47 -0
  154. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  155. opentrons/protocol_engine/types/labware.py +110 -0
  156. opentrons/protocol_engine/types/labware_movement.py +22 -0
  157. opentrons/protocol_engine/types/labware_offset_location.py +108 -0
  158. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  159. opentrons/protocol_engine/types/liquid.py +40 -0
  160. opentrons/protocol_engine/types/liquid_class.py +59 -0
  161. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  162. opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
  163. opentrons/protocol_engine/types/location.py +193 -0
  164. opentrons/protocol_engine/types/module.py +269 -0
  165. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  166. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  167. opentrons/protocol_engine/types/tip.py +18 -0
  168. opentrons/protocol_engine/types/util.py +21 -0
  169. opentrons/protocol_engine/types/well_position.py +107 -0
  170. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  171. opentrons/protocol_reader/file_format_validator.py +5 -3
  172. opentrons/protocol_runner/json_translator.py +4 -2
  173. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  174. opentrons/protocol_runner/run_orchestrator.py +4 -1
  175. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  176. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  177. opentrons/protocols/api_support/definitions.py +1 -1
  178. opentrons/protocols/api_support/instrument.py +16 -3
  179. opentrons/protocols/labware.py +5 -6
  180. opentrons/protocols/models/__init__.py +0 -21
  181. opentrons/simulate.py +4 -2
  182. opentrons/types.py +15 -6
  183. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
  184. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
  185. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  186. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  187. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  188. opentrons/protocol_engine/types.py +0 -1311
  189. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
  190. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
  191. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
  192. {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]