opentrons 8.7.0a9__py3-none-any.whl → 8.8.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.
Files changed (189) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/cli/analyze.py +4 -1
  3. opentrons/config/__init__.py +7 -0
  4. opentrons/drivers/asyncio/communication/serial_connection.py +126 -49
  5. opentrons/drivers/heater_shaker/abstract.py +5 -0
  6. opentrons/drivers/heater_shaker/driver.py +10 -0
  7. opentrons/drivers/heater_shaker/simulator.py +4 -0
  8. opentrons/drivers/thermocycler/abstract.py +6 -0
  9. opentrons/drivers/thermocycler/driver.py +61 -10
  10. opentrons/drivers/thermocycler/simulator.py +6 -0
  11. opentrons/drivers/vacuum_module/__init__.py +5 -0
  12. opentrons/drivers/vacuum_module/abstract.py +93 -0
  13. opentrons/drivers/vacuum_module/driver.py +208 -0
  14. opentrons/drivers/vacuum_module/errors.py +39 -0
  15. opentrons/drivers/vacuum_module/simulator.py +85 -0
  16. opentrons/drivers/vacuum_module/types.py +79 -0
  17. opentrons/execute.py +3 -0
  18. opentrons/hardware_control/api.py +24 -5
  19. opentrons/hardware_control/backends/controller.py +8 -2
  20. opentrons/hardware_control/backends/flex_protocol.py +1 -0
  21. opentrons/hardware_control/backends/ot3controller.py +35 -2
  22. opentrons/hardware_control/backends/ot3simulator.py +3 -1
  23. opentrons/hardware_control/backends/ot3utils.py +37 -0
  24. opentrons/hardware_control/backends/simulator.py +2 -1
  25. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  26. opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
  27. opentrons/hardware_control/emulation/connection_handler.py +8 -5
  28. opentrons/hardware_control/emulation/heater_shaker.py +12 -3
  29. opentrons/hardware_control/emulation/settings.py +1 -1
  30. opentrons/hardware_control/emulation/thermocycler.py +67 -15
  31. opentrons/hardware_control/module_control.py +105 -10
  32. opentrons/hardware_control/modules/__init__.py +3 -0
  33. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  34. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  35. opentrons/hardware_control/modules/heater_shaker.py +42 -5
  36. opentrons/hardware_control/modules/magdeck.py +8 -4
  37. opentrons/hardware_control/modules/mod_abc.py +14 -6
  38. opentrons/hardware_control/modules/tempdeck.py +25 -5
  39. opentrons/hardware_control/modules/thermocycler.py +68 -11
  40. opentrons/hardware_control/modules/types.py +20 -1
  41. opentrons/hardware_control/modules/utils.py +11 -4
  42. opentrons/hardware_control/motion_utilities.py +6 -6
  43. opentrons/hardware_control/nozzle_manager.py +3 -0
  44. opentrons/hardware_control/ot3api.py +85 -17
  45. opentrons/hardware_control/poller.py +22 -8
  46. opentrons/hardware_control/protocols/liquid_handler.py +6 -2
  47. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  48. opentrons/hardware_control/types.py +43 -2
  49. opentrons/legacy_commands/commands.py +58 -5
  50. opentrons/legacy_commands/module_commands.py +52 -0
  51. opentrons/legacy_commands/protocol_commands.py +53 -1
  52. opentrons/legacy_commands/types.py +155 -1
  53. opentrons/motion_planning/deck_conflict.py +17 -12
  54. opentrons/motion_planning/waypoints.py +15 -29
  55. opentrons/protocol_api/__init__.py +5 -1
  56. opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
  57. opentrons/protocol_api/_types.py +8 -1
  58. opentrons/protocol_api/core/common.py +3 -1
  59. opentrons/protocol_api/core/engine/_default_labware_versions.py +33 -11
  60. opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
  61. opentrons/protocol_api/core/engine/instrument.py +109 -26
  62. opentrons/protocol_api/core/engine/labware.py +8 -1
  63. opentrons/protocol_api/core/engine/module_core.py +95 -4
  64. opentrons/protocol_api/core/engine/protocol.py +51 -2
  65. opentrons/protocol_api/core/engine/stringify.py +2 -0
  66. opentrons/protocol_api/core/engine/tasks.py +48 -0
  67. opentrons/protocol_api/core/engine/well.py +8 -0
  68. opentrons/protocol_api/core/instrument.py +19 -2
  69. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  70. opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
  71. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
  72. opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
  73. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  74. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  75. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  76. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  77. opentrons/protocol_api/core/module.py +58 -2
  78. opentrons/protocol_api/core/protocol.py +23 -2
  79. opentrons/protocol_api/core/tasks.py +31 -0
  80. opentrons/protocol_api/core/well.py +4 -0
  81. opentrons/protocol_api/instrument_context.py +388 -2
  82. opentrons/protocol_api/labware.py +10 -2
  83. opentrons/protocol_api/module_contexts.py +170 -6
  84. opentrons/protocol_api/protocol_context.py +87 -21
  85. opentrons/protocol_api/robot_context.py +41 -25
  86. opentrons/protocol_api/tasks.py +48 -0
  87. opentrons/protocol_api/validation.py +49 -3
  88. opentrons/protocol_engine/__init__.py +4 -0
  89. opentrons/protocol_engine/actions/__init__.py +6 -2
  90. opentrons/protocol_engine/actions/actions.py +31 -9
  91. opentrons/protocol_engine/clients/sync_client.py +42 -7
  92. opentrons/protocol_engine/commands/__init__.py +56 -0
  93. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  94. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  95. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  96. opentrons/protocol_engine/commands/aspirate.py +1 -0
  97. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  98. opentrons/protocol_engine/commands/capture_image.py +302 -0
  99. opentrons/protocol_engine/commands/command.py +2 -0
  100. opentrons/protocol_engine/commands/command_unions.py +62 -0
  101. opentrons/protocol_engine/commands/create_timer.py +83 -0
  102. opentrons/protocol_engine/commands/dispense.py +1 -0
  103. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  104. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  105. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  106. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  107. opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
  108. opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
  110. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
  111. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
  112. opentrons/protocol_engine/commands/move_labware.py +3 -4
  113. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  114. opentrons/protocol_engine/commands/movement_common.py +31 -2
  115. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  116. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  117. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  118. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
  119. opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
  120. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  121. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  122. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +44 -7
  123. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
  124. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
  125. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  126. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  127. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  128. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  129. opentrons/protocol_engine/engine_support.py +3 -0
  130. opentrons/protocol_engine/errors/__init__.py +12 -0
  131. opentrons/protocol_engine/errors/exceptions.py +119 -0
  132. opentrons/protocol_engine/execution/__init__.py +4 -0
  133. opentrons/protocol_engine/execution/command_executor.py +62 -1
  134. opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
  135. opentrons/protocol_engine/execution/labware_movement.py +13 -15
  136. opentrons/protocol_engine/execution/movement.py +2 -0
  137. opentrons/protocol_engine/execution/pipetting.py +19 -25
  138. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  139. opentrons/protocol_engine/execution/run_control.py +8 -0
  140. opentrons/protocol_engine/execution/task_handler.py +157 -0
  141. opentrons/protocol_engine/protocol_engine.py +137 -36
  142. opentrons/protocol_engine/resources/__init__.py +4 -0
  143. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  144. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  145. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  146. opentrons/protocol_engine/resources/file_provider.py +133 -58
  147. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  148. opentrons/protocol_engine/slot_standardization.py +2 -0
  149. opentrons/protocol_engine/state/_well_math.py +60 -18
  150. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  151. opentrons/protocol_engine/state/camera.py +54 -0
  152. opentrons/protocol_engine/state/commands.py +37 -14
  153. opentrons/protocol_engine/state/geometry.py +276 -379
  154. opentrons/protocol_engine/state/labware.py +62 -108
  155. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  156. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
  157. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  158. opentrons/protocol_engine/state/modules.py +30 -8
  159. opentrons/protocol_engine/state/motion.py +44 -0
  160. opentrons/protocol_engine/state/preconditions.py +59 -0
  161. opentrons/protocol_engine/state/state.py +44 -0
  162. opentrons/protocol_engine/state/state_summary.py +4 -0
  163. opentrons/protocol_engine/state/tasks.py +139 -0
  164. opentrons/protocol_engine/state/tips.py +177 -258
  165. opentrons/protocol_engine/state/update_types.py +26 -9
  166. opentrons/protocol_engine/types/__init__.py +23 -4
  167. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  168. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  169. opentrons/protocol_engine/types/instrument.py +8 -1
  170. opentrons/protocol_engine/types/labware.py +1 -13
  171. opentrons/protocol_engine/types/location.py +26 -2
  172. opentrons/protocol_engine/types/module.py +11 -1
  173. opentrons/protocol_engine/types/tasks.py +38 -0
  174. opentrons/protocol_engine/types/tip.py +9 -0
  175. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  176. opentrons/protocol_runner/protocol_runner.py +14 -1
  177. opentrons/protocol_runner/run_orchestrator.py +49 -2
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/types.py +2 -1
  181. opentrons/simulate.py +51 -15
  182. opentrons/system/camera.py +334 -4
  183. opentrons/system/ffmpeg.py +110 -0
  184. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/METADATA +4 -4
  185. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/RECORD +188 -160
  186. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  187. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/WHEEL +0 -0
  188. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/entry_points.txt +0 -0
  189. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,35 @@
