opentrons 8.3.2__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.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
  188. {opentrons-8.3.2.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.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
  194. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
  195. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
  196. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,194 @@
1
+ """Convert labware offset creation requests and stored elements between legacy and new."""
2
+
3
+ from opentrons_shared_data.robot.types import RobotType
4
+ from opentrons_shared_data.deck.types import DeckDefinitionV5
5
+ from .errors import (
6
+ OffsetLocationInvalidError,
7
+ FixtureDoesNotExistError,
8
+ )
9
+ from .types import (
10
+ LabwareOffsetCreate,
11
+ LegacyLabwareOffsetCreate,
12
+ LabwareOffsetCreateInternal,
13
+ LegacyLabwareOffsetLocation,
14
+ LabwareOffsetLocationSequence,
15
+ OnLabwareOffsetLocationSequenceComponent,
16
+ OnAddressableAreaOffsetLocationSequenceComponent,
17
+ OnModuleOffsetLocationSequenceComponent,
18
+ ModuleModel,
19
+ )
20
+ from .resources import deck_configuration_provider
21
+
22
+
23
+ def standardize_labware_offset_create(
24
+ request: LabwareOffsetCreate | LegacyLabwareOffsetCreate,
25
+ robot_type: RobotType,
26
+ deck_definition: DeckDefinitionV5,
27
+ ) -> LabwareOffsetCreateInternal:
28
+ """Turn a union of old and new labware offset create requests into a new one."""
29
+ location_sequence, legacy_location = _locations_for_create(
30
+ request, robot_type, deck_definition
31
+ )
32
+ return LabwareOffsetCreateInternal(
33
+ definitionUri=request.definitionUri,
34
+ locationSequence=location_sequence,
35
+ legacyLocation=legacy_location,
36
+ vector=request.vector,
37
+ )
38
+
39
+
40
+ def legacy_offset_location_to_offset_location_sequence(
41
+ location: LegacyLabwareOffsetLocation, deck_definition: DeckDefinitionV5
42
+ ) -> LabwareOffsetLocationSequence:
43
+ """Convert a legacy location to a new-style sequence."""
44
+ sequence: LabwareOffsetLocationSequence = []
45
+ if location.definitionUri:
46
+ sequence.append(
47
+ OnLabwareOffsetLocationSequenceComponent(labwareUri=location.definitionUri)
48
+ )
49
+ if location.moduleModel:
50
+ sequence.append(
51
+ OnModuleOffsetLocationSequenceComponent(moduleModel=location.moduleModel)
52
+ )
53
+ cutout_id = deck_configuration_provider.get_cutout_id_by_deck_slot_name(
54
+ location.slotName
55
+ )
56
+
57
+ # Given a module model, try to figure out the equivalent cutout fixture.
58
+ #
59
+ # The Thermocycler is special. A single Thermocycler is represented in a deck
60
+ # configuration as two separate cutout fixtures, because it spans two separate
61
+ # cutouts. This makes it the only module whose module model string does not map
62
+ # 1:1 with a cutout fixture ID string.
63
+ #
64
+ # TODO(mm, 2025-04-11): This is fragile, and the failure mode when it does the
65
+ # wrong thing can mean labware offsets don't apply, which is pretty bad. We
66
+ # either need a more explicit module<->cutout-fixture mapping, or we need to
67
+ # avoid this mapping entirely.
68
+ if (
69
+ # Check for v2 specifically because v1 is OT-2-only and OT-2s don't have
70
+ # modules in their deck definitions; and v3 does not exist at the time of writing.
71
+ location.moduleModel
72
+ == ModuleModel.THERMOCYCLER_MODULE_V2
73
+ ):
74
+ possible_cutout_fixture_id = "thermocyclerModuleV2Front"
75
+ else:
76
+ possible_cutout_fixture_id = location.moduleModel.value
77
+
78
+ try:
79
+ addressable_area = deck_configuration_provider.get_labware_hosting_addressable_area_name_for_cutout_and_cutout_fixture(
80
+ cutout_id, possible_cutout_fixture_id, deck_definition
81
+ )
82
+ sequence.append(
83
+ OnAddressableAreaOffsetLocationSequenceComponent(
84
+ addressableAreaName=addressable_area
85
+ )
86
+ )
87
+ except FixtureDoesNotExistError:
88
+ # this is an OT-2 (or this module isn't supported in the deck definition) and we should use a
89
+ # slot addressable area name
90
+ sequence.append(
91
+ OnAddressableAreaOffsetLocationSequenceComponent(
92
+ addressableAreaName=location.slotName.value
93
+ )
94
+ )
95
+
96
+ else:
97
+ # Slight hack: we should have a more formal association here. However, since the slot
98
+ # name is already standardized, and since the addressable areas for slots are just the
99
+ # name of the slots, we can rely on this.
100
+ sequence.append(
101
+ OnAddressableAreaOffsetLocationSequenceComponent(
102
+ addressableAreaName=location.slotName.value
103
+ )
104
+ )
105
+ return sequence
106
+
107
+
108
+ def _offset_location_sequence_head_to_labware_and_module(
109
+ location_sequence: LabwareOffsetLocationSequence,
110
+ ) -> tuple[ModuleModel | None, str | None]:
111
+ labware_uri: str | None = None
112
+ module_model: ModuleModel | None = None
113
+ for location in location_sequence:
114
+ if isinstance(location, OnAddressableAreaOffsetLocationSequenceComponent):
115
+ raise OffsetLocationInvalidError(
116
+ "Addressable areas may only be the final element of an offset location."
117
+ )
118
+ elif isinstance(location, OnLabwareOffsetLocationSequenceComponent):
119
+ if labware_uri is not None:
120
+ # We only take the first location
121
+ continue
122
+ if module_model is not None:
123
+ # Labware can't be underneath modules
124
+ raise OffsetLocationInvalidError(
125
+ "Labware must not be underneath a module."
126
+ )
127
+ labware_uri = location.labwareUri
128
+ elif isinstance(location, OnModuleOffsetLocationSequenceComponent):
129
+ if module_model is not None:
130
+ # Bad, somebody put more than one module in here
131
+ raise OffsetLocationInvalidError(
132
+ "Only one module location may exist in an offset location."
133
+ )
134
+ module_model = location.moduleModel
135
+ else:
136
+ raise OffsetLocationInvalidError(
137
+ f"Invalid location component in offset location: {repr(location)}"
138
+ )
139
+ return module_model, labware_uri
140
+
141
+
142
+ def _offset_location_sequence_to_legacy_offset_location(
143
+ location_sequence: LabwareOffsetLocationSequence, deck_definition: DeckDefinitionV5
144
+ ) -> LegacyLabwareOffsetLocation:
145
+ if len(location_sequence) == 0:
146
+ raise OffsetLocationInvalidError(
147
+ "Offset locations must contain at least one component."
148
+ )
149
+ last_element = location_sequence[-1]
150
+ if not isinstance(last_element, OnAddressableAreaOffsetLocationSequenceComponent):
151
+ raise OffsetLocationInvalidError(
152
+ "Offset locations must end with an addressable area."
153
+ )
154
+ module_model, labware_uri = _offset_location_sequence_head_to_labware_and_module(
155
+ location_sequence[:-1]
156
+ )
157
+ (
158
+ cutout_id,
159
+ _cutout_fixtures,
160
+ ) = deck_configuration_provider.get_potential_cutout_fixtures(
161
+ last_element.addressableAreaName, deck_definition
162
+ )
163
+ slot_name = deck_configuration_provider.get_deck_slot_for_cutout_id(cutout_id)
164
+ return LegacyLabwareOffsetLocation(
165
+ slotName=slot_name, moduleModel=module_model, definitionUri=labware_uri
166
+ )
167
+
168
+
169
+ def _locations_for_create(
170
+ request: LabwareOffsetCreate | LegacyLabwareOffsetCreate,
171
+ robot_type: RobotType,
172
+ deck_definition: DeckDefinitionV5,
173
+ ) -> tuple[LabwareOffsetLocationSequence, LegacyLabwareOffsetLocation]:
174
+ if isinstance(request, LabwareOffsetCreate):
175
+ return (
176
+ request.locationSequence,
177
+ _offset_location_sequence_to_legacy_offset_location(
178
+ request.locationSequence, deck_definition
179
+ ),
180
+ )
181
+ else:
182
+ normalized = request.location.model_copy(
183
+ update={
184
+ "slotName": request.location.slotName.to_equivalent_for_robot_type(
185
+ robot_type
186
+ )
187
+ }
188
+ )
189
+ return (
190
+ legacy_offset_location_to_offset_location_sequence(
191
+ normalized, deck_definition
192
+ ),
193
+ normalized,
194
+ )
@@ -1,29 +1,32 @@
1
1
  """ProtocolEngine class definition."""
2
+
2
3
  from contextlib import AsyncExitStack
3
4
  from logging import getLogger
4
5
  from typing import Dict, Optional, Union, AsyncGenerator, Callable
5
- from opentrons.protocol_engine.actions.actions import (
6
- ResumeFromRecoveryAction,
7
- SetErrorRecoveryPolicyAction,
8
- )
9
6
 
10
- from opentrons.protocols.models import LabwareDefinition
11
- from opentrons.hardware_control import HardwareControlAPI
12
- from opentrons.hardware_control.modules import AbstractModule as HardwareModuleAPI
13
- from opentrons.hardware_control.types import PauseType as HardwarePauseType
14
7
  from opentrons_shared_data.errors import (
15
8
  ErrorCodes,
16
9
  EnumeratedError,
17
10
  )
11
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
18
12
 
13
+ from opentrons.hardware_control import HardwareControlAPI
14
+ from opentrons.hardware_control.modules import AbstractModule as HardwareModuleAPI
15
+ from opentrons.hardware_control.types import PauseType as HardwarePauseType
16
+
17
+ from .actions.actions import (
18
+ ResumeFromRecoveryAction,
19
+ SetErrorRecoveryPolicyAction,
20
+ )
19
21
  from .errors import ProtocolCommandFailedError, ErrorOccurrence, CommandNotAllowedError
20
22
  from .errors.exceptions import EStopActivatedError
21
23
  from .error_recovery_policy import ErrorRecoveryPolicy
22
- from . import commands, slot_standardization
24
+ from . import commands, slot_standardization, labware_offset_standardization
23
25
  from .resources import ModelUtils, ModuleDataProvider, FileProvider
24
26
  from .types import (
25
27
  LabwareOffset,
26
28
  LabwareOffsetCreate,
29
+ LegacyLabwareOffsetCreate,
27
30
  LabwareUri,
28
31
  ModuleModel,
29
32
  Liquid,
@@ -516,15 +519,21 @@ class ProtocolEngine:
516
519
  )
517
520
  )
