opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.0a0__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 (191) 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 +7 -2
  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 +245 -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 +2 -9
  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 +3 -1
  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/errors/__init__.py +8 -0
  119. opentrons/protocol_engine/errors/exceptions.py +50 -0
  120. opentrons/protocol_engine/execution/equipment.py +123 -106
  121. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  122. opentrons/protocol_engine/execution/pipetting.py +233 -26
  123. opentrons/protocol_engine/execution/tip_handler.py +14 -5
  124. opentrons/protocol_engine/labware_offset_standardization.py +173 -0
  125. opentrons/protocol_engine/protocol_engine.py +22 -13
  126. opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
  127. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  128. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  129. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  130. opentrons/protocol_engine/slot_standardization.py +11 -23
  131. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  132. opentrons/protocol_engine/state/frustum_helpers.py +26 -10
  133. opentrons/protocol_engine/state/geometry.py +683 -100
  134. opentrons/protocol_engine/state/labware.py +252 -55
  135. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  136. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  137. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  138. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  139. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  140. opentrons/protocol_engine/state/modules.py +178 -52
  141. opentrons/protocol_engine/state/pipettes.py +54 -0
  142. opentrons/protocol_engine/state/state.py +1 -1
  143. opentrons/protocol_engine/state/tips.py +14 -0
  144. opentrons/protocol_engine/state/update_types.py +180 -25
  145. opentrons/protocol_engine/state/wells.py +54 -8
  146. opentrons/protocol_engine/types/__init__.py +292 -0
  147. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  148. opentrons/protocol_engine/types/command_annotations.py +53 -0
  149. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  150. opentrons/protocol_engine/types/execution.py +96 -0
  151. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  152. opentrons/protocol_engine/types/instrument.py +47 -0
  153. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  154. opentrons/protocol_engine/types/labware.py +110 -0
  155. opentrons/protocol_engine/types/labware_movement.py +22 -0
  156. opentrons/protocol_engine/types/labware_offset_location.py +108 -0
  157. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  158. opentrons/protocol_engine/types/liquid.py +40 -0
  159. opentrons/protocol_engine/types/liquid_class.py +59 -0
  160. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  161. opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
  162. opentrons/protocol_engine/types/location.py +193 -0
  163. opentrons/protocol_engine/types/module.py +269 -0
  164. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  165. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  166. opentrons/protocol_engine/types/tip.py +18 -0
  167. opentrons/protocol_engine/types/util.py +21 -0
  168. opentrons/protocol_engine/types/well_position.py +107 -0
  169. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  170. opentrons/protocol_reader/file_format_validator.py +5 -3
  171. opentrons/protocol_runner/json_translator.py +4 -2
  172. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  173. opentrons/protocol_runner/run_orchestrator.py +4 -1
  174. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  175. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  176. opentrons/protocols/api_support/definitions.py +1 -1
  177. opentrons/protocols/api_support/instrument.py +16 -3
  178. opentrons/protocols/labware.py +5 -6
  179. opentrons/protocols/models/__init__.py +0 -21
  180. opentrons/simulate.py +4 -2
  181. opentrons/types.py +15 -6
  182. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/METADATA +4 -4
  183. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/RECORD +187 -147
  184. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  185. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  186. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  187. opentrons/protocol_engine/types.py +0 -1311
  188. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/LICENSE +0 -0
  189. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/WHEEL +0 -0
  190. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/entry_points.txt +0 -0
  191. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,8 @@
3
3
  from typing import List, Optional, cast, Dict
4
4
 
5
5
  from opentrons_shared_data.labware.types import (
6
- LabwareParameters as LabwareParametersDict,
6
+ LabwareParameters2 as LabwareParameters2Dict,
7
+ LabwareParameters3 as LabwareParameters3Dict,
7
8
  LabwareDefinition as LabwareDefinitionDict,
8
9
  )
9
10
 
@@ -29,6 +30,9 @@ from ..labware import AbstractLabware, LabwareLoadParams
29
30
  from .well import WellCore
30
31
 
31
32
 
33
+ _LabwareParametersDict = LabwareParameters2Dict | LabwareParameters3Dict
34
+
35
+
32
36
  class LabwareCore(AbstractLabware[WellCore]):