1
- """Command models to start heating a Thermocycler's lid."""
1
+ """Command models for 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
-
14
+ from opentrons.protocol_engine.execution import EquipmentHandler, TaskHandler
15
15
 
16
16
  SetTargetLidTemperatureCommandType = Literal["thermocycler/setTargetLidTemperature"]
17
17
 
18
18
 
19
+ def _remove_default(s: dict[str, Any]) -> None:
20
+ s.pop("default", None)
21
+
22
+
19
23
  class SetTargetLidTemperatureParams(BaseModel):
20
- """Input parameters to set a Thermocycler's target lid temperature."""
24
+ """Input parameters to to set a Thermocycler's target lid temperature."""
21
25
 
22
26
  moduleId: str = Field(..., description="Unique ID of the Thermocycler Module.")
23
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
+ )
24
33
 
25
34
 
26
35
  class SetTargetLidTemperatureResult(BaseModel):
@@ -30,29 +39,37 @@ class SetTargetLidTemperatureResult(BaseModel):
30
39
  ...,
31
40
  description="The target lid temperature that was set after validation.",
32
41
  )
42
+ taskId: str | SkipJsonSchema[None] = Field(
43
+ None,
44
+ description="The task id for the setTargetBlockTemperature",
45
+ json_schema_extra=_remove_default,
46
+ )
33
47
 
