opentrons 8.7.0a5__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.

Files changed (144) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/drivers/asyncio/communication/serial_connection.py +129 -52
  3. opentrons/drivers/heater_shaker/abstract.py +5 -0
  4. opentrons/drivers/heater_shaker/driver.py +10 -0
  5. opentrons/drivers/heater_shaker/simulator.py +4 -0
  6. opentrons/drivers/thermocycler/abstract.py +6 -0
  7. opentrons/drivers/thermocycler/driver.py +61 -10
  8. opentrons/drivers/thermocycler/simulator.py +6 -0
  9. opentrons/hardware_control/api.py +24 -5
  10. opentrons/hardware_control/backends/controller.py +8 -2
  11. opentrons/hardware_control/backends/ot3controller.py +3 -0
  12. opentrons/hardware_control/backends/ot3simulator.py +2 -1
  13. opentrons/hardware_control/backends/simulator.py +2 -1
  14. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  15. opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
  16. opentrons/hardware_control/emulation/connection_handler.py +8 -5
  17. opentrons/hardware_control/emulation/heater_shaker.py +12 -3
  18. opentrons/hardware_control/emulation/settings.py +1 -1
  19. opentrons/hardware_control/emulation/thermocycler.py +67 -15
  20. opentrons/hardware_control/module_control.py +82 -8
  21. opentrons/hardware_control/modules/__init__.py +3 -0
  22. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  23. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  24. opentrons/hardware_control/modules/heater_shaker.py +42 -5
  25. opentrons/hardware_control/modules/magdeck.py +8 -4
  26. opentrons/hardware_control/modules/mod_abc.py +13 -5
  27. opentrons/hardware_control/modules/tempdeck.py +25 -5
  28. opentrons/hardware_control/modules/thermocycler.py +68 -11
  29. opentrons/hardware_control/modules/types.py +20 -1
  30. opentrons/hardware_control/modules/utils.py +11 -4
  31. opentrons/hardware_control/nozzle_manager.py +3 -0
  32. opentrons/hardware_control/ot3api.py +26 -5
  33. opentrons/hardware_control/poller.py +22 -8
  34. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  35. opentrons/hardware_control/types.py +31 -2
  36. opentrons/legacy_commands/module_commands.py +23 -0
  37. opentrons/legacy_commands/protocol_commands.py +20 -0
  38. opentrons/legacy_commands/types.py +80 -0
  39. opentrons/motion_planning/deck_conflict.py +17 -12
  40. opentrons/motion_planning/waypoints.py +15 -29
  41. opentrons/protocol_api/__init__.py +5 -1
  42. opentrons/protocol_api/_types.py +6 -1
  43. opentrons/protocol_api/core/common.py +3 -1
  44. opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
  45. opentrons/protocol_api/core/engine/labware.py +8 -1
  46. opentrons/protocol_api/core/engine/module_core.py +75 -8
  47. opentrons/protocol_api/core/engine/protocol.py +18 -1
  48. opentrons/protocol_api/core/engine/tasks.py +48 -0
  49. opentrons/protocol_api/core/engine/well.py +8 -0
  50. opentrons/protocol_api/core/legacy/legacy_module_core.py +24 -4
  51. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
  52. opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
  53. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  54. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  55. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  56. opentrons/protocol_api/core/module.py +37 -4
  57. opentrons/protocol_api/core/protocol.py +11 -2
  58. opentrons/protocol_api/core/tasks.py +31 -0
  59. opentrons/protocol_api/core/well.py +4 -0
  60. opentrons/protocol_api/labware.py +5 -0
  61. opentrons/protocol_api/module_contexts.py +117 -11
  62. opentrons/protocol_api/protocol_context.py +26 -4
  63. opentrons/protocol_api/robot_context.py +38 -21
  64. opentrons/protocol_api/tasks.py +48 -0
  65. opentrons/protocol_api/validation.py +6 -1
  66. opentrons/protocol_engine/actions/__init__.py +4 -2
  67. opentrons/protocol_engine/actions/actions.py +22 -9
  68. opentrons/protocol_engine/clients/sync_client.py +42 -7
  69. opentrons/protocol_engine/commands/__init__.py +42 -0
  70. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  71. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  72. opentrons/protocol_engine/commands/aspirate.py +1 -0
  73. opentrons/protocol_engine/commands/command.py +1 -0
  74. opentrons/protocol_engine/commands/command_unions.py +49 -0
  75. opentrons/protocol_engine/commands/create_timer.py +83 -0
  76. opentrons/protocol_engine/commands/dispense.py +1 -0
  77. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  78. opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
  79. opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
  80. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
  81. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
  82. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
  83. opentrons/protocol_engine/commands/movement_common.py +2 -0
  84. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  85. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  86. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
  87. opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
  88. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  89. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  90. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +40 -6
  91. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +29 -5
  92. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
  93. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  94. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  95. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  96. opentrons/protocol_engine/errors/__init__.py +4 -0
  97. opentrons/protocol_engine/errors/exceptions.py +55 -0
  98. opentrons/protocol_engine/execution/__init__.py +2 -0
  99. opentrons/protocol_engine/execution/command_executor.py +8 -0
  100. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  101. opentrons/protocol_engine/execution/labware_movement.py +9 -12
  102. opentrons/protocol_engine/execution/movement.py +2 -0
  103. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  104. opentrons/protocol_engine/execution/run_control.py +8 -0
  105. opentrons/protocol_engine/execution/task_handler.py +157 -0
  106. opentrons/protocol_engine/protocol_engine.py +75 -34
  107. opentrons/protocol_engine/resources/__init__.py +2 -0
  108. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  109. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  110. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  111. opentrons/protocol_engine/state/_well_math.py +60 -18
  112. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  113. opentrons/protocol_engine/state/commands.py +14 -11
  114. opentrons/protocol_engine/state/geometry.py +213 -374
  115. opentrons/protocol_engine/state/labware.py +52 -102
  116. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  117. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
  118. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  119. opentrons/protocol_engine/state/modules.py +21 -8
  120. opentrons/protocol_engine/state/motion.py +44 -0
  121. opentrons/protocol_engine/state/state.py +14 -0
  122. opentrons/protocol_engine/state/state_summary.py +2 -0
  123. opentrons/protocol_engine/state/tasks.py +139 -0
  124. opentrons/protocol_engine/state/tips.py +177 -258
  125. opentrons/protocol_engine/state/update_types.py +16 -9
  126. opentrons/protocol_engine/types/__init__.py +9 -3
  127. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  128. opentrons/protocol_engine/types/instrument.py +8 -1
  129. opentrons/protocol_engine/types/labware.py +1 -13
  130. opentrons/protocol_engine/types/module.py +10 -0
  131. opentrons/protocol_engine/types/tasks.py +38 -0
  132. opentrons/protocol_engine/types/tip.py +9 -0
  133. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  134. opentrons/protocol_runner/run_orchestrator.py +18 -2
  135. opentrons/protocols/api_support/definitions.py +1 -1
  136. opentrons/protocols/api_support/types.py +2 -1
  137. opentrons/simulate.py +48 -15
  138. opentrons/system/camera.py +1 -1
  139. {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/METADATA +4 -4
  140. {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/RECORD +143 -127
  141. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  142. {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/WHEEL +0 -0
  143. {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/entry_points.txt +0 -0
  144. {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,136 @@
1
+ """Command models to set a shake speed for a Heater-Shaker Module."""
2
+ from __future__ import annotations
3
+ from typing import Optional, TYPE_CHECKING
4
+ from typing_extensions import Literal, Type
5
+ from pydantic import BaseModel, Field
6
+
7
+ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
8
+ from ...errors.error_occurrence import ErrorOccurrence
9
+ from ...state import update_types
10
+ from .common import get_heatershaker_ready_to_shake
11
+
12
+ if TYPE_CHECKING:
13
+ from opentrons.protocol_engine.state.state import StateView
14
+ from opentrons.protocol_engine.execution import (
15
+ EquipmentHandler,
16
+ MovementHandler,
17
+ TaskHandler,
18
+ )
19
+
20
+ SetShakeSpeedCommandType = Literal["heaterShaker/setShakeSpeed"]
21
+
22
+
23
+ class SetShakeSpeedParams(BaseModel):
24
+ """Input parameters to set a shake speed for a Heater-Shaker Module."""
25
+
26
+ moduleId: str = Field(..., description="Unique ID of the Heater-Shaker Module.")
27
+ rpm: float = Field(..., description="Target speed in rotations per minute.")
28
+ taskId: str | None = Field(
29
+ None,
30
+ description="Id for the background task that manages the temperature",
31
+ )
32
+
33
+
34
+ class SetShakeSpeedResult(BaseModel):
35
+ """Result data from setting and waiting for a Heater-Shaker's shake speed."""
36
+
37
+ pipetteRetracted: bool = Field(
38
+ ...,
39
+ description=(
40
+ "Whether this command automatically retracted the pipettes"
41
+ " before starting the shake, to avoid a potential collision."
42
+ ),
43
+ )
44
+ taskId: str = Field(
45
+ description="The task id for the setTargetTemperature task",
46
+ )
47
+
48
+
49
+ class SetShakeSpeedImpl(
50
+ AbstractCommandImpl[SetShakeSpeedParams, SuccessData[SetShakeSpeedResult]]
51
+ ):
52
+ """Execution implementation of Heater-Shaker's set shake speed command."""
53
+
54
+ def __init__(
55
+ self,
56
+ state_view: StateView,
57
+ equipment: EquipmentHandler,
58
+ movement: MovementHandler,
59
+ task_handler: TaskHandler,
60
+ **unused_dependencies: object,
61
+ ) -> None:
62
+ self._state_view = state_view
63
+ self._equipment = equipment
64
+ self._movement = movement
65
+ self._task_handler = task_handler
66
+
67
+ async def execute(
68
+ self,
69
+ params: SetShakeSpeedParams,
70
+ ) -> SuccessData[SetShakeSpeedResult]:
71
+ """Set a Heater-Shaker's target shake speed."""
72
+ state_update = update_types.StateUpdate()
73
+
74
+ # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError.
75
+ hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate(
76
+ module_id=params.moduleId
77
+ )
78
+
79
+ validated_speed = await get_heatershaker_ready_to_shake(
80
+ hs_module_substate, params.rpm
81
+ )
82
+ pipette_should_retract = (
83
+ self._state_view.motion.check_pipette_blocking_hs_shaker(
84
+ hs_module_substate.module_id
85
+ )
86
+ )
87
+ if pipette_should_retract:
88
+ # Move pipette away if it is close to the heater-shaker
89
+ # TODO(jbl 2022-07-28) replace home movement with a retract movement
90
+ await self._movement.home(
91
+ axes=self._state_view.motion.get_robot_mount_axes()
92
+ )
93
+ state_update.clear_all_pipette_locations()
94
+
95
+ # Allow propagation of ModuleNotAttachedError.
96
+ hs_hardware_module = self._equipment.get_module_hardware_api(
97
+ hs_module_substate.module_id
98
+ )
99
+
100
+ async def start_shake(task_handler: TaskHandler) -> None:
101
+ if hs_hardware_module is not None:
102
+ async with task_handler.synchronize_cancel_previous(
103
+ hs_module_substate.module_id
104
+ ):
105
+ await hs_hardware_module.set_speed(rpm=validated_speed)
106
+
107
+ task = await self._task_handler.create_task(
108
+ task_function=start_shake, id=params.taskId
109
+ )
110
+ return SuccessData(
111
+ public=SetShakeSpeedResult(
112
+ pipetteRetracted=pipette_should_retract, taskId=task.id
113
+ ),
114
+ state_update=state_update,
115
+ )
116
+
117
+
118
+ class SetShakeSpeed(
119
+ BaseCommand[SetShakeSpeedParams, SetShakeSpeedResult, ErrorOccurrence]
120
+ ):
121
+ """A command to set a Heater-Shaker's shake speed."""
122
+
123
+ commandType: SetShakeSpeedCommandType = "heaterShaker/setShakeSpeed"
124
+ params: SetShakeSpeedParams
125
+ result: Optional[SetShakeSpeedResult] = None
126
+
127
+ _ImplementationCls: Type[SetShakeSpeedImpl] = SetShakeSpeedImpl
128
+
129
+
130
+ class SetShakeSpeedCreate(BaseCommandCreate[SetShakeSpeedParams]):
131
+ """A request to create a Heater-Shaker's set shake speed command."""
132
+
133
+ commandType: SetShakeSpeedCommandType = "heaterShaker/setShakeSpeed"
134
+ params: SetShakeSpeedParams
135
+
136
+ _CommandCls: Type[SetShakeSpeed] = SetShakeSpeed
@@ -1,31 +1,47 @@
1
1
  """Command models to start heating a Heater-Shaker Module."""
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
  SetTargetTemperatureCommandType = Literal["heaterShaker/setTargetTemperature"]
17
18
 
18
19
 
20
+ def _remove_default(s: dict[str, Any]) -> None:
21
+ s.pop("default", None)
22
+
23
+
19
24
  class SetTargetTemperatureParams(BaseModel):
20
25
  """Input parameters to set a Heater-Shaker's target temperature."""
21
26
 
22
27
  moduleId: str = Field(..., description="Unique ID of the Heater-Shaker 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 SetTargetTemperatureResult(BaseModel):
27
37
  """Result data from setting a Heater-Shaker's target temperature."""
28
38
 
39
+ taskId: str | SkipJsonSchema[None] = Field(
40
+ None,
41
+ description="The task id for the setTargetTemperature task",
42
+ json_schema_extra=_remove_default,
43
+ )
44
+
29
45
 
30
46
  class SetTargetTemperatureImpl(
31
47
  AbstractCommandImpl[
@@ -38,10 +54,12 @@ class SetTargetTemperatureImpl(
38
54
  self,
39
55
  state_view: StateView,
40
56
  equipment: EquipmentHandler,
57
+ task_handler: TaskHandler,
41
58
  **unused_dependencies: object,
42
59
  ) -> None:
43
60
  self._state_view = state_view
44
61
  self._equipment = equipment
62
+ self._task_handler = task_handler
45
63
 
46
64
  async def execute(
47
65
  self,
@@ -61,11 +79,19 @@ class SetTargetTemperatureImpl(
61
79
  hs_module_substate.module_id
62
80
  )
63
81
 
64
- if hs_hardware_module is not None:
65
- await hs_hardware_module.start_set_temperature(validated_temp)
82
+ async def start_set_temperature(task_handler: TaskHandler) -> None:
83
+ if hs_hardware_module is not None:
84
+ async with task_handler.synchronize_cancel_previous(
85
+ hs_module_substate.module_id
86
+ ):
87
+ await hs_hardware_module.start_set_temperature(validated_temp)
88
+ await hs_hardware_module.await_temperature(validated_temp)
66
89
 
90
+ task = await self._task_handler.create_task(
91
+ task_function=start_set_temperature, id=params.taskId
92
+ )
67
93
  return SuccessData(
68
- public=SetTargetTemperatureResult(),
94
+ public=SetTargetTemperatureResult(taskId=task.id),
69
95
  )
70
96
 
71
97
 
@@ -152,6 +152,7 @@ async def move_to_well(
152
152
  minimum_z_height: Optional[float] = None,
153
153
  speed: Optional[float] = None,
154
154
  operation_volume: Optional[float] = None,
155
+ offset_pipette_for_reservoir_subwells: bool = False,
155
156
  ) -> MoveToWellOperationReturn:
156
157
  """Execute a move to well microoperation."""
157
158
  try:
@@ -165,6 +166,7 @@ async def move_to_well(
165
166
  minimum_z_height=minimum_z_height,
166
167
  speed=speed,
167
168
  operation_volume=operation_volume,
169
+ offset_pipette_for_reservoir_subwells=offset_pipette_for_reservoir_subwells,
168
170
  )
169
171
  except StallOrCollisionDetectedError as e:
170
172
  return DefinedErrorData(
@@ -10,7 +10,7 @@ from typing_extensions import Literal
10
10
  from ..errors import ErrorOccurrence, PickUpTipTipNotAttachedError
11
11
  from ..resources import ModelUtils
12
12
  from ..state import update_types
13
- from ..types import PickUpTipWellLocation, LabwareWellId
13
+ from ..types import PickUpTipWellLocation, LabwareWellId, TipRackWellState
14
14
  from .pipetting_common import (
15
15
  PipetteIdMixin,
16
16
  )
@@ -121,10 +121,14 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
121
121
  labware_id = params.labwareId
122
122
  well_name = params.wellName
123
123
 
124
- tips_to_mark_as_used = self._state_view.tips.compute_tips_to_mark_as_used(
125
- labware_id=labware_id,
126
- well_name=well_name,
127
- nozzle_map=self._state_view.pipettes.get_nozzle_configuration(pipette_id),
124
+ tips_to_mark_as_empty = (
125
+ self._state_view.tips.compute_tips_to_mark_as_used_or_empty(
126
+ labware_id=labware_id,
127
+ well_name=well_name,
128
+ nozzle_map=self._state_view.pipettes.get_nozzle_configuration(
129
+ pipette_id
130
+ ),
131
+ )
128
132
  )
129
133
 
130
134
  well_location = self._state_view.geometry.convert_pick_up_tip_well_location(
@@ -160,16 +164,20 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
160
164
  ),
161
165
  )
162
166
  .set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
163
- .mark_tips_as_used(
164
- labware_id=labware_id, well_names=tips_to_mark_as_used
167
+ .update_tip_rack_well_state(
168
+ tip_state=TipRackWellState.EMPTY,
169
+ labware_id=labware_id,
170
+ well_names=tips_to_mark_as_empty,
165
171
  )
166
172
  )
167
173
  state_update = (
168
174
  update_types.StateUpdate.reduce(
169
175
  update_types.StateUpdate(), move_result.state_update
170
176
  )
171
- .mark_tips_as_used(
172
- labware_id=labware_id, well_names=tips_to_mark_as_used
177
+ .update_tip_rack_well_state(
178
+ tip_state=TipRackWellState.EMPTY,
179
+ labware_id=labware_id,
180
+ well_names=tips_to_mark_as_empty,
173
181
  )
174
182
  .set_fluid_unknown(pipette_id=pipette_id)
175
183
  )
@@ -197,8 +205,10 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
197
205
  labware_id=labware_id, well_name=well_name
198
206
  ),
199
207
  )
200
- .mark_tips_as_used(
201
- labware_id=labware_id, well_names=tips_to_mark_as_used
208
+ .update_tip_rack_well_state(
209
+ tip_state=TipRackWellState.EMPTY,
210
+ labware_id=labware_id,
211
+ well_names=tips_to_mark_as_empty,
202
212
  )
203
213
  .set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
204
214
  .set_pipette_ready_to_aspirate(
@@ -0,0 +1,97 @@
1
+ """Set tip state command request, result, and implementation models."""
2
+
3
+ from __future__ import annotations
4
+ from pydantic import BaseModel, Field
5
+ from typing import TYPE_CHECKING, Optional, Type, List
6
+ from typing_extensions import Literal
7
+
8
+ from ..errors.error_occurrence import ErrorOccurrence
9
+ from ..state.update_types import StateUpdate
10
+ from ..types import TipRackWellState
11
+
12
+
13
+ from .command import (
14
+ AbstractCommandImpl,
15
+ BaseCommand,
16
+ BaseCommandCreate,
17
+ SuccessData,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from ..state.state import StateView
22
+
23
+
24
+ SetTipStateCommandType = Literal["setTipState"]
25
+
26
+
27
+ class SetTipStateParams(BaseModel):
28
+ """Payload needed to set tip wells of a tip rack to the requested state."""
29
+
30
+ labwareId: str = Field(
31
+ ..., description="Identifier of tip rack labware to set tip wells in."
32
+ )
33
+ wellNames: List[str] = Field(
34
+ ..., description="Names of the well to set tip well state for."
35
+ )
36
+ tipWellState: TipRackWellState = Field(
37
+ ..., description="State to set tip wells to."
38
+ )
39
+
40
+
41
+ class SetTipStateResult(BaseModel):
42
+ """Result data from the execution of a setTipState command."""
43
+
44
+ pass
45
+
46
+
47
+ class SetTipStateImplementation(
48
+ AbstractCommandImpl[SetTipStateParams, SuccessData[SetTipStateResult]]
49
+ ):
50
+ """Set tip state command implementation."""
51
+
52
+ def __init__(
53
+ self,
54
+ state_view: StateView,
55
+ **kwargs: object,
56
+ ) -> None:
57
+ self._state_view = state_view
58
+
59
+ async def execute(
60
+ self, params: SetTipStateParams
61
+ ) -> SuccessData[SetTipStateResult]:
62
+ """Set the tip rack wells to the requested state."""
63
+ labware_id = params.labwareId
64
+ well_names = params.wellNames
65
+
66
+ self._state_view.labware.raise_if_not_tip_rack(labware_id=labware_id)
67
+ self._state_view.labware.raise_if_wells_are_invalid(
68
+ labware_id=labware_id, well_names=well_names
69
+ )
70
+
71
+ return SuccessData(
72
+ public=SetTipStateResult(),
73
+ state_update=StateUpdate().update_tip_rack_well_state(
74
+ tip_state=params.tipWellState,
75
+ labware_id=labware_id,
76
+ well_names=well_names,
77
+ ),
78
+ )
79
+
80
+
81
+ class SetTipState(BaseCommand[SetTipStateParams, SetTipStateResult, ErrorOccurrence]):
82
+ """Set tip state command model."""
83
+
84
+ commandType: SetTipStateCommandType = "setTipState"
85
+ params: SetTipStateParams
86
+ result: Optional[SetTipStateResult] = None
87
+
88
+ _ImplementationCls: Type[SetTipStateImplementation] = SetTipStateImplementation
89
+
90
+
91
+ class SetTipStateCreate(BaseCommandCreate[SetTipStateParams]):
92
+ """Set tip state command creation request model."""
93
+
94
+ commandType: SetTipStateCommandType = "setTipState"
95
+ params: SetTipStateParams
96
+
97
+ _CommandCls: Type[SetTipState] = SetTipState
@@ -1,25 +1,35 @@
1
1
  """Command models to start heating a Temperature Module."""
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
  SetTargetTemperatureCommandType = Literal["temperatureModule/setTargetTemperature"]
16
17
 
17
18
 
19
+ def _remove_default(s: dict[str, Any]) -> None:
20
+ s.pop("default", None)
21
+
22
+
18
23
  class SetTargetTemperatureParams(BaseModel):
19
24
  """Input parameters to set a Temperature Module's target temperature."""
20
25
 
21
26
  moduleId: str = Field(..., description="Unique ID of the Temperature Module.")
22
27
  celsius: float = Field(..., description="Target temperature in °C.")
28
+ taskId: str | SkipJsonSchema[None] = Field(
29
+ None,
30
+ description="Id for the background task that manages the temperature",
31
+ json_schema_extra=_remove_default,
32
+ )
23
33
 
24
34
 
25
35
  class SetTargetTemperatureResult(BaseModel):
@@ -30,6 +40,11 @@ class SetTargetTemperatureResult(BaseModel):
30
40
  description="The target temperature that was set after validation "
31
41
  "and type conversion (if any).",
32
42
  )
43
+ taskId: str | SkipJsonSchema[None] = Field(
44
+ None,
45
+ description="The task id for the setTargetTemperature task",
46
+ json_schema_extra=_remove_default,
47
+ )
33
48
 
34
49
 
35
50
  class SetTargetTemperatureImpl(
@@ -43,10 +58,12 @@ class SetTargetTemperatureImpl(
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, params: SetTargetTemperatureParams
@@ -56,19 +73,33 @@ class SetTargetTemperatureImpl(
56
73
  module_substate = self._state_view.modules.get_temperature_module_substate(
57
74
  module_id=params.moduleId
58
75
  )
59
-
60
76
  # Verify temperature from temperature module view
61
77
  validated_temp = module_substate.validate_target_temperature(params.celsius)
62
-
63
78
  # Allow propagation of ModuleNotAttachedError.
64
79
  temp_hardware_module = self._equipment.get_module_hardware_api(
65
80
  module_substate.module_id
66
81
  )
67
82
 
68
- if temp_hardware_module is not None:
69
- await temp_hardware_module.start_set_temperature(celsius=validated_temp)
83
+ async def start_set_temperature(task_handler: TaskHandler) -> None:
84
+ if temp_hardware_module is not None:
85
+ async with task_handler.synchronize_cancel_previous(
86
+ module_substate.module_id
87
+ ):
88
+ await temp_hardware_module.start_set_temperature(
89
+ celsius=validated_temp
90
+ )
91
+ await temp_hardware_module.await_temperature(
92
+ awaiting_temperature=validated_temp
93
+ )
94
+
95
+ task = await self._task_handler.create_task(
96
+ task_function=start_set_temperature, id=params.taskId
97
+ )
98
+
70
99
  return SuccessData(
71
- public=SetTargetTemperatureResult(targetTemperature=validated_temp),
100
+ public=SetTargetTemperatureResult(
101
+ targetTemperature=validated_temp, taskId=task.id
102
+ ),
72
103
  )
73
104
 
74
105
 
@@ -73,6 +73,15 @@ from .run_profile import (
73
73
  RunProfileCreate,
74
74
  )
75
75
 
76
+ from .start_run_extended_profile import (
77
+ StartRunExtendedProfileCommandType,
78
+ StartRunExtendedProfileParams,
79
+ StartRunExtendedProfileStepParams,
80
+ StartRunExtendedProfileResult,
81
+ StartRunExtendedProfile,
82
+ StartRunExtendedProfileCreate,
83
+ )
84
+
76
85
  from .run_extended_profile import (
77
86
  RunExtendedProfileCommandType,
78
87
  RunExtendedProfileParams,
@@ -140,6 +149,13 @@ __all__ = [
140
149
  "RunProfileResult",
141
150
  "RunProfile",
142
151
  "RunProfileCreate",
152
+ # Start run profile command models,
153
+ "StartRunExtendedProfileCommandType",
154
+ "StartRunExtendedProfileParams",
155
+ "StartRunExtendedProfileStepParams",
156
+ "StartRunExtendedProfileResult",
157
+ "StartRunExtendedProfile",
158
+ "StartRunExtendedProfileCreate",
143
159
  # Run extended profile command models.
144
160
  "RunExtendedProfileCommandType",
145
161
  "RunExtendedProfileParams",
@@ -33,6 +33,11 @@ class ProfileStep(BaseModel):
33
33
  holdSeconds: float = Field(
34
34
  ..., description="Time to hold target temperature in seconds."
35
35
  )
36
+ rampRate: float | SkipJsonSchema[None] = Field(
37
+ None,
38
+ description="How quickly to change temperature in °C/second.",
39
+ json_schema_extra=_remove_default,
40
+ )
36
41
 
37
42
 
38
43
  class ProfileCycle(BaseModel):
@@ -68,6 +73,7 @@ def _transform_profile_step(
68
73
  return ThermocyclerStep(
69
74
  temperature=thermocycler_state.validate_target_block_temperature(step.celsius),
70
75
  hold_time_seconds=step.holdSeconds,
76
+ ramp_rate=thermocycler_state.validate_ramp_rate(step.rampRate, step.celsius),
71
77
  )
72
78
 
73
79
 
@@ -30,6 +30,11 @@ class RunProfileStepParams(BaseModel):
30
30
  holdSeconds: float = Field(
31
31
  ..., description="Time to hold target temperature at in seconds."
32
32
  )
33
+ rampRate: float | SkipJsonSchema[None] = Field(
34
+ None,
35
+ description="How quickly to change temperature in °C/second.",
36
+ json_schema_extra=_remove_default,
37
+ )
33
38
 
34
39
 
35
40
  class RunProfileParams(BaseModel):
@@ -81,6 +86,9 @@ class RunProfileImpl(
81
86
  profile_step.celsius
82
87
  ),
83
88
  hold_time_seconds=profile_step.holdSeconds,
89
+ ramp_rate=thermocycler_state.validate_ramp_rate(
90
+ profile_step.rampRate, profile_step.celsius
91
+ ),
84
92
  )
85
93
  for profile_step in params.profile
86
94
  ]
@@ -11,7 +11,7 @@ from ...errors.error_occurrence import ErrorOccurrence
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from opentrons.protocol_engine.state.state import StateView
14
- from opentrons.protocol_engine.execution import EquipmentHandler
14
+ from opentrons.protocol_engine.execution import EquipmentHandler, TaskHandler
15
15
 
16
16
 
17
17
  SetTargetBlockTemperatureCommandType = Literal["thermocycler/setTargetBlockTemperature"]
@@ -39,6 +39,17 @@ class SetTargetBlockTemperatureParams(BaseModel):
39
39
  " the given hold time has elapsed.",
40
40
  json_schema_extra=_remove_default,
41
41
  )
42
+ ramp_rate: float | SkipJsonSchema[None] = Field(
43
+ None,
44
+ description="The rate in C°/second to change temperature from the current target."
45
+ " If unspecified, the Thermocycler will change temperature at the fastest possible rate.",
46
+ json_schema_extra=_remove_default,
47
+ )
48
+ taskId: str | SkipJsonSchema[None] = Field(
49
+ None,
50
+ description="Id for the background task that manages the temperature.",
51
+ json_schema_extra=_remove_default,
52
+ )
42
53
 
43
54
 
44
55
  class SetTargetBlockTemperatureResult(BaseModel):
@@ -48,6 +59,11 @@ class SetTargetBlockTemperatureResult(BaseModel):
48
59
  ...,
49
60
  description="The target block temperature that was set after validation.",
50
61
  )
62
+ taskId: str | SkipJsonSchema[None] = Field(
63
+ None,
64
+ description="The task id for the setTargetBlockTemperature",
65
+ json_schema_extra=_remove_default,
66
+ )
51
67
 
52
68
 
53
69
  class SetTargetBlockTemperatureImpl(
@@ -62,10 +78,12 @@ class SetTargetBlockTemperatureImpl(
62
78
  self,
63
79
  state_view: StateView,
64
80
  equipment: EquipmentHandler,
81
+ task_handler: TaskHandler,
65
82
  **unused_dependencies: object,
66
83
  ) -> None:
67
84
  self._state_view = state_view
68
85
  self._equipment = equipment
86
+ self._task_handler = task_handler
69
87
 
70
88
  async def execute(
71
89
  self,
@@ -90,19 +108,35 @@ class SetTargetBlockTemperatureImpl(
90
108
  hold_time = thermocycler_state.validate_hold_time(params.holdTimeSeconds)
91
109
  else:
92
110
  hold_time = None
111
+ target_ramp_rate: Optional[float]
112
+ if params.ramp_rate is not None:
113
+ target_ramp_rate = thermocycler_state.validate_ramp_rate(
114
+ params.ramp_rate, target_temperature
115
+ )
116
+ else:
117
+ target_ramp_rate = None
93
118
 
94
119
  thermocycler_hardware = self._equipment.get_module_hardware_api(
95
120
  thermocycler_state.module_id
96
121
  )
97
122
 
98
- if thermocycler_hardware is not None:
99
- await thermocycler_hardware.set_target_block_temperature(
100
- target_temperature, volume=target_volume, hold_time_seconds=hold_time
101
- )
123
+ async def set_target_block_temperature(task_handler: TaskHandler) -> None:
124
+ if thermocycler_hardware is not None:
125
+ await thermocycler_hardware.set_target_block_temperature(
126
+ target_temperature,
127
+ volume=target_volume,
128
+ hold_time_seconds=hold_time,
129
+ ramp_rate=target_ramp_rate,
130
+ )
131
+ await thermocycler_hardware.wait_for_block_target()
132
+
133
+ task = await self._task_handler.create_task(
134
+ task_function=set_target_block_temperature, id=params.taskId
135
+ )
102
136
 
103
137
  return SuccessData(
104
138
  public=SetTargetBlockTemperatureResult(
105
- targetBlockTemperature=target_temperature
139
+ targetBlockTemperature=target_temperature, taskId=task.id
106
140
  ),
107
141
  )
108
142