33
37
  """Labware API core using a ProtocolEngine.
34
38
 
@@ -96,9 +100,9 @@ class LabwareCore(AbstractLabware[WellCore]):
96
100
  LabwareDefinitionDict, self._definition.model_dump(exclude_none=True)
97
101
  )
98
102
 
99
- def get_parameters(self) -> LabwareParametersDict:
103
+ def get_parameters(self) -> _LabwareParametersDict:
100
104
  return cast(
101
- LabwareParametersDict,
105
+ _LabwareParametersDict,
102
106
  self._definition.parameters.model_dump(exclude_none=True),
103
107
  )
104
108
 
@@ -122,7 +126,7 @@ class LabwareCore(AbstractLabware[WellCore]):
122
126
 
123
127
  request = LabwareOffsetCreate.model_construct(
124
128
  definitionUri=self.get_uri(),
125
- location=offset_location,
129
+ locationSequence=offset_location,
126
130
  vector=LabwareOffsetVector(x=delta.x, y=delta.y, z=delta.z),
127
131
  )
128
132
  self._engine_client.add_labware_offset(request)
@@ -1,7 +1,6 @@
1
- from typing import Dict, Tuple, List, Optional
2
-
3
1
  from opentrons.protocols.api_support.constants import OPENTRONS_NAMESPACE
4
2
  from opentrons.protocol_engine.state.labware import LabwareLoadParams
3
+ from opentrons.protocols.api_support.types import APIVersion
5
4
 
6
5
 
7
6
  # Default versions of Opentrons standard labware definitions in Python Protocol API
@@ -9,7 +8,7 @@ from opentrons.protocol_engine.state.labware import LabwareLoadParams
9
8
  #
10
9
  # TODO(jbl 2023-08-01) this needs to be done more holistically, both to find the version and make sure that
11
10
  # it corresponds to the API level is was released with
12
- _APILEVEL_2_14_OT_DEFAULT_VERSIONS: Dict[str, int] = {
11
+ _APILEVEL_2_14_OT_DEFAULT_VERSIONS: dict[str, int] = {
13
12
  # v1 of many labware definitions have wrong `zDimension`s. (Jira RSS-202.)
14
13
  # For "opentrons_96_aluminumblock_generic_pcr_strip_200ul" and
15
14
  # "opentrons_24_aluminumblock_generic_2ml_screwcap", they're wrong enough to
@@ -34,6 +33,54 @@ _APILEVEL_2_14_OT_DEFAULT_VERSIONS: Dict[str, int] = {
34
33
  "corning_24_wellplate_3.4ml_flat": 2,
35
34
  }
36
35
 
36
+ _APILEVEL_2_23_OT_DEFAULT_VERSIONS: dict[str, int] = {
37
+ "agilent_1_reservoir_290ml": 2,
38
+ "appliedbiosystemsmicroamp_384_wellplate_40ul": 2,
39
+ "armadillo_96_wellplate_200ul_pcr_full_skirt": 3,
40
+ "axygen_1_reservoir_90ml": 2,
41
+ "biorad_384_wellplate_50ul": 3,
42
+ "biorad_96_wellplate_200ul_pcr": 3,
43
+ "corning_12_wellplate_6.9ml_flat": 3,
44
+ "corning_24_wellplate_3.4ml_flat": 3,
45
+ "corning_384_wellplate_112ul_flat": 3,
46
+ "corning_48_wellplate_1.6ml_flat": 3,
47
+ "corning_6_wellplate_16.8ml_flat": 3,
48
+ "corning_96_wellplate_360ul_flat": 3,
49
+ "nest_12_reservoir_15ml": 2,
50
+ "nest_1_reservoir_195ml": 3,
51
+ "nest_1_reservoir_290ml": 2,
52
+ "nest_96_wellplate_100ul_pcr_full_skirt": 3,
53
+ "nest_96_wellplate_200ul_flat": 3,
54
+ "nest_96_wellplate_2ml_deep": 3,
55
+ "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical": 2,
56
+ "opentrons_10_tuberack_nest_4x50ml_6x15ml_conical": 2,
57
+ "opentrons_15_tuberack_falcon_15ml_conical": 2,
58
+ "opentrons_15_tuberack_nest_15ml_conical": 2,
59
+ "opentrons_24_aluminumblock_generic_2ml_screwcap": 3,
60
+ "opentrons_24_aluminumblock_nest_0.5ml_screwcap": 2,
61
+ "opentrons_24_aluminumblock_nest_1.5ml_screwcap": 2,
62
+ "opentrons_24_aluminumblock_nest_1.5ml_snapcap": 2,
63
+ "opentrons_24_aluminumblock_nest_2ml_screwcap": 2,
64
+ "opentrons_24_aluminumblock_nest_2ml_snapcap": 2,
65
+ "opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap": 2,
66
+ "opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap": 2,
67
+ "opentrons_24_tuberack_generic_2ml_screwcap": 2,
68
+ "opentrons_24_tuberack_nest_0.5ml_screwcap": 2,
69
+ "opentrons_24_tuberack_nest_1.5ml_screwcap": 2,
70
+ "opentrons_24_tuberack_nest_1.5ml_snapcap": 2,
71
+ "opentrons_24_tuberack_nest_2ml_screwcap": 2,
72
+ "opentrons_24_tuberack_nest_2ml_snapcap": 2,
73
+ "opentrons_6_tuberack_falcon_50ml_conical": 2,
74
+ "opentrons_6_tuberack_nest_50ml_conical": 2,
75
+ "opentrons_96_aluminumblock_generic_pcr_strip_200ul": 3,
76
+ "opentrons_96_wellplate_200ul_pcr_full_skirt": 3,
77
+ "opentrons_tough_pcr_auto_sealing_lid": 2,
78
+ "thermoscientificnunc_96_wellplate_1300ul": 2,
79
+ "thermoscientificnunc_96_wellplate_2000ul": 2,
80
+ "usascientific_12_reservoir_22ml": 2,
81
+ "usascientific_96_wellplate_2.4ml_deep": 2,
82
+ }
83
+
37
84
 
38
85
  class AmbiguousLoadLabwareParamsError(RuntimeError):
39
86
  """Error raised when specific labware parameters cannot be found due to multiple matching labware definitions."""
@@ -41,10 +88,11 @@ class AmbiguousLoadLabwareParamsError(RuntimeError):
41
88
 
42
89
  def resolve(
43
90
  load_name: str,
44
- namespace: Optional[str],
45
- version: Optional[int],
46
- custom_load_labware_params: List[LabwareLoadParams],
47
- ) -> Tuple[str, int]:
91
+ namespace: str | None,
92
+ version: int | None,
93
+ custom_load_labware_params: list[LabwareLoadParams],
94
+ api_version: APIVersion,
95
+ ) -> tuple[str, int]:
48
96
  """Resolve the load labware parameters that best matches any custom labware, or default to opentrons standards