34
48
 
35
49
  class SetTargetLidTemperatureImpl(
36
50
  AbstractCommandImpl[
37
- SetTargetLidTemperatureParams, SuccessData[SetTargetLidTemperatureResult]
51
+ SetTargetLidTemperatureParams,
52
+ SuccessData[SetTargetLidTemperatureResult],
38
53
  ]
39
54
  ):
40
- """Execution implementation of a Thermocycler's set lid temperature command."""
55
+ """Execution implementation of a Thermocycler's to set lid temperature command."""
41
56
 
42
57
  def __init__(
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,
53
70
  params: SetTargetLidTemperatureParams,
54
71
  ) -> SuccessData[SetTargetLidTemperatureResult]:
55
- """Set a Thermocycler's target lid temperature."""
72
+ """To set a Thermocycler's target lid temperature."""
56
73
  thermocycler_state = self._state_view.modules.get_thermocycler_module_substate(
57
74
  params.moduleId
58
75
  )
@@ -63,22 +80,34 @@ class SetTargetLidTemperatureImpl(
63
80
  thermocycler_state.module_id
64
81
  )
65
82
 
66
- if thermocycler_hardware is not None:
67
- await thermocycler_hardware.set_target_lid_temperature(target_temperature)
68
-
83
+ async def set_target_lid_temperature(task_handler: TaskHandler) -> None:
84
+ if thermocycler_hardware is not None:
85
+ async with task_handler.synchronize_cancel_latest(
86
+ thermocycler_state.module_id + "-lid"
87
+ ):
88
+ await thermocycler_hardware.set_target_lid_temperature(
89
+ target_temperature
90
+ )
91
+ await thermocycler_hardware.wait_for_lid_target()
92
+
93
+ task = await self._task_handler.create_task(
94
+ task_function=set_target_lid_temperature, id=params.taskId
95
+ )
69
96
  return SuccessData(
70
97
  public=SetTargetLidTemperatureResult(
71
- targetLidTemperature=target_temperature
98
+ targetLidTemperature=target_temperature, taskId=task.id
72
99
  ),
73
100
  )
