opentrons 8.7.0a1__py3-none-any.whl → 8.7.0a2__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 (119) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/drivers/thermocycler/abstract.py +1 -0
  3. opentrons/drivers/thermocycler/driver.py +33 -4
  4. opentrons/drivers/thermocycler/simulator.py +2 -0
  5. opentrons/hardware_control/api.py +24 -5
  6. opentrons/hardware_control/backends/controller.py +8 -2
  7. opentrons/hardware_control/backends/ot3controller.py +3 -0
  8. opentrons/hardware_control/backends/ot3simulator.py +2 -1
  9. opentrons/hardware_control/backends/simulator.py +2 -1
  10. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  11. opentrons/hardware_control/module_control.py +82 -8
  12. opentrons/hardware_control/modules/__init__.py +3 -0
  13. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  14. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  15. opentrons/hardware_control/modules/heater_shaker.py +30 -5
  16. opentrons/hardware_control/modules/magdeck.py +8 -4
  17. opentrons/hardware_control/modules/mod_abc.py +13 -5
  18. opentrons/hardware_control/modules/tempdeck.py +25 -5
  19. opentrons/hardware_control/modules/thermocycler.py +56 -10
  20. opentrons/hardware_control/modules/types.py +20 -1
  21. opentrons/hardware_control/modules/utils.py +11 -4
  22. opentrons/hardware_control/nozzle_manager.py +3 -0
  23. opentrons/hardware_control/ot3api.py +26 -5
  24. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  25. opentrons/hardware_control/types.py +31 -2
  26. opentrons/legacy_commands/protocol_commands.py +20 -0
  27. opentrons/legacy_commands/types.py +42 -0
  28. opentrons/motion_planning/waypoints.py +15 -29
  29. opentrons/protocol_api/__init__.py +5 -0
  30. opentrons/protocol_api/_types.py +6 -1
  31. opentrons/protocol_api/core/common.py +3 -1
  32. opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
  33. opentrons/protocol_api/core/engine/labware.py +8 -1
  34. opentrons/protocol_api/core/engine/module_core.py +4 -0
  35. opentrons/protocol_api/core/engine/protocol.py +18 -1
  36. opentrons/protocol_api/core/engine/tasks.py +35 -0
  37. opentrons/protocol_api/core/legacy/legacy_module_core.py +2 -0
  38. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
  39. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  40. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  41. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  42. opentrons/protocol_api/core/module.py +1 -0
  43. opentrons/protocol_api/core/protocol.py +11 -2
  44. opentrons/protocol_api/core/tasks.py +31 -0
  45. opentrons/protocol_api/module_contexts.py +1 -0
  46. opentrons/protocol_api/protocol_context.py +26 -4
  47. opentrons/protocol_api/robot_context.py +38 -21
  48. opentrons/protocol_api/tasks.py +48 -0
  49. opentrons/protocol_api/validation.py +6 -1
  50. opentrons/protocol_engine/actions/__init__.py +4 -2
  51. opentrons/protocol_engine/actions/actions.py +22 -9
  52. opentrons/protocol_engine/clients/sync_client.py +6 -7
  53. opentrons/protocol_engine/commands/__init__.py +42 -0
  54. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  55. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  56. opentrons/protocol_engine/commands/aspirate.py +1 -0
  57. opentrons/protocol_engine/commands/command.py +1 -0
  58. opentrons/protocol_engine/commands/command_unions.py +39 -0
  59. opentrons/protocol_engine/commands/create_timer.py +83 -0
  60. opentrons/protocol_engine/commands/dispense.py +1 -0
  61. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  62. opentrons/protocol_engine/commands/movement_common.py +2 -0
  63. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  64. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  65. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  66. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  67. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +17 -1
  68. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  69. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  70. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  71. opentrons/protocol_engine/errors/__init__.py +4 -0
  72. opentrons/protocol_engine/errors/exceptions.py +55 -0
  73. opentrons/protocol_engine/execution/__init__.py +2 -0
  74. opentrons/protocol_engine/execution/command_executor.py +8 -0
  75. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  76. opentrons/protocol_engine/execution/labware_movement.py +9 -12
  77. opentrons/protocol_engine/execution/movement.py +2 -0
  78. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  79. opentrons/protocol_engine/execution/run_control.py +8 -0
  80. opentrons/protocol_engine/execution/task_handler.py +157 -0
  81. opentrons/protocol_engine/protocol_engine.py +67 -33
  82. opentrons/protocol_engine/resources/__init__.py +2 -0
  83. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  84. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  85. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  86. opentrons/protocol_engine/state/_well_math.py +60 -18
  87. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  88. opentrons/protocol_engine/state/commands.py +7 -7
  89. opentrons/protocol_engine/state/geometry.py +204 -374
  90. opentrons/protocol_engine/state/labware.py +52 -102
  91. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  92. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
  93. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  94. opentrons/protocol_engine/state/modules.py +21 -8
  95. opentrons/protocol_engine/state/motion.py +44 -0
  96. opentrons/protocol_engine/state/state.py +14 -0
  97. opentrons/protocol_engine/state/state_summary.py +2 -0
  98. opentrons/protocol_engine/state/tasks.py +139 -0
  99. opentrons/protocol_engine/state/tips.py +177 -258
  100. opentrons/protocol_engine/state/update_types.py +16 -9
  101. opentrons/protocol_engine/types/__init__.py +9 -3
  102. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  103. opentrons/protocol_engine/types/instrument.py +8 -1
  104. opentrons/protocol_engine/types/labware.py +1 -13
  105. opentrons/protocol_engine/types/module.py +10 -0
  106. opentrons/protocol_engine/types/tasks.py +38 -0
  107. opentrons/protocol_engine/types/tip.py +9 -0
  108. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  109. opentrons/protocol_runner/run_orchestrator.py +18 -2
  110. opentrons/protocols/api_support/definitions.py +1 -1
  111. opentrons/protocols/api_support/types.py +2 -1
  112. opentrons/simulate.py +48 -15
  113. opentrons/system/camera.py +1 -1
  114. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/METADATA +4 -4
  115. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/RECORD +118 -105
  116. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  117. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/WHEEL +0 -0
  118. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/entry_points.txt +0 -0
  119. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -2,12 +2,26 @@ from asyncio import Queue
