opentrons 8.7.0a6__py3-none-any.whl → 8.7.0a7__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/asyncio/communication/serial_connection.py +129 -52
- opentrons/drivers/heater_shaker/abstract.py +5 -0
- opentrons/drivers/heater_shaker/driver.py +10 -0
- opentrons/drivers/heater_shaker/simulator.py +4 -0
- opentrons/drivers/thermocycler/abstract.py +6 -0
- opentrons/drivers/thermocycler/driver.py +61 -10
- opentrons/drivers/thermocycler/simulator.py +6 -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/emulation/abstract_emulator.py +6 -4
- opentrons/hardware_control/emulation/connection_handler.py +8 -5
- opentrons/hardware_control/emulation/heater_shaker.py +12 -3
- opentrons/hardware_control/emulation/settings.py +1 -1
- opentrons/hardware_control/emulation/thermocycler.py +67 -15
- 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 +42 -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 +68 -11
- 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/poller.py +22 -8
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +31 -2
- opentrons/legacy_commands/module_commands.py +23 -0
- opentrons/legacy_commands/protocol_commands.py +20 -0
- opentrons/legacy_commands/types.py +80 -0
- opentrons/motion_planning/deck_conflict.py +17 -12
- opentrons/motion_planning/waypoints.py +15 -29
- opentrons/protocol_api/__init__.py +5 -1
- 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/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +75 -8
- opentrons/protocol_api/core/engine/protocol.py +18 -1
- opentrons/protocol_api/core/engine/tasks.py +48 -0
- opentrons/protocol_api/core/engine/well.py +8 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +24 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
- opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
- 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 +37 -4
- opentrons/protocol_api/core/protocol.py +11 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/core/well.py +4 -0
- opentrons/protocol_api/labware.py +5 -0
- opentrons/protocol_api/module_contexts.py +117 -11
- 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 +42 -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 +49 -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/heater_shaker/__init__.py +14 -0
- opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
- opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
- 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/temperature_module/set_target_temperature.py +38 -7
- opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -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 +40 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +29 -5
- opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
- 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 +75 -34
- 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 +14 -11
- opentrons/protocol_engine/state/geometry.py +213 -374
- 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 +21 -8
- 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.0a6.dist-info → opentrons-8.7.0a7.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a6.dist-info → opentrons-8.7.0a7.dist-info}/RECORD +143 -127
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a6.dist-info → opentrons-8.7.0a7.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a6.dist-info → opentrons-8.7.0a7.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a6.dist-info → opentrons-8.7.0a7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
"""Command models to start heating a Thermocycler's lid."""
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
from typing import Optional, TYPE_CHECKING
|
|
3
|
+
from typing import Optional, TYPE_CHECKING, Any
|
|
4
4
|
from typing_extensions import Literal, Type
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
7
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
7
8
|
|
|
8
9
|
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
9
10
|
from ...errors.error_occurrence import ErrorOccurrence
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from opentrons.protocol_engine.state.state import StateView
|
|
13
|
-
from opentrons.protocol_engine.execution import EquipmentHandler
|
|
14
|
+
from opentrons.protocol_engine.execution import EquipmentHandler, TaskHandler
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
SetTargetLidTemperatureCommandType = Literal["thermocycler/setTargetLidTemperature"]
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def _remove_default(s: dict[str, Any]) -> None:
|
|
21
|
+
s.pop("default", None)
|
|
22
|
+
|
|
23
|
+
|
|
19
24
|
class SetTargetLidTemperatureParams(BaseModel):
|
|
20
25
|
"""Input parameters to set a Thermocycler's target lid temperature."""
|
|
21
26
|
|
|
22
27
|
moduleId: str = Field(..., description="Unique ID of the Thermocycler Module.")
|
|
23
28
|
celsius: float = Field(..., description="Target temperature in °C.")
|
|
29
|
+
taskId: str | SkipJsonSchema[None] = Field(
|
|
30
|
+
None,
|
|
31
|
+
description="Id for the background task that manages the temperature.",
|
|
32
|
+
json_schema_extra=_remove_default,
|
|
33
|
+
)
|
|
24
34
|
|
|
25
35
|
|
|
26
36
|
class SetTargetLidTemperatureResult(BaseModel):
|
|
@@ -30,6 +40,11 @@ class SetTargetLidTemperatureResult(BaseModel):
|
|
|
30
40
|
...,
|
|
31
41
|
description="The target lid temperature that was set after validation.",
|
|
32
42
|
)
|
|
43
|
+
taskId: str | SkipJsonSchema[None] = Field(
|
|
44
|
+
None,
|
|
45
|
+
description="The task id for the setTargetBlockTemperature",
|
|
46
|
+
json_schema_extra=_remove_default,
|
|
47
|
+
)
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
class SetTargetLidTemperatureImpl(
|
|
@@ -43,10 +58,12 @@ class SetTargetLidTemperatureImpl(
|
|
|
43
58
|
self,
|
|
44
59
|
state_view: StateView,
|
|
45
60
|
equipment: EquipmentHandler,
|
|
61
|
+
task_handler: TaskHandler,
|
|
46
62
|
**unused_dependencies: object,
|
|
47
63
|
) -> None:
|
|
48
64
|
self._state_view = state_view
|
|
49
65
|
self._equipment = equipment
|
|
66
|
+
self._task_handler = task_handler
|
|
50
67
|
|
|
51
68
|
async def execute(
|
|
52
69
|
self,
|
|
@@ -63,12 +80,19 @@ class SetTargetLidTemperatureImpl(
|
|
|
63
80
|
thermocycler_state.module_id
|
|
64
81
|
)
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
async def set_target_lid_temperature(task_handler: TaskHandler) -> None:
|
|
84
|
+
if thermocycler_hardware is not None:
|
|
85
|
+
await thermocycler_hardware.set_target_lid_temperature(
|
|
86
|
+
target_temperature
|
|
87
|
+
)
|
|
88
|
+
await thermocycler_hardware.wait_for_lid_target()
|
|
68
89
|
|
|
90
|
+
task = await self._task_handler.create_task(
|
|
91
|
+
task_function=set_target_lid_temperature, id=params.taskId
|
|
92
|
+
)
|
|
69
93
|
return SuccessData(
|
|
70
94
|
public=SetTargetLidTemperatureResult(
|
|
71
|
-
targetLidTemperature=target_temperature
|
|
95
|
+
targetLidTemperature=target_temperature, taskId=task.id
|
|
72
96
|
),
|
|
73
97
|
)
|
|
74
98
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""StartRunProfile command request, result, and implementation models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from typing import List, Optional, TYPE_CHECKING, overload, Union, Any
|
|
6
|
+
from typing_extensions import Literal, Type
|
|
7
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
8
|
+
|
|
9
|
+
from opentrons.hardware_control.modules.types import ThermocyclerStep, ThermocyclerCycle
|
|
10
|
+
|
|
11
|
+
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
12
|
+
from ...errors.error_occurrence import ErrorOccurrence
|
|
13
|
+
from .run_extended_profile import ProfileStep, ProfileCycle
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from opentrons.protocol_engine.state.state import StateView
|
|
17
|
+
from opentrons.protocol_engine.execution import (
|
|
18
|
+
TaskHandler,
|
|
19
|
+
EquipmentHandler,
|
|
20
|
+
)
|
|
21
|
+
from opentrons.protocol_engine.state.module_substates.thermocycler_module_substate import (
|
|
22
|
+
ThermocyclerModuleSubState,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
StartRunExtendedProfileCommandType = Literal["thermocycler/startRunExtendedProfile"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _remove_default(s: dict[str, Any]) -> None:
|
|
29
|
+
s.pop("default", None)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StartRunExtendedProfileStepParams(BaseModel):
|
|
33
|
+
"""Input parameters for an individual Thermocycler profile step."""
|
|
34
|
+
|
|
35
|
+
celsius: float = Field(..., description="Target temperature in °C.")
|
|
36
|
+
holdSeconds: float = Field(
|
|
37
|
+
..., description="Time to hold target temperature at in seconds."
|
|
38
|
+
)
|
|
39
|
+
rampRate: float | SkipJsonSchema[None] = Field(
|
|
40
|
+
None,
|
|
41
|
+
description="How quickly to change temperature in °C/second.",
|
|
42
|
+
json_schema_extra=_remove_default,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class StartRunExtendedProfileParams(BaseModel):
|
|
47
|
+
"""Input parameters to run a Thermocycler profile."""
|
|
48
|
+
|
|
49
|
+
moduleId: str = Field(..., description="Unique ID of the Thermocycler.")
|
|
50
|
+
profileElements: List[Union[ProfileStep, ProfileCycle]] = Field(
|
|
51
|
+
...,
|
|
52
|
+
description="Elements of the profile. Each can be either a step or a cycle.",
|
|
53
|
+
)
|
|
54
|
+
blockMaxVolumeUl: float | SkipJsonSchema[None] = Field(
|
|
55
|
+
None,
|
|
56
|
+
description="Amount of liquid in uL of the most-full well"
|
|
57
|
+
" in labware loaded onto the thermocycler.",
|
|
58
|
+
json_schema_extra=_remove_default,
|
|
59
|
+
)
|
|
60
|
+
taskId: str | None = Field(None, description="The id of the profile task")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class StartRunExtendedProfileResult(BaseModel):
|
|
64
|
+
"""Result data from running a Thermocycler profile."""
|
|
65
|
+
|
|
66
|
+
taskId: str = Field(..., description="The id of the profile task")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _transform_profile_step(
|
|
70
|
+
step: ProfileStep, thermocycler_state: ThermocyclerModuleSubState
|
|
71
|
+
) -> ThermocyclerStep:
|
|
72
|
+
return ThermocyclerStep(
|
|
73
|
+
temperature=thermocycler_state.validate_target_block_temperature(step.celsius),
|
|
74
|
+
hold_time_seconds=step.holdSeconds,
|
|
75
|
+
ramp_rate=thermocycler_state.validate_ramp_rate(step.rampRate, step.celsius),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@overload
|
|
80
|
+
def _transform_profile_element(
|
|
81
|
+
element: ProfileStep, thermocycler_state: ThermocyclerModuleSubState
|
|
82
|
+
) -> ThermocyclerStep:
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@overload
|
|
87
|
+
def _transform_profile_element(
|
|
88
|
+
element: ProfileCycle, thermocycler_state: ThermocyclerModuleSubState
|
|
89
|
+
) -> ThermocyclerCycle:
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _transform_profile_element(
|
|
94
|
+
element: Union[ProfileStep, ProfileCycle],
|
|
95
|
+
thermocycler_state: ThermocyclerModuleSubState,
|
|
96
|
+
) -> Union[ThermocyclerStep, ThermocyclerCycle]:
|
|
97
|
+
if isinstance(element, ProfileStep):
|
|
98
|
+
return _transform_profile_step(element, thermocycler_state)
|
|
99
|
+
else:
|
|
100
|
+
return ThermocyclerCycle(
|
|
101
|
+
steps=[
|
|
102
|
+
_transform_profile_step(step, thermocycler_state)
|
|
103
|
+
for step in element.steps
|
|
104
|
+
],
|
|
105
|
+
repetitions=element.repetitions,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class StartRunExtendedProfileImpl(
|
|
110
|
+
AbstractCommandImpl[
|
|
111
|
+
StartRunExtendedProfileParams, SuccessData[StartRunExtendedProfileResult]
|
|
112
|
+
]
|
|
113
|
+
):
|
|
114
|
+
"""Execution implementation of a Thermocycler's run profile command."""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
state_view: StateView,
|
|
119
|
+
equipment: EquipmentHandler,
|
|
120
|
+
task_handler: TaskHandler,
|
|
121
|
+
**unused_dependencies: object,
|
|
122
|
+
) -> None:
|
|
123
|
+
self._state_view = state_view
|
|
124
|
+
self._equipment = equipment
|
|
125
|
+
self._task_handler = task_handler
|
|
126
|
+
|
|
127
|
+
async def execute(
|
|
128
|
+
self, params: StartRunExtendedProfileParams
|
|
129
|
+
) -> SuccessData[StartRunExtendedProfileResult]:
|
|
130
|
+
"""Run a Thermocycler profile."""
|
|
131
|
+
thermocycler_state = self._state_view.modules.get_thermocycler_module_substate(
|
|
132
|
+
params.moduleId
|
|
133
|
+
)
|
|
134
|
+
thermocycler_hardware = self._equipment.get_module_hardware_api(
|
|
135
|
+
thermocycler_state.module_id
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
profile = [
|
|
139
|
+
_transform_profile_element(element, thermocycler_state)
|
|
140
|
+
for element in params.profileElements
|
|
141
|
+
]
|
|
142
|
+
target_volume: Optional[float]
|
|
143
|
+
if params.blockMaxVolumeUl is not None:
|
|
144
|
+
target_volume = thermocycler_state.validate_max_block_volume(
|
|
145
|
+
params.blockMaxVolumeUl
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
target_volume = None
|
|
149
|
+
|
|
150
|
+
async def start_run_profile(task_handler: TaskHandler) -> None:
|
|
151
|
+
if thermocycler_hardware is not None:
|
|
152
|
+
async with task_handler.synchronize_cancel_latest(
|
|
153
|
+
thermocycler_state.module_id
|
|
154
|
+
):
|
|
155
|
+
await thermocycler_hardware.execute_profile(
|
|
156
|
+
profile=profile, volume=target_volume
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
task = await self._task_handler.create_task(
|
|
160
|
+
task_function=start_run_profile, id=params.taskId
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return SuccessData(
|
|
164
|
+
public=StartRunExtendedProfileResult(taskId=task.id),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class StartRunExtendedProfile(
|
|
169
|
+
BaseCommand[
|
|
170
|
+
StartRunExtendedProfileParams, StartRunExtendedProfileResult, ErrorOccurrence
|
|
171
|
+
]
|
|
172
|
+
):
|
|
173
|
+
"""A command to start the execution of a Thermocycler profile run without waiting for its completion."""
|
|
174
|
+
|
|
175
|
+
commandType: StartRunExtendedProfileCommandType = (
|
|
176
|
+
"thermocycler/startRunExtendedProfile"
|
|
177
|
+
)
|
|
178
|
+
params: StartRunExtendedProfileParams
|
|
179
|
+
|
|
180
|
+
_ImplementationCls: Type[StartRunExtendedProfileImpl] = StartRunExtendedProfileImpl
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class StartRunExtendedProfileCreate(BaseCommandCreate[StartRunExtendedProfileParams]):
|
|
184
|
+
"""A request to execute a Thermocycler profile run."""
|
|
185
|
+
|
|
186
|
+
commandType: StartRunExtendedProfileCommandType = (
|
|
187
|
+
"thermocycler/startRunExtendedProfile"
|
|
188
|
+
)
|
|
189
|
+
params: StartRunExtendedProfileParams
|
|
190
|
+
|
|
191
|
+
_CommandCls: Type[StartRunExtendedProfile] = StartRunExtendedProfile
|
|
@@ -114,7 +114,7 @@ class TouchTipImplementation(
|
|
|
114
114
|
|
|
115
115
|
if self._state_view.labware.get_has_quirk(labware_id, "touchTipDisabled"):
|
|
116
116
|
raise TouchTipDisabledError(
|
|
117
|
-
f"Touch tip not allowed on labware {labware_id}"
|
|
117
|
+
f"Touch tip not allowed on labware {self._state_view.labware.get_display_name(labware_id)}"
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
if self._state_view.labware.is_tiprack(labware_id):
|
|
@@ -14,12 +14,11 @@ from opentrons.protocol_engine.errors.exceptions import (
|
|
|
14
14
|
CannotPerformGripperAction,
|
|
15
15
|
GripperNotAttachedError,
|
|
16
16
|
)
|
|
17
|
-
from opentrons.types import Point
|
|
18
|
-
|
|
19
17
|
from ...types import (
|
|
20
18
|
DeckSlotLocation,
|
|
21
19
|
ModuleModel,
|
|
22
20
|
OnDeckLabwareLocation,
|
|
21
|
+
GripperMoveType,
|
|
23
22
|
)
|
|
24
23
|
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
25
24
|
from ...errors.error_occurrence import ErrorOccurrence
|
|
@@ -100,22 +99,6 @@ class UnsafePlaceLabwareImplementation(
|
|
|
100
99
|
LabwareUri(params.labwareURI)
|
|
101
100
|
)
|
|
102
101
|
|
|
103
|
-
# todo(mm, 2024-11-06): This is only correct in the special case of an
|
|
104
|
-
# absorbance reader lid. Its definition currently puts the offsets for *itself*
|
|
105
|
-
# in the property that's normally meant for offsets for its *children.*
|
|
106
|
-
final_offsets = self._state_view.labware.get_child_gripper_offsets(
|
|
107
|
-
labware_definition=definition, slot_name=None
|
|
108
|
-
)
|
|
109
|
-
drop_offset = (
|
|
110
|
-
Point(
|
|
111
|
-
final_offsets.dropOffset.x,
|
|
112
|
-
final_offsets.dropOffset.y,
|
|
113
|
-
final_offsets.dropOffset.z,
|
|
114
|
-
)
|
|
115
|
-
if final_offsets
|
|
116
|
-
else None
|
|
117
|
-
)
|
|
118
|
-
|
|
119
102
|
if isinstance(params.location, DeckSlotLocation):
|
|
120
103
|
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
|
|
121
104
|
params.location.slotName.id
|
|
@@ -139,7 +122,7 @@ class UnsafePlaceLabwareImplementation(
|
|
|
139
122
|
await ot3api.update_axis_position_estimations([Axis.X, Axis.Y])
|
|
140
123
|
|
|
141
124
|
# Place the labware down
|
|
142
|
-
await self._start_movement(ot3api, definition, location
|
|
125
|
+
await self._start_movement(ot3api, definition, location)
|
|
143
126
|
|
|
144
127
|
return SuccessData(public=UnsafePlaceLabwareResult())
|
|
145
128
|
|
|
@@ -148,7 +131,6 @@ class UnsafePlaceLabwareImplementation(
|
|
|
148
131
|
ot3api: OT3HardwareControlAPI,
|
|
149
132
|
labware_definition: LabwareDefinition,
|
|
150
133
|
location: OnDeckLabwareLocation,
|
|
151
|
-
drop_offset: Optional[Point],
|
|
152
134
|
) -> None:
|
|
153
135
|
gripper_homed_position = await ot3api.gantry_position(
|
|
154
136
|
mount=OT3Mount.GRIPPER,
|
|
@@ -156,13 +138,15 @@ class UnsafePlaceLabwareImplementation(
|
|
|
156
138
|
)
|
|
157
139
|
|
|
158
140
|
to_labware_center = self._state_view.geometry.get_labware_grip_point(
|
|
159
|
-
labware_definition=labware_definition,
|
|
141
|
+
labware_definition=labware_definition,
|
|
142
|
+
location=location,
|
|
143
|
+
move_type=GripperMoveType.DROP_LABWARE,
|
|
144
|
+
user_additional_offset=None,
|
|
160
145
|
)
|
|
161
146
|
|
|
162
147
|
movement_waypoints = get_gripper_labware_placement_waypoints(
|
|
163
148
|
to_labware_center=to_labware_center,
|
|
164
149
|
gripper_home_z=gripper_homed_position.z,
|
|
165
|
-
drop_offset=drop_offset,
|
|
166
150
|
)
|
|
167
151
|
|
|
168
152
|
# start movement
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""WaitForTasks command request, result, and implementation models."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from typing import Optional, Type, TYPE_CHECKING
|
|
5
|
+
from typing_extensions import Literal
|
|
6
|
+
|
|
7
|
+
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
8
|
+
from ..errors.error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
|
|
9
|
+
from ..errors.exceptions import TaskFailedError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..execution import TaskHandler, RunControlHandler
|
|
13
|
+
from ..state.state import StateView
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
WaitForTasksCommandType = Literal["waitForTasks"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WaitForTasksParams(BaseModel):
|
|
20
|
+
"""Payload required to annotate execution with a WaitForTasks."""
|
|
21
|
+
|
|
22
|
+
task_ids: list[str] = Field(
|
|
23
|
+
...,
|
|
24
|
+
description="The list of task ids to wait for.",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WaitForTasksResult(BaseModel):
|
|
29
|
+
"""Result data from the execution of a WaitForTasks command."""
|
|
30
|
+
|
|
31
|
+
task_ids: list[str] = Field(
|
|
32
|
+
...,
|
|
33
|
+
description="The list of completed task ids.",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WaitForTasksImplementation(
|
|
38
|
+
AbstractCommandImpl[WaitForTasksParams, SuccessData[WaitForTasksResult]]
|
|
39
|
+
):
|
|
40
|
+
"""WaitForTasks command implementation."""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
task_handler: TaskHandler,
|
|
45
|
+
run_control: RunControlHandler,
|
|
46
|
+
state_view: StateView,
|
|
47
|
+
**kwargs: object,
|
|
48
|
+
) -> None:
|
|
49
|
+
self._task_handler = task_handler
|
|
50
|
+
self._run_control = run_control
|
|
51
|
+
self._state_view = state_view
|
|
52
|
+
|
|
53
|
+
async def execute(
|
|
54
|
+
self, params: WaitForTasksParams
|
|
55
|
+
) -> SuccessData[WaitForTasksResult]:
|
|
56
|
+
"""Checks for existance of task id and then asynchronously waits for the valid, specified tasks to finish."""
|
|
57
|
+
# Raises the exception if we don't have a valid task id.
|
|
58
|
+
for task_id in params.task_ids:
|
|
59
|
+
_ = self._state_view.tasks.get(task_id)
|
|
60
|
+
|
|
61
|
+
await self._run_control.wait_for_tasks(params.task_ids)
|
|
62
|
+
|
|
63
|
+
failed_tasks = self._state_view.tasks.get_failed_tasks(params.task_ids)
|
|
64
|
+
if failed_tasks:
|
|
65
|
+
raise TaskFailedError(
|
|
66
|
+
message=f"{len(failed_tasks)} tasks failed.",
|
|
67
|
+
details={"failed_task_ids": failed_tasks},
|
|
68
|
+
wrapping=[
|
|
69
|
+
ProtocolCommandFailedError(
|
|
70
|
+
original_error=self._state_view.tasks.get_finished(
|
|
71
|
+
task_id
|
|
72
|
+
).error
|
|
73
|
+
)
|
|
74
|
+
for task_id in failed_tasks
|
|
75
|
+
],
|
|
76
|
+
)
|
|
77
|
+
return SuccessData(public=WaitForTasksResult(task_ids=params.task_ids))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class WaitForTasks(
|
|
81
|
+
BaseCommand[WaitForTasksParams, WaitForTasksResult, ErrorOccurrence]
|
|
82
|
+
):
|
|
83
|
+
"""WaitForTasks command model."""
|
|
84
|
+
|
|
85
|
+
commandType: WaitForTasksCommandType = "waitForTasks"
|
|
86
|
+
params: WaitForTasksParams
|
|
87
|
+
result: Optional[WaitForTasksResult] = None
|
|
88
|
+
|
|
89
|
+
_ImplementationCls: Type[WaitForTasksImplementation] = WaitForTasksImplementation
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class WaitForTasksCreate(BaseCommandCreate[WaitForTasksParams]):
|
|
93
|
+
"""WaitForTasks command request model."""
|
|
94
|
+
|
|
95
|
+
commandType: WaitForTasksCommandType = "waitForTasks"
|
|
96
|
+
params: WaitForTasksParams
|
|
97
|
+
|
|
98
|
+
_CommandCls: Type[WaitForTasks] = WaitForTasks
|
|
@@ -57,6 +57,7 @@ from .exceptions import (
|
|
|
57
57
|
InvalidTargetSpeedError,
|
|
58
58
|
InvalidTargetTemperatureError,
|
|
59
59
|
InvalidBlockVolumeError,
|
|
60
|
+
InvalidRampRateError,
|
|
60
61
|
InvalidHoldTimeError,
|
|
61
62
|
InvalidWavelengthError,
|
|
62
63
|
CannotPerformModuleAction,
|
|
@@ -90,6 +91,7 @@ from .exceptions import (
|
|
|
90
91
|
FlexStackerLabwarePoolNotYetDefinedError,
|
|
91
92
|
FlexStackerNotLogicallyEmptyError,
|
|
92
93
|
InvalidLabwarePositionError,
|
|
94
|
+
InvalidModuleOrientation,
|
|
93
95
|
)
|
|
94
96
|
|
|
95
97
|
from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
|
|
@@ -151,6 +153,7 @@ __all__ = [
|
|
|
151
153
|
"NoTargetTemperatureSetError",
|
|
152
154
|
"InvalidTargetTemperatureError",
|
|
153
155
|
"InvalidTargetSpeedError",
|
|
156
|
+
"InvalidRampRateError",
|
|
154
157
|
"InvalidBlockVolumeError",
|
|
155
158
|
"InvalidHoldTimeError",
|
|
156
159
|
"InvalidLiquidError",
|
|
@@ -174,6 +177,7 @@ __all__ = [
|
|
|
174
177
|
"FlexStackerLabwarePoolNotYetDefinedError",
|
|
175
178
|
"FlexStackerNotLogicallyEmptyError",
|
|
176
179
|
"InvalidLabwarePositionError",
|
|
180
|
+
"InvalidModuleOrientation",
|
|
177
181
|
# error occurrence models
|
|
178
182
|
"ErrorOccurrence",
|
|
179
183
|
"CommandNotAllowedError",
|
|
@@ -413,6 +413,36 @@ class WellDoesNotExistError(ProtocolEngineError):
|
|
|
413
413
|
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
414
414
|
|
|
415
415
|
|
|
416
|
+
class NoTaskFoundError(ProtocolEngineError):
|
|
417
|
+
"""Raised when referencing a task that does not exist.
|
|
418
|
+
|
|
419
|
+
This error could be raised if a protocol references a task before it
|
|
420
|
+
has been created.
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
message: Optional[str] = None,
|
|
426
|
+
details: Optional[Dict[str, Any]] = None,
|
|
427
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
428
|
+
) -> None:
|
|
429
|
+
"""Build a NoTaskFoundError."""
|
|
430
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class TaskFailedError(ProtocolEngineError):
|
|
434
|
+
"""Raised when waiting on a task that failed."""
|
|
435
|
+
|
|
436
|
+
def __init__(
|
|
437
|
+
self,
|
|
438
|
+
message: Optional[str] = None,
|
|
439
|
+
details: Optional[Dict[str, Any]] = None,
|
|
440
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
441
|
+
) -> None:
|
|
442
|
+
"""Build a TaskFailedError."""
|
|
443
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
444
|
+
|
|
445
|
+
|
|
416
446
|
class PipetteNotLoadedError(ProtocolEngineError):
|
|
417
447
|
"""Raised when referencing a pipette that has not been loaded."""
|
|
418
448
|
|
|
@@ -825,6 +855,19 @@ class InvalidTargetTemperatureError(ProtocolEngineError):
|
|
|
825
855
|
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
826
856
|
|
|
827
857
|
|
|
858
|
+
class InvalidRampRateError(ProtocolEngineError):
|
|
859
|
+
"""Raised when attempting to set an invalid ramp rate."""
|
|
860
|
+
|
|
861
|
+
def __init__(
|
|
862
|
+
self,
|
|
863
|
+
message: Optional[str] = None,
|
|
864
|
+
details: Optional[Dict[str, Any]] = None,
|
|
865
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
866
|
+
) -> None:
|
|
867
|
+
"""Build a InvalidRampRateError."""
|
|
868
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
869
|
+
|
|
870
|
+
|
|
828
871
|
class InvalidBlockVolumeError(ProtocolEngineError):
|
|
829
872
|
"""Raised when attempting to set an invalid block max volume."""
|
|
830
873
|
|
|
@@ -1306,3 +1349,15 @@ class InvalidLabwarePositionError(ProtocolEngineError):
|
|
|
1306
1349
|
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1307
1350
|
) -> None:
|
|
1308
1351
|
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
class InvalidModuleOrientation(ProtocolEngineError):
|
|
1355
|
+
"""Raised when a module orientation is invalid for a slot id."""
|
|
1356
|
+
|
|
1357
|
+
def __init__(
|
|
1358
|
+
self,
|
|
1359
|
+
message: Optional[str] = None,
|
|
1360
|
+
details: Optional[dict[str, Any]] = None,
|
|
1361
|
+
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
1362
|
+
) -> None:
|
|
1363
|
+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
|
|
@@ -21,6 +21,7 @@ from .run_control import RunControlHandler
|
|
|
21
21
|
from .hardware_stopper import HardwareStopper
|
|
22
22
|
from .door_watcher import DoorWatcher
|
|
23
23
|
from .status_bar import StatusBarHandler
|
|
24
|
+
from .task_handler import TaskHandler
|
|
24
25
|
from ..resources.file_provider import FileProvider
|
|
25
26
|
|
|
26
27
|
# .thermocycler_movement_flagger omitted from package's public interface.
|
|
@@ -46,5 +47,6 @@ __all__ = [
|
|
|
46
47
|
"DoorWatcher",
|
|
47
48
|
"RailLightsHandler",
|
|
48
49
|
"StatusBarHandler",
|
|
50
|
+
"TaskHandler",
|
|
49
51
|
"FileProvider",
|
|
50
52
|
]
|
|
@@ -35,6 +35,7 @@ from .tip_handler import TipHandler
|
|
|
35
35
|
from .run_control import RunControlHandler
|
|
36
36
|
from .rail_lights import RailLightsHandler
|
|
37
37
|
from .status_bar import StatusBarHandler
|
|
38
|
+
from .task_handler import TaskHandler
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
log = getLogger(__name__)
|
|
@@ -85,6 +86,7 @@ class CommandExecutor:
|
|
|
85
86
|
run_control: RunControlHandler,
|
|
86
87
|
rail_lights: RailLightsHandler,
|
|
87
88
|
status_bar: StatusBarHandler,
|
|
89
|
+
task_handler: TaskHandler,
|
|
88
90
|
model_utils: Optional[ModelUtils] = None,
|
|
89
91
|
command_note_tracker_provider: Optional[CommandNoteTrackerProvider] = None,
|
|
90
92
|
) -> None:
|
|
@@ -106,6 +108,7 @@ class CommandExecutor:
|
|
|
106
108
|
self._command_note_tracker_provider = (
|
|
107
109
|
command_note_tracker_provider or _NoteTracker
|
|
108
110
|
)
|
|
111
|
+
self._task_handler = task_handler
|
|
109
112
|
|
|
110
113
|
async def execute(self, command_id: str) -> None:
|
|
111
114
|
"""Run a given command's execution procedure.
|
|
@@ -131,6 +134,7 @@ class CommandExecutor:
|
|
|
131
134
|
model_utils=self._model_utils,
|
|
132
135
|
status_bar=self._status_bar,
|
|
133
136
|
command_note_adder=note_tracker,
|
|
137
|
+
task_handler=self._task_handler,
|
|
134
138
|
)
|
|
135
139
|
|
|
136
140
|
started_at = self._model_utils.get_timestamp()
|
|
@@ -214,3 +218,7 @@ class CommandExecutor:
|
|
|
214
218
|
type=error_recovery_type,
|
|
215
219
|
)
|
|
216
220
|
)
|
|
221
|
+
|
|
222
|
+
def cancel_tasks(self, message: str | None = None) -> None:
|
|
223
|
+
"""Cancel all concurrent tasks."""
|
|
224
|
+
self._task_handler.cancel_all(message=message)
|
|
@@ -17,6 +17,7 @@ from .run_control import RunControlHandler
|
|
|
17
17
|
from .command_executor import CommandExecutor
|
|
18
18
|
from .queue_worker import QueueWorker
|
|
19
19
|
from .status_bar import StatusBarHandler
|
|
20
|
+
from .task_handler import TaskHandler
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def create_queue_worker(
|
|
@@ -76,7 +77,9 @@ def create_queue_worker(
|
|
|
76
77
|
rail_lights_handler = RailLightsHandler(
|
|
77
78
|
hardware_api=hardware_api,
|
|
78
79
|
)
|
|
79
|
-
|
|
80
|
+
task_handler = TaskHandler(
|
|
81
|
+
state_store=state_store, action_dispatcher=action_dispatcher
|
|
82
|
+
)
|
|
80
83
|
status_bar_handler = StatusBarHandler(hardware_api=hardware_api)
|
|
81
84
|
|
|
82
85
|
command_executor = CommandExecutor(
|
|
@@ -93,6 +96,7 @@ def create_queue_worker(
|
|
|
93
96
|
run_control=run_control_handler,
|
|
94
97
|
rail_lights=rail_lights_handler,
|
|
95
98
|
status_bar=status_bar_handler,
|
|
99
|
+
task_handler=task_handler,
|
|
96
100
|
)
|
|
97
101
|
|
|
98
102
|
return QueueWorker(
|
|
@@ -31,6 +31,7 @@ from ..types import (
|
|
|
31
31
|
OnLabwareLocation,
|
|
32
32
|
LabwareLocation,
|
|
33
33
|
OnDeckLabwareLocation,
|
|
34
|
+
GripperMoveType,
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
@@ -141,10 +142,16 @@ class LabwareMovementHandler:
|
|
|
141
142
|
labware_definition = self._state_store.labware.get_definition(labware_id)
|
|
142
143
|
|
|
143
144
|
from_labware_center = self._state_store.geometry.get_labware_grip_point(
|
|
144
|
-
labware_definition=labware_definition,
|
|
145
|
+
labware_definition=labware_definition,
|
|
146
|
+
location=current_location,
|
|
147
|
+
move_type=GripperMoveType.PICK_UP_LABWARE,
|
|
148
|
+
user_additional_offset=user_pick_up_offset,
|
|
145
149
|
)
|
|
146
150
|
to_labware_center = self._state_store.geometry.get_labware_grip_point(
|
|
147
|
-
labware_definition=labware_definition,
|
|
151
|
+
labware_definition=labware_definition,
|
|
152
|
+
location=new_location,
|
|
153
|
+
move_type=GripperMoveType.DROP_LABWARE,
|
|
154
|
+
user_additional_offset=user_drop_offset,
|
|
148
155
|
)
|
|
149
156
|
|
|
150
157
|
if use_virtual_gripper:
|
|
@@ -193,20 +200,10 @@ class LabwareMovementHandler:
|
|
|
193
200
|
async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement(
|
|
194
201
|
labware_location=current_location
|
|
195
202
|
):
|
|
196
|
-
final_offsets = (
|
|
197
|
-
self._state_store.geometry.get_final_labware_movement_offset_vectors(
|
|
198
|
-
from_location=current_location,
|
|
199
|
-
to_location=new_location,
|
|
200
|
-
additional_pick_up_offset=user_pick_up_offset,
|
|
201
|
-
additional_drop_offset=user_drop_offset,
|
|
202
|
-
current_labware=labware_definition,
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
203
|
movement_waypoints = get_gripper_labware_movement_waypoints(
|
|
206
204
|
from_labware_center=from_labware_center,
|
|
207
205
|
to_labware_center=to_labware_center,
|
|
208
206
|
gripper_home_z=gripper_homed_position.z,
|
|
209
|
-
offset_data=final_offsets,
|
|
210
207
|
post_drop_slide_offset=post_drop_slide_offset,
|
|
211
208
|
gripper_home_z_offset=gripper_z_offset,
|
|
212
209
|
)
|