74
101
 
75
102
 
76
103
  class SetTargetLidTemperature(
77
104
  BaseCommand[
78
- SetTargetLidTemperatureParams, SetTargetLidTemperatureResult, ErrorOccurrence
105
+ SetTargetLidTemperatureParams,
106
+ SetTargetLidTemperatureResult,
107
+ ErrorOccurrence,
79
108
  ]
80
109
  ):
81
- """A command to set a Thermocycler's target lid temperature."""
110
+ """A command to to set a Thermocycler's target lid temperature."""
82
111
 
83
112
  commandType: SetTargetLidTemperatureCommandType = (
84
113
  "thermocycler/setTargetLidTemperature"
@@ -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 + "-block"
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, drop_offset)
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, location=location
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
@@ -11,6 +11,7 @@ from opentrons.protocol_engine.execution.error_recovery_hardware_state_synchroni
11
11
  from opentrons.protocol_engine.resources.labware_data_provider import (
12
12
  LabwareDataProvider,
13
13
  )
14
+ from opentrons.protocol_engine.resources.camera_provider import CameraProvider
14
15
  from opentrons.util.async_helpers import async_context_manager_in_thread
15
16
 
16
17
  from opentrons_shared_data.robot import load as load_robot
@@ -38,6 +39,7 @@ async def create_protocol_engine(
38
39
  load_fixed_trash: bool = False,
39
40
  deck_configuration: typing.Optional[DeckConfigurationType] = None,
40
41
  file_provider: typing.Optional[FileProvider] = None,
42
+ camera_provider: typing.Optional[CameraProvider] = None,
41
43
  notify_publishers: typing.Optional[typing.Callable[[], None]] = None,
42
44
  ) -> ProtocolEngine:
43
45
  """Create a ProtocolEngine instance.
@@ -50,6 +52,7 @@ async def create_protocol_engine(
50
52
  load_fixed_trash: Automatically load fixed trash labware in engine.
51
53
  deck_configuration: The initial deck configuration the engine will be instantiated with.
52
54
  file_provider: Provides access to robot server file writing procedures for protocol output.
55
+ camera_provider: Provides access to camera interface with image capture and callbacks.
53
56
  notify_publishers: Notifies robot server publishers of internal state change.
54
57
  """
55
58
  deck_data = DeckDataProvider(config.deck_type)
