opentrons 8.3.2a0__py2.py3-none-any.whl → 8.4.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (196) 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 +102 -5
  39. opentrons/legacy_commands/helpers.py +74 -1
  40. opentrons/legacy_commands/types.py +33 -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 +1356 -107
  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/pipette_movement_conflict.py +6 -14
  52. opentrons/protocol_api/core/engine/protocol.py +253 -11
  53. opentrons/protocol_api/core/engine/stringify.py +19 -8
  54. opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
  55. opentrons/protocol_api/core/engine/well.py +73 -5
  56. opentrons/protocol_api/core/instrument.py +71 -21
  57. opentrons/protocol_api/core/labware.py +6 -2
  58. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  59. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
  60. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  61. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  62. opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
  63. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  64. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  65. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
  67. opentrons/protocol_api/core/module.py +43 -0
  68. opentrons/protocol_api/core/protocol.py +33 -0
  69. opentrons/protocol_api/core/well.py +23 -2
  70. opentrons/protocol_api/instrument_context.py +454 -150
  71. opentrons/protocol_api/labware.py +98 -50
  72. opentrons/protocol_api/module_contexts.py +140 -0
  73. opentrons/protocol_api/protocol_context.py +163 -19
  74. opentrons/protocol_api/validation.py +51 -41
  75. opentrons/protocol_engine/__init__.py +21 -2
  76. opentrons/protocol_engine/actions/actions.py +5 -5
  77. opentrons/protocol_engine/clients/sync_client.py +6 -0
  78. opentrons/protocol_engine/commands/__init__.py +66 -36
  79. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  80. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  81. opentrons/protocol_engine/commands/aspirate.py +6 -2
  82. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  83. opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
  84. opentrons/protocol_engine/commands/blow_out.py +2 -0
  85. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  86. opentrons/protocol_engine/commands/command_unions.py +102 -33
  87. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  88. opentrons/protocol_engine/commands/dispense.py +3 -1
  89. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  90. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  91. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  92. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  93. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  94. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  95. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  96. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  97. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  98. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  99. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  100. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  101. opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
  102. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  103. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  104. opentrons/protocol_engine/commands/liquid_probe.py +27 -13
  105. opentrons/protocol_engine/commands/load_labware.py +42 -39
  106. opentrons/protocol_engine/commands/load_lid.py +21 -13
  107. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  108. opentrons/protocol_engine/commands/load_module.py +18 -17
  109. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  110. opentrons/protocol_engine/commands/move_labware.py +139 -20
  111. opentrons/protocol_engine/commands/move_to_well.py +5 -11
  112. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  113. opentrons/protocol_engine/commands/pipetting_common.py +159 -8
  114. opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
  115. opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
  118. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  119. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  120. opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
  121. opentrons/protocol_engine/errors/__init__.py +10 -0
  122. opentrons/protocol_engine/errors/exceptions.py +62 -0
  123. opentrons/protocol_engine/execution/equipment.py +123 -106
  124. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  125. opentrons/protocol_engine/execution/pipetting.py +235 -25
  126. opentrons/protocol_engine/execution/tip_handler.py +82 -32
  127. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  128. opentrons/protocol_engine/protocol_engine.py +22 -13
  129. opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
  130. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  131. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  132. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  133. opentrons/protocol_engine/slot_standardization.py +11 -23
  134. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  135. opentrons/protocol_engine/state/frustum_helpers.py +36 -14
  136. opentrons/protocol_engine/state/geometry.py +892 -227
  137. opentrons/protocol_engine/state/labware.py +252 -55
  138. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  139. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  140. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  141. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  142. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  143. opentrons/protocol_engine/state/modules.py +210 -67
  144. opentrons/protocol_engine/state/pipettes.py +54 -0
  145. opentrons/protocol_engine/state/state.py +1 -1
  146. opentrons/protocol_engine/state/tips.py +14 -0
  147. opentrons/protocol_engine/state/update_types.py +180 -25
  148. opentrons/protocol_engine/state/wells.py +55 -9
  149. opentrons/protocol_engine/types/__init__.py +300 -0
  150. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  151. opentrons/protocol_engine/types/command_annotations.py +53 -0
  152. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  153. opentrons/protocol_engine/types/execution.py +96 -0
  154. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  155. opentrons/protocol_engine/types/instrument.py +47 -0
  156. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  157. opentrons/protocol_engine/types/labware.py +111 -0
  158. opentrons/protocol_engine/types/labware_movement.py +22 -0
  159. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  160. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  161. opentrons/protocol_engine/types/liquid.py +40 -0
  162. opentrons/protocol_engine/types/liquid_class.py +59 -0
  163. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  164. opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
  165. opentrons/protocol_engine/types/location.py +194 -0
  166. opentrons/protocol_engine/types/module.py +301 -0
  167. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  168. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  169. opentrons/protocol_engine/types/tip.py +18 -0
  170. opentrons/protocol_engine/types/util.py +21 -0
  171. opentrons/protocol_engine/types/well_position.py +124 -0
  172. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  173. opentrons/protocol_reader/file_format_validator.py +5 -3
  174. opentrons/protocol_runner/json_translator.py +4 -2
  175. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  176. opentrons/protocol_runner/run_orchestrator.py +4 -1
  177. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/instrument.py +16 -3
  181. opentrons/protocols/labware.py +27 -23
  182. opentrons/protocols/models/__init__.py +0 -21
  183. opentrons/simulate.py +4 -2
  184. opentrons/types.py +20 -7
  185. opentrons/util/logging_config.py +94 -25
  186. opentrons/util/logging_queue_handler.py +61 -0
  187. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
  188. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
  189. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  190. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  191. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  192. opentrons/protocol_engine/types.py +0 -1311
  193. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
  194. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
  195. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
  196. {opentrons-8.3.2a0.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,111 @@
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
+ deprecated=True,
38
+ description="Where the labware is located on the robot. Deprecated and present only for backwards compatibility; cannot represent certain locations. Use locationSequence instead.",
39
+ )
40
+ locationSequence: Optional[LabwareOffsetLocationSequence] = Field(
41
+ default=None,
42
+ description="Where the labware is located on the robot. Can represent all locations, but may not be present for older runs.",
43
+ )
44
+ vector: LabwareOffsetVector = Field(
45
+ ...,
46
+ description="The offset applied to matching labware.",
47
+ )
48
+
49
+
50
+ class LegacyLabwareOffsetCreate(BaseModel):
51
+ """Create request data for a labware offset with a legacy location field."""
52
+
53
+ definitionUri: str = Field(..., description="The URI for the labware's definition.")
54
+ location: LegacyLabwareOffsetLocation = Field(
55
+ ...,
56
+ description="Where the labware is located on the robot.",
57
+ )
58
+ vector: LabwareOffsetVector = Field(
59
+ ...,
60
+ description="The offset applied to matching labware.",
61
+ )
62
+
63
+
64
+ class LabwareOffsetCreate(BaseModel):
65
+ """Create request data for a labware offset with a modern location sequence."""
66
+
67
+ definitionUri: str = Field(..., description="The URI for the labware's definition.")
68
+ locationSequence: LabwareOffsetLocationSequence = Field(
69
+ ..., description="Where the labware is located on the robot."
70
+ )
71
+ vector: LabwareOffsetVector = Field(
72
+ ..., description="The offset applied to matching labware."
73
+ )
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class LabwareOffsetCreateInternal:
78
+ """An internal-only labware offset creator that captures both old and new location arguments."""
79
+
80
+ definitionUri: str
81
+ locationSequence: LabwareOffsetLocationSequence
82
+ legacyLocation: LegacyLabwareOffsetLocation
83
+ vector: LabwareOffsetVector
84
+
85
+
86
+ class LoadedLabware(BaseModel):
87
+ """A labware that has been loaded."""
88
+
89
+ id: str
90
+ loadName: str
91
+ definitionUri: str
92
+ location: LabwareLocation = Field(
93
+ ..., description="The labware's current location."
94
+ )
95
+ lid_id: Optional[str] = Field(
96
+ None,
97
+ description=("Labware ID of a Lid currently loaded on top of the labware."),
98
+ )
99
+ offsetId: Optional[str] = Field(
100
+ None,
101
+ description=(
102
+ "An ID referencing the labware offset"
103
+ " that applies to this labware placement."
104
+ " Null or undefined means no offset was provided for this load,"
105
+ " so the default of (0, 0, 0) will be used."
106
+ ),
107
+ )
108
+ displayName: Optional[str] = Field(
109
+ None,
110
+ description="A user-specified display name for this labware, if provided.",
111
+ )
@@ -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,111 @@
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=(
22
+ "The definition URI of another labware, probably an adapter,"
23
+ " that the labware will be loaded onto."
24
+ ),
25
+ )
26
+
27
+
28
+ class OnModuleOffsetLocationSequenceComponent(BaseModel):
29
+ """Offset location sequence component for a labware on a module."""
30
+
31
+ kind: Literal["onModule"] = "onModule"
32
+ moduleModel: ModuleModel = Field(
33
+ ..., description="The model of a module that a labware can be loaded on to."
34
+ )
35
+
36
+
37
+ class OnAddressableAreaOffsetLocationSequenceComponent(BaseModel):
38
+ """Offset location sequence component for a labware on an addressable area."""
39
+
40
+ kind: Literal["onAddressableArea"] = "onAddressableArea"
41
+ addressableAreaName: str = Field(
42
+ ...,
43
+ description=(
44
+ 'The ID of an addressable area that a labware or module can be loaded onto, such as (on the OT-2) "2" '
45
+ 'or (on the Flex) "C1". '
46
+ "\n\n"
47
+ "On the Flex, this field must be correct for the kind of entity it hosts. For instance, if the prior entity "
48
+ "in the location sequence is an `OnModuleOffsetLocationSequenceComponent(moduleModel=temperatureModuleV2)`, "
49
+ "this entity must be temperatureModuleV2NN where NN is the slot name in which the module resides. "
50
+ ),
51
+ )
52
+
53
+
54
+ LabwareOffsetLocationSequenceComponentsUnion = (
55
+ OnLabwareOffsetLocationSequenceComponent
56
+ | OnModuleOffsetLocationSequenceComponent
57
+ | OnAddressableAreaOffsetLocationSequenceComponent
58
+ )
59
+
60
+ LabwareOffsetLocationSequenceComponents = Annotated[
61
+ LabwareOffsetLocationSequenceComponentsUnion, Field(discriminator="kind")
62
+ ]
63
+
64
+ LabwareOffsetLocationSequence = list[LabwareOffsetLocationSequenceComponents]
65
+
66
+
67
+ class LegacyLabwareOffsetLocation(BaseModel):
68
+ """Parameters describing when a given offset may apply to a given labware load."""
69
+
70
+ slotName: DeckSlotName = Field(
71
+ ...,
72
+ description=(
73
+ "The deck slot where the protocol will load the labware."
74
+ " Or, if the protocol will load the labware on a module,"
75
+ " the deck slot where the protocol will load that module."
76
+ "\n\n"
77
+ # This description should be kept in sync with DeckSlotLocation.slotName.
78
+ 'The plain numbers like `"5"` are for the OT-2,'
79
+ ' and the coordinates like `"C2"` are for the Flex.'
80
+ "\n\n"
81
+ "When you provide one of these values, you can use either style."
82
+ " It will automatically be converted to match the robot."
83
+ "\n\n"
84
+ "When one of these values is returned, it will always match the robot."
85
+ ),
86
+ )
87
+ moduleModel: Optional[ModuleModel] = Field(
88
+ None,
89
+ description=(
90
+ "The model of the module that the labware will be loaded onto,"
91
+ " if applicable."
92
+ "\n\n"
93
+ "Because of module compatibility, the model that the protocol requests"
94
+ " may not be exactly the same"
95
+ " as what it will find physically connected during execution."
96
+ " For this labware offset to apply,"
97
+ " this field must be the *requested* model, not the connected one."
98
+ " You can retrieve this from a `loadModule` command's `params.model`"
99
+ " in the protocol's analysis."
100
+ ),
101
+ )
102
+ definitionUri: Optional[str] = Field(
103
+ None,
104
+ description=(
105
+ "The definition URI of another labware, probably an adapter, that the"
106
+ " labware will be loaded onto, if applicable."
107
+ "\n\n"
108
+ "This can be combined with moduleModel if the labware is loaded on top of"
109
+ " an adapter that is loaded on a module."
110
+ ),
111
+ )
@@ -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,131 @@
1
+ """Protocol Engine types to do with liquid level detection."""
2
+
3
+ from __future__ import annotations
4
+ from dataclasses import dataclass
5
+ from datetime import datetime
6
+ from typing import Optional, List, Any
7
+ from pydantic import BaseModel, model_serializer, model_validator
8
+
9
+
10
+ class SimulatedProbeResult(BaseModel):
11
+ """A sentinel value to substitute for the resulting volume/height of a liquid probe during simulation."""
12
+
13
+ operations_after_probe: List[float] = []
14
+ net_liquid_exchanged_after_probe: float = 0.0
15
+
16
+ @model_serializer
17
+ def serialize_model(self) -> str:
18
+ """Serialize instances of this class as a string."""
19
+ return "SimulatedProbeResult"
20
+
21
+ @model_validator(mode="before")
22
+ @classmethod
23
+ def validate_model(cls, data: object) -> Any:
24
+ """Handle deserializing from a simulated probe result."""
25
+ if isinstance(data, str) and data == "SimulatedProbeResult":
26
+ return {}
27
+ return data
28
+
29
+ def __add__(
30
+ self, other: float | SimulatedProbeResult
31
+ ) -> float | SimulatedProbeResult:
32
+ """Bypass addition and just return self."""
33
+ return self
34
+
35
+ def __sub__(
36
+ self, other: float | SimulatedProbeResult
37
+ ) -> float | SimulatedProbeResult:
38
+ """Bypass subtraction and just return self."""
39
+ return self
40
+
41
+ def __radd__(
42
+ self, other: float | SimulatedProbeResult
43
+ ) -> float | SimulatedProbeResult:
44
+ """Bypass addition and just return self."""
45
+ return self
46
+
47
+ def __rsub__(
48
+ self, other: float | SimulatedProbeResult
49
+ ) -> float | SimulatedProbeResult:
50
+ """Bypass subtraction and just return self."""
51
+ return self
52
+
53
+ def __gt__(self, other: float | SimulatedProbeResult) -> bool:
54
+ """Bypass 'greater than' and just return self."""
55
+ return True
56
+
57
+ def __lt__(self, other: float | SimulatedProbeResult) -> bool:
58
+ """Bypass 'less than' and just return self."""
59
+ return False
60
+
61
+ def __ge__(self, other: float | SimulatedProbeResult) -> bool:
62
+ """Bypass 'greater than or eaqual to' and just return self."""
63
+ return True
64
+
65
+ def __le__(self, other: float | SimulatedProbeResult) -> bool:
66
+ """Bypass 'less than or equal to' and just return self."""
67
+ return False
68
+
69
+ def __eq__(self, other: object) -> bool:
70
+ """A SimulatedProbeResult should only be equal to the same instance of its class."""
71
+ if not isinstance(other, SimulatedProbeResult):
72
+ return False
73
+ return self is other
74
+
75
+ def __neq__(self, other: object) -> bool:
76
+ """A SimulatedProbeResult should only be equal to the same instance of its class."""
77
+ if not isinstance(other, SimulatedProbeResult):
78
+ return True
79
+ return self is not other
80
+
81
+ def simulate_probed_aspirate_dispense(self, volume: float) -> None:
82
+ """Record the current state of aspirate/dispense calls."""
83
+ self.net_liquid_exchanged_after_probe += volume
84
+ self.operations_after_probe.append(volume)
85
+
86
+
87
+ # Work around https://github.com/pydantic/pydantic/issues/6830 - do not change the order of
88
+ # this union
89
+ LiquidTrackingType = float | SimulatedProbeResult
90
+
91
+
92
+ class LoadedVolumeInfo(BaseModel):
93
+ """A well's liquid volume, initialized by a LoadLiquid, updated by Aspirate and Dispense."""
94
+
95
+ volume: LiquidTrackingType | None = None
96
+ last_loaded: datetime
97
+ operations_since_load: int
98
+
99
+
100
+ class ProbedHeightInfo(BaseModel):
101
+ """A well's liquid height, initialized by a LiquidProbe, cleared by Aspirate and Dispense."""
102
+
103
+ height: LiquidTrackingType | None = None
104
+ last_probed: datetime
105
+
106
+
107
+ class ProbedVolumeInfo(BaseModel):
108
+ """A well's liquid volume, initialized by a LiquidProbe, updated by Aspirate and Dispense."""
109
+
110
+ volume: LiquidTrackingType | None = None
111
+ last_probed: datetime
112
+ operations_since_probe: int
113
+
114
+
115
+ class WellInfoSummary(BaseModel):
116
+ """Payload for a well's liquid info in StateSummary."""
117
+
118
+ labware_id: str
119
+ well_name: str
120
+ loaded_volume: Optional[float] = None
121
+ probed_height: LiquidTrackingType | None = None
122
+ probed_volume: LiquidTrackingType | None = None
123
+
124
+
125
+ @dataclass
126
+ class WellLiquidInfo:
127
+ """Tracked and sensed information about liquid in a well."""
128
+
129
+ probed_height: Optional[ProbedHeightInfo]
130
+ loaded_volume: Optional[LoadedVolumeInfo]
131
+ probed_volume: Optional[ProbedVolumeInfo]