49
97
 
50
98
  Args:
@@ -82,7 +130,9 @@ def resolve(
82
130
  resolved_version = (
83
131
  version
84
132
  if version is not None
85
- else _get_default_version_for_standard_labware(load_name=load_name)
133
+ else _get_default_version_for_standard_labware(
134
+ load_name=load_name, api_version=api_version
135
+ )
86
136
  )
87
137
 
88
138
  elif len(filtered_custom_params) > 1:
@@ -99,7 +149,15 @@ def resolve(
99
149
  return resolved_namespace, resolved_version
100
150
 
101
151
 
102
- def _get_default_version_for_standard_labware(load_name: str) -> int:
152
+ def _get_default_version_for_standard_labware(
153
+ load_name: str, api_version: APIVersion
154
+ ) -> int:
103
155
  # We know the protocol is running at least apiLevel 2.14 by this point because
104
156
  # apiLevel 2.13 and below has its own separate code path for resolving labware.
105
- return _APILEVEL_2_14_OT_DEFAULT_VERSIONS.get(load_name, 1)
157
+ if (
158
+ api_version >= APIVersion(2, 23)
159
+ and load_name in _APILEVEL_2_23_OT_DEFAULT_VERSIONS
160
+ ):
161
+ return _APILEVEL_2_23_OT_DEFAULT_VERSIONS[load_name]
162
+ else:
163
+ return _APILEVEL_2_14_OT_DEFAULT_VERSIONS.get(load_name, 1)
@@ -1,4 +1,5 @@
1
1
  """Protocol API module implementation logic."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Optional, List, Dict, Union
@@ -17,7 +18,7 @@ from opentrons.drivers.types import (
17
18
  )
18
19
 
19
20
  from opentrons.protocol_engine import commands as cmd
20
- from opentrons.protocol_engine.types import ABSMeasureMode
21
+ from opentrons.protocol_engine.types import ABSMeasureMode, StackerFillEmptyStrategy
21
22
  from opentrons.types import DeckSlotName
22
23
  from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
23
24
  from opentrons.protocol_engine.errors.exceptions import (
@@ -37,9 +38,10 @@ from ..module import (
37
38
  AbstractHeaterShakerCore,
38
39
  AbstractMagneticBlockCore,
39
40
  AbstractAbsorbanceReaderCore,
41
+ AbstractFlexStackerCore,
40
42
  )
41
43
  from .exceptions import InvalidMagnetEngageHeightError
42
-
44
+ from . import load_labware_params
43
45
 
44
46
  # Valid wavelength range for absorbance reader
45
47
  ABS_WAVELENGTH_MIN = 350
@@ -692,3 +694,117 @@ class AbsorbanceReaderCore(ModuleCore, AbstractAbsorbanceReaderCore):
692
694
  self.module_id
693
695
  )
694
696
  return abs_state.is_lid_on
697
+
698
+
699
+ class FlexStackerCore(ModuleCore, AbstractFlexStackerCore):
700
+ """Flex Stacker core logic implementation for Python protocols."""
701
+
702
+ _sync_module_hardware: SynchronousAdapter[hw_modules.FlexStacker]
703
+
704
+ def retrieve(self) -> None:
705
+ """Retrieve a labware from the Flex Stacker's hopper."""
706
+ self._engine_client.execute_command(
707
+ cmd.flex_stacker.RetrieveParams(
708
+ moduleId=self.module_id,
709
+ )
710
+ )
711
+
712
+ def store(self) -> None:
713
+ """Store a labware into Flex Stacker's hopper."""
714
+ self._engine_client.execute_command(
715
+ cmd.flex_stacker.StoreParams(
716
+ moduleId=self.module_id,
717
+ )
718
+ )
719
+
720
+ def fill(self, message: str, count: int | None) -> None:
721
+ """Pause the protocol to add more labware to the Flex Stacker's hopper."""
722
+ self._engine_client.execute_command(
723
+ cmd.flex_stacker.FillParams(
724
+ moduleId=self.module_id,
725
+ strategy=StackerFillEmptyStrategy.MANUAL_WITH_PAUSE,
726
+ message=message,
727
+ count=count,
728
+ )
729
+ )
730
+
731
+ def empty(self, message: str) -> None:
732
+ """Pause the protocol to remove labware from the Flex Stacker's hopper."""
733
+ self._engine_client.execute_command(
734
+ cmd.flex_stacker.EmptyParams(
735
+ moduleId=self.module_id,
736
+ strategy=StackerFillEmptyStrategy.MANUAL_WITH_PAUSE,
737
+ message=message,
738
+ count=0,
739
+ )
740
+ )
741
+
742
+ def set_stored_labware(
743
+ self,
744
+ main_load_name: str,
745
+ main_namespace: str | None,
746
+ main_version: int | None,
747
+ lid_load_name: str | None,
748
+ lid_namespace: str | None,
749
+ lid_version: int | None,
750
+ adapter_load_name: str | None,
751
+ adapter_namespace: str | None,
752
+ adapter_version: int | None,
753
+ count: int | None,
754
+ ) -> None:
755
+ """Configure the kind of labware that the stacker stores."""
756
+
757
+ custom_labware_params = (
758
+ self._engine_client.state.labware.find_custom_labware_load_params()
759
+ )
760
+
761
+ main_namespace, main_version = load_labware_params.resolve(
762
+ main_load_name,
763
+ main_namespace,
764
+ main_version,
765
+ custom_labware_params,
766
+ self._api_version,
767
+ )
768
+ main_labware = cmd.flex_stacker.StackerStoredLabwareDetails(
769
+ loadName=main_load_name, namespace=main_namespace, version=main_version
770
+ )
771
+
772
+ lid_labware: cmd.flex_stacker.StackerStoredLabwareDetails | None = None
773
+
774
+ if lid_load_name:
775
+ lid_namespace, lid_version = load_labware_params.resolve(
776
+ lid_load_name,
777
+ lid_namespace,
778
+ lid_version,
779
+ custom_labware_params,
780
+ self._api_version,
781
+ )
782
+ lid_labware = cmd.flex_stacker.StackerStoredLabwareDetails(
783
+ loadName=lid_load_name, namespace=lid_namespace, version=lid_version
784
+ )
785
+
786
+ adapter_labware: cmd.flex_stacker.StackerStoredLabwareDetails | None = None
787
+
788
+ if adapter_load_name:
789
+ adapter_namespace, adapter_version = load_labware_params.resolve(
790
+ adapter_load_name,
791
+ adapter_namespace,
792
+ adapter_version,
793
+ custom_labware_params,
794
+ self._api_version,
795
+ )
796
+ adapter_labware = cmd.flex_stacker.StackerStoredLabwareDetails(
797
+ loadName=adapter_load_name,
798
+ namespace=adapter_namespace,
799
+ version=adapter_version,
800
+ )
801
+
802
+ self._engine_client.execute_command(
803
+ cmd.flex_stacker.SetStoredLabwareParams(
804
+ moduleId=self.module_id,
805
+ initialCount=count,
806
+ primaryLabware=main_labware,
807
+ lidLabware=lid_labware,
808
+ adapterLabware=adapter_labware,
809
+ )
810
+ )
@@ -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)