@@ -83,6 +86,7 @@ async def create_protocol_engine(
83
86
  door_watcher = DoorWatcher(state_store, hardware_api, action_dispatcher)
84
87
  module_data_provider = ModuleDataProvider()
85
88
  file_provider = file_provider or FileProvider()
89
+ camera_provider = camera_provider or CameraProvider()
86
90
 
87
91
  pe = ProtocolEngine(
88
92
  hardware_api=hardware_api,
@@ -94,6 +98,7 @@ async def create_protocol_engine(
94
98
  door_watcher=door_watcher,
95
99
  module_data_provider=module_data_provider,
96
100
  file_provider=file_provider,
101
+ camera_provider=camera_provider,
97
102
  )
98
103
 
99
104
  # todo(mm, 2024-11-08): This is a quick hack to support the absorbance reader, which
@@ -121,6 +126,7 @@ def create_protocol_engine_in_thread(
121
126
  drop_tips_after_run: bool,
122
127
  post_run_hardware_state: PostRunHardwareState,
123
128
  load_fixed_trash: bool = False,
129
+ camera_provider: typing.Optional[CameraProvider] = None,
124
130
  ) -> typing.Generator[
125
131
  typing.Tuple[ProtocolEngine, asyncio.AbstractEventLoop], None, None
126
132
  ]:
@@ -151,6 +157,7 @@ def create_protocol_engine_in_thread(
151
157
  drop_tips_after_run,
152
158
  post_run_hardware_state,
153
159
  load_fixed_trash,
160
+ camera_provider,
154
161
  )
155
162
  ) as (
156
163
  protocol_engine,
@@ -169,6 +176,7 @@ async def _protocol_engine(
169
176
  drop_tips_after_run: bool,
170
177
  post_run_hardware_state: PostRunHardwareState,
171
178
  load_fixed_trash: bool = False,
179
+ camera_provider: typing.Optional[CameraProvider] = None,
172
180
  ) -> typing.AsyncGenerator[ProtocolEngine, None]:
173
181
  protocol_engine = await create_protocol_engine(
174
182
  hardware_api=hardware_api,
@@ -179,9 +187,13 @@ async def _protocol_engine(
179
187
  )
180
188
 
181
189
  # TODO(tz, 6-20-2024): This feels like a hack, we should probably return the orchestrator instead of pe.
190
+
182
191
  orchestrator = create_run_orchestrator(
183
192
  hardware_api=hardware_api,
184
193
  protocol_engine=protocol_engine,
194
+ camera_provider=camera_provider
195
+ if camera_provider is not None
196
+ else CameraProvider(),
185
197
  )
186
198
  try:
187
199
  orchestrator.play(deck_configuration)
@@ -3,16 +3,19 @@ from . import ProtocolEngine
3
3
  from ..hardware_control import HardwareControlAPI
4
4
 
5
5
  from opentrons.protocol_runner import protocol_runner, RunOrchestrator
6
+ from opentrons.protocol_engine.resources.camera_provider import CameraProvider
6
7
 
7
8
 
8
9
  def create_run_orchestrator(
9
10
  hardware_api: HardwareControlAPI,
10
11
  protocol_engine: ProtocolEngine,
12
+ camera_provider: CameraProvider,
11
13
  ) -> RunOrchestrator:
12
14
  """Create a RunOrchestrator instance."""
13
15
  return RunOrchestrator(
14
16
  protocol_engine=protocol_engine,
15
17
  hardware_api=hardware_api,
18
+ camera_provider=camera_provider,
16
19
  setup_runner=protocol_runner.LiveRunner(
17
20
  protocol_engine=protocol_engine,
18
21
  hardware_api=hardware_api,
@@ -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,
@@ -83,6 +84,7 @@ from .exceptions import (
83
84
  OperationLocationNotInWellError,
84
85
  InvalidDispenseVolumeError,
85
86
  StorageLimitReachedError,
87
+ FileNameInvalidError,
86
88
  InvalidLiquidError,
87
89
  LiquidClassDoesNotExistError,
88
90
  LiquidClassRedefinitionError,
@@ -90,6 +92,10 @@ from .exceptions import (
90
92
  FlexStackerLabwarePoolNotYetDefinedError,
91
93
  FlexStackerNotLogicallyEmptyError,
92
94
  InvalidLabwarePositionError,
95
+ InvalidModuleOrientation,
96
+ CameraCaptureError,
97
+ CameraDisabledError,
98
+ CameraSettingsInvalidError,
93
99
  )
94
100
 
95
101
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -151,6 +157,7 @@ __all__ = [
151
157
  "NoTargetTemperatureSetError",
152
158
  "InvalidTargetTemperatureError",
153
159
  "InvalidTargetSpeedError",
160
+ "InvalidRampRateError",
154
161
  "InvalidBlockVolumeError",
155
162
  "InvalidHoldTimeError",
156
163
  "InvalidLiquidError",
@@ -174,6 +181,7 @@ __all__ = [
174
181
  "FlexStackerLabwarePoolNotYetDefinedError",
175
182
  "FlexStackerNotLogicallyEmptyError",
176
183
  "InvalidLabwarePositionError",
184
+ "InvalidModuleOrientation",
177
185
  # error occurrence models
178
186
  "ErrorOccurrence",
179
187
  "CommandNotAllowedError",
@@ -186,6 +194,10 @@ __all__ = [
186
194
  "OperationLocationNotInWellError",
187
195
  "InvalidDispenseVolumeError",
188
196
  "StorageLimitReachedError",
197
+ "FileNameInvalidError",
189
198
  "LiquidClassDoesNotExistError",
190
199
  "LiquidClassRedefinitionError",
200
+ "CameraCaptureError",
201
+ "CameraDisabledError",
202
+ "CameraSettingsInvalidError",
191
203
  ]