2
2
  import enum
3
3
  import logging
4
4
  from dataclasses import dataclass
5
- from typing import cast, Tuple, Union, List, Callable, Dict, TypeVar, Type
5
+ from typing import (
6
+ cast,
7
+ Tuple,
8
+ Union,
9
+ List,
10
+ Callable,
11
+ Dict,
12
+ TypeVar,
13
+ Type,
14
+ TYPE_CHECKING,
15
+ )
6
16
  from typing_extensions import Literal
7
17
  from opentrons import types as top_types
8
18
  from opentrons_shared_data.pipette.types import PipetteChannelType
19
+ from opentrons_shared_data.errors.exceptions import EnumeratedError
9
20
  from opentrons.config import feature_flags
10
21
 
22
+ if TYPE_CHECKING:
23
+ from .modules.types import ModuleModel
24
+
11
25
  MODULE_LOG = logging.getLogger(__name__)
12
26
 
13
27
 
@@ -384,6 +398,7 @@ class HardwareEventType(enum.Enum):
384
398
  DOOR_SWITCH_CHANGE = enum.auto()
385
399
  ERROR_MESSAGE = enum.auto()
386
400
  ESTOP_CHANGE = enum.auto()
401
+ ASYNCHRONOUS_MODULE_ERROR = enum.auto()
387
402
 
388
403
 
389
404
  @dataclass
@@ -428,10 +443,24 @@ class ErrorMessageNotification:
428
443
  event: Literal[HardwareEventType.ERROR_MESSAGE] = HardwareEventType.ERROR_MESSAGE
429
444
 
430
445
 
446
+ @dataclass(frozen=True)
447
+ class AsynchronousModuleErrorNotification:
448
+ exception: EnumeratedError
449
+ module_serial: str | None
450
+ module_model: "ModuleModel"
451
+ port: str
452
+ event: Literal[
453
+ HardwareEventType.ASYNCHRONOUS_MODULE_ERROR
454
+ ] = HardwareEventType.ASYNCHRONOUS_MODULE_ERROR
455
+
456
+
431
457
  # new event types get new dataclasses
432
458
  # when we add more event types we add them here
433
459
  HardwareEvent = Union[
434
- DoorStateNotification, ErrorMessageNotification, EstopStateNotification
460
+ DoorStateNotification,
461
+ ErrorMessageNotification,
462
+ EstopStateNotification,
463
+ AsynchronousModuleErrorNotification,
435
464
  ]
436
465
 
437
466
  HardwareEventHandler = Callable[[HardwareEvent], None]
@@ -1,6 +1,7 @@
1
1
  from datetime import timedelta
2
2
  from typing import Optional
3
3
  from . import types as command_types
4
+ from opentrons.protocol_api.tasks import Task
4
5
 
5
6
 
6
7
  def comment(msg: str) -> command_types.CommentCommand:
