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
@@ -51,6 +51,7 @@ from ..module import (
51
51
  from .exceptions import InvalidMagnetEngageHeightError
52
52
 
53
53
  from .labware import LabwareCore
54
+ from .tasks import EngineTaskCore
54
55
  from . import load_labware_params
55
56
 
56
57
  if TYPE_CHECKING:
@@ -175,13 +176,17 @@ class TemperatureModuleCore(ModuleCore, AbstractTemperatureModuleCore[LabwareCor
175
176
 
176
177
  _sync_module_hardware: SynchronousAdapter[hw_modules.TempDeck]
177
178
 
178
- def set_target_temperature(self, celsius: float) -> None:
179
+ def set_target_temperature(self, celsius: float) -> EngineTaskCore:
179
180
  """Set the Temperature Module's target temperature in °C."""
180
- self._engine_client.execute_command(
181
+ result = self._engine_client.execute_command_without_recovery(
181
182
  cmd.temperature_module.SetTargetTemperatureParams(
182
183
  moduleId=self.module_id, celsius=celsius
183
184
  )
184
185
  )
186
+ temperature_task = EngineTaskCore(
187
+ engine_client=self._engine_client, task_id=result.taskId
188
+ )
189
+ return temperature_task
185
190
 
186
191
  def wait_for_target_temperature(self, celsius: Optional[float] = None) -> None:
187
192
  """Wait until the module's target temperature is reached.
@@ -317,18 +322,24 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
317
322
  def set_target_block_temperature(
318
323
  self,
319
324
  celsius: float,
325
+ ramp_rate: Optional[float],
320
326
  hold_time_seconds: Optional[float] = None,
321
327
  block_max_volume: Optional[float] = None,
322
- ) -> None:
328
+ ) -> EngineTaskCore:
323
329
  """Set the target temperature for the well block, in °C."""
324
- self._engine_client.execute_command(
330
+ result = self._engine_client.execute_command_without_recovery(
325
331
  cmd.thermocycler.SetTargetBlockTemperatureParams(
326
332
  moduleId=self.module_id,
327
333
  celsius=celsius,
328
334
  blockMaxVolumeUl=block_max_volume,
329
335
  holdTimeSeconds=hold_time_seconds,
336
+ ramp_rate=ramp_rate,
330
337
  )
331
338
  )
339
+ block_temperature_task = EngineTaskCore(
340
+ engine_client=self._engine_client, task_id=result.taskId
341
+ )
342
+ return block_temperature_task
332
343
 
333
344
  def wait_for_block_temperature(self) -> None:
334
345
  """Wait for target block temperature to be reached."""
@@ -336,13 +347,17 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
336
347
  cmd.thermocycler.WaitForBlockTemperatureParams(moduleId=self.module_id)
337
348
  )
338
349
 
339
- def set_target_lid_temperature(self, celsius: float) -> None:
350
+ def set_target_lid_temperature(self, celsius: float) -> EngineTaskCore:
340
351
  """Set the target temperature for the heated lid, in °C."""
341
- self._engine_client.execute_command(
352
+ result = self._engine_client.execute_command_without_recovery(
342
353
  cmd.thermocycler.SetTargetLidTemperatureParams(
343
354
  moduleId=self.module_id, celsius=celsius
344
355
  )
345
356
  )
357
+ lid_temperature_task = EngineTaskCore(
358
+ engine_client=self._engine_client, task_id=result.taskId
359
+ )
360
+ return lid_temperature_task
346
361
 
347
362
  def wait_for_lid_temperature(self) -> None:
348
363
  """Wait for target lid temperature to be reached."""
@@ -361,6 +376,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
361
376
  cmd.thermocycler.RunProfileStepParams(
362
377
  celsius=step["temperature"],
363
378
  holdSeconds=step["hold_time_seconds"],
379
+ rampRate=step["ramp_rate"],
364
380
  )
365
381
  for step in steps
366
382
  ]
@@ -389,6 +405,7 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
389
405
  cmd.thermocycler.ProfileStep(
390
406
  celsius=step["temperature"],
391
407
  holdSeconds=step["hold_time_seconds"],
408
+ rampRate=step["ramp_rate"],
392
409
  )
393
410
  for step in steps
394
411
  ],
@@ -416,6 +433,42 @@ class ThermocyclerModuleCore(ModuleCore, AbstractThermocyclerCore[LabwareCore]):
416
433
  else:
417
434
  return self._execute_profile_pre_221(steps, repetitions, block_max_volume)
418
435
 
436
+ def start_execute_profile(
437
+ self,
438
+ steps: List[ThermocyclerStep],
439
+ repetitions: int,
440
+ block_max_volume: Optional[float] = None,
441
+ ) -> EngineTaskCore:
442
+ """Start the execution of a hermocycler profile and return a task."""
443
+ self._repetitions = repetitions
444
+ self._step_count = len(steps)
445
+ engine_steps: List[
446
+ Union[cmd.thermocycler.ProfileStep, cmd.thermocycler.ProfileCycle]
447
+ ] = [
448
+ cmd.thermocycler.ProfileCycle(
449
+ repetitions=repetitions,
450
+ steps=[
451
+ cmd.thermocycler.ProfileStep(
452
+ celsius=step["temperature"],
453
+ holdSeconds=step["hold_time_seconds"],
454
+ rampRate=step["ramp_rate"],
455
+ )
456
+ for step in steps
457
+ ],
458
+ )
459
+ ]
460
+ result = self._engine_client.execute_command_without_recovery(
461
+ cmd.thermocycler.StartRunExtendedProfileParams(
462
+ moduleId=self.module_id,
463
+ profileElements=engine_steps,
464
+ blockMaxVolumeUl=block_max_volume,
465
+ )
466
+ )
467
+ start_execute_profile_result = EngineTaskCore(
468
+ engine_client=self._engine_client, task_id=result.taskId
469
+ )
470
+ return start_execute_profile_result
471
+
419
472
  def deactivate_lid(self) -> None:
420
473
  """Turn off the heated lid."""
421
474
  self._engine_client.execute_command(
@@ -507,13 +560,17 @@ class HeaterShakerModuleCore(ModuleCore, AbstractHeaterShakerCore[LabwareCore]):
507
560
 
508
561
  _sync_module_hardware: SynchronousAdapter[hw_modules.HeaterShaker]
509
562
 
510
- def set_target_temperature(self, celsius: float) -> None:
563
+ def set_target_temperature(self, celsius: float) -> EngineTaskCore:
511
564
  """Set the labware plate's target temperature in °C."""
512
- self._engine_client.execute_command(
565
+ result = self._engine_client.execute_command_without_recovery(
513
566
  cmd.heater_shaker.SetTargetTemperatureParams(
514
567
  moduleId=self.module_id, celsius=celsius
515
568
  )
516
569
  )
570
+ temperature_task = EngineTaskCore(
571
+ engine_client=self._engine_client, task_id=result.taskId
572
+ )
573
+ return temperature_task
517
574
 
518
575
  def wait_for_target_temperature(self) -> None:
519
576
  """Wait for the labware plate's target temperature to be reached."""
@@ -529,6 +586,16 @@ class HeaterShakerModuleCore(ModuleCore, AbstractHeaterShakerCore[LabwareCore]):
529
586
  )
530
587
  )
531
588
 
589
+ def set_shake_speed(self, rpm: int) -> EngineTaskCore:
590
+ """Set the shaker's target shake speed and wait for it to spin up."""
591
+ result = self._engine_client.execute_command_without_recovery(
592
+ cmd.heater_shaker.SetShakeSpeedParams(moduleId=self.module_id, rpm=rpm)
593
+ )
594
+ shake_task = EngineTaskCore(
595
+ engine_client=self._engine_client, task_id=result.taskId
596
+ )
597
+ return shake_task
598
+
532
599
  def open_labware_latch(self) -> None:
533
600
  """Open the labware latch."""
534
601
  self._engine_client.execute_command(
@@ -1,7 +1,7 @@
1
1
  """ProtocolEngine-based Protocol API core implementation."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING
4
+ from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING, Sequence
5
5
 
6
6
  from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExist
7
7
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
@@ -64,6 +64,7 @@ from ...disposal_locations import TrashBin, WasteChute
64
64
  from ..protocol import AbstractProtocol
65
65
  from ..labware import LabwareLoadParams
66
66
  from .labware import LabwareCore
67
+ from .tasks import EngineTaskCore
67
68
  from .instrument import InstrumentCore
68
69
  from .robot import RobotCore
69
70
  from .module_core import (
@@ -95,6 +96,7 @@ class ProtocolCore(
95
96
  InstrumentCore,
96
97
  LabwareCore,
97
98
  Union[ModuleCore, NonConnectedModuleCore],
99
+ EngineTaskCore,
98
100
  ]
99
101
  ):
100
102
  """Protocol API core using a ProtocolEngine.
@@ -912,6 +914,21 @@ class ProtocolCore(
912
914
  cmd.WaitForDurationParams(seconds=seconds, message=msg)
913
915
  )
914
916
 
917
+ def wait_for_tasks(self, task_cores: Sequence[EngineTaskCore]) -> None:
918
+ """Wait for specified tasks to complete."""
919
+ task_ids = task_ids = [task._id for task in task_cores if task._id is not None]
920
+ self._engine_client.execute_command(cmd.WaitForTasksParams(task_ids=task_ids))
921
+
922
+ def create_timer(self, seconds: float) -> EngineTaskCore:
923
+ """Create a timer task that runs in the background."""
924
+ result = self._engine_client.execute_command_without_recovery(
925
+ cmd.CreateTimerParams(time=seconds)
926
+ )
927
+ timer_task = EngineTaskCore(
928
+ engine_client=self._engine_client, task_id=result.task_id
929
+ )
930
+ return timer_task
931
+
915
932
  def home(self) -> None:
916
933
  """Move all axes to their home positions."""
917
934
  self._engine_client.execute_command(cmd.HomeParams(axes=None))
@@ -0,0 +1,48 @@
1
+ from datetime import datetime
2
+ from ..tasks import AbstractTaskCore
3
+ from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
4
+ from opentrons.protocol_engine.errors.exceptions import NoTaskFoundError
5
+
6
+
7
+ class EngineTaskCore(AbstractTaskCore):
8
+ def __init__(
9
+ self, task_id: str | None, engine_client: ProtocolEngineClient
10
+ ) -> None:
11
+ self._id = task_id
12
+ self._engine_client = engine_client
13
+
14
+ def get_created_at_timestamp(self) -> datetime:
15
+ if self._id is None:
16
+ raise NoTaskFoundError
17
+ try:
18
+ task = self._engine_client.state.tasks.get(self._id)
19
+ return task.createdAt
20
+ except NoTaskFoundError:
21
+ raise NoTaskFoundError
22
+
23
+ def is_done(self) -> bool:
24
+ if self._id is None:
25
+ raise NoTaskFoundError
26
+ try:
27
+ self._engine_client.state.tasks.get_finished(self._id)
28
+ return True
29
+ except NoTaskFoundError:
30
+ return False
31
+
32
+ def is_started(self) -> bool:
33
+ if self._id is None:
34
+ raise NoTaskFoundError
35
+ try:
36
+ self._engine_client.state.tasks.get_current(self._id)
37
+ return True
38
+ except NoTaskFoundError:
39
+ return self.is_done()
40
+
41
+ def get_finished_at_timestamp(self) -> datetime | None:
42
+ if self._id is None:
43
+ raise NoTaskFoundError
44
+ try:
45
+ finished_task = self._engine_client.state.tasks.get_finished(self._id)
46
+ return finished_task.finishedAt
47
+ except NoTaskFoundError:
48
+ return None
@@ -216,6 +216,14 @@ class WellCore(AbstractWellCore):
216
216
  labware_id=labware_id, well_name=well_name
217
217
  )
218
218
 
219
+ def has_tracked_liquid(self) -> bool:
220
+ """Return true if liquid has been loaded or probed."""
221
+ labware_id = self.labware_id
222
+ well_name = self._name
223
+ return self._engine_client.state.geometry.well_has_tracked_liquid(
224
+ labware_id=labware_id, well_name=well_name
225
+ )
226
+
219
227
  def get_liquid_volume(self) -> LiquidTrackingType:
220
228
  """Return the current volume in a well."""
221
229
  labware_id = self.labware_id
@@ -31,6 +31,7 @@ from ..module import (
31
31
  )
32
32
 
33
33
  from .legacy_labware_core import LegacyLabwareCore
34
+ from .tasks import LegacyTaskCore
34
35
  from .module_geometry import ModuleGeometry, ThermocyclerGeometry, HeaterShakerGeometry
35
36
  from ...labware import Labware
36
37
 
@@ -116,9 +117,10 @@ class LegacyTemperatureModuleCore(
116
117
 
117
118
  _sync_module_hardware: SynchronousAdapter[hw_modules.TempDeck]
118
119
 
119
- def set_target_temperature(self, celsius: float) -> None:
120
+ def set_target_temperature(self, celsius: float) -> LegacyTaskCore:
120
121
  """Set the Temperature Module's target temperature in °C."""
121
122
  self._sync_module_hardware.start_set_temperature(celsius)
123
+ return LegacyTaskCore()
122
124
 
123
125
  def wait_for_target_temperature(self, celsius: Optional[float] = None) -> None:
124
126
  """Wait until the module's target temperature is reached.
@@ -263,23 +265,27 @@ class LegacyThermocyclerCore(
263
265
  def set_target_block_temperature(
264
266
  self,
265
267
  celsius: float,
268
+ ramp_rate: Optional[float],
266
269
  hold_time_seconds: Optional[float] = None,
267
270
  block_max_volume: Optional[float] = None,
268
- ) -> None:
271
+ ) -> LegacyTaskCore:
269
272
  """Set the target temperature for the well block, in °C."""
270
273
  self._sync_module_hardware.set_target_block_temperature(
271
274
  celsius=celsius,
272
275
  hold_time_seconds=hold_time_seconds,
273
276
  volume=block_max_volume,
277
+ ramp_rate=ramp_rate,
274
278
  )
279
+ return LegacyTaskCore()
275
280
 
276
281
  def wait_for_block_temperature(self) -> None:
277
282
  """Wait for target block temperature to be reached."""
278
283
  self._sync_module_hardware.wait_for_block_target()
279
284
 
280
- def set_target_lid_temperature(self, celsius: float) -> None:
285
+ def set_target_lid_temperature(self, celsius: float) -> LegacyTaskCore:
281
286
  """Set the target temperature for the heated lid, in °C."""
282
287
  self._sync_module_hardware.set_target_lid_temperature(celsius=celsius)
288
+ return LegacyTaskCore()
283
289
 
284
290
  def wait_for_lid_temperature(self) -> None:
285
291
  """Wait for target lid temperature to be reached."""
@@ -296,6 +302,15 @@ class LegacyThermocyclerCore(
296
302
  steps=steps, repetitions=repetitions, volume=block_max_volume
297
303
  )
298
304
 
305
+ def start_execute_profile(
306
+ self,
307
+ steps: List[ThermocyclerStep],
308
+ repetitions: int,
309
+ block_max_volume: Optional[float] = None,
310
+ ) -> LegacyTaskCore:
311
+ """Start a Thermocycler Profile."""
312
+ assert False, "start_execute_profile only supported on engine core"
313
+
299
314
  def deactivate_lid(self) -> None:
300
315
  """Turn off the heated lid."""
301
316
  self._sync_module_hardware.deactivate_lid()
@@ -413,9 +428,10 @@ class LegacyHeaterShakerCore(
413
428
  _sync_module_hardware: SynchronousAdapter[hw_modules.HeaterShaker]
414
429
  _geometry: HeaterShakerGeometry
415
430
 
416
- def set_target_temperature(self, celsius: float) -> None:
431
+ def set_target_temperature(self, celsius: float) -> LegacyTaskCore:
417
432
  """Set the labware plate's target temperature in °C."""
418
433
  self._sync_module_hardware.start_set_temperature(celsius)
434
+ return LegacyTaskCore()
419
435
 
420
436
  def wait_for_target_temperature(self) -> None:
421
437
  """Wait for the labware plate's target temperature to be reached."""
@@ -442,6 +458,10 @@ class LegacyHeaterShakerCore(
442
458
  "Cannot start shaking unless labware latch is closed."
443
459
  )
444
460
 
461
+ def set_shake_speed(self, rpm: int) -> LegacyTaskCore:
462
+ """Set heatershaker speed."""
463
+ assert False, "set_shake_speed only supported on engine core."
464
+
445
465
  def open_labware_latch(self) -> None:
446
466
  """Open the labware latch."""
447
467
  if self.get_speed_status() != SpeedStatus.IDLE:
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Dict, List, Optional, Set, Union, cast, Tuple
2
+ from typing import Dict, List, Optional, Set, Union, cast, Tuple, Sequence
3
3
 
4
4
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
5
5
  from opentrons_shared_data.labware.types import LabwareDefinition
@@ -34,6 +34,7 @@ from .legacy_instrument_core import LegacyInstrumentCore
34
34
  from .labware_offset_provider import AbstractLabwareOffsetProvider
35
35
  from .legacy_labware_core import LegacyLabwareCore
36
36
  from .load_info import LoadInfo, InstrumentLoadInfo, LabwareLoadInfo, ModuleLoadInfo
37
+ from .tasks import LegacyTaskCore
37
38
 
38
39
  logger = logging.getLogger(__name__)
39
40
 
@@ -43,6 +44,7 @@ class LegacyProtocolCore(
43
44
  LegacyInstrumentCore,
44
45
  LegacyLabwareCore,
45
46
  legacy_module_core.LegacyModuleCore,
47
+ LegacyTaskCore,
46
48
  ]
47
49
  ):
48
50
  def __init__(
@@ -610,3 +612,11 @@ class LegacyProtocolCore(
610
612
  ]:
611
613
  """Get labware parent location."""
612
614
  assert False, "get_labware_location only supported on engine core"
615
+
616
+ def wait_for_tasks(self, task: Sequence[LegacyTaskCore]) -> None:
617
+ """Wait for list of tasks to complete before executing subsequent commands."""
618
+ assert False, "wait_for_tasks only supported on engine core"
619
+
620
+ def create_timer(self, seconds: float) -> LegacyTaskCore:
621
+ """Create a timer task that runs in the background."""
622
+ assert False, "create_timer only supported on engine core"
@@ -151,6 +151,10 @@ class LegacyWellCore(AbstractWellCore):
151
151
  """Return the volume contained in a well at any height."""
152
152
  return 0.0
153
153
 
154
+ def has_tracked_liquid(self) -> bool:
155
+ """Return true if liquid has been loaded or probed."""
156
+ return False
157
+
154
158
  # TODO(mc, 2022-10-28): is this used and/or necessary?
155
159
  def __repr__(self) -> str:
156
160
  """Use the well's display name as its repr."""
@@ -0,0 +1,19 @@
1
+ from ..tasks import AbstractTaskCore
2
+ from datetime import datetime
3
+
4
+
5
+ class LegacyTaskCore(AbstractTaskCore):
6
+ def __init__(self) -> None:
7
+ pass
8
+
9
+ def get_created_at_timestamp(self) -> datetime:
10
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
11
+
12
+ def is_done(self) -> bool:
13
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
14
+
15
+ def is_started(self) -> bool:
16
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
17
+
18
+ def get_finished_at_timestamp(self) -> datetime | None:
19
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Dict, Optional
2
+ from typing import Dict, Optional, Sequence
3
3
 
4
4
  from opentrons_shared_data.pipette.types import PipetteNameType
5
5
  from opentrons_shared_data.pipette.pipette_load_name_conversions import (
@@ -16,6 +16,7 @@ from ..legacy.legacy_module_core import LegacyModuleCore
16
16
  from ..legacy.load_info import InstrumentLoadInfo
17
17
 
18
18
  from .legacy_instrument_core import LegacyInstrumentCoreSimulator
19
+ from .tasks import LegacyTaskCore
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
@@ -23,7 +24,10 @@ logger = logging.getLogger(__name__)
23
24
  class LegacyProtocolCoreSimulator(
24
25
  LegacyProtocolCore,
25
26
  AbstractProtocol[
26
- LegacyInstrumentCoreSimulator, LegacyLabwareCore, LegacyModuleCore
27
+ LegacyInstrumentCoreSimulator,
28
+ LegacyLabwareCore,
29
+ LegacyModuleCore,
30
+ LegacyTaskCore,
27
31
  ],
28
32
  ):
29
33
  _instruments: Dict[Mount, Optional[LegacyInstrumentCoreSimulator]] # type: ignore[assignment]
@@ -83,3 +87,11 @@ class LegacyProtocolCoreSimulator(
83
87
  )
84
88
 
85
89
  return new_instr
90
+
91
+ def wait_for_tasks(self, task: Sequence[LegacyTaskCore]) -> None:
92
+ """Wait for list of tasks to complete before executing subsequent commands."""
93
+ assert False, "wait_for_tasks only supported on engine core"
94
+
95
+ def create_timer(self, seconds: float) -> LegacyTaskCore:
96
+ """Create a timer task that runs in the background."""
97
+ assert False, "create_timer only supported on engine core"
@@ -0,0 +1,19 @@
1
+ from ..tasks import AbstractTaskCore
2
+ from datetime import datetime
3
+
4
+
5
+ class LegacyTaskCore(AbstractTaskCore):
6
+ def __init__(self, created_at: datetime) -> None:
7
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
8
+
9
+ def get_created_at_timestamp(self) -> datetime:
10
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
11
+
12
+ def is_done(self) -> bool:
13
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
14
+
15
+ def is_started(self) -> bool:
16
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
17
+
18
+ def get_finished_at_timestamp(self) -> datetime | None:
19
+ raise NotImplementedError("Legacy protocols do not implement tasks.")
@@ -18,6 +18,7 @@ from opentrons.hardware_control.modules.types import (
18
18
  SpeedStatus,
19
19
  )
20
20
  from .labware import LabwareCoreType, AbstractLabware
21
+ from .tasks import AbstractTaskCore
21
22
  from opentrons.protocol_engine.types import ABSMeasureMode
22
23
  from opentrons.types import DeckSlotName
23
24
 
@@ -59,7 +60,7 @@ class AbstractTemperatureModuleCore(
59
60
  """Get the module's unique hardware serial number."""
60
61
 
61
62
  @abstractmethod
62
- def set_target_temperature(self, celsius: float) -> None:
63
+ def set_target_temperature(self, celsius: float) -> AbstractTaskCore:
63
64
  """Set the Temperature Module's target temperature in °C."""
64
65
 
65
66
  @abstractmethod
@@ -161,9 +162,10 @@ class AbstractThermocyclerCore(
161
162
  def set_target_block_temperature(
162
163
  self,
163
164
  celsius: float,
165
+ ramp_rate: Optional[float],
164
166
  hold_time_seconds: Optional[float] = None,
165
167
  block_max_volume: Optional[float] = None,
166
- ) -> None:
168
+ ) -> AbstractTaskCore:
167
169
  """Set the target temperature for the well block, in °C.
168
170
 
169
171
  Note:
@@ -183,7 +185,7 @@ class AbstractThermocyclerCore(
183
185
  """Wait for target block temperature to be reached."""
184
186
 
185
187
  @abstractmethod
186
- def set_target_lid_temperature(self, celsius: float) -> None:
188
+ def set_target_lid_temperature(self, celsius: float) -> AbstractTaskCore:
187
189
  """Set the target temperature for the heated lid, in °C."""
188
190
 
189
191
  @abstractmethod
@@ -217,6 +219,33 @@ class AbstractThermocyclerCore(
217
219
  will default to 25µL/well.
218
220
  """
219
221
 
222
+ @abstractmethod
223
+ def start_execute_profile(
224
+ self,
225
+ steps: List[ThermocyclerStep],
226
+ repetitions: int,
227
+ block_max_volume: Optional[float] = None,
228
+ ) -> AbstractTaskCore:
229
+ """Start a Thermocycler Profile.
230
+
231
+ Profile defined as a cycle of ``steps`` to repeat for a given number of ``repetitions``
232
+
233
+ Note:
234
+ Unlike the :py:meth:`execute_profile`, once the profile has started
235
+ the protocol will immediately move on to the next command, rather than waiting
236
+ for it to finish.
237
+ Args:
238
+ steps: List of unique steps that make up a single cycle.
239
+ Each list item should be a dictionary that maps to
240
+ the parameters of the :py:meth:`set_block_temperature`
241
+ method with keys 'temperature', 'hold_time_seconds',
242
+ and 'hold_time_minutes'.
243
+ repetitions: The number of times to repeat the cycled steps.
244
+ block_max_volume: The maximum volume of any individual well
245
+ of the loaded labware. If not supplied, the thermocycler
246
+ will default to 25µL/well.
247
+ """
248
+
220
249
  @abstractmethod
221
250
  def deactivate_lid(self) -> None:
222
251
  """Turn off the heated lid."""
@@ -294,7 +323,7 @@ class AbstractHeaterShakerCore(
294
323
  """Get the module's unique hardware serial number."""
295
324
 
296
325
  @abstractmethod
297
- def set_target_temperature(self, celsius: float) -> None:
326
+ def set_target_temperature(self, celsius: float) -> AbstractTaskCore:
298
327
  """Set the labware plate's target temperature in °C."""
299
328
 
300
329
  @abstractmethod
@@ -305,6 +334,10 @@ class AbstractHeaterShakerCore(
305
334
  def set_and_wait_for_shake_speed(self, rpm: int) -> None:
306
335
  """Set the shaker's target shake speed and wait for it to spin up."""
307
336
 
337
+ @abstractmethod
338
+ def set_shake_speed(self, rpm: int) -> AbstractTaskCore:
339
+ """Set the shaker's target shake speed."""
340
+
308
341
  @abstractmethod
309
342
  def open_labware_latch(self) -> None:
310
343
  """Open the labware latch."""
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import abstractmethod, ABC
6
- from typing import Generic, List, Optional, Union, Tuple, Dict, TYPE_CHECKING
6
+ from typing import Generic, List, Optional, Union, Tuple, Dict, TYPE_CHECKING, Sequence
7
7
 
8
8
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
9
9
  from opentrons_shared_data.pipette.types import PipetteNameType
@@ -24,6 +24,7 @@ from opentrons.protocols.api_support.util import AxisMaxSpeeds
24
24
  from .instrument import InstrumentCoreType
25
25
  from .labware import LabwareCoreType, LabwareLoadParams
26
26
  from .module import ModuleCoreType
27
+ from .tasks import TaskCoreType
27
28
  from .._liquid import Liquid, LiquidClass
28
29
  from .robot import AbstractRobot
29
30
  from .._types import OffDeckType
@@ -34,7 +35,7 @@ if TYPE_CHECKING:
34
35
 
35
36
 
36
37
  class AbstractProtocol(
37
- ABC, Generic[InstrumentCoreType, LabwareCoreType, ModuleCoreType]
38
+ ABC, Generic[InstrumentCoreType, LabwareCoreType, ModuleCoreType, TaskCoreType]
38
39
  ):
39
40
  @property
40
41
  @abstractmethod
@@ -192,6 +193,14 @@ class AbstractProtocol(
192
193
  def delay(self, seconds: float, msg: Optional[str]) -> None:
193
194
  ...
194
195
 
196
+ @abstractmethod
197
+ def wait_for_tasks(self, task_cores: Sequence[TaskCoreType]) -> None:
198
+ ...
199
+
200
+ @abstractmethod
201
+ def create_timer(self, seconds: float) -> TaskCoreType:
202
+ ...
203
+
195
204
  @abstractmethod
196
205
  def home(self) -> None:
197
206
  ...
@@ -0,0 +1,31 @@
1
+ from abc import abstractmethod, ABC
2
+ from datetime import datetime
3
+ from typing import TypeVar
4
+
5
+
6
+ class AbstractTaskCore(ABC):
7
+ @abstractmethod
8
+ def get_created_at_timestamp(self) -> datetime:
9
+ """Get the createdAt timestamp of the task."""
10
+ ...
11
+
12
+ @abstractmethod
13
+ def is_done(self) -> bool:
14
+ """Returns ``True`` if the task is done."""
15
+ ...
16
+
17
+ @abstractmethod
18
+ def is_started(self) -> bool:
19
+ """Returns ``True`` if the task has started."""
20
+ ...
21
+
22
+ @abstractmethod
23
+ def get_finished_at_timestamp(self) -> datetime | None:
24
+ """The timestamp of the when the task finished.
25
+
26
+ Returns ``None`` if the task hasn't finished yet.
27
+ """
28
+ ...
29
+
30
+
31
+ TaskCoreType = TypeVar("TaskCoreType", bound=AbstractTaskCore)
@@ -112,5 +112,9 @@ class AbstractWellCore(ABC):
112
112
  def volume_from_height(self, height: LiquidTrackingType) -> LiquidTrackingType:
113
113
  """Return the volume contained in a well at any height."""
114
114
 
115
+ @abstractmethod
116
+ def has_tracked_liquid(self) -> bool:
117
+ """Return true if liquid has been loaded or probed."""
118
+
115
119
 
116
120
  WellCoreType = TypeVar("WellCoreType", bound=AbstractWellCore)