518
521
 
519
- def add_labware_offset(self, request: LabwareOffsetCreate) -> LabwareOffset:
522
+ def add_labware_offset(
523
+ self, request: LabwareOffsetCreate | LegacyLabwareOffsetCreate
524
+ ) -> LabwareOffset:
520
525
  """Add a new labware offset and return it.
521
526
 
522
527
  The added offset will apply to subsequent `LoadLabwareCommand`s.
523
528
 
524
529
  To retrieve offsets later, see `.state_view.labware`.
525
530
  """
526
- request = slot_standardization.standardize_labware_offset(
527
- request, self.state_view.config.robot_type
531
+ internal_request = (
532
+ labware_offset_standardization.standardize_labware_offset_create(
533
+ request,
534
+ self.state_view.config.robot_type,
535
+ self.state_view.addressable_areas.deck_definition,
536
+ )
528
537
  )
529
538
 
530
539
  labware_offset_id = self._model_utils.generate_id()
@@ -533,7 +542,7 @@ class ProtocolEngine:
533
542
  AddLabwareOffsetAction(
534
543
  labware_offset_id=labware_offset_id,
535
544
  created_at=created_at,
536
- request=request,
545
+ request=internal_request,
537
546
  )
538
547
  )
539
548
  return self.state_view.labware.get_labware_offset(
@@ -1,7 +1,11 @@
1
1
  """Deck configuration resource provider."""
2
+
2
3
  from typing import List, Set, Tuple
3
4
 
4
- from opentrons_shared_data.deck.types import DeckDefinitionV5, CutoutFixture
5
+ from opentrons_shared_data.deck.types import (
6
+ DeckDefinitionV5,
7
+ CutoutFixture,
8
+ )
5
9
 
6
10
  from opentrons.types import DeckSlotName
7
11
 
@@ -17,6 +21,7 @@ from ..errors import (
17
21
  CutoutDoesNotExistError,
18
22
  FixtureDoesNotExistError,
19
23
  AddressableAreaDoesNotExistError,
24
+ SlotDoesNotExistError,
20
25
  )
21
26
 
22
27
 
@@ -98,12 +103,15 @@ def get_potential_cutout_fixtures(
98
103
  def get_addressable_area_from_name(
99
104
  addressable_area_name: str,
100
105
  cutout_position: DeckPoint,
101
- base_slot: DeckSlotName,
102
106
  deck_definition: DeckDefinitionV5,
103
107
  ) -> AddressableArea:
104
108
  """Given a name and a cutout position, get an addressable area on the deck."""
105
109
  for addressable_area in deck_definition["locations"]["addressableAreas"]:
106
110
  if addressable_area["id"] == addressable_area_name:
111
+ cutout_id, _ = get_potential_cutout_fixtures(
112
+ addressable_area_name, deck_definition
113
+ )
114
+ base_slot = get_deck_slot_for_cutout_id(cutout_id)
107
115
  area_offset = addressable_area["offsetFromCutoutFixture"]
108
116
  position = AddressableOffsetVector(
109
117
  x=area_offset[0] + cutout_position.x,
@@ -130,3 +138,91 @@ def get_addressable_area_from_name(
130
138
  raise AddressableAreaDoesNotExistError(
131
139
  f"Could not find addressable area with name {addressable_area_name}"
132
140
  )
141
+
142
+
143
+ def get_deck_slot_for_cutout_id(cutout_id: str) -> DeckSlotName:
144
+ """Get the corresponding deck slot for an addressable area."""
145
+ try:
146
+ return CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
147
+ except KeyError as e:
148
+ raise CutoutDoesNotExistError(
149
+ f"Could not find data for cutout {cutout_id}"
150
+ ) from e
151
+
152
+
153
+ def get_cutout_id_by_deck_slot_name(slot_name: DeckSlotName) -> str:
154
+ """Get the Cutout ID of a given Deck Slot by Deck Slot Name."""
155
+ try:
156
+ return DECK_SLOT_TO_CUTOUT_MAP[slot_name]
157
+ except KeyError as e:
158
+ raise SlotDoesNotExistError(
159
+ f"Could not find data for slot {slot_name.value}"
160
+ ) from e
161
+
162
+
163
+ def get_labware_hosting_addressable_area_name_for_cutout_and_cutout_fixture(
164
+ cutout_id: str, cutout_fixture_id: str, deck_definition: DeckDefinitionV5
165
+ ) -> str:
166
+ """Get the first addressable area that can contain labware for a cutout and fixture.
167
+
168
+ This probably isn't relevant outside of labware offset locations, where (for now) nothing
169
+ provides more than one labware-containing addressable area.
170
+ """
171
+ for cutoutFixture in deck_definition["cutoutFixtures"]:
172
+ if cutoutFixture["id"] != cutout_fixture_id:
173
+ continue
174
+ provided_aas = cutoutFixture["providesAddressableAreas"].get(cutout_id, None)
175
+ if provided_aas is None:
176
+ raise CutoutDoesNotExistError(
177
+ f"{cutout_fixture_id} does not go in {cutout_id}"
178
+ )
179
+ for aa_id in provided_aas:
180
+ for addressable_area in deck_definition["locations"]["addressableAreas"]:
181
+ if addressable_area["id"] != aa_id:
182
+ continue
183
+ # TODO: In deck def v6 this will be easier, but as of right now there isn't really
184
+ # a way to tell from an addressable area whether it takes labware so let's take the
185
+ # first one
186
+ return aa_id
187
+ raise AddressableAreaDoesNotExistError(
188
+ f"Could not find an addressable area that allows labware from cutout fixture {cutout_fixture_id} in cutout {cutout_id}"
189
+ )
190
+
191
+ raise FixtureDoesNotExistError(f"Could not find entry for {cutout_fixture_id}")
192
+
193
+
194
+ # This is a temporary shim while Protocol Engine's conflict-checking code
195
+ # can only take deck slots as input.
196
+ # Long-term solution: Check for conflicts based on bounding boxes, not slot adjacencies.
197
+ # Shorter-term: Change the conflict-checking code to take cutouts instead of deck slots.
198
+ CUTOUT_TO_DECK_SLOT_MAP: dict[str, DeckSlotName] = {
199
+ # OT-2
200
+ "cutout1": DeckSlotName.SLOT_1,
201
+ "cutout2": DeckSlotName.SLOT_2,
202
+ "cutout3": DeckSlotName.SLOT_3,
203
+ "cutout4": DeckSlotName.SLOT_4,
204
+ "cutout5": DeckSlotName.SLOT_5,
205
+ "cutout6": DeckSlotName.SLOT_6,
206
+ "cutout7": DeckSlotName.SLOT_7,
207
+ "cutout8": DeckSlotName.SLOT_8,
208
+ "cutout9": DeckSlotName.SLOT_9,
209
+ "cutout10": DeckSlotName.SLOT_10,
210
+ "cutout11": DeckSlotName.SLOT_11,
211
+ "cutout12": DeckSlotName.FIXED_TRASH,
212
+ # Flex
213
+ "cutoutA1": DeckSlotName.SLOT_A1,
214
+ "cutoutA2": DeckSlotName.SLOT_A2,
215
+ "cutoutA3": DeckSlotName.SLOT_A3,
216
+ "cutoutB1": DeckSlotName.SLOT_B1,
217
+ "cutoutB2": DeckSlotName.SLOT_B2,
218
+ "cutoutB3": DeckSlotName.SLOT_B3,
219
+ "cutoutC1": DeckSlotName.SLOT_C1,
220
+ "cutoutC2": DeckSlotName.SLOT_C2,
221
+ "cutoutC3": DeckSlotName.SLOT_C3,
222
+ "cutoutD1": DeckSlotName.SLOT_D1,
223
+ "cutoutD2": DeckSlotName.SLOT_D2,
224
+ "cutoutD3": DeckSlotName.SLOT_D3,
225
+ }
226
+ DECK_SLOT_TO_CUTOUT_MAP = {
227
+ deck_slot: cutout for cutout, deck_slot in CUTOUT_TO_DECK_SLOT_MAP.items()
228
+ }
@@ -10,7 +10,7 @@ from opentrons_shared_data.deck import (
10
10
  DEFAULT_DECK_DEFINITION_VERSION,
11
11
  )
12
12
  from opentrons_shared_data.deck.types import DeckDefinitionV5
13
- from opentrons.protocols.models import LabwareDefinition
13
+ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
14
14
  from opentrons.types import DeckSlotName
15
15
 
16
16
  from ..types import (
@@ -6,7 +6,12 @@ abstract away rough edges until we can improve those underlying interfaces.
6
6
  import logging
7
7
  from anyio import to_thread
8
8
 
9
- from opentrons.protocols.models import LabwareDefinition
9
+ from opentrons_shared_data.labware.labware_definition import (
10
+ LabwareDefinition,
11
+ LabwareDefinition3,
12
+ labware_definition_type_adapter,
13
+ )
14
+
10
15
  from opentrons.protocols.labware import get_labware_definition
11
16
 
12
17
  # TODO (lc 09-26-2022) We should conditionally import ot2 or ot3 calibration
@@ -44,7 +49,7 @@ class LabwareDataProvider:
44
49
  def _get_labware_definition_sync(
45
50
  load_name: str, namespace: str, version: int
46
51
  ) -> LabwareDefinition:
47
- return LabwareDefinition.model_validate(
52
+ return labware_definition_type_adapter.validate_python(
48
53
  get_labware_definition(load_name, namespace, version)
49
54
  )
50
55
 
@@ -72,15 +77,30 @@ class LabwareDataProvider:
72
77
  labware_definition: LabwareDefinition,
73
78
  nominal_fallback: float,
74
79
  ) -> float:
75
- try:
76
- return instr_cal.load_tip_length_for_pipette(
77
- pipette_serial, labware_definition
78
- ).tip_length
79
-
80
- except TipLengthCalNotFound as e:
81
- message = (
82
- f"No calibrated tip length found for {pipette_serial},"
83
- f" using nominal fallback value of {nominal_fallback}"
80
+ if isinstance(labware_definition, LabwareDefinition3):
81
+ # FIXME(mm, 2025-02-19): This needs to be resolved for v8.4.0.
82
+ # Tip length calibration internals don't yet support schema 3 because
83
+ # it's probably an incompatible change at the filesystem level
84
+ # (not downgrade-safe), and because robot-server's calibration sessions
85
+ # are built atop opentrons.protocol_api.core.legacy, which does not (yet?)
86
+ # support labware schema 3.
87
+ # https://opentrons.atlassian.net/browse/EXEC-1230
88
+ log.warning(
89
+ f"Tip rack"
90
+ f" {labware_definition.namespace}/{labware_definition.parameters.loadName}/{labware_definition.version}"
91
+ f" has schema 3, so tip length calibration is currently unsupported."
92
+ f" Using nominal fallback of {nominal_fallback}."
84
93
  )
85
- log.debug(message, exc_info=e)
86
94
  return nominal_fallback
95
+ else:
96
+ try:
97
+ return instr_cal.load_tip_length_for_pipette(
98
+ pipette_serial, labware_definition
99
+ ).tip_length
100
+ except TipLengthCalNotFound as e:
101
+ message = (
102
+ f"No calibrated tip length found for {pipette_serial},"
103
+ f" using nominal fallback value of {nominal_fallback}"
104
+ )
105
+ log.debug(message, exc_info=e)
106
+ return nominal_fallback
@@ -1,7 +1,9 @@
1
1
  """Validation file for labware role and location checking functions."""
2
2
 
3
- from opentrons_shared_data.labware.labware_definition import LabwareRole
4
- from opentrons.protocols.models import LabwareDefinition
3
+ from opentrons_shared_data.labware.labware_definition import (
4
+ LabwareDefinition,
5
+ LabwareRole,
6
+ )
5
7
 
6
8
 
7
9
  def is_flex_trash(load_name: str) -> bool:
@@ -14,9 +16,9 @@ def is_absorbance_reader_lid(load_name: str) -> bool:
14
16
  return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
15
17
 
16
18
 
17
- def is_evotips(load_name: str) -> bool:
18
- """Check if a labware is an evotips tiprack."""
19
- return load_name == "evotips_opentrons_96_labware"
19
+ def is_lid_stack(load_name: str) -> bool:
20
+ """Check if a labware object is a system lid stack object."""
21
+ return load_name == "protocol_engine_lid_stack_object"
20
22
 
21
23
 
22
24
  def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
@@ -14,7 +14,6 @@ This module does that conversion, for any Protocol Engine input that contains a
14
14
  deck slot.
15
15
  """
16
16
 
17
-
18
17
  from typing import Any, Callable, Dict, Type
19
18
 
20
19
  from opentrons_shared_data.robot.types import RobotType
@@ -22,32 +21,15 @@ from opentrons_shared_data.robot.types import RobotType
22
21
  from . import commands
23
22
  from .types import (
24
23
  OFF_DECK_LOCATION,
24
+ SYSTEM_LOCATION,
25
25
  DeckSlotLocation,
26
- LabwareLocation,
26
+ LoadableLabwareLocation,
27
27
  AddressableAreaLocation,
28
- LabwareOffsetCreate,
29
28
  ModuleLocation,
30
29
  OnLabwareLocation,
31
30
  )
32
31
 
33
32
 
34
- def standardize_labware_offset(
35
- original: LabwareOffsetCreate, robot_type: RobotType
36
- ) -> LabwareOffsetCreate:
37
- """Convert the deck slot in the given `LabwareOffsetCreate` to match the given robot type."""
38
- return original.model_copy(
39
- update={
40
- "location": original.location.model_copy(
41
- update={
42
- "slotName": original.location.slotName.to_equivalent_for_robot_type(
43
- robot_type
44
- )
45
- }
46
- )
47
- }
48
- )
49
-
50
-
51
33
  def standardize_command(
52
34
  original: commands.CommandCreate, robot_type: RobotType
53
35
  ) -> commands.CommandCreate:
@@ -119,15 +101,21 @@ _standardize_command_functions: Dict[
119
101
 
120
102
 
121
103
  def _standardize_labware_location(
122
- original: LabwareLocation, robot_type: RobotType
123
- ) -> LabwareLocation:
104
+ original: LoadableLabwareLocation, robot_type: RobotType
105
+ ) -> LoadableLabwareLocation:
124
106
  if isinstance(original, DeckSlotLocation):
125
107
  return _standardize_deck_slot_location(original, robot_type)
126
108
  elif (
127
109
  isinstance(
128
- original, (ModuleLocation, OnLabwareLocation, AddressableAreaLocation)
110
+ original,
111
+ (
112
+ ModuleLocation,
113
+ OnLabwareLocation,
114
+ AddressableAreaLocation,
115
+ ),
129
116
  )
130
117
  or original == OFF_DECK_LOCATION
118
+ or original == SYSTEM_LOCATION
131
119
  ):
132
120
  return original
133
121