@@ -52,3 +53,22 @@ def move_labware(text: str) -> command_types.MoveLabwareCommand:
52
53
  "name": command_types.MOVE_LABWARE,
53
54
  "payload": {"text": text},
54
55
  }
56
+
57
+
58
+ def wait_for_tasks(tasks: list[Task]) -> command_types.WaitForTasksCommand:
59
+ task_ids = [task.created_at.strftime("%Y-%m-%d %H:%M:%S") for task in tasks]
60
+ msg = f"Waiting for tasks that started at: {task_ids}."
61
+ return {
62
+ "name": command_types.WAIT_FOR_TASKS,
63
+ "payload": {"text": msg},
64
+ }
65
+
66
+
67
+ def create_timer(seconds: float) -> command_types.CreateTimerCommand:
68
+ return {
69
+ "name": command_types.CREATE_TIMER,
70
+ "payload": {
71
+ "text": f"Creating background timer for {seconds} seconds.",
72
+ "time": seconds,
73
+ },
74
+ }
@@ -102,6 +102,10 @@ ROBOT_MOVE_RELATIVE_TO: Final = "command.ROBOT_MOVE_RELATIVE_TO"
102
102
  ROBOT_OPEN_GRIPPER_JAW: Final = "command.ROBOT_OPEN_GRIPPER_JAW"
103
103
  ROBOT_CLOSE_GRIPPER_JAW: Final = "command.ROBOT_CLOSE_GRIPPER_JAW"
104
104
 
105
+ # Tasks #
106
+ WAIT_FOR_TASKS: Final = "command.WAIT_FOR_TASKS"
107
+ CREATE_TIMER: Final = "command.CREATE_TIMER"
108
+
105
109
 
106
110
  class TextOnlyPayload(TypedDict):
107
111
  text: str
@@ -714,6 +718,27 @@ class RobotCloseGripperJawCommand(TypedDict):
714
718
  payload: GripperCommandPayload
715
719
 
716
720
 
