opentrons 8.7.0a0__py3-none-any.whl → 8.7.0a2__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 (121) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/drivers/thermocycler/abstract.py +1 -0
  3. opentrons/drivers/thermocycler/driver.py +33 -4
  4. opentrons/drivers/thermocycler/simulator.py +2 -0
  5. opentrons/hardware_control/api.py +24 -5
  6. opentrons/hardware_control/backends/controller.py +8 -2
  7. opentrons/hardware_control/backends/ot3controller.py +3 -0
  8. opentrons/hardware_control/backends/ot3simulator.py +2 -1
  9. opentrons/hardware_control/backends/simulator.py +2 -1
  10. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  11. opentrons/hardware_control/module_control.py +82 -8
  12. opentrons/hardware_control/modules/__init__.py +3 -0
  13. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  14. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  15. opentrons/hardware_control/modules/heater_shaker.py +30 -5
  16. opentrons/hardware_control/modules/magdeck.py +8 -4
  17. opentrons/hardware_control/modules/mod_abc.py +13 -5
  18. opentrons/hardware_control/modules/tempdeck.py +25 -5
  19. opentrons/hardware_control/modules/thermocycler.py +56 -10
  20. opentrons/hardware_control/modules/types.py +20 -1
  21. opentrons/hardware_control/modules/utils.py +11 -4
  22. opentrons/hardware_control/nozzle_manager.py +3 -0
  23. opentrons/hardware_control/ot3api.py +26 -5
  24. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  25. opentrons/hardware_control/types.py +31 -2
  26. opentrons/legacy_commands/protocol_commands.py +20 -0
  27. opentrons/legacy_commands/types.py +42 -0
  28. opentrons/motion_planning/waypoints.py +15 -29
  29. opentrons/protocol_api/__init__.py +5 -0
  30. opentrons/protocol_api/_types.py +6 -1
  31. opentrons/protocol_api/core/common.py +3 -1
  32. opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
  33. opentrons/protocol_api/core/engine/_default_liquid_class_versions.py +2 -0
  34. opentrons/protocol_api/core/engine/labware.py +8 -1
  35. opentrons/protocol_api/core/engine/module_core.py +4 -0
  36. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +77 -17
  37. opentrons/protocol_api/core/engine/protocol.py +18 -1
  38. opentrons/protocol_api/core/engine/tasks.py +35 -0
  39. opentrons/protocol_api/core/legacy/legacy_module_core.py +2 -0
  40. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
  41. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  42. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  43. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  44. opentrons/protocol_api/core/module.py +1 -0
  45. opentrons/protocol_api/core/protocol.py +11 -2
  46. opentrons/protocol_api/core/tasks.py +31 -0
  47. opentrons/protocol_api/module_contexts.py +1 -0
  48. opentrons/protocol_api/protocol_context.py +26 -4
  49. opentrons/protocol_api/robot_context.py +38 -21
  50. opentrons/protocol_api/tasks.py +48 -0
  51. opentrons/protocol_api/validation.py +6 -1
  52. opentrons/protocol_engine/actions/__init__.py +4 -2
  53. opentrons/protocol_engine/actions/actions.py +22 -9
  54. opentrons/protocol_engine/clients/sync_client.py +6 -7
  55. opentrons/protocol_engine/commands/__init__.py +42 -0
  56. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  57. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  58. opentrons/protocol_engine/commands/aspirate.py +1 -0
  59. opentrons/protocol_engine/commands/command.py +1 -0
  60. opentrons/protocol_engine/commands/command_unions.py +39 -0
  61. opentrons/protocol_engine/commands/create_timer.py +83 -0
  62. opentrons/protocol_engine/commands/dispense.py +1 -0
  63. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  64. opentrons/protocol_engine/commands/movement_common.py +2 -0
  65. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  66. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  67. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  68. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  69. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +17 -1
  70. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  71. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  72. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  73. opentrons/protocol_engine/errors/__init__.py +4 -0
  74. opentrons/protocol_engine/errors/exceptions.py +55 -0
  75. opentrons/protocol_engine/execution/__init__.py +2 -0
  76. opentrons/protocol_engine/execution/command_executor.py +8 -0
  77. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  78. opentrons/protocol_engine/execution/labware_movement.py +9 -12
  79. opentrons/protocol_engine/execution/movement.py +2 -0
  80. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  81. opentrons/protocol_engine/execution/run_control.py +8 -0
  82. opentrons/protocol_engine/execution/task_handler.py +157 -0
  83. opentrons/protocol_engine/protocol_engine.py +67 -33
  84. opentrons/protocol_engine/resources/__init__.py +2 -0
  85. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  86. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  87. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  88. opentrons/protocol_engine/state/_well_math.py +60 -18
  89. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  90. opentrons/protocol_engine/state/commands.py +7 -7
  91. opentrons/protocol_engine/state/geometry.py +237 -379
  92. opentrons/protocol_engine/state/labware.py +52 -102
  93. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  94. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
  95. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  96. opentrons/protocol_engine/state/modules.py +26 -7
  97. opentrons/protocol_engine/state/motion.py +44 -0
  98. opentrons/protocol_engine/state/state.py +14 -0
  99. opentrons/protocol_engine/state/state_summary.py +2 -0
  100. opentrons/protocol_engine/state/tasks.py +139 -0
  101. opentrons/protocol_engine/state/tips.py +177 -258
  102. opentrons/protocol_engine/state/update_types.py +16 -9
  103. opentrons/protocol_engine/types/__init__.py +9 -3
  104. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  105. opentrons/protocol_engine/types/instrument.py +8 -1
  106. opentrons/protocol_engine/types/labware.py +1 -13
  107. opentrons/protocol_engine/types/module.py +10 -0
  108. opentrons/protocol_engine/types/tasks.py +38 -0
  109. opentrons/protocol_engine/types/tip.py +9 -0
  110. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  111. opentrons/protocol_runner/run_orchestrator.py +18 -2
  112. opentrons/protocols/api_support/definitions.py +1 -1
  113. opentrons/protocols/api_support/types.py +2 -1
  114. opentrons/simulate.py +48 -15
  115. opentrons/system/camera.py +1 -1
  116. {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/METADATA +4 -4
  117. {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/RECORD +120 -107
  118. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  119. {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/WHEEL +0 -0
  120. {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/entry_points.txt +0 -0
  121. {opentrons-8.7.0a0.dist-info → opentrons-8.7.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -263,6 +263,7 @@ class LegacyThermocyclerCore(
263
263
  def set_target_block_temperature(
264
264
  self,
265
265
  celsius: float,
266
+ ramp_rate: Optional[float],
266
267
  hold_time_seconds: Optional[float] = None,
267
268
  block_max_volume: Optional[float] = None,
268
269
  ) -> None:
@@ -271,6 +272,7 @@ class LegacyThermocyclerCore(
271
272
  celsius=celsius,
272
273
  hold_time_seconds=hold_time_seconds,
273
274
  volume=block_max_volume,
275
+ ramp_rate=ramp_rate,
274
276
  )
275
277
 
276
278
  def wait_for_block_temperature(self) -> None:
@@ -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"
@@ -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.")
@@ -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.")
@@ -161,6 +161,7 @@ class AbstractThermocyclerCore(
161
161
  def set_target_block_temperature(
162
162
  self,
163
163
  celsius: float,
164
+ ramp_rate: Optional[float],
164
165
  hold_time_seconds: Optional[float] = None,
165
166
  block_max_volume: Optional[float] = None,
166
167
  ) -> None:
@@ -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)
@@ -686,6 +686,7 @@ class ThermocyclerContext(ModuleContext):
686
686
  celsius=temperature,
687
687
  hold_time_seconds=seconds,
688
688
  block_max_volume=block_max_volume,
689
+ ramp_rate=ramp_rate,
689
690
  )
690
691
  self._core.wait_for_block_temperature()
691
692
 
@@ -89,6 +89,7 @@ from .module_contexts import (
89
89
  FlexStackerContext,
90
90
  ModuleContext,
91
91
  )
92
+ from .tasks import Task
92
93
  from ._parameters import Parameters
93
94
 
94
95
 
@@ -234,10 +235,7 @@ class ProtocolContext(CommandPublisher):
234
235
  @property
235
236
  @requires_version(2, 22)
236
237
  def robot(self) -> RobotContext:
237
- """The :py:class:`.RobotContext` for the protocol.
238
-
239
- :meta private:
240
- """
238
+ """The :py:class:`.RobotContext` for the protocol."""
241
239
  if self._core.robot_type != "OT-3 Standard" or not self._robot:
242
240
  raise RobotTypeError("The RobotContext is only available on Flex robots.")
243
241
  return self._robot
@@ -1289,6 +1287,30 @@ class ProtocolContext(CommandPublisher):
1289
1287
  delay_time = seconds + minutes * 60
1290
1288
  self._core.delay(seconds=delay_time, msg=msg)
1291
1289
 
1290
+ @publish(command=cmds.wait_for_tasks)
1291
+ @requires_version(2, 27)
1292
+ def wait_for_tasks(self, tasks: list[Task]) -> None:
1293
+ """Wait for a list of tasks to complete before executing subsequent commands.
1294
+
1295
+ :param list Task: tasks: A list of Task objects to wait for.
1296
+
1297
+ Task objects can be commands that are allowed to run concurrently.
1298
+ """
1299
+ task_cores = [task._core for task in tasks]
1300
+ self._core.wait_for_tasks(task_cores)
1301
+
1302
+ @publish(command=cmds.create_timer)
1303
+ @requires_version(2, 27)
1304
+ def create_timer(self, seconds: float) -> Task:
1305
+ """Create a timer task that runs in the background.
1306
+
1307
+ :param float seconds: The time to delay in seconds.
1308
+
1309
+ This timer will continue to run until it is complete and will not block subsequent commands.
1310
+ """
1311
+ task_core = self._core.create_timer(seconds=seconds)
1312
+ return Task(core=task_core, api_version=self._api_version)
1313
+
1292
1314
  @requires_version(2, 0)
1293
1315
  def home(self) -> None:
1294
1316
  """Home the movement system of the robot."""
@@ -46,7 +46,7 @@ class RobotContext(publisher.CommandPublisher):
46
46
  Objects in this class should not be instantiated directly. Instead, instances are
47
47
  returned by :py:meth:`ProtocolContext.robot`.
48
48
 
49
- .. versionadded:: 2.20
49
+ .. versionadded:: 2.22
50
50
 
51
51
  """
52
52
 
@@ -83,15 +83,19 @@ class RobotContext(publisher.CommandPublisher):
83
83
  speed: Optional[float] = None,
84
84
  ) -> None:
85
85
  """
86
- Move a specified mount to a destination location on the deck.
86
+ Move a specified mount to a location on the deck.
87
87
 
88
88
  :param mount: The mount of the instrument you wish to move.
89
89
  This can either be an instance of :py:class:`.types.Mount` or one
90
90
  of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note
91
91
  that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``.
92
92
  :type mount: types.Mount or str
93
- :param Location destination:
94
- :param speed:
93
+ :param destination: Any location on the deck, specified as:
94
+
95
+ - a slot, like ``"A1"``
96
+ - a defined location, like labware in a deck slot
97
+ - an absolute location, like a point {x=10 , y=10, z=10} or a deck location and point ("A1" + point {x=10 , y=10, z=10})
98
+ :param speed: The absolute speed in mm/s.
95
99
  """
96
100
  mount = validation.ensure_instrument_mount(mount)
97
101
  with publisher.publish_context(
@@ -116,10 +120,9 @@ class RobotContext(publisher.CommandPublisher):
116
120
  Move a set of axes to an absolute position on the deck.
117
121
 
118
122
  :param axis_map: A dictionary mapping axes to an absolute position on the deck in mm.
119
- :param critical_point: The critical point to move the axes with. It should only
120
- specify the gantry axes (i.e. `x`, `y`, `z`).
121
- :param float speed: The maximum speed with which you want to move all the axes
122
- in the axis map.
123
+ :param critical_point: The critical point, or specific point on the object being moved, to move the axes with. It should only specify the gantry axes (i.e. `x`, `y`, `z`). When you specify a critical point, you're specifying the object on the gantry to be moved. If not specified, the critical point defaults to the center of the carriage attached to the gantry.
124
+ :param float speed: The maximum speed with which to move all axes in mm/s.
125
+
123
126
  """
124
127
  instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
125
128
  is_96_channel = instrument_on_left == PipetteNameType.P1000_96
@@ -154,11 +157,9 @@ class RobotContext(publisher.CommandPublisher):
154
157
  """
155
158
  Move a set of axes to a relative position on the deck.
156
159
 
157
- :param axis_map: A dictionary mapping axes to relative movements in mm.
158
- :type mount: types.Mount or str
160
+ :param axis_map: A dictionary mapping axes to relative movements from the current position in mm.
159
161
 
160
- :param float speed: The maximum speed with which you want to move all the axes
161
- in the axis map.
162
+ :param float speed: The maximum speed with which to move all axes in mm/s.
162
163
  """
163
164
  instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
164
165
  is_96_channel = instrument_on_left == PipetteNameType.P1000_96
@@ -177,7 +178,10 @@ class RobotContext(publisher.CommandPublisher):
177
178
  self._core.move_axes_relative(axis_map, speed)
178
179
 
179
180
  def close_gripper_jaw(self, force: Optional[float] = None) -> None:
180
- """Command the gripper closed with some force."""
181
+ """Closes the Flex Gripper jaws with a specified force.
182
+
183
+ :param force: Force with which to close the gripper jaws in newtons.
184
+ """
181
185
  with publisher.publish_context(
182
186
  broker=self.broker,
183
187
  command=cmds.close_gripper(
@@ -187,7 +191,10 @@ class RobotContext(publisher.CommandPublisher):
187
191
  self._core.close_gripper(force)
188
192
 
189
193
  def open_gripper_jaw(self) -> None:
190
- """Command the gripper open."""
194
+ """Opens the Flex Gripper jaws with a specified force.
195
+
196
+ :param force: Force with which to open the gripper jaws in newtons.
197
+ """
191
198
  with publisher.publish_context(
192
199
  broker=self.broker,
193
200
  command=cmds.open_gripper(),
@@ -200,9 +207,9 @@ class RobotContext(publisher.CommandPublisher):
200
207
  location: Union[Location, ModuleContext, DeckLocation],
201
208
  ) -> AxisMapType:
202
209
  """
203
- Build a :py:class:`.types.AxisMapType` from a location to be compatible with
210
+ Build an axis map from a location to provide to
204
211
  either :py:meth:`.RobotContext.move_axes_to` or :py:meth:`.RobotContext.move_axes_relative`.
205
- You must provide only one of `location`, `slot`, or `module` to build
212
+ You must provide only one of either a `location`, `slot`, or `module` to build
206
213
  the axis map.
207
214
 
208
215
  :param mount: The mount of the instrument you wish create an axis map for.
@@ -210,7 +217,10 @@ class RobotContext(publisher.CommandPublisher):
210
217
  of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note
211
218
  that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``.
212
219
  :type mount: types.Mount or str
213
- :param location: The location to format an axis map for.
220
+ :param location: Any location on the deck, specified as:
221
+
222
+ - a deck location, like slot ``"A1"``.
223
+ - a defined location, like a module on the deck.
214
224
  :type location: `Well`, `ModuleContext`, `DeckLocation` or `OffDeckType`
215
225
  """
216
226
  mount = validation.ensure_instrument_mount(mount)
@@ -248,7 +258,11 @@ class RobotContext(publisher.CommandPublisher):
248
258
  self, mount: Union[Mount, str], volume: float, action: PipetteActionTypes
249
259
  ) -> AxisMapType:
250
260
  """
251
- Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from volume.
261
+ Build an axis map to move a pipette plunger motor to complete liquid handling actions.
262
+
263
+ :mount: The left or right instrument mount the pipette is attached to.
264
+ :param volume: A volume to convert to an axis map for linear plunger displacement.
265
+ :param action: Choose to ``aspirate`` or ``dispense``.
252
266
 
253
267
  """
254
268
  pipette_name = self._core.get_pipette_type_from_engine(mount)
@@ -268,7 +282,9 @@ class RobotContext(publisher.CommandPublisher):
268
282
  self, mount: Union[Mount, str], position_name: PlungerPositionTypes
269
283
  ) -> AxisMapType:
270
284
  """
271
- Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from position_name.
285
+ Build an axis map to move a pipette plunger motor to a named position.
286
+
287
+ :param position_name: A named position to move the pipette plunger to. Choose from ``top``, ``bottom``, ``blowout``, or ``drop`` plunger positions.
272
288
 
273
289
  """
274
290
  pipette_name = self._core.get_pipette_type_from_engine(mount)
@@ -284,8 +300,9 @@ class RobotContext(publisher.CommandPublisher):
284
300
  return {pipette_axis: pipette_position}
285
301
 
286
302
  def build_axis_map(self, axis_map: StringAxisMap) -> AxisMapType:
287
- """Take in a :py:class:`.types.StringAxisMap` and output a :py:class:`.types.AxisMapType`.
288
- A :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings:
303
+ """Take in a :py:class:`.types.StringAxisMap` and output an axis map.
304
+
305
+ The :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings:
289
306
  ``"x"``, ``"y"``, "``z_l"``, "``z_r"``, "``z_g"``, ``"q"``.
290
307
 
291
308
  An example of a valid axis map could be:
@@ -0,0 +1,48 @@
1
+ """Data for concurrent protocol tasks."""
2
+ from typing import TYPE_CHECKING
3
+ from datetime import datetime
4
+ from opentrons.protocols.api_support.util import requires_version
5
+ from opentrons.protocols.api_support.types import APIVersion
6
+
7
+ if TYPE_CHECKING:
8
+ from .core.common import TaskCore
9
+
10
+
11
+ class Task:
12
+ """A concurrent protocol task created by a protocol API function.
13
+
14
+ .. versionadded:: 2.27
15
+ """
16
+
17
+ def __init__(self, core: "TaskCore", api_version: APIVersion) -> None:
18
+ """Initialize a Task."""
19
+ self._core = core
20
+ self._api_version = api_version
21
+
22
+ @property
23
+ @requires_version(2, 27)
24
+ def created_at(self) -> datetime:
25
+ """The timestamp of when the task was created."""
26
+ return self._core.get_created_at_timestamp()
27
+
28
+ @property
29
+ @requires_version(2, 27)
30
+ def done(self) -> bool:
31
+ """Returns ``True`` if the task is done."""
32
+ return self._core.is_done()
33
+
34
+ @property
35
+ @requires_version(2, 27)
36
+ def started(self) -> bool:
37
+ """Returns ``True`` if the task has started."""
38
+ return self._core.is_started()
39
+ ...
40
+
41
+ @property
42
+ @requires_version(2, 27)
43
+ def finished_at(self) -> datetime | None:
44
+ """The timestamp of the when the task finished.
45
+
46
+ Returns ``None`` if the task hasn't finished yet.
47
+ """
48
+ return self._core.get_finished_at_timestamp()
@@ -489,6 +489,7 @@ def ensure_thermocycler_profile_steps(
489
489
  temperature = step.get("temperature")
490
490
  hold_mins = step.get("hold_time_minutes")
491
491
  hold_secs = step.get("hold_time_seconds")
492
+ ramp_rate = step.get("ramp_rate")
492
493
  if temperature is None:
493
494
  raise ValueError("temperature must be defined for each step in cycle")
494
495
  if hold_mins is None and hold_secs is None:
@@ -496,10 +497,14 @@ def ensure_thermocycler_profile_steps(
496
497
  "either hold_time_minutes or hold_time_seconds must be"
497
498
  "defined for each step in cycle"
498
499
  )
500
+ if ramp_rate is not None and ramp_rate <= 0:
501
+ raise ValueError("Ramp rate must be greater than 0.")
499
502
  validated_seconds = ensure_hold_time_seconds(hold_secs, hold_mins)
500
503
  validated_steps.append(
501
504
  ThermocyclerStep(
502
- temperature=temperature, hold_time_seconds=validated_seconds
505
+ temperature=temperature,
506
+ hold_time_seconds=validated_seconds,
507
+ ramp_rate=ramp_rate,
503
508
  )
504
509
  )
505
510
  return validated_steps
@@ -26,8 +26,9 @@ from .actions import (
26
26
  AddModuleAction,
27
27
  FinishErrorDetails,
28
28
  DoorChangeAction,
29
- ResetTipsAction,
30
29
  SetPipetteMovementSpeedAction,
30
+ StartTaskAction,
31
+ FinishTaskAction,
31
32
  )
32
33
  from .get_state_update import get_state_updates
33
34
 
@@ -55,8 +56,9 @@ __all__ = [
55
56
  "AddAddressableAreaAction",
56
57
  "AddModuleAction",
57
58
  "DoorChangeAction",
58
- "ResetTipsAction",
59
59
  "SetPipetteMovementSpeedAction",
60
+ "StartTaskAction",
61
+ "FinishTaskAction",
60
62
  # action payload values
61
63
  "PauseSource",
62
64
  "FinishErrorDetails",
@@ -3,6 +3,7 @@
3
3
  Actions can be passed to the ActionDispatcher, where they will trigger
4
4
  reactions in objects that subscribe to the pipeline, like the StateStore.
5
5
  """
6
+
6
7
  import dataclasses
7
8
  from datetime import datetime
8
9
  from enum import Enum
@@ -20,6 +21,7 @@ from ..commands import (
20
21
  CommandDefinedErrorData,
21
22
  )
22
23
  from ..error_recovery_policy import ErrorRecoveryPolicy, ErrorRecoveryType
24
+ from ..errors import ErrorOccurrence
23
25
  from ..notes.notes import CommandNote
24
26
  from ..state.update_types import StateUpdate
25
27
  from ..types import (
@@ -27,6 +29,7 @@ from ..types import (
27
29
  ModuleDefinition,
28
30
  Liquid,
29
31
  DeckConfigurationType,
32
+ Task,
30
33
  )
31
34
 
32
35
 
@@ -60,7 +63,7 @@ class PauseAction:
60
63
  class StopAction:
61
64
  """Request engine execution to stop soon."""
62
65
 
63
- from_estop: bool = False
66
+ from_asynchronous_error: bool = False
64
67
 
65
68
 
66
69
  @dataclasses.dataclass(frozen=True)
@@ -201,6 +204,22 @@ class FailCommandAction:
201
204
  """The command to fail, in its prior `running` state."""
202
205
 
203
206
 
207
+ @dataclasses.dataclass(frozen=True)
208
+ class StartTaskAction:
209
+ """Store new task in state."""
210
+
211
+ task: Task
212
+
213
+
214
+ @dataclasses.dataclass(frozen=True)
215
+ class FinishTaskAction:
216
+ """Mark task as finished in state."""
217
+
218
+ task_id: str
219
+ finished_at: datetime
220
+ error: ErrorOccurrence | None
221
+
222
+
204
223
  @dataclasses.dataclass(frozen=True)
205
224
  class AddLabwareOffsetAction:
206
225
  """Add a labware offset, to apply to subsequent `LoadLabwareCommand`s."""
@@ -253,13 +272,6 @@ class AddModuleAction:
253
272
  module_live_data: LiveData
254
273
 
255
274
 
256
- @dataclasses.dataclass(frozen=True)
257
- class ResetTipsAction:
258
- """Reset the tip tracking state of a given tip rack."""
259
-
260
- labware_id: str
261
-
262
-
263
275
  @dataclasses.dataclass(frozen=True)
264
276
  class SetPipetteMovementSpeedAction:
265
277
  """Set the speed of a pipette's X/Y/Z movements. Does not affect plunger speed.
@@ -296,7 +308,8 @@ Action = Union[
296
308
  SetDeckConfigurationAction,
297
309
  AddAddressableAreaAction,
298
310
  AddLiquidAction,
299
- ResetTipsAction,
300
311
  SetPipetteMovementSpeedAction,
301
312
  SetErrorRecoveryPolicyAction,
313
+ StartTaskAction,
314
+ FinishTaskAction,
302
315
  ]
@@ -65,6 +65,12 @@ class SyncClient:
65
65
  ) -> commands.LoadLabwareResult:
66
66
  pass
67
67
 
68
+ @overload
69
+ def execute_command_without_recovery(
70
+ self, params: commands.CreateTimerParams
71
+ ) -> commands.CreateTimerResult:
72
+ pass
73
+
68
74
  @overload
69
75
  def execute_command_without_recovery(
70
76
  self, params: commands.LoadModuleParams
@@ -149,13 +155,6 @@ class SyncClient:
149
155
  """Add a liquid to the engine."""
150
156
  return self._transport.call_method("add_liquid", name=name, color=color, description=description) # type: ignore[no-any-return]
151
157
 
152
- def reset_tips(self, labware_id: str) -> None:
153
- """Reset a labware's tip tracking state.."""
154
- self._transport.call_method(
155
- "reset_tips",
156
- labware_id=labware_id,
157
- )
158
-
159
158
  def add_labware_offset(self, request: LabwareOffsetCreate) -> None:
160
159
  """Add a labware offset."""
161
160
  self._transport.call_method("add_labware_offset", request=request)