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
@@ -13,7 +13,7 @@ from .pipetting_common import (
13
13
  aspirate_while_tracking,
14
14
  )
15
15
  from .movement_common import (
16
- LiquidHandlingWellLocationMixin,
16
+ DynamicLiquidHandlingWellLocationMixin,
17
17
  DestinationPositionResult,
18
18
  StallOrCollisionError,
19
19
  move_to_well,
@@ -45,7 +45,7 @@ class AspirateWhileTrackingParams(
45
45
  PipetteIdMixin,
46
46
  AspirateVolumeMixin,
47
47
  FlowRateMixin,
48
- LiquidHandlingWellLocationMixin,
48
+ DynamicLiquidHandlingWellLocationMixin,
49
49
  ):
50
50
  """Parameters required to aspirate from a specific well."""
51
51
 
@@ -107,14 +107,20 @@ class AspirateWhileTrackingImplementation(
107
107
  )
108
108
  state_update = StateUpdate()
109
109
 
110
+ end_point = self._state_view.geometry.get_well_position(
111
+ labware_id=params.labwareId,
112
+ well_name=params.wellName,
113
+ well_location=params.trackToLocation,
114
+ operation_volume=-params.volume,
115
+ pipette_id=params.pipetteId,
116
+ )
110
117
  move_result = await move_to_well(
111
118
  movement=self._movement,
112
119
  model_utils=self._model_utils,
113
120
  pipette_id=params.pipetteId,
114
121
  labware_id=params.labwareId,
115
122
  well_name=params.wellName,
116
- well_location=params.wellLocation,
117
- operation_volume=-params.volume,
123
+ well_location=params.trackFromLocation,
118
124
  )
119
125
  state_update.append(move_result.state_update)
120
126
  if isinstance(move_result, DefinedErrorData):
@@ -128,6 +134,7 @@ class AspirateWhileTrackingImplementation(
128
134
  well_name=params.wellName,
129
135
  volume=params.volume,
130
136
  flow_rate=params.flowRate,
137
+ end_point=end_point,
131
138
  location_if_error={
132
139
  "retryLocation": (
133
140
  move_result.public.position.x,
@@ -138,7 +145,48 @@ class AspirateWhileTrackingImplementation(
138
145
  command_note_adder=self._command_note_adder,
139
146
  pipetting=self._pipetting,
140
147
  model_utils=self._model_utils,
148
+ movement_delay=params.movement_delay,
141
149
  )
150
+ if isinstance(aspirate_result, DefinedErrorData):
151
+ if isinstance(aspirate_result.public, OverpressureError):
152
+ return DefinedErrorData(
153
+ public=OverpressureError(
154
+ id=aspirate_result.public.id,
155
+ createdAt=aspirate_result.public.createdAt,
156
+ wrappedErrors=aspirate_result.public.wrappedErrors,
157
+ errorInfo=aspirate_result.public.errorInfo,
158
+ ),
159
+ state_update=aspirate_result.state_update.set_liquid_operated(
160
+ labware_id=params.labwareId,
161
+ well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
162
+ params.labwareId,
163
+ params.wellName,
164
+ params.pipetteId,
165
+ ),
166
+ volume_added=CLEAR,
167
+ ),
168
+ state_update_if_false_positive=aspirate_result.state_update_if_false_positive,
169
+ )
170
+ elif isinstance(aspirate_result.public, StallOrCollisionError):
171
+ return DefinedErrorData(
172
+ public=StallOrCollisionError(
173
+ id=aspirate_result.public.id,
174
+ createdAt=aspirate_result.public.createdAt,
175
+ wrappedErrors=aspirate_result.public.wrappedErrors,
176
+ errorInfo=aspirate_result.public.errorInfo,
177
+ ),
178
+ state_update=aspirate_result.state_update.set_liquid_operated(
179
+ labware_id=params.labwareId,
180
+ well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
181
+ params.labwareId,
182
+ params.wellName,
183
+ params.pipetteId,
184
+ ),
185
+ volume_added=CLEAR,
186
+ ),
187
+ state_update_if_false_positive=aspirate_result.state_update_if_false_positive,
188
+ )
189
+
142
190
  position_after_aspirate = await self._gantry_mover.get_position(
143
191
  params.pipetteId
144
192
  )
@@ -147,21 +195,6 @@ class AspirateWhileTrackingImplementation(
147
195
  y=position_after_aspirate.y,
148
196
  z=position_after_aspirate.z,
149
197
  )
150
- if isinstance(aspirate_result, DefinedErrorData):
151
- return DefinedErrorData(
152
- public=aspirate_result.public,
153
- state_update=aspirate_result.state_update.set_liquid_operated(
154
- labware_id=params.labwareId,
155
- well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well(
156
- params.labwareId,
157
- params.wellName,
158
- params.pipetteId,
159
- ),
160
- volume_added=CLEAR,
161
- ),
162
- state_update_if_false_positive=aspirate_result.state_update_if_false_positive,
163
- )
164
-
165
198
  return SuccessData(
166
199
  public=AspirateWhileTrackingResult(
167
200
  volume=aspirate_result.public.volume,
@@ -0,0 +1,302 @@
1
+ """Command models to capture an image with a camera."""
2
+ from __future__ import annotations
3
+ from typing import Optional, TYPE_CHECKING, Tuple, Any
4
+ from datetime import datetime
5
+
6
+ from typing_extensions import Literal, Type
7
+ from pydantic import BaseModel, Field
8
+ from pydantic.json_schema import SkipJsonSchema
9
+
10
+ from opentrons_shared_data.data_files import MimeType
11
+ from opentrons.system.camera import (
12
+ CONTRAST_DEFAULT,
13
+ BRIGHTNESS_DEFAULT,
14
+ SATURATION_DEFAULT,
15
+ ZOOM_MIN,
16
+ ZOOM_MAX,
17
+ ZOOM_DEFAULT,
18
+ RESOLUTION_MIN,
19
+ RESOLUTION_MAX,
20
+ RESOLUTION_DEFAULT,
21
+ )
22
+ from ..types import PreconditionTypes
23
+ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
24
+ from ..errors import (
25
+ CameraDisabledError,
26
+ CameraSettingsInvalidError,
27
+ FileNameInvalidError,
28
+ )
29
+ from ..errors.error_occurrence import ErrorOccurrence
30
+
31
+ from ..resources.file_provider import (
32
+ ImageCaptureCmdFileNameMetadata,
33
+ )
34
+ from ..resources import FileProvider
35
+ from ..resources.file_provider import SPECIAL_CHARACTERS
36
+ from ..resources import CameraProvider
37
+ from ..resources.camera_provider import ImageParameters
38
+ from ..state import update_types
39
+
40
+ if TYPE_CHECKING:
41
+ from opentrons.protocol_engine.state.state import StateView
42
+
43
+
44
+ def _remove_default(s: dict[str, Any]) -> None:
45
+ s.pop("default", None)
46
+
47
+
48
+ CaptureImageCommandType = Literal["captureImage"]
49
+
50
+
51
+ class CaptureImageParams(BaseModel):
52
+ """Input parameters for an image capture."""
53
+
54
+ fileName: str | SkipJsonSchema[None] = Field(
55
+ None,
56
+ description="Optional file name to use when storing the results of an Image Capture.",
57
+ json_schema_extra=_remove_default,
58
+ )
59
+ resolution: Optional[Tuple[int, int]] = Field(
60
+ None,
61
+ description="Width by height resolution in pixels for the image to be captured with.",
62
+ )
63
+ zoom: Optional[float] = Field(
64
+ None,
65
+ description="Multiplier to use when cropping and scaling a captured image. Scale is 1.0 to 2.0.",
66
+ )
67
+ pan: Optional[Tuple[int, int]] = Field(
68
+ None,
69
+ description="X/Y (pixels) position to pan to for a given zoom. Default is the center of the image.",
70
+ )
71
+ contrast: Optional[float] = Field(
72
+ None,
73
+ description="The contrast to use when processing an image. Scale is 0% to 100%.",
74
+ )
75
+ brightness: Optional[float] = Field(
76
+ None,
77
+ description="The brightness to use when processing an image. Scale is 0% to 100%.",
78
+ )
79
+ saturation: Optional[float] = Field(
80
+ None,
81
+ description="The saturation to use when processing an image. Scale is 0% to 100%.",
82
+ )
83
+
84
+
85
+ class CaptureImageResult(BaseModel):
86
+ """Result data from running an image capture."""
87
+
88
+ fileId: Optional[str] = Field(
89
+ None,
90
+ description="File ID for image files output as a result of an image capture action.",
91
+ )
92
+ resolution: Tuple[int, int] = Field(
93
+ ...,
94
+ description="Width by height resolution in pixels the image was captured with.",
95
+ )
96
+ zoom: float = Field(
97
+ ...,
98
+ description="Multiplier used when cropping and scaling the captured image. Scale is 1.0 to 2.0.",
99
+ )
100
+ pan: Optional[Tuple[int, int]] = Field(
101
+ None,
102
+ description="X/Y (pixels) position panned to.",
103
+ )
104
+ contrast: float = Field(
105
+ ...,
106
+ description="The contrast used when processing the image. Scale is 0% to 100%.",
107
+ )
108
+ brightness: float = Field(
109
+ ...,
110
+ description="The brightness used when processing the image. Scale is 0% to 100%.",
111
+ )
112
+ saturation: float = Field(
113
+ ...,
114
+ description="The saturation used when processing the image. Scale is 0% to 100%.",
115
+ )
116
+
117
+
118
+ def _converted_image_params(params: CaptureImageParams) -> ImageParameters:
119
+ return ImageParameters(
120
+ resolution=params.resolution,
121
+ zoom=params.zoom,
122
+ pan=params.pan,
123
+ contrast=(
124
+ (params.contrast / 100) * 2.0 if params.contrast is not None else None
125
+ ),
126
+ brightness=(
127
+ int(((params.brightness * 256) // 100) - 128) * -1
128
+ if params.brightness is not None
129
+ else None
130
+ ),
131
+ saturation=(
132
+ (params.saturation / 100) * 2.0 if params.saturation is not None else None
133
+ ),
134
+ )
135
+
136
+
137
+ def _revert_image_parameters(
138
+ file_id: Optional[str], image_params: ImageParameters
139
+ ) -> CaptureImageResult:
140
+ contrast = (
141
+ image_params.contrast if image_params.contrast is not None else CONTRAST_DEFAULT
142
+ )
143
+ brightness = (
144
+ image_params.brightness
145
+ if image_params.brightness is not None
146
+ else BRIGHTNESS_DEFAULT
147
+ )
148
+ saturation = (
149
+ image_params.saturation
150
+ if image_params.saturation is not None
151
+ else SATURATION_DEFAULT
152
+ )
153
+ # todo (chb, 2025-10-29): Eventually we will have override defaults that can be passed into the Camera state, load those if they exist
154
+
155
+ return CaptureImageResult(
156
+ fileId=file_id,
157
+ resolution=image_params.resolution
158
+ if image_params.resolution is not None
159
+ else RESOLUTION_DEFAULT,
160
+ zoom=image_params.zoom if image_params.zoom is not None else ZOOM_DEFAULT,
161
+ pan=image_params.pan,
162
+ contrast=(contrast / 2) * 100.0,
163
+ brightness=round((((brightness * -1) + 128) * 100) / 256),
164
+ saturation=(saturation / 2) * 100.0,
165
+ )
166
+
167
+
168
+ def _validate_image_params(params: CaptureImageParams) -> None:
169
+ # Validate the filename param provided to fail analysis
170
+ if params.fileName is not None and set(SPECIAL_CHARACTERS).intersection(
171
+ set(params.fileName)
172
+ ):
173
+ raise FileNameInvalidError(
174
+ message=f"Capture image filename cannot contain character(s): {SPECIAL_CHARACTERS.intersection(set(params.fileName))}"
175
+ )
176
+
177
+ # Validate the image filter parameters
178
+ if params.zoom is not None and (params.zoom < ZOOM_MIN or params.zoom > ZOOM_MAX):
179
+ raise CameraSettingsInvalidError(
180
+ message="Capture image zoom must be a valid value from 1.0X to 2.0X zoom."
181
+ )
182
+ if params.resolution is not None and (
183
+ params.resolution[0] < RESOLUTION_MIN[0]
184
+ or params.resolution[1] < RESOLUTION_MIN[1]
185
+ or params.resolution[0] > RESOLUTION_MAX[0]
186
+ or params.resolution[1] > RESOLUTION_MAX[1]
187
+ ):
188
+ raise CameraSettingsInvalidError(
189
+ message="Capture image resolution must be a valid resolution from 240p through 8K resolutuon."
190
+ )
191
+ if params.brightness is not None and (
192
+ params.brightness < 0 or params.brightness > 100
193
+ ):
194
+ raise CameraSettingsInvalidError(
195
+ message="Capture image brightness must be a percentage from 0% to 100%."
196
+ )
197
+ if params.contrast is not None and (params.contrast < 0 or params.contrast > 100):
198
+ raise CameraSettingsInvalidError(
199
+ message="Capture image contrast must be a percentage from 0% to 100%."
200
+ )
201
+ if params.saturation is not None and (
202
+ params.saturation < 0 or params.saturation > 100
203
+ ):
204
+ raise CameraSettingsInvalidError(
205
+ message="Capture image saturation must be a percentage from 0% to 100%."
206
+ )
207
+
208
+
209
+ class CaptureImageImpl(
210
+ AbstractCommandImpl[CaptureImageParams, SuccessData[CaptureImageResult]]
211
+ ):
212
+ """Execution implementation of an image capture."""
213
+
214
+ def __init__(
215
+ self,
216
+ state_view: StateView,
217
+ file_provider: FileProvider,
218
+ camera_provider: CameraProvider,
219
+ **unused_dependencies: object,
220
+ ) -> None:
221
+ self._state_view = state_view
222
+ self._file_provider = file_provider
223
+ self._camera_provider = camera_provider
224
+
225
+ async def execute(
226
+ self, params: CaptureImageParams
227
+ ) -> SuccessData[CaptureImageResult]:
228
+ """Initiate an image capture with a camera."""
229
+ state_update = update_types.StateUpdate()
230
+ state_update.precondition_update = update_types.PreconditionUpdate(
231
+ {PreconditionTypes.IS_CAMERA_USED: True}
232
+ )
233
+
234
+ # Validate that the provided parameters are all acceptable. We do this here and in system/camera.py to ensure analysis fails properly.
235
+ _validate_image_params(params)
236
+
237
+ # Handle capturing an image with the CameraProvider - Engine camera settings take priority
238
+ camera_settings = await self._camera_provider.get_camera_settings()
239
+ engine_camera_settings = self._state_view.camera.get_enablement_settings()
240
+ if (
241
+ engine_camera_settings is None and camera_settings.cameraEnabled is False
242
+ ) or (
243
+ engine_camera_settings is not None
244
+ and engine_camera_settings.cameraEnabled is False
245
+ ):
246
+ raise CameraDisabledError(
247
+ "Cannot capture image because Camera is disabled."
248
+ )
249
+
250
+ parameters = _converted_image_params(params=params)
251
+ camera_data = await self._camera_provider.capture_image(
252
+ self._state_view.config.robot_type, parameters
253
+ )
254
+
255
+ # Conditionally save file if camera data was returned - in simulation we don't return anything.
256
+ file_id: str | None = None
257
+ if camera_data:
258
+ this_cmd_id = self._state_view.commands.get_running_command_id()
259
+ prev_cmd = self._state_view.commands.get_most_recently_finalized_command()
260
+ prev_cmd_id = prev_cmd.command.id if prev_cmd is not None else None
261
+
262
+ file_info = await self._file_provider.write_file(
263
+ data=camera_data,
264
+ mime_type=MimeType.IMAGE_JPEG,
265
+ command_metadata=ImageCaptureCmdFileNameMetadata(
266
+ step_number=len(self._state_view.commands.get_all()),
267
+ command_timestamp=datetime.now(),
268
+ base_filename=params.fileName,
269
+ command_id=this_cmd_id or "",
270
+ prev_command_id=prev_cmd_id or "",
271
+ ),
272
+ )
273
+ file_id = file_info.id
274
+ state_update.files_added = update_types.FilesAddedUpdate(file_ids=[file_id])
275
+
276
+ result = _revert_image_parameters(file_id=file_id, image_params=parameters)
277
+
278
+ return SuccessData(
279
+ public=result,
280
+ state_update=state_update,
281
+ )
282
+
283
+
284
+ class CaptureImage(
285
+ BaseCommand[CaptureImageParams, CaptureImageResult, ErrorOccurrence]
286
+ ):
287
+ """A command to execute an Absorbance Reader measurement."""
288
+
289
+ commandType: CaptureImageCommandType = "captureImage"
290
+ params: CaptureImageParams
291
+ result: Optional[CaptureImageResult] = None
292
+
293
+ _ImplementationCls: Type[CaptureImageImpl] = CaptureImageImpl
294
+
295
+
296
+ class CaptureImageCreate(BaseCommandCreate[CaptureImageParams]):
297
+ """A request to execute an Absorbance Reader measurement."""
298
+
299
+ commandType: CaptureImageCommandType = "captureImage"
300
+ params: CaptureImageParams
301
+
302
+ _CommandCls: Type[CaptureImage] = CaptureImage
@@ -178,6 +178,7 @@ class AbstractCommandImpl(
178
178
  hardware_api: HardwareControlAPI,
179
179
  equipment: execution.EquipmentHandler,
180
180
  file_provider: execution.FileProvider,
181
+ camera_provider: execution.CameraProvider,
181
182
  movement: execution.MovementHandler,
182
183
  gantry_mover: execution.GantryMover,
183
184
  labware_movement: execution.LabwareMovementHandler,
@@ -185,6 +186,7 @@ class AbstractCommandImpl(
185
186
  tip_handler: execution.TipHandler,
186
187
  run_control: execution.RunControlHandler,
187
188
  rail_lights: execution.RailLightsHandler,
189
+ task_handler: execution.TaskHandler,
188
190
  model_utils: ModelUtils,
189
191
  status_bar: execution.StatusBarHandler,
190
192
  command_note_adder: CommandNoteAdder,
@@ -267,6 +267,22 @@ from .wait_for_duration import (
267
267
  WaitForDurationCommandType,
268
268
  )
269
269
 
270
+ from .create_timer import (
271
+ CreateTimer,
272
+ CreateTimerCreate,
273
+ CreateTimerParams,
274
+ CreateTimerResult,
275
+ CreateTimerCommandType,
276
+ )
277
+
278
+ from .wait_for_tasks import (
279
+ WaitForTasks,
280
+ WaitForTasksCreate,
281
+ WaitForTasksParams,
282
+ WaitForTasksResult,
283
+ WaitForTasksCommandType,
284
+ )
285
+
270
286
  from .pick_up_tip import (
271
287
  PickUpTip,
272
288
  PickUpTipParams,
@@ -372,6 +388,14 @@ from .get_next_tip import (
372
388
  GetNextTipCommandType,
373
389
  )
374
390
 
391
+ from .set_tip_state import (
392
+ SetTipState,
393
+ SetTipStateCreate,
394
+ SetTipStateParams,
395
+ SetTipStateResult,
396
+ SetTipStateCommandType,
397
+ )
398
+
375
399
  from .liquid_probe import (
376
400
  LiquidProbe,
377
401
  LiquidProbeParams,
@@ -417,6 +441,14 @@ from .identify_module import (
417
441
  IdentifyModuleCommandType,
418
442
  )
419
443
 
444
+ from .capture_image import (
445
+ CaptureImage,
446
+ CaptureImageParams,
447
+ CaptureImageCreate,
448
+ CaptureImageResult,
449
+ CaptureImageCommandType,
450
+ )
451
+
420
452
  Command = Annotated[
421
453
  Union[
422
454
  AirGapInPlace,
@@ -454,6 +486,8 @@ Command = Annotated[
454
486
  PrepareToAspirate,
455
487
  WaitForResume,
456
488
  WaitForDuration,
489
+ WaitForTasks,
490
+ CreateTimer,
457
491
  PickUpTip,
458
492
  SavePosition,
459
493
  SetRailLights,
@@ -462,15 +496,18 @@ Command = Annotated[
462
496
  VerifyTipPresence,
463
497
  GetTipPresence,
464
498
  GetNextTip,
499
+ SetTipState,
465
500
  LiquidProbe,
466
501
  TryLiquidProbe,
467
502
  SealPipetteToTip,
468
503
  PressureDispense,
469
504
  UnsealPipetteFromTip,
505
+ CaptureImage,
470
506
  heater_shaker.WaitForTemperature,
471
507
  heater_shaker.SetTargetTemperature,
472
508
  heater_shaker.DeactivateHeater,
473
509
  heater_shaker.SetAndWaitForShakeSpeed,
510
+ heater_shaker.SetShakeSpeed,
474
511
  heater_shaker.DeactivateShaker,
475
512
  heater_shaker.OpenLabwareLatch,
476
513
  heater_shaker.CloseLabwareLatch,
@@ -488,6 +525,7 @@ Command = Annotated[
488
525
  thermocycler.OpenLid,
489
526
  thermocycler.CloseLid,
490
527
  thermocycler.RunProfile,
528
+ thermocycler.StartRunExtendedProfile,
491
529
  thermocycler.RunExtendedProfile,
492
530
  absorbance_reader.CloseLid,
493
531
  absorbance_reader.OpenLid,
@@ -557,6 +595,8 @@ CommandParams = Union[
557
595
  PrepareToAspirateParams,
558
596
  WaitForResumeParams,
559
597
  WaitForDurationParams,
598
+ WaitForTasksParams,
599
+ CreateTimerParams,
560
600
  PickUpTipParams,
561
601
  SavePositionParams,
562
602
  SetRailLightsParams,
@@ -565,15 +605,18 @@ CommandParams = Union[
565
605
  VerifyTipPresenceParams,
566
606
  GetTipPresenceParams,
567
607
  GetNextTipParams,
608
+ SetTipStateParams,
568
609
  LiquidProbeParams,
569
610
  TryLiquidProbeParams,
570
611
  SealPipetteToTipParams,
571
612
  PressureDispenseParams,
572
613
  UnsealPipetteFromTipParams,
614
+ CaptureImageParams,
573
615
  heater_shaker.WaitForTemperatureParams,
574
616
  heater_shaker.SetTargetTemperatureParams,
575
617
  heater_shaker.DeactivateHeaterParams,
576
618
  heater_shaker.SetAndWaitForShakeSpeedParams,
619
+ heater_shaker.SetShakeSpeedParams,
577
620
  heater_shaker.DeactivateShakerParams,
578
621
  heater_shaker.OpenLabwareLatchParams,
579
622
  heater_shaker.CloseLabwareLatchParams,
@@ -591,6 +634,7 @@ CommandParams = Union[
591
634
  thermocycler.OpenLidParams,
592
635
  thermocycler.CloseLidParams,
593
636
  thermocycler.RunProfileParams,
637
+ thermocycler.StartRunExtendedProfileParams,
594
638
  thermocycler.RunExtendedProfileParams,
595
639
  absorbance_reader.CloseLidParams,
596
640
  absorbance_reader.OpenLidParams,
@@ -658,6 +702,8 @@ CommandType = Union[
658
702
  PrepareToAspirateCommandType,
659
703
  WaitForResumeCommandType,
660
704
  WaitForDurationCommandType,
705
+ WaitForTasksCommandType,
706
+ CreateTimerCommandType,
661
707
  PickUpTipCommandType,
662
708
  SavePositionCommandType,
663
709
  SetRailLightsCommandType,
@@ -666,15 +712,18 @@ CommandType = Union[
666
712
  VerifyTipPresenceCommandType,
667
713
  GetTipPresenceCommandType,
668
714
  GetNextTipCommandType,
715
+ SetTipStateCommandType,
669
716
  LiquidProbeCommandType,
670
717
  TryLiquidProbeCommandType,
671
718
  SealPipetteToTipCommandType,
672
719
  PressureDispenseCommandType,
673
720
  UnsealPipetteFromTipCommandType,
721
+ CaptureImageCommandType,
674
722
  heater_shaker.WaitForTemperatureCommandType,
675
723
  heater_shaker.SetTargetTemperatureCommandType,
676
724
  heater_shaker.DeactivateHeaterCommandType,
677
725
  heater_shaker.SetAndWaitForShakeSpeedCommandType,
726
+ heater_shaker.SetShakeSpeedCommandType,
678
727
  heater_shaker.DeactivateShakerCommandType,
679
728
  heater_shaker.OpenLabwareLatchCommandType,
680
729
  heater_shaker.CloseLabwareLatchCommandType,
@@ -692,6 +741,7 @@ CommandType = Union[
692
741
  thermocycler.OpenLidCommandType,
693
742
  thermocycler.CloseLidCommandType,
694
743
  thermocycler.RunProfileCommandType,
744
+ thermocycler.StartRunExtendedProfileCommandType,
695
745
  thermocycler.RunExtendedProfileCommandType,
696
746
  absorbance_reader.CloseLidCommandType,
697
747
  absorbance_reader.OpenLidCommandType,
@@ -760,6 +810,8 @@ CommandCreate = Annotated[
760
810
  PrepareToAspirateCreate,
761
811
  WaitForResumeCreate,
762
812
  WaitForDurationCreate,
813
+ WaitForTasksCreate,
814
+ CreateTimerCreate,
763
815
  PickUpTipCreate,
764
816
  SavePositionCreate,
765
817
  SetRailLightsCreate,
@@ -768,15 +820,18 @@ CommandCreate = Annotated[
768
820
  VerifyTipPresenceCreate,
769
821
  GetTipPresenceCreate,
770
822
  GetNextTipCreate,
823
+ SetTipStateCreate,
771
824
  LiquidProbeCreate,
772
825
  TryLiquidProbeCreate,
773
826
  SealPipetteToTipCreate,
774
827
  PressureDispenseCreate,
775
828
  UnsealPipetteFromTipCreate,
829
+ CaptureImageCreate,
776
830
  heater_shaker.WaitForTemperatureCreate,
777
831
  heater_shaker.SetTargetTemperatureCreate,
778
832
  heater_shaker.DeactivateHeaterCreate,
779
833
  heater_shaker.SetAndWaitForShakeSpeedCreate,
834
+ heater_shaker.SetShakeSpeedCreate,
780
835
  heater_shaker.DeactivateShakerCreate,
781
836
  heater_shaker.OpenLabwareLatchCreate,
782
837
  heater_shaker.CloseLabwareLatchCreate,
@@ -794,6 +849,7 @@ CommandCreate = Annotated[
794
849
  thermocycler.OpenLidCreate,
795
850
  thermocycler.CloseLidCreate,
796
851
  thermocycler.RunProfileCreate,
852
+ thermocycler.StartRunExtendedProfileCreate,
797
853
  thermocycler.RunExtendedProfileCreate,
798
854
  absorbance_reader.CloseLidCreate,
799
855
  absorbance_reader.OpenLidCreate,
@@ -870,6 +926,8 @@ CommandResult = Union[
870
926
  PrepareToAspirateResult,
871
927
  WaitForResumeResult,
872
928
  WaitForDurationResult,
929
+ WaitForTasksResult,
930
+ CreateTimerResult,
873
931
  PickUpTipResult,
874
932
  SavePositionResult,
875
933
  SetRailLightsResult,
@@ -878,15 +936,18 @@ CommandResult = Union[
878
936
  VerifyTipPresenceResult,
879
937
  GetTipPresenceResult,
880
938
  GetNextTipResult,
939
+ SetTipStateResult,
881
940
  LiquidProbeResult,
882
941
  TryLiquidProbeResult,
883
942
  SealPipetteToTipResult,
884
943
  PressureDispenseResult,
885
944
  UnsealPipetteFromTipResult,
945
+ CaptureImageResult,
886
946
  heater_shaker.WaitForTemperatureResult,
887
947
  heater_shaker.SetTargetTemperatureResult,
888
948
  heater_shaker.DeactivateHeaterResult,
889
949
  heater_shaker.SetAndWaitForShakeSpeedResult,
950
+ heater_shaker.SetShakeSpeedResult,
890
951
  heater_shaker.DeactivateShakerResult,
891
952
  heater_shaker.OpenLabwareLatchResult,
892
953
  heater_shaker.CloseLabwareLatchResult,
@@ -904,6 +965,7 @@ CommandResult = Union[
904
965
  thermocycler.OpenLidResult,
905
966
  thermocycler.CloseLidResult,
906
967
  thermocycler.RunProfileResult,
968
+ thermocycler.StartRunExtendedProfileResult,
907
969
  thermocycler.RunExtendedProfileResult,
908
970
  absorbance_reader.CloseLidResult,
909
971
  absorbance_reader.OpenLidResult,