721
+ # Task Commands and Payloads
722
+
723
+
724
+ class WaitForTasksPayload(TextOnlyPayload):
725
+ pass
726
+
727
+
728
+ class CreateTimerPayload(TextOnlyPayload):
729
+ time: float
730
+
731
+
732
+ class WaitForTasksCommand(TypedDict):
733
+ name: Literal["command.WAIT_FOR_TASKS"]
734
+ payload: WaitForTasksPayload
735
+
736
+
737
+ class CreateTimerCommand(TypedDict):
738
+ name: Literal["command.CREATE_TIMER"]
739
+ payload: CreateTimerPayload
740
+
741
+
717
742
  Command = Union[
718
743
  DropTipCommand,
719
744
  DropTipInDisposalLocationCommand,
@@ -782,6 +807,9 @@ Command = Union[
782
807
  FlexStackerStoreCommand,
783
808
  FlexStackerEmptyCommand,
784
809
  FlexStackerFillCommand,
810
+ # Task commands
811
+ WaitForTasksCommand,
812
+ CreateTimerCommand,
785
813
  ]
786
814
 
787
815
 
@@ -842,6 +870,9 @@ CommandPayload = Union[
842
870
  RobotMoveAxisRelativeCommandPayload,
843
871
  RobotMoveAxisToCommandPayload,
844
872
  GripperCommandPayload,
873
+ # Task payloads
874
+ WaitForTasksPayload,
875
+ CreateTimerPayload,
845
876
  ]
846
877
 
847
878
 
@@ -1124,6 +1155,14 @@ class RobotCloseGripperJawMessage(CommandMessageFields, RobotCloseGripperJawComm
1124
1155
  pass
1125
1156
 
1126
1157
 
1158
+ class WaitForTasksMessage(CommandMessageFields, WaitForTasksCommand):
1159
+ pass
1160
+
1161
+
1162
+ class CreateTimerMessage(CommandMessageFields, CreateTimerCommand):
1163
+ pass
1164
+
1165
+
1127
1166
  CommandMessage = Union[
1128
1167
  DropTipMessage,
1129
1168
  DropTipInDisposalLocationMessage,
@@ -1183,4 +1222,7 @@ CommandMessage = Union[
1183
1222
  FlexStackerStoreMessage,
1184
1223
  FlexStackerEmptyMessage,
1185
1224
  FlexStackerFillMessage,
1225
+ # Task Messages
1226
+ WaitForTasksMessage,
1227
+ CreateTimerMessage,
1186
1228
  ]
@@ -7,7 +7,6 @@ from opentrons.hardware_control.types import CriticalPoint
7
7
 
8
8
  from .types import Waypoint, MoveType, GripperMovementWaypointsWithJawStatus
9
9
  from .errors import DestinationOutOfBoundsError, ArcOutOfBoundsError
10
- from ..protocol_engine.types import LabwareMovementOffsetData
11
10
 
12
11
  DEFAULT_GENERAL_ARC_Z_MARGIN: Final[float] = 10.0
13
12
  DEFAULT_IN_LABWARE_ARC_Z_MARGIN: Final[float] = 5.0
@@ -125,47 +124,41 @@ def get_gripper_labware_movement_waypoints(
125
124
  from_labware_center: Point,
126
125
  to_labware_center: Point,
127
126
  gripper_home_z: float,
128
- offset_data: LabwareMovementOffsetData,
129
127
  post_drop_slide_offset: Optional[Point],
130
128
  gripper_home_z_offset: Optional[float] = None,
131
129
  ) -> List[GripperMovementWaypointsWithJawStatus]:
132
130
  """Get waypoints for moving labware using a gripper."""
133
- pick_up_offset = offset_data.pickUpOffset
134
- drop_offset = offset_data.dropOffset
135
-
136
- pick_up_location = from_labware_center + Point(
137
- pick_up_offset.x, pick_up_offset.y, pick_up_offset.z
138
- )
139
- drop_location = to_labware_center + Point(
140
- drop_offset.x, drop_offset.y, drop_offset.z
141
- )
142
-
143
131
  gripper_max_z_home = gripper_home_z - (gripper_home_z_offset or 0)
144
-
145
- post_drop_home_pos = Point(drop_location.x, drop_location.y, gripper_home_z)
132
+ post_drop_home_pos = Point(to_labware_center.x, to_labware_center.y, gripper_home_z)
146
133
 
147
134
  waypoints_with_jaw_status = [
148
135
  GripperMovementWaypointsWithJawStatus(
149
- position=Point(pick_up_location.x, pick_up_location.y, gripper_home_z),
136
+ position=Point(
137
+ from_labware_center.x, from_labware_center.y, gripper_home_z
138
+ ),
150
139
  jaw_open=False,
151
140
  dropping=False,
152
141
  ),
153
142
  GripperMovementWaypointsWithJawStatus(
154
- position=pick_up_location, jaw_open=True, dropping=False
143
+ position=from_labware_center, jaw_open=True, dropping=False
155
144
  ),
156
145
  # Gripper grips the labware here
157
146
  GripperMovementWaypointsWithJawStatus(
158
- position=Point(pick_up_location.x, pick_up_location.y, gripper_max_z_home),
147
+ position=Point(
148
+ from_labware_center.x, from_labware_center.y, gripper_max_z_home
149
+ ),
159
150
  jaw_open=False,
160
151
  dropping=False,
161
152
  ),
162
153
  GripperMovementWaypointsWithJawStatus(
163
- position=Point(drop_location.x, drop_location.y, gripper_max_z_home),
154
+ position=Point(
155
+ to_labware_center.x, to_labware_center.y, gripper_max_z_home
156
+ ),
164
157
  jaw_open=False,
165
158
  dropping=False,
166
159
  ),
167
160
  GripperMovementWaypointsWithJawStatus(
168
- position=drop_location, jaw_open=False, dropping=False
161
+ position=to_labware_center, jaw_open=False, dropping=False
169
162
  ),
170
163
  # Gripper ungrips here
171
164
  GripperMovementWaypointsWithJawStatus(
@@ -189,25 +182,18 @@ def get_gripper_labware_movement_waypoints(
189
182
  def get_gripper_labware_placement_waypoints(
190
183
  to_labware_center: Point,
191
184
  gripper_home_z: float,
192
- drop_offset: Optional[Point],
193
185
  ) -> List[GripperMovementWaypointsWithJawStatus]:
194
186
  """Get waypoints for placing labware using a gripper."""
195
- drop_offset = drop_offset or Point()
196
-
197
- drop_location = to_labware_center + Point(
198
- drop_offset.x, drop_offset.y, drop_offset.z
199
- )
200
-
201
- post_drop_home_pos = Point(drop_location.x, drop_location.y, gripper_home_z)
187
+ post_drop_home_pos = Point(to_labware_center.x, to_labware_center.y, gripper_home_z)
202
188
 
203
189
  return [
204
190
  GripperMovementWaypointsWithJawStatus(
205
- position=Point(drop_location.x, drop_location.y, gripper_home_z),
191
+ position=Point(to_labware_center.x, to_labware_center.y, gripper_home_z),
206
192
  jaw_open=False,
207
193
  dropping=False,
208
194
  ),
209
195
  GripperMovementWaypointsWithJawStatus(
210
- position=drop_location, jaw_open=False, dropping=False
196
+ position=to_labware_center, jaw_open=False, dropping=False
211
197
  ),
212
198
  # Gripper ungrips here
213
199
  GripperMovementWaypointsWithJawStatus(
@@ -30,9 +30,11 @@ from .module_contexts import (
30
30
  AbsorbanceReaderContext,
31
31
  FlexStackerContext,
32
32
  )
33
+ from .tasks import Task
33
34
  from .disposal_locations import TrashBin, WasteChute
34
35
  from ._liquid import Liquid, LiquidClass
35
36
  from ._types import (
37
+ OffDeckType,
36
38
  OFF_DECK,
37
39
  PLUNGER_BLOWOUT,
38
40
  PLUNGER_TOP,
@@ -88,6 +90,7 @@ __all__ = [
88
90
  "ROW",
89
91
  "ALL",
90
92
  # Deck location types
93
+ "OffDeckType",
91
94
  "OFF_DECK",
92
95
  # Pipette plunger types
93
96
  "PLUNGER_BLOWOUT",
@@ -99,6 +102,8 @@ __all__ = [
99
102
  "BLOWOUT_ACTION",
100
103
  "RuntimeParameterRequiredError",
101
104
  "CSVParameter",
105
+ # Concurrent task types
106
+ "Task",
102
107
  # For internal Opentrons use only:
103
108
  "create_protocol_context",
104
109
  "ProtocolEngineCoreRequiredError",
@@ -3,8 +3,13 @@ from typing_extensions import Final
3
3
  import enum
4
4
 
5
5
 
6
- # TODO (tz, 5-18-23): think about a better name for it that would also work when we include staging area slots in the type.
6
+ # Implemented with an enum to support type narrowing via `== OFF_DECK`.
7
7
  class OffDeckType(enum.Enum):
8
+ """The type of the :py:obj:`OFF_DECK` constant.
9
+
10
+ Do not use directly, except in type annotations and ``isinstance`` calls.
11
+ """
12
+
8
13
  OFF_DECK = "off-deck"
9
14
 
10
15
 
@@ -16,6 +16,7 @@ from .module import (
16
16
  from .protocol import AbstractProtocol
17
17
  from .well import AbstractWellCore
18
18
  from .robot import AbstractRobot
19
+ from .tasks import AbstractTaskCore
19
20
 
20
21
 
21
22
  WellCore = AbstractWellCore
@@ -30,4 +31,5 @@ MagneticBlockCore = AbstractMagneticBlockCore[LabwareCore]
30
31
  AbsorbanceReaderCore = AbstractAbsorbanceReaderCore[LabwareCore]
31
32
  FlexStackerCore = AbstractFlexStackerCore[LabwareCore]
32
33
  RobotCore = AbstractRobot
33
- ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore]
34
+ TaskCore = AbstractTaskCore
35
+ ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore, TaskCore]
@@ -112,6 +112,37 @@ DEFAULT_LABWARE_VERSIONS: DefaultLabwareVersions = {
112
112
  "thermoscientificnunc_96_wellplate_2000ul": 3,
113
113
  "usascientific_96_wellplate_2.4ml_deep": 3,
114
114
  },
115
+ APIVersion(2, 27): {
116
+ "agilent_1_reservoir_290ml": 4,
117
+ "axygen_1_reservoir_90ml": 3,
118
+ "biorad_96_wellplate_200ul_pcr": 5,
119
+ "corning_12_wellplate_6.9ml_flat": 5,
120
+ "corning_24_wellplate_3.4ml_flat": 5,
121
+ "corning_384_wellplate_112ul_flat": 5,
122
+ "corning_48_wellplate_1.6ml_flat": 6,
123
+ "corning_6_wellplate_16.8ml_flat": 5,
124
+ "corning_96_wellplate_360ul_flat": 5,
125
+ "nest_12_reservoir_15ml": 3,
126
+ "nest_1_reservoir_195ml": 4,
127
+ "nest_1_reservoir_290ml": 4,
128
+ "nest_96_wellplate_100ul_pcr_full_skirt": 5,
129
+ "nest_96_wellplate_200ul_flat": 5,
130
+ "nest_96_wellplate_2ml_deep": 5,
131
+ "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical": 3,
132
+ "opentrons_15_tuberack_falcon_15ml_conical": 3,
133
+ "opentrons_24_aluminumblock_nest_0.5ml_screwcap": 4,
134
+ "opentrons_24_aluminumblock_nest_1.5ml_screwcap": 3,
135
+ "opentrons_24_aluminumblock_nest_1.5ml_snapcap": 3,
136
+ "opentrons_24_aluminumblock_nest_2ml_screwcap": 3,
137
+ "opentrons_24_aluminumblock_nest_2ml_snapcap": 3,
138
+ "opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap": 3,
139
+ "opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap": 3,
140
+ "opentrons_24_tuberack_nest_0.5ml_screwcap": 4,
141
+ "opentrons_6_tuberack_nest_50ml_conical": 3,
142
+ "opentrons_96_aluminumblock_generic_pcr_strip_200ul": 4,
143
+ "usascientific_12_reservoir_22ml": 4,
144
+ "usascientific_96_wellplate_2.4ml_deep": 4,
145
+ },
115
146
  }
116
147
 
117
148
 
@@ -139,17 +170,7 @@ KNOWN_EXCEPTIONS_FOR_TESTS: set[str] = {
139
170
  "schema3test_flex_tiprack_lid",
140
171
  "schema3test_tough_pcr_auto_sealing_lid",
141
172
  "schema3test_universal_flat_adapter",
142
- # These were supposed to be short-lived drafts as part of of a one-two punch of
143
- # https://github.com/Opentrons/opentrons/pull/18266 + https://github.com/Opentrons/opentrons/pull/18284,
144
- # but the second punch took a while. We should merge the second punch after v8.6.0
145
- # and remove these exceptions as part of that.
146
- "agilent_1_reservoir_290ml",
147
- "corning_384_wellplate_112ul_flat",
148
- "nest_1_reservoir_290ml",
149
- "opentrons_24_aluminumblock_nest_0.5ml_screwcap",
150
- "opentrons_24_tuberack_nest_0.5ml_screwcap",
151
- "opentrons_96_aluminumblock_generic_pcr_strip_200ul",
152
- "usascientific_12_reservoir_22ml",
173
+ "schema3test_96_wellplate_360ul_flat",
153
174
  }
154
175
 
155
176
 
@@ -23,6 +23,7 @@ from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
23
23
  from opentrons.protocol_engine.types import (
24
24
  LabwareOffsetCreate,
25
25
  LabwareOffsetVector,
26
+ TipRackWellState,
26
27
  )
27
28
  from opentrons.types import DeckSlotName, NozzleMapInterface, Point, StagingSlotName
28
29
 
@@ -165,7 +166,13 @@ class LabwareCore(AbstractLabware[WellCore]):
165
166
 
166
167
  def reset_tips(self) -> None:
167
168
  if self.is_tip_rack():
168
- self._engine_client.reset_tips(labware_id=self.labware_id)
169
+ self._engine_client.execute_command(
170
+ cmd.SetTipStateParams(
171
+ labwareId=self._labware_id,
172
+ wellNames=list(self._definition.wells),
173
+ tipWellState=TipRackWellState.CLEAN,
174
+ )
175
+ )
169
176
  else:
170
177
  raise TypeError(f"{self.get_display_name()} is not a tip rack.")
171
178
 
@@ -317,6 +317,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
317
317
  def set_target_block_temperature(
318
318
  self,
319
319
  celsius: float,
320
+ ramp_rate: Optional[float],
320
321
  hold_time_seconds: Optional[float] = None,
321
322
  block_max_volume: Optional[float] = None,
322
323
  ) -> None:
@@ -327,6 +328,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
327
328
  celsius=celsius,
328
329
  blockMaxVolumeUl=block_max_volume,
329
330
  holdTimeSeconds=hold_time_seconds,
331
+ ramp_rate=ramp_rate,
330
332
  )
331
333
  )
332
334
 
@@ -361,6 +363,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
361
363
  cmd.thermocycler.RunProfileStepParams(
362
364
  celsius=step["temperature"],
363
365
  holdSeconds=step["hold_time_seconds"],
366
+ rampRate=step["ramp_rate"],
364
367
  )
365
368
  for step in steps
366
369
  ]
@@ -389,6 +392,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
389
392
  cmd.thermocycler.ProfileStep(
390
393
  celsius=step["temperature"],
391
394
  holdSeconds=step["hold_time_seconds"],
395
+ rampRate=step["ramp_rate"],
392
396
  )
393
397
  for step in steps
394
398
  ],
@@ -1,7 +1,7 @@
1
1
  """ProtocolEngine-based Protocol API core implementation."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING
4
+ from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING, Sequence
5
5
 
6
6
  from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExist
7
7
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
@@ -64,6 +64,7 @@ from ...disposal_locations import TrashBin, WasteChute
64
64
  from ..protocol import AbstractProtocol
65
65
  from ..labware import LabwareLoadParams
66
66
  from .labware import LabwareCore
67
+ from .tasks import EngineTaskCore
67
68
  from .instrument import InstrumentCore
68
69
  from .robot import RobotCore
69
70
  from .module_core import (
@@ -95,6 +96,7 @@ class ProtocolCore(
95
96
  InstrumentCore,
96
97
  LabwareCore,
97
98
  Union[ModuleCore, NonConnectedModuleCore],
99
+ EngineTaskCore,
98
100
  ]
99
101
  ):
100
102
  """Protocol API core using a ProtocolEngine.
@@ -875,6 +877,21 @@ class ProtocolCore(
875
877
  cmd.WaitForDurationParams(seconds=seconds, message=msg)
876
878
  )
877
879
 
880
+ def wait_for_tasks(self, task_cores: Sequence[EngineTaskCore]) -> None:
881
+ """Wait for specified tasks to complete."""
882
+ task_ids = [task._id for task in task_cores]
883
+ self._engine_client.execute_command(cmd.WaitForTasksParams(task_ids=task_ids))
884
+
885
+ def create_timer(self, seconds: float) -> EngineTaskCore:
886
+ """Create a timer task that runs in the background."""
887
+ result = self._engine_client.execute_command_without_recovery(
888
+ cmd.CreateTimerParams(time=seconds)
889
+ )
890
+ timer_task = EngineTaskCore(
891
+ engine_client=self._engine_client, task_id=result.task_id
892
+ )
893
+ return timer_task
894
+
878
895
  def home(self) -> None:
879
896
  """Move all axes to their home positions."""
880
897
  self._engine_client.execute_command(cmd.HomeParams(axes=None))
@@ -0,0 +1,35 @@
1
+ from datetime import datetime
2
+ from ..tasks import AbstractTaskCore
3
+ from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
4
+ from opentrons.protocol_engine.errors.exceptions import NoTaskFoundError
5
+
6
+
7
+ class EngineTaskCore(AbstractTaskCore):
8
+ def __init__(self, task_id: str, engine_client: ProtocolEngineClient) -> None:
9
+ self._id = task_id
10
+ self._engine_client = engine_client
11
+
12
+ def get_created_at_timestamp(self) -> datetime:
13
+ task = self._engine_client.state.tasks.get(self._id)
14
+ return task.createdAt
15
+
16
+ def is_done(self) -> bool:
17
+ try:
18
+ self._engine_client.state.tasks.get_finished(self._id)
19
+ return True
20
+ except NoTaskFoundError:
21
+ return False
22
+
23
+ def is_started(self) -> bool:
24
+ try:
25
+ self._engine_client.state.tasks.get_current(self._id)
26
+ return True
27
+ except NoTaskFoundError:
28
+ return self.is_done()
29
+
30
+ def get_finished_at_timestamp(self) -> datetime | None:
31
+ try:
32
+ finished_task = self._engine_client.state.tasks.get_finished(self._id)
33
+ return finished_task.finishedAt
34
+ except NoTaskFoundError:
35
+ return None
@@ -263,6 +263,7 @@ class LegacyThermocyclerCore(
263
263
  def set_target_block_temperature(
264
264
  self,
265
265
  celsius: float,
266
+ ramp_rate: Optional[float],
266
267
  hold_time_seconds: Optional[float] = None,
267
268
  block_max_volume: Optional[float] = None,
268
269
  ) -> None:
@@ -271,6 +272,7 @@ class LegacyThermocyclerCore(
271
272
  celsius=celsius,
272
273
  hold_time_seconds=hold_time_seconds,
273
274
  volume=block_max_volume,
275
+ ramp_rate=ramp_rate,
274
276
  )
275
277
 
276
278
  def wait_for_block_temperature(self) -> None:
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Dict, List, Optional, Set, Union, cast, Tuple
2
+ from typing import Dict, List, Optional, Set, Union, cast, Tuple, Sequence
3
3
 
4
4
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
5
5
  from opentrons_shared_data.labware.types import LabwareDefinition
@@ -34,6 +34,7 @@ from .legacy_instrument_core import LegacyInstrumentCore
34
34
  from .labware_offset_provider import AbstractLabwareOffsetProvider
35
35
  from .legacy_labware_core import LegacyLabwareCore
36
36
  from .load_info import LoadInfo, InstrumentLoadInfo, LabwareLoadInfo, ModuleLoadInfo
37
+ from .tasks import LegacyTaskCore
37
38
 
38
39
  logger = logging.getLogger(__name__)
39
40
 
@@ -43,6 +44,7 @@ class LegacyProtocolCore(
43
44
  LegacyInstrumentCore,
44
45
  LegacyLabwareCore,
45
46
  legacy_module_core.LegacyModuleCore,
47
+ LegacyTaskCore,
46
48
  ]
47
49
  ):
48
50
  def __init__(
@@ -610,3 +612,11 @@ class LegacyProtocolCore(
610
612
  ]:
611
613
  """Get labware parent location."""
612
614
  assert False, "get_labware_location only supported on engine core"
615
+
616
+ def wait_for_tasks(self, task: Sequence[LegacyTaskCore]) -> None:
617
+ """Wait for list of tasks to complete before executing subsequent commands."""
618
+ assert False, "wait_for_tasks only supported on engine core"
619
+
620
+ def create_timer(self, seconds: float) -> LegacyTaskCore:
621
+ """Create a timer task that runs in the background."""
622
+ assert False, "create_timer only supported on engine core"
@@ -0,0 +1,19 @@
1
+ from ..tasks import AbstractTaskCore
2
+ from datetime import datetime
3
+
4
+
5
+ class LegacyTaskCore(AbstractTaskCore):
6
+ def __init__(self, created_at: datetime) -> None:
7
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
8
+
9
+ def get_created_at_timestamp(self) -> datetime:
10
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
11
+
12
+ def is_done(self) -> bool:
13
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
14
+
15
+ def is_started(self) -> bool:
16
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
17
+
18
+ def get_finished_at_timestamp(self) -> datetime | None:
19
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Dict, Optional
2
+ from typing import Dict, Optional, Sequence
3
3
 
4
4
  from opentrons_shared_data.pipette.types import PipetteNameType
5
5
  from opentrons_shared_data.pipette.pipette_load_name_conversions import (
@@ -16,6 +16,7 @@ from ..legacy.legacy_module_core import LegacyModuleCore
16
16
  from ..legacy.load_info import InstrumentLoadInfo
17
17
 
18
18
  from .legacy_instrument_core import LegacyInstrumentCoreSimulator
19
+ from .tasks import LegacyTaskCore
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
@@ -23,7 +24,10 @@ logger = logging.getLogger(__name__)
23
24
  class LegacyProtocolCoreSimulator(
24
25
  LegacyProtocolCore,
25
26
  AbstractProtocol[
26
- LegacyInstrumentCoreSimulator, LegacyLabwareCore, LegacyModuleCore
27
+ LegacyInstrumentCoreSimulator,
28
+ LegacyLabwareCore,
29
+ LegacyModuleCore,
30
+ LegacyTaskCore,
27
31
  ],
28
32
  ):
29
33
  _instruments: Dict[Mount, Optional[LegacyInstrumentCoreSimulator]] # type: ignore[assignment]
@@ -83,3 +87,11 @@ class LegacyProtocolCoreSimulator(
83
87
  )
84
88
 
85
89
  return new_instr
90
+
91
+ def wait_for_tasks(self, task: Sequence[LegacyTaskCore]) -> None:
92
+ """Wait for list of tasks to complete before executing subsequent commands."""
93
+ assert False, "wait_for_tasks only supported on engine core"
94
+
95
+ def create_timer(self, seconds: float) -> LegacyTaskCore:
96
+ """Create a timer task that runs in the background."""
97
+ assert False, "create_timer only supported on engine core"
@@ -0,0 +1,19 @@
1
+ from ..tasks import AbstractTaskCore
2
+ from datetime import datetime
3
+
4
+
5
+ class LegacyTaskCore(AbstractTaskCore):
6
+ def __init__(self, created_at: datetime) -> None:
7
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
8
+
9
+ def get_created_at_timestamp(self) -> datetime:
10
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
11
+
12
+ def is_done(self) -> bool:
13
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
14
+
15
+ def is_started(self) -> bool:
16
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
17
+
18
+ def get_finished_at_timestamp(self) -> datetime | None:
19
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
@@ -161,6 +161,7 @@ class AbstractThermocyclerCore(
161
161
  def set_target_block_temperature(
162
162
  self,
163
163
  celsius: float,
164
+ ramp_rate: Optional[float],
164
165
  hold_time_seconds: Optional[float] = None,
165
166
  block_max_volume: Optional[float] = None,
166
167
  ) -> None: