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
@@ -1,4 +1,5 @@
1
1
  """ProtocolEngine-based Protocol API core implementation."""
2
+
2
3
  from __future__ import annotations
3
4
  from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING
4
5
 
@@ -8,7 +9,9 @@ from opentrons.protocol_engine import commands as cmd
8
9
  from opentrons.protocol_engine.commands import LoadModuleResult
9
10
 
10
11
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
11
- from opentrons_shared_data.labware.labware_definition import LabwareDefinition
12
+ from opentrons_shared_data.labware.labware_definition import (
13
+ labware_definition_type_adapter,
14
+ )
12
15
  from opentrons_shared_data.labware.types import LabwareDefinition as LabwareDefDict
13
16
  from opentrons_shared_data import liquid_classes
14
17
  from opentrons_shared_data.liquid_classes.liquid_class_definition import (
@@ -47,7 +50,8 @@ from opentrons.protocol_engine import (
47
50
  from opentrons.protocol_engine.types import (
48
51
  ModuleModel as ProtocolEngineModuleModel,
49
52
  OFF_DECK_LOCATION,
50
- LabwareLocation,
53
+ SYSTEM_LOCATION,
54
+ LoadableLabwareLocation,
51
55
  NonStackedLocation,
52
56
  )
53
57
  from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
@@ -74,9 +78,11 @@ from .module_core import (
74
78
  NonConnectedModuleCore,
75
79
  MagneticBlockCore,
76
80
  AbsorbanceReaderCore,
81
+ FlexStackerCore,
77
82
  )
78
83
  from .exceptions import InvalidModuleLocationError
79
84
  from . import load_labware_params, deck_conflict, overlap_versions
85
+ from opentrons.protocol_engine.resources import labware_validation
80
86
 
81
87
  if TYPE_CHECKING:
82
88
  from ...labware import Labware
@@ -193,7 +199,7 @@ class ProtocolCore(
193
199
  ) -> LabwareLoadParams:
194
200
  """Add a labware definition to the set of loadable definitions."""
195
201
  uri = self._engine_client.add_labware_definition(
196
- LabwareDefinition.model_validate(definition)
202
+ labware_definition_type_adapter.validate_python(definition)
197
203
  )
198
204
  return LabwareLoadParams.from_uri(uri)
199
205
 
@@ -219,7 +225,7 @@ class ProtocolCore(
219
225
  self._engine_client.state.labware.find_custom_labware_load_params()
220
226
  )
221
227
  namespace, version = load_labware_params.resolve(
222
- load_name, namespace, version, custom_labware_params
228
+ load_name, namespace, version, custom_labware_params, self._api_version
223
229
  )
224
230
 
225
231
  load_result = self._engine_client.execute_command_without_recovery(
@@ -290,7 +296,7 @@ class ProtocolCore(
290
296
  self._engine_client.state.labware.find_custom_labware_load_params()
291
297
  )
292
298
  namespace, version = load_labware_params.resolve(
293
- load_name, namespace, version, custom_labware_params
299
+ load_name, namespace, version, custom_labware_params, self._api_version
294
300
  )
295
301
  load_result = self._engine_client.execute_command_without_recovery(
296
302
  cmd.LoadLabwareParams(
@@ -338,7 +344,7 @@ class ProtocolCore(
338
344
  self._engine_client.state.labware.find_custom_labware_load_params()
339
345
  )
340
346
  namespace, version = load_labware_params.resolve(
341
- load_name, namespace, version, custom_labware_params
347
+ load_name, namespace, version, custom_labware_params, self._api_version
342
348
  )
343
349
  load_result = self._engine_client.execute_command_without_recovery(
344
350
  cmd.LoadLidParams(
@@ -371,6 +377,34 @@ class ProtocolCore(
371
377
  self._labware_cores_by_id[labware_core.labware_id] = labware_core
372
378
  return labware_core
373
379
 
380
+ def load_labware_to_flex_stacker_hopper(
381
+ self,
382
+ module_core: Union[ModuleCore, NonConnectedModuleCore],
383
+ load_name: str,
384
+ quantity: int,
385
+ label: Optional[str],
386
+ namespace: Optional[str],
387
+ version: Optional[int],
388
+ lid: Optional[str],
389
+ ) -> None:
390
+ """Load one or more labware with or without a lid to the flex stacker hopper."""
391
+ assert isinstance(module_core, FlexStackerCore)
392
+ for _ in range(quantity):
393
+ labware_core = self.load_labware(
394
+ load_name=load_name,
395
+ location=module_core,
396
+ label=label,
397
+ namespace=namespace,
398
+ version=version,
399
+ )
400
+ if lid is not None:
401
+ self.load_lid(
402
+ load_name=lid,
403
+ location=labware_core,
404
+ namespace=namespace,
405
+ version=version,
406
+ )
407
+
374
408
  def move_labware(
375
409
  self,
376
410
  labware_core: LabwareCore,
@@ -442,6 +476,203 @@ class ProtocolCore(
442
476
  existing_module_ids=list(self._module_cores_by_id.keys()),
443
477
  )
444
478
 
479
+ def move_lid( # noqa: C901
480
+ self,
481
+ source_location: Union[DeckSlotName, StagingSlotName, LabwareCore],
482
+ new_location: Union[
483
+ DeckSlotName,
484
+ StagingSlotName,
485
+ LabwareCore,
486
+ OffDeckType,
487
+ WasteChute,
488
+ TrashBin,
489
+ ],
490
+ use_gripper: bool,
491
+ pause_for_manual_move: bool,
492
+ pick_up_offset: Optional[Tuple[float, float, float]],
493
+ drop_offset: Optional[Tuple[float, float, float]],
494
+ ) -> LabwareCore | None:
495
+ """Move the given lid to a new location."""
496
+ if use_gripper:
497
+ strategy = LabwareMovementStrategy.USING_GRIPPER
498
+ elif pause_for_manual_move:
499
+ strategy = LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE
500
+ else:
501
+ strategy = LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE
502
+
503
+ if isinstance(source_location, DeckSlotName) or isinstance(
504
+ source_location, StagingSlotName
505
+ ):
506
+ # Find the source labware at the provided deck slot
507
+ labware_in_slot = self._engine_client.state.labware.get_by_slot(
508
+ source_location
509
+ )
510
+ if labware_in_slot is None:
511
+ raise LabwareNotLoadedOnLabwareError(
512
+ "Lid cannot be loaded on non-labware position."
513
+ )
514
+ else:
515
+ labware = LabwareCore(labware_in_slot.id, self._engine_client)
516
+ else:
517
+ labware = source_location
518
+
519
+ # if this is a labware stack, we need to find the labware at the top of the stack
520
+ if labware_validation.is_lid_stack(labware.load_name):
521
+ lid_id = self._engine_client.state.labware.get_highest_child_labware(
522
+ labware.labware_id
523
+ )
524
+ # if this is a labware with a lid, we just need to find its lid_id
525
+ else:
526
+ lid = self._engine_client.state.labware.get_lid_by_labware_id(
527
+ labware.labware_id
528
+ )
529
+ if lid is not None:
530
+ lid_id = lid.id
531
+ else:
532
+ raise ValueError("Cannot move a lid off of a labware with no lid.")
533
+
534
+ _pick_up_offset = (
535
+ LabwareOffsetVector(
536
+ x=pick_up_offset[0], y=pick_up_offset[1], z=pick_up_offset[2]
537
+ )
538
+ if pick_up_offset
539
+ else None
540
+ )
541
+ _drop_offset = (
542
+ LabwareOffsetVector(x=drop_offset[0], y=drop_offset[1], z=drop_offset[2])
543
+ if drop_offset
544
+ else None
545
+ )
546
+
547
+ create_new_lid_stack = False
548
+
549
+ if isinstance(new_location, DeckSlotName) or isinstance(
550
+ new_location, StagingSlotName
551
+ ):
552
+ # Find the destination labware at the provided deck slot
553
+ destination_labware_in_slot = self._engine_client.state.labware.get_by_slot(
554
+ new_location
555
+ )
556
+ if destination_labware_in_slot is None:
557
+ to_location = self._convert_labware_location(location=new_location)
558
+ # absolutely must make a new lid stack
559
+ create_new_lid_stack = True
560
+ else:
561
+ highest_child_location = (
562
+ self._engine_client.state.labware.get_highest_child_labware(
563
+ destination_labware_in_slot.id
564
+ )
565
+ )
566
+ if labware_validation.validate_definition_is_adapter(
567
+ self._engine_client.state.labware.get_definition(
568
+ highest_child_location
569
+ )
570
+ ):
571
+ # absolutely must make a new lid stack
572
+ create_new_lid_stack = True
573
+
574
+ to_location = self._convert_labware_location(
575
+ location=LabwareCore(highest_child_location, self._engine_client)
576
+ )
577
+ elif isinstance(new_location, LabwareCore):
578
+ highest_child_location = (
579
+ self._engine_client.state.labware.get_highest_child_labware(
580
+ new_location.labware_id
581
+ )
582
+ )
583
+ if labware_validation.validate_definition_is_adapter(
584
+ self._engine_client.state.labware.get_definition(highest_child_location)
585
+ ):
586
+ # absolutely must make a new lid stack
587
+ create_new_lid_stack = True
588
+ to_location = self._convert_labware_location(
589
+ location=LabwareCore(highest_child_location, self._engine_client)
590
+ )
591
+ else:
592
+ to_location = self._convert_labware_location(location=new_location)
593
+
594
+ output_result = None
595
+ if create_new_lid_stack:
596
+ # Make a new lid stack object that is empty
597
+ result = self._engine_client.execute_command_without_recovery(
598
+ cmd.LoadLidStackParams(
599
+ location=SYSTEM_LOCATION,
600
+ loadName="empty",
601
+ version=1,
602
+ namespace="empty",
603
+ quantity=0,
604
+ )
605
+ )
606
+
607
+ # Move the lid stack object from the SYSTEM_LOCATION space to the desired deck location
608
+ self._engine_client.execute_command(
609
+ cmd.MoveLabwareParams(
610
+ labwareId=result.stackLabwareId,
611
+ newLocation=to_location,
612
+ strategy=LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE,
613
+ pickUpOffset=None,
614
+ dropOffset=None,
615
+ )
616
+ )
617
+
618
+ output_result = LabwareCore(
619
+ labware_id=result.stackLabwareId, engine_client=self._engine_client
620
+ )
621
+ destination = self._convert_labware_location(location=output_result)
622
+ else:
623
+ destination = to_location
624
+
625
+ self._engine_client.execute_command(
626
+ cmd.MoveLabwareParams(
627
+ labwareId=lid_id,
628
+ newLocation=destination,
629
+ strategy=strategy,
630
+ pickUpOffset=_pick_up_offset,
631
+ dropOffset=_drop_offset,
632
+ )
633
+ )
634
+
635
+ # Handle leftover empty lid stack if there is one
636
+ if (
637
+ labware_validation.is_lid_stack(labware.load_name)
638
+ and self._engine_client.state.labware.get_highest_child_labware(
639
+ labware_id=labware.labware_id
640
+ )
641
+ == labware.labware_id
642
+ ):
643
+ # The originating lid stack is now empty, so we need to move it to the SYSTEM_LOCATION
644
+ self._engine_client.execute_command(
645
+ cmd.MoveLabwareParams(
646
+ labwareId=labware.labware_id,
647
+ newLocation=SYSTEM_LOCATION,
648
+ strategy=LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE,
649
+ pickUpOffset=None,
650
+ dropOffset=None,
651
+ )
652
+ )
653
+
654
+ if strategy == LabwareMovementStrategy.USING_GRIPPER:
655
+ # Clear out last location since it is not relevant to pipetting
656
+ # and we only use last location for in-place pipetting commands
657
+ self.set_last_location(location=None, mount=Mount.EXTENSION)
658
+
659
+ # FIXME(jbl, 2024-01-04) deck conflict after execution logic issue, read notes in load_labware for more info:
660
+ deck_conflict.check(
661
+ engine_state=self._engine_client.state,
662
+ new_labware_id=lid_id,
663
+ existing_disposal_locations=self._disposal_locations,
664
+ # TODO: We can now fetch these IDs from engine too.
665
+ # See comment in self.load_labware().
666
+ existing_labware_ids=[
667
+ labware_id
668
+ for labware_id in self._labware_cores_by_id
669
+ if labware_id != labware_id
670
+ ],
671
+ existing_module_ids=list(self._module_cores_by_id.keys()),
672
+ )
673
+
674
+ return output_result
675
+
445
676
  def _resolve_module_hardware(
446
677
  self, serial_number: str, model: ModuleModel
447
678
  ) -> AbstractModule:
@@ -527,6 +758,7 @@ class ProtocolCore(
527
758
  ModuleType.THERMOCYCLER: ThermocyclerModuleCore,
528
759
  ModuleType.HEATER_SHAKER: HeaterShakerModuleCore,
529
760
  ModuleType.ABSORBANCE_READER: AbsorbanceReaderCore,
761
+ ModuleType.FLEX_STACKER: FlexStackerCore,
530
762
  }
531
763
 
532
764
  module_type = load_module_result.model.as_type()
@@ -557,6 +789,15 @@ class ProtocolCore(
557
789
  load_module_result=load_module_result, model=model
558
790
  )
559
791
 
792
+ def add_or_get_labware_core(self, labware_id: str) -> LabwareCore:
793
+ """Create a LabwareCore and add it to the map or return one if it exists."""
794
+ if labware_id in self._labware_cores_by_id:
795
+ return self._labware_cores_by_id[labware_id]
796
+ else:
797
+ core = LabwareCore(labware_id, self._engine_client)
798
+ self._labware_cores_by_id[labware_id] = core
799
+ return core
800
+
560
801
  def load_robot(self) -> RobotCore:
561
802
  """Load a robot core into the RobotContext."""
562
803
  return RobotCore(
@@ -720,7 +961,7 @@ class ProtocolCore(
720
961
  self._engine_client.state.labware.find_custom_labware_load_params()
721
962
  )
722
963
  namespace, version = load_labware_params.resolve(
723
- load_name, namespace, version, custom_labware_params
964
+ load_name, namespace, version, custom_labware_params, self._api_version
724
965
  )
725
966
 
726
967
  load_result = self._engine_client.execute_command_without_recovery(
@@ -734,6 +975,7 @@ class ProtocolCore(
734
975
  )
735
976
 
736
977
  # FIXME(CHB, 2024-12-04) just like load labware and load adapter we have a validating after loading the object issue
978
+ assert load_result.definition is not None
737
979
  validation.ensure_definition_is_lid(load_result.definition)
738
980
 
739
981
  deck_conflict.check(
@@ -803,9 +1045,9 @@ class ProtocolCore(
803
1045
  labware_id = self._engine_client.state.labware.get_id_by_module(
804
1046
  module_core.module_id
805
1047
  )
806
- return self._labware_cores_by_id[labware_id]
807
1048
  except LabwareNotLoadedOnModuleError:
808
1049
  return None
1050
+ return self.add_or_get_labware_core(labware_id)
809
1051
 
810
1052
  def get_labware_on_labware(
811
1053
  self, labware_core: LabwareCore
@@ -815,9 +1057,9 @@ class ProtocolCore(
815
1057
  labware_id = self._engine_client.state.labware.get_id_by_labware(
816
1058
  labware_core.labware_id
817
1059
  )
818
- return self._labware_cores_by_id[labware_id]
819
1060
  except LabwareNotLoadedOnLabwareError:
820
1061
  return None
1062
+ return self.add_or_get_labware_core(labware_id)
821
1063
 
822
1064
  def get_slot_center(self, slot_name: Union[DeckSlotName, StagingSlotName]) -> Point:
823
1065
  """Get the absolute coordinate of a slot's center."""
@@ -905,7 +1147,7 @@ class ProtocolCore(
905
1147
  WasteChute,
906
1148
  TrashBin,
907
1149
  ],
908
- ) -> LabwareLocation:
1150
+ ) -> LoadableLabwareLocation:
909
1151
  if isinstance(location, LabwareCore):
910
1152
  return OnLabwareLocation(labwareId=location.labware_id)
911
1153
  else:
@@ -921,7 +1163,7 @@ class ProtocolCore(
921
1163
  OffDeckType,
922
1164
  WasteChute,
923
1165
  TrashBin,
924
- ]
1166
+ ],
925
1167
  ) -> NonStackedLocation:
926
1168
  if isinstance(location, (ModuleCore, NonConnectedModuleCore)):
927
1169
  return ModuleLocation(moduleId=location.module_id)
@@ -5,6 +5,7 @@ from opentrons.protocol_engine.types import (
5
5
  ModuleLocation,
6
6
  OnLabwareLocation,
7
7
  AddressableAreaLocation,
8
+ InStackerHopperLocation,
8
9
  )
9
10
 
10
11
 
@@ -17,6 +18,15 @@ def well(engine_client: SyncClient, well_name: str, labware_id: str) -> str:
17
18
  return f"{well_name} of {_labware_location_string(engine_client, labware_location)}"
18
19
 
19
20
 
21
+ def _module_in_location_string(module_id: str, engine_client: SyncClient) -> str:
22
+ module_name = engine_client.state.modules.get_definition(
23
+ module_id=module_id
24
+ ).displayName
25
+ module_on = engine_client.state.modules.get_location(module_id=module_id)
26
+ module_on_string = _labware_location_string(engine_client, module_on)
27
+ return f"{module_name} on {module_on_string}"
28
+
29
+
20
30
  def _labware_location_string(
21
31
  engine_client: SyncClient, location: LabwareLocation
22
32
  ) -> str:
@@ -26,14 +36,7 @@ def _labware_location_string(
26
36
  return f"slot {location.slotName.id}"
27
37
 
28
38
  elif isinstance(location, ModuleLocation):
29
- module_name = engine_client.state.modules.get_definition(
30
- module_id=location.moduleId
31
- ).displayName
32
- module_on = engine_client.state.modules.get_location(
33
- module_id=location.moduleId
34
- )
35
- module_on_string = _labware_location_string(engine_client, module_on)
36
- return f"{module_name} on {module_on_string}"
39
+ return _module_in_location_string(location.moduleId, engine_client)
37
40
 
38
41
  elif isinstance(location, OnLabwareLocation):
39
42
  labware_name = _labware_name(engine_client, location.labwareId)
@@ -50,6 +53,14 @@ def _labware_location_string(
50
53
  elif location == "offDeck":
51
54
  return "[off-deck]"
52
55
 
56
+ elif location == "systemLocation":
57
+ return "[systemLocation]"
58
+
59
+ elif isinstance(location, InStackerHopperLocation):
60
+ return (
61
+ f"stored in {_module_in_location_string(location.moduleId, engine_client)}"
62
+ )
63
+
53
64
 
54
65
  def _labware_name(engine_client: SyncClient, labware_id: str) -> str:
55
66
  """Return the user-specified labware label, or fall back to the display name from the def."""