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
@@ -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,87 @@ 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:
148
+ raise CutoutDoesNotExistError(f"Could not find data for cutout {cutout_id}")
149
+
150
+
151
+ def get_cutout_id_by_deck_slot_name(slot_name: DeckSlotName) -> str:
152
+ """Get the Cutout ID of a given Deck Slot by Deck Slot Name."""
153
+ try:
154
+ return DECK_SLOT_TO_CUTOUT_MAP[slot_name]
155
+ except KeyError:
156
+ raise SlotDoesNotExistError(f"Could not find data for slot {slot_name.value}")
157
+
158
+
159
+ def get_labware_hosting_addressable_area_name_for_cutout_and_cutout_fixture(
160
+ cutout_id: str, cutout_fixture_id: str, deck_definition: DeckDefinitionV5
161
+ ) -> str:
162
+ """Get the first addressable area that can contain labware for a cutout and fixture.
163
+
164
+ This probably isn't relevant outside of labware offset locations, where (for now) nothing
165
+ provides more than one labware-containing addressable area.
166
+ """
167
+ for cutoutFixture in deck_definition["cutoutFixtures"]:
168
+ if cutoutFixture["id"] != cutout_fixture_id:
169
+ continue
170
+ provided_aas = cutoutFixture["providesAddressableAreas"].get(cutout_id, None)
171
+ if provided_aas is None:
172
+ raise CutoutDoesNotExistError(
173
+ f"{cutout_fixture_id} does not go in {cutout_id}"
174
+ )
175
+ for aa_id in provided_aas:
176
+ for addressable_area in deck_definition["locations"]["addressableAreas"]:
177
+ if addressable_area["id"] != aa_id:
178
+ continue
179
+ # TODO: In deck def v6 this will be easier, but as of right now there isn't really
180
+ # a way to tell from an addressable area whether it takes labware so let's take the
181
+ # first one
182
+ return aa_id
183
+ raise AddressableAreaDoesNotExistError(
184
+ f"Could not find an addressable area that allows labware from cutout fixture {cutout_fixture_id} in cutout {cutout_id}"
185
+ )
186
+
187
+ raise FixtureDoesNotExistError(f"Could not find entry for {cutout_fixture_id}")
188
+
189
+
190
+ # This is a temporary shim while Protocol Engine's conflict-checking code
191
+ # can only take deck slots as input.
192
+ # Long-term solution: Check for conflicts based on bounding boxes, not slot adjacencies.
193
+ # Shorter-term: Change the conflict-checking code to take cutouts instead of deck slots.
194
+ CUTOUT_TO_DECK_SLOT_MAP: dict[str, DeckSlotName] = {
195
+ # OT-2
196
+ "cutout1": DeckSlotName.SLOT_1,
197
+ "cutout2": DeckSlotName.SLOT_2,
198
+ "cutout3": DeckSlotName.SLOT_3,
199
+ "cutout4": DeckSlotName.SLOT_4,
200
+ "cutout5": DeckSlotName.SLOT_5,
201
+ "cutout6": DeckSlotName.SLOT_6,
202
+ "cutout7": DeckSlotName.SLOT_7,
203
+ "cutout8": DeckSlotName.SLOT_8,
204
+ "cutout9": DeckSlotName.SLOT_9,
205
+ "cutout10": DeckSlotName.SLOT_10,
206
+ "cutout11": DeckSlotName.SLOT_11,
207
+ "cutout12": DeckSlotName.FIXED_TRASH,
208
+ # Flex
209
+ "cutoutA1": DeckSlotName.SLOT_A1,
210
+ "cutoutA2": DeckSlotName.SLOT_A2,
211
+ "cutoutA3": DeckSlotName.SLOT_A3,
212
+ "cutoutB1": DeckSlotName.SLOT_B1,
213
+ "cutoutB2": DeckSlotName.SLOT_B2,
214
+ "cutoutB3": DeckSlotName.SLOT_B3,
215
+ "cutoutC1": DeckSlotName.SLOT_C1,
216
+ "cutoutC2": DeckSlotName.SLOT_C2,
217
+ "cutoutC3": DeckSlotName.SLOT_C3,
218
+ "cutoutD1": DeckSlotName.SLOT_D1,
219
+ "cutoutD2": DeckSlotName.SLOT_D2,
220
+ "cutoutD3": DeckSlotName.SLOT_D3,
221
+ }
222
+ DECK_SLOT_TO_CUTOUT_MAP = {
223
+ deck_slot: cutout for cutout, deck_slot in CUTOUT_TO_DECK_SLOT_MAP.items()
224
+ }
@@ -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
 
@@ -1,4 +1,5 @@
1
1
  """Basic addressable area data state and store."""
2
+
2
3
  from dataclasses import dataclass
3
4
  from functools import cached_property
4
5
  from typing import Dict, List, Optional, Set
@@ -112,43 +113,6 @@ def _get_conflicting_addressable_areas_error_string(
112
113
  return ", ".join(display_names)
113
114
 
114
115
 
115
- # This is a temporary shim while Protocol Engine's conflict-checking code
116
- # can only take deck slots as input.
117
- # Long-term solution: Check for conflicts based on bounding boxes, not slot adjacencies.
118
- # Shorter-term: Change the conflict-checking code to take cutouts instead of deck slots.
119
- CUTOUT_TO_DECK_SLOT_MAP: Dict[str, DeckSlotName] = {
120
- # OT-2
121
- "cutout1": DeckSlotName.SLOT_1,
122
- "cutout2": DeckSlotName.SLOT_2,
123
- "cutout3": DeckSlotName.SLOT_3,
124
- "cutout4": DeckSlotName.SLOT_4,
125
- "cutout5": DeckSlotName.SLOT_5,
126
- "cutout6": DeckSlotName.SLOT_6,
127
- "cutout7": DeckSlotName.SLOT_7,
128
- "cutout8": DeckSlotName.SLOT_8,
129
- "cutout9": DeckSlotName.SLOT_9,
130
- "cutout10": DeckSlotName.SLOT_10,
131
- "cutout11": DeckSlotName.SLOT_11,
132
- "cutout12": DeckSlotName.FIXED_TRASH,
133
- # Flex
134
- "cutoutA1": DeckSlotName.SLOT_A1,
135
- "cutoutA2": DeckSlotName.SLOT_A2,
136
- "cutoutA3": DeckSlotName.SLOT_A3,
137
- "cutoutB1": DeckSlotName.SLOT_B1,
138
- "cutoutB2": DeckSlotName.SLOT_B2,
139
- "cutoutB3": DeckSlotName.SLOT_B3,
140
- "cutoutC1": DeckSlotName.SLOT_C1,
141
- "cutoutC2": DeckSlotName.SLOT_C2,
142
- "cutoutC3": DeckSlotName.SLOT_C3,
143
- "cutoutD1": DeckSlotName.SLOT_D1,
144
- "cutoutD2": DeckSlotName.SLOT_D2,
145
- "cutoutD3": DeckSlotName.SLOT_D3,
146
- }
147
- DECK_SLOT_TO_CUTOUT_MAP = {
148
- deck_slot: cutout for cutout, deck_slot in CUTOUT_TO_DECK_SLOT_MAP.items()
149
- }
150
-
151
-
152
116
  class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
153
117
  """Addressable area state container."""
154
118
 
@@ -221,13 +185,11 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
221
185
  cutout_position = deck_configuration_provider.get_cutout_position(
222
186
  cutout_id, deck_definition
223
187
  )
224
- base_slot = CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
225
188
  for addressable_area_name in provided_addressable_areas:
226
189
  addressable_areas.append(
227
190
  deck_configuration_provider.get_addressable_area_from_name(
228
191
  addressable_area_name=addressable_area_name,
229
192
  cutout_position=cutout_position,
230
- base_slot=base_slot,
231
193
  deck_definition=deck_definition,
232
194
  )
233
195
  )
@@ -242,12 +204,10 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
242
204
  cutout_position = deck_configuration_provider.get_cutout_position(
243
205
  cutout_id, self._state.deck_definition
244
206
  )
245
- base_slot = CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
246
207
  addressable_area = (
247
208
  deck_configuration_provider.get_addressable_area_from_name(
248
209
  addressable_area_name=addressable_area_name,
249
210
  cutout_position=cutout_position,
250
- base_slot=base_slot,
251
211
  deck_definition=self._state.deck_definition,
252
212
  )
253
213
  )
@@ -300,6 +260,11 @@ class AddressableAreaView:
300
260
  """
301
261
  self._state = state
302
262
 
263
+ @cached_property
264
+ def deck_definition(self) -> DeckDefinitionV5:
265
+ """The full deck definition."""
266
+ return self._state.deck_definition
267
+
303
268
  @cached_property
304
269
  def deck_extents(self) -> Point:
305
270
  """The maximum space on the deck."""
@@ -426,11 +391,9 @@ class AddressableAreaView:
426
391
  cutout_position = deck_configuration_provider.get_cutout_position(
427
392
  cutout_id, self._state.deck_definition
428
393
  )
429
- base_slot = CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
430
394
  return deck_configuration_provider.get_addressable_area_from_name(
431
395
  addressable_area_name=addressable_area_name,
432
396
  cutout_position=cutout_position,
433
- base_slot=base_slot,
434
397
  deck_definition=self._state.deck_definition,
435
398
  )
436
399
 
@@ -526,7 +489,7 @@ class AddressableAreaView:
526
489
 
527
490
  def get_cutout_id_by_deck_slot_name(self, slot_name: DeckSlotName) -> str:
528
491
  """Get the Cutout ID of a given Deck Slot by Deck Slot Name."""
529
- return DECK_SLOT_TO_CUTOUT_MAP[slot_name]
492
+ return deck_configuration_provider.get_cutout_id_by_deck_slot_name(slot_name)
530
493
 
531
494
  def get_fixture_by_deck_slot_name(
532
495
  self, slot_name: DeckSlotName
@@ -534,7 +497,9 @@ class AddressableAreaView:
534
497
  """Get the Cutout Fixture currently loaded where a specific Deck Slot would be."""
535
498
  deck_config = self._state.deck_configuration
536
499
  if deck_config:
537
- slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name]
500
+ slot_cutout_id = (
501
+ deck_configuration_provider.get_cutout_id_by_deck_slot_name(slot_name)
502
+ )
538
503
  slot_cutout_fixture = None
539
504
  # This will only ever be one under current assumptions
540
505
  for (
@@ -571,7 +536,9 @@ class AddressableAreaView:
571
536
  """Get the serial number provided by the deck configuration for a Fixture at a given location."""
572
537
  deck_config = self._state.deck_configuration
573
538
  if deck_config:
574
- slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name]
539
+ slot_cutout_id = (
540
+ deck_configuration_provider.get_cutout_id_by_deck_slot_name(slot_name)
541
+ )
575
542
  # This will only ever be one under current assumptions
576
543
  for (
577
544
  cutout_id,
@@ -582,6 +549,44 @@ class AddressableAreaView:
582
549
  return opentrons_module_serial_number
583
550
  return None
584
551
 
552
+ def get_serial_number_by_cutout_id(self, slot_cutout_id: str) -> str | None:
553
+ """Gets serial number from deck at a given cutout ID if one exists."""
554
+ deck_config = self._state.deck_configuration
555
+ if deck_config:
556
+ for (
557
+ cutout_id,
558
+ cutout_fixture_id,
559
+ opentrons_module_serial_number,
560
+ ) in deck_config:
561
+ if cutout_id == slot_cutout_id:
562
+ return opentrons_module_serial_number
563
+ return None
564
+
565
+ def get_fixture_serial_from_deck_configuration_by_addressable_area(
566
+ self, addressable_area_name: str
567
+ ) -> Optional[str]:
568
+ """Get the serial number provided by the deck configuration for a Fixture that provides a given addressable area."""
569
+ deck_config = self._state.deck_configuration
570
+ if deck_config:
571
+ potential_fixtures = (
572
+ deck_configuration_provider.get_potential_cutout_fixtures(
573
+ addressable_area_name, self._state.deck_definition
574
+ )
575
+ )
576
+ slot_cutout_id = potential_fixtures[0]
577
+ fixture_ids = [
578
+ fixture.cutout_fixture_id for fixture in potential_fixtures[1]
579
+ ]
580
+ # This will only ever be one under current assumptions
581
+ for (
582
+ cutout_id,
583
+ cutout_fixture_id,
584
+ opentrons_module_serial_number,
585
+ ) in deck_config:
586
+ if cutout_id == slot_cutout_id and cutout_fixture_id in fixture_ids:
587
+ return opentrons_module_serial_number
588
+ return None
589
+
585
590
  def get_slot_definition(self, slot_id: str) -> SlotDefV3:
586
591
  """Get the definition of a slot in the deck.
587
592
 
@@ -658,3 +663,36 @@ class AddressableAreaView:
658
663
  raise AreaNotInDeckConfigurationError(
659
664
  f"{addressable_area_name} not provided by deck configuration."
660
665
  )
666
+
667
+ def get_current_potential_cutout_fixtures_for_addressable_area(
668
+ self, addressable_area_name: str
669
+ ) -> tuple[str, Set[PotentialCutoutFixture]]:
670
+ """Get the set of cutout fixtures that might provide a given addressable area.
671
+
672
+ This takes into account the constraints already established by load commands or by a loaded deck
673
+ configuration, and may therefore return different results for the same addressable area at
674
+ different points in the protocol after deck configuration constraints have changed.
675
+
676
+ This returns the common cutout id and the potential fixtures.
677
+ """
678
+ (
679
+ cutout_id,
680
+ base_potential_fixtures,
681
+ ) = deck_configuration_provider.get_potential_cutout_fixtures(
682
+ addressable_area_name, self._state.deck_definition
683
+ )
684
+ try:
685
+ loaded_potential_fixtures = (
686
+ self._state.potential_cutout_fixtures_by_cutout_id[cutout_id]
687
+ )
688
+ return cutout_id, loaded_potential_fixtures.intersection(
689
+ base_potential_fixtures
690
+ )
691
+ except KeyError:
692
+ # If there was a key error here, it's because this function was (eventually) called
693
+ # from the body of a command implementation whose state update will load the
694
+ # addressable area it's querying... but that state update has not been submitted
695
+ # and processed, so nothing has created the entry for this cutout id yet. Do what
696
+ # we'll do when we actually get to that state update, which is apply the base
697
+ # potential fixtures from the deck def.
698
+ return cutout_id, base_potential_fixtures
@@ -5,6 +5,10 @@ from math import isclose
5
5
 
6
6
  from ..errors.exceptions import InvalidLiquidHeightFound
7
7
 
8
+ from opentrons.protocol_engine.types.liquid_level_detection import (
9
+ LiquidTrackingType,
10
+ SimulatedProbeResult,
11
+ )
8
12
  from opentrons_shared_data.labware.labware_definition import (
9
13
  InnerWellGeometry,
10
14
  WellSegment,
@@ -241,9 +245,8 @@ def _get_segment_capacity(segment: WellSegment) -> float:
241
245
  def get_well_volumetric_capacity(
242
246
  well_geometry: InnerWellGeometry,
243
247
  ) -> List[Tuple[float, float]]:
244
- """Return the total volumetric capacity of a well as a map of height borders to volume."""
245
- # dictionary map of heights to volumetric capacities within their respective segment
246
- # {top_height_0: volume_0, top_height_1: volume_1, top_height_2: volume_2}
248
+ """Return the volumetric capacity of a well as a list of pairs relating segment heights to volumes."""
249
+ # [(top_height_0, section_0_volume), (top_height_1, section_1_volume), ...]
247
250
  well_volume = []
248
251
 
249
252
  # get the well segments sorted in ascending order
@@ -341,7 +344,7 @@ def _find_volume_in_partial_frustum(
341
344
  ) -> float:
342
345
  """Look through a sorted list of frusta for a target height, and find the volume at that height."""
343
346
  for segment in sorted_well:
344
- if segment.bottomHeight < target_height < segment.topHeight:
347
+ if segment.bottomHeight <= target_height <= segment.topHeight:
345
348
  relative_target_height = target_height - segment.bottomHeight
346
349
  section_height = segment.topHeight - segment.bottomHeight
347
350
  return volume_at_height_within_section(
@@ -356,9 +359,13 @@ def _find_volume_in_partial_frustum(
356
359
 
357
360
 
358
361
  def find_volume_at_well_height(
359
- target_height: float, well_geometry: InnerWellGeometry
360
- ) -> float:
362
+ target_height: LiquidTrackingType,
363
+ well_geometry: InnerWellGeometry,
364
+ ) -> LiquidTrackingType:
361
365
  """Find the volume within a well, at a known height."""
366
+ # comparisons with SimulatedProbeResult objects aren't meaningful, just return
367
+ if isinstance(target_height, SimulatedProbeResult):
368
+ return target_height
362
369
  volumetric_capacity = get_well_volumetric_capacity(well_geometry)
363
370
  max_height = volumetric_capacity[-1][0]
364
371
  if target_height < 0 or target_height > max_height:
@@ -417,13 +424,22 @@ def _find_height_in_partial_frustum(
417
424
 
418
425
 
419
426
  def find_height_at_well_volume(
420
- target_volume: float, well_geometry: InnerWellGeometry
421
- ) -> float:
427
+ target_volume: LiquidTrackingType,
428
+ well_geometry: InnerWellGeometry,
429
+ raise_error_if_result_invalid: bool = True,
430
+ ) -> LiquidTrackingType:
422
431
  """Find the height within a well, at a known volume."""
432
+ # comparisons with SimulatedProbeResult objects aren't meaningful, just
433
+ # return if we have one of those
434
+ if isinstance(target_volume, SimulatedProbeResult):
435
+ return target_volume
436
+
423
437
  volumetric_capacity = get_well_volumetric_capacity(well_geometry)
424
438
  max_volume = sum(row[1] for row in volumetric_capacity)
425
- if target_volume < 0 or target_volume > max_volume:
426
- raise InvalidLiquidHeightFound("Invalid target volume.")
439
+
440
+ if raise_error_if_result_invalid:
441
+ if target_volume < 0 or target_volume > max_volume:
442
+ raise InvalidLiquidHeightFound("Invalid target volume.")
427
443
 
428
444
  sorted_well = sorted(well_geometry.sections, key=lambda section: section.topHeight)
429
445
  # find the section the target volume is in and compute the height