opentrons 8.7.0a0__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.
- opentrons/_version.py +2 -2
- opentrons/drivers/thermocycler/abstract.py +1 -0
- opentrons/drivers/thermocycler/driver.py +33 -4
- opentrons/drivers/thermocycler/simulator.py +2 -0
- opentrons/hardware_control/api.py +24 -5
- opentrons/hardware_control/backends/controller.py +8 -2
- opentrons/hardware_control/backends/ot3controller.py +3 -0
- opentrons/hardware_control/backends/ot3simulator.py +2 -1
- opentrons/hardware_control/backends/simulator.py +2 -1
- opentrons/hardware_control/backends/subsystem_manager.py +5 -2
- opentrons/hardware_control/module_control.py +82 -8
- opentrons/hardware_control/modules/__init__.py +3 -0
- opentrons/hardware_control/modules/absorbance_reader.py +11 -4
- opentrons/hardware_control/modules/flex_stacker.py +38 -9
- opentrons/hardware_control/modules/heater_shaker.py +30 -5
- opentrons/hardware_control/modules/magdeck.py +8 -4
- opentrons/hardware_control/modules/mod_abc.py +13 -5
- opentrons/hardware_control/modules/tempdeck.py +25 -5
- opentrons/hardware_control/modules/thermocycler.py +56 -10
- opentrons/hardware_control/modules/types.py +20 -1
- opentrons/hardware_control/modules/utils.py +11 -4
- opentrons/hardware_control/nozzle_manager.py +3 -0
- opentrons/hardware_control/ot3api.py +26 -5
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +31 -2
- opentrons/legacy_commands/protocol_commands.py +20 -0
- opentrons/legacy_commands/types.py +42 -0
- opentrons/motion_planning/waypoints.py +15 -29
- opentrons/protocol_api/__init__.py +5 -0
- opentrons/protocol_api/_types.py +6 -1
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
- opentrons/protocol_api/core/engine/_default_liquid_class_versions.py +2 -0
- opentrons/protocol_api/core/engine/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +4 -0
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +77 -17
- opentrons/protocol_api/core/engine/protocol.py +18 -1
- opentrons/protocol_api/core/engine/tasks.py +35 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
- opentrons/protocol_api/core/legacy/tasks.py +19 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
- opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
- opentrons/protocol_api/core/module.py +1 -0
- opentrons/protocol_api/core/protocol.py +11 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/module_contexts.py +1 -0
- opentrons/protocol_api/protocol_context.py +26 -4
- opentrons/protocol_api/robot_context.py +38 -21
- opentrons/protocol_api/tasks.py +48 -0
- opentrons/protocol_api/validation.py +6 -1
- opentrons/protocol_engine/actions/__init__.py +4 -2
- opentrons/protocol_engine/actions/actions.py +22 -9
- opentrons/protocol_engine/clients/sync_client.py +6 -7
- opentrons/protocol_engine/commands/__init__.py +42 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
- opentrons/protocol_engine/commands/aspirate.py +1 -0
- opentrons/protocol_engine/commands/command.py +1 -0
- opentrons/protocol_engine/commands/command_unions.py +39 -0
- opentrons/protocol_engine/commands/create_timer.py +83 -0
- opentrons/protocol_engine/commands/dispense.py +1 -0
- opentrons/protocol_engine/commands/drop_tip.py +32 -8
- opentrons/protocol_engine/commands/movement_common.py +2 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
- opentrons/protocol_engine/commands/set_tip_state.py +97 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +17 -1
- opentrons/protocol_engine/commands/touch_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
- opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
- opentrons/protocol_engine/errors/__init__.py +4 -0
- opentrons/protocol_engine/errors/exceptions.py +55 -0
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +8 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/labware_movement.py +9 -12
- opentrons/protocol_engine/execution/movement.py +2 -0
- opentrons/protocol_engine/execution/queue_worker.py +4 -0
- opentrons/protocol_engine/execution/run_control.py +8 -0
- opentrons/protocol_engine/execution/task_handler.py +157 -0
- opentrons/protocol_engine/protocol_engine.py +67 -33
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
- opentrons/protocol_engine/resources/labware_validation.py +10 -6
- opentrons/protocol_engine/state/_well_math.py +60 -18
- opentrons/protocol_engine/state/addressable_areas.py +2 -0
- opentrons/protocol_engine/state/commands.py +7 -7
- opentrons/protocol_engine/state/geometry.py +237 -379
- opentrons/protocol_engine/state/labware.py +52 -102
- opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
- opentrons/protocol_engine/state/modules.py +26 -7
- opentrons/protocol_engine/state/motion.py +44 -0
- opentrons/protocol_engine/state/state.py +14 -0
- opentrons/protocol_engine/state/state_summary.py +2 -0
- opentrons/protocol_engine/state/tasks.py +139 -0
- opentrons/protocol_engine/state/tips.py +177 -258
- opentrons/protocol_engine/state/update_types.py +16 -9
- opentrons/protocol_engine/types/__init__.py +9 -3
- opentrons/protocol_engine/types/deck_configuration.py +5 -1
- opentrons/protocol_engine/types/instrument.py +8 -1
- opentrons/protocol_engine/types/labware.py +1 -13
- opentrons/protocol_engine/types/module.py +10 -0
- opentrons/protocol_engine/types/tasks.py +38 -0
- opentrons/protocol_engine/types/tip.py +9 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
- opentrons/protocol_runner/run_orchestrator.py +18 -2
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +2 -1
- opentrons/simulate.py +48 -15
- opentrons/system/camera.py +1 -1
- {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/RECORD +120 -107
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a0.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
|
|
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,
|
|
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(
|
|
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=
|
|
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(
|
|
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(
|
|
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=
|
|
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
|
-
|
|
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(
|
|
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=
|
|
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",
|
opentrons/protocol_api/_types.py
CHANGED
|
@@ -3,8 +3,13 @@ from typing_extensions import Final
|
|
|
3
3
|
import enum
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
],
|
|
@@ -21,7 +21,11 @@ from opentrons.protocol_engine import (
|
|
|
21
21
|
OnLabwareLocation,
|
|
22
22
|
DropTipWellLocation,
|
|
23
23
|
)
|
|
24
|
-
from opentrons.protocol_engine.types import
|
|
24
|
+
from opentrons.protocol_engine.types import (
|
|
25
|
+
StagingSlotLocation,
|
|
26
|
+
WellLocationType,
|
|
27
|
+
LoadedModule,
|
|
28
|
+
)
|
|
25
29
|
from opentrons.types import DeckSlotName, StagingSlotName, Point
|
|
26
30
|
from . import point_calculations
|
|
27
31
|
|
|
@@ -136,22 +140,47 @@ def check_safe_for_pipette_movement( # noqa: C901
|
|
|
136
140
|
f" will result in collision with thermocycler lid in deck slot A1."
|
|
137
141
|
)
|
|
138
142
|
|
|
143
|
+
def _check_for_column_4_module_collision(slot: DeckSlotName) -> None:
|
|
144
|
+
slot_module = engine_state.modules.get_by_slot(slot)
|
|
145
|
+
if (
|
|
146
|
+
slot_module
|
|
147
|
+
and engine_state.modules.is_column_4_module(slot_module.model)
|
|
148
|
+
and _slot_has_potential_colliding_object(
|
|
149
|
+
engine_state=engine_state,
|
|
150
|
+
pipette_bounds=pipette_bounds_at_well_location,
|
|
151
|
+
surrounding_location=slot_module,
|
|
152
|
+
)
|
|
153
|
+
):
|
|
154
|
+
raise PartialTipMovementNotAllowedError(
|
|
155
|
+
f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot"
|
|
156
|
+
f" {slot} with {primary_nozzle} nozzle partial configuration will"
|
|
157
|
+
f" result in collision with items on {slot_module.model} mounted in {slot}."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# We check the labware slot for a module that is mounted in the same cutout
|
|
161
|
+
# as the labwares slot but does not occupy the same heirarchy (like the stacker).
|
|
162
|
+
_check_for_column_4_module_collision(labware_slot)
|
|
163
|
+
|
|
139
164
|
for regular_slot in surrounding_slots.regular_slots:
|
|
140
165
|
if _slot_has_potential_colliding_object(
|
|
141
166
|
engine_state=engine_state,
|
|
142
167
|
pipette_bounds=pipette_bounds_at_well_location,
|
|
143
|
-
|
|
168
|
+
surrounding_location=regular_slot,
|
|
144
169
|
):
|
|
145
170
|
raise PartialTipMovementNotAllowedError(
|
|
146
171
|
f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot"
|
|
147
172
|
f" {labware_slot} with {primary_nozzle} nozzle partial configuration"
|
|
148
173
|
f" will result in collision with items in deck slot {regular_slot}."
|
|
149
174
|
)
|
|
175
|
+
|
|
176
|
+
# Check for Column 4 Modules that may be descendants of a given surrounding slot
|
|
177
|
+
_check_for_column_4_module_collision(regular_slot)
|
|
178
|
+
|
|
150
179
|
for staging_slot in surrounding_slots.staging_slots:
|
|
151
180
|
if _slot_has_potential_colliding_object(
|
|
152
181
|
engine_state=engine_state,
|
|
153
182
|
pipette_bounds=pipette_bounds_at_well_location,
|
|
154
|
-
|
|
183
|
+
surrounding_location=staging_slot,
|
|
155
184
|
):
|
|
156
185
|
raise PartialTipMovementNotAllowedError(
|
|
157
186
|
f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot"
|
|
@@ -178,18 +207,45 @@ def _get_critical_point_to_use(
|
|
|
178
207
|
def _slot_has_potential_colliding_object(
|
|
179
208
|
engine_state: StateView,
|
|
180
209
|
pipette_bounds: Tuple[Point, Point, Point, Point],
|
|
181
|
-
|
|
210
|
+
surrounding_location: Union[DeckSlotName, StagingSlotName, LoadedModule],
|
|
182
211
|
) -> bool:
|
|
183
|
-
"""Return the slot, if any, that has an item that the pipette might collide into.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
"""Return the slot, if any, that has an item that the pipette might collide into.
|
|
213
|
+
Can be provided a Deck Slot, Staging Slot, or Column 4 Module.
|
|
214
|
+
"""
|
|
215
|
+
if isinstance(surrounding_location, LoadedModule):
|
|
216
|
+
if (
|
|
217
|
+
engine_state.modules.is_column_4_module(surrounding_location.model)
|
|
218
|
+
and surrounding_location.location is not None
|
|
219
|
+
):
|
|
220
|
+
module_area = (
|
|
221
|
+
engine_state.modules.ensure_and_convert_module_fixture_location(
|
|
222
|
+
surrounding_location.location.slotName, surrounding_location.model
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
slot_pos = engine_state.addressable_areas.get_addressable_area_position(
|
|
226
|
+
addressable_area_name=module_area,
|
|
227
|
+
do_compatibility_check=False,
|
|
228
|
+
)
|
|
229
|
+
slot_bounds = (
|
|
230
|
+
engine_state.addressable_areas.get_addressable_area_bounding_box(
|
|
231
|
+
addressable_area_name=module_area,
|
|
232
|
+
do_compatibility_check=False,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Error during collision validation, Module {surrounding_location.model} must be in Column 4."
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
# Check if slot overlaps with pipette position
|
|
241
|
+
slot_pos = engine_state.addressable_areas.get_addressable_area_position(
|
|
242
|
+
addressable_area_name=surrounding_location.id,
|
|
243
|
+
do_compatibility_check=False,
|
|
244
|
+
)
|
|
245
|
+
slot_bounds = engine_state.addressable_areas.get_addressable_area_bounding_box(
|
|
246
|
+
addressable_area_name=surrounding_location.id,
|
|
247
|
+
do_compatibility_check=False,
|
|
248
|
+
)
|
|
193
249
|
slot_back_left_coords = Point(slot_pos.x, slot_pos.y + slot_bounds.y, slot_pos.z)
|
|
194
250
|
slot_front_right_coords = Point(slot_pos.x + slot_bounds.x, slot_pos.y, slot_pos.z)
|
|
195
251
|
|
|
@@ -199,13 +255,17 @@ def _slot_has_potential_colliding_object(
|
|
|
199
255
|
rectangle2=(slot_back_left_coords, slot_front_right_coords),
|
|
200
256
|
):
|
|
201
257
|
# Check z-height of items in overlapping slot
|
|
202
|
-
if isinstance(
|
|
258
|
+
if isinstance(surrounding_location, DeckSlotName):
|
|
203
259
|
slot_highest_z = engine_state.geometry.get_highest_z_in_slot(
|
|
204
|
-
DeckSlotLocation(slotName=
|
|
260
|
+
DeckSlotLocation(slotName=surrounding_location)
|
|
261
|
+
)
|
|
262
|
+
elif isinstance(surrounding_location, LoadedModule):
|
|
263
|
+
slot_highest_z = engine_state.geometry.get_highest_z_of_column_4_module(
|
|
264
|
+
surrounding_location
|
|
205
265
|
)
|
|
206
266
|
else:
|
|
207
267
|
slot_highest_z = engine_state.geometry.get_highest_z_in_slot(
|
|
208
|
-
StagingSlotLocation(slotName=
|
|
268
|
+
StagingSlotLocation(slotName=surrounding_location)
|
|
209
269
|
)
|
|
210
270
|
return slot_highest_z >= pipette_bounds[0].z
|
|
211
271
|
return False
|
|
@@ -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
|