opentrons 8.7.0a6__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.0a6.dist-info → opentrons-8.7.0a7.dist-info}/METADATA +4 -4
  140. {opentrons-8.7.0a6.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.0a6.dist-info → opentrons-8.7.0a7.dist-info}/WHEEL +0 -0
  143. {opentrons-8.7.0a6.dist-info → opentrons-8.7.0a7.dist-info}/entry_points.txt +0 -0
  144. {opentrons-8.7.0a6.dist-info → opentrons-8.7.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,10 @@ from enum import Enum
6
6
 
7
7
  from opentrons.types import DeckSlotName
8
8
 
9
- from opentrons_shared_data.module.types import ModuleType as SharedDataModuleType
9
+ from opentrons_shared_data.module.types import (
10
+ ModuleType as SharedDataModuleType,
11
+ ModuleOrientation,
12
+ )
10
13
  from opentrons_shared_data.deck.types import SlotDefV3
11
14
  from opentrons_shared_data.labware.types import LocatingFeatures
12
15
 
@@ -57,6 +60,7 @@ class AddressableArea:
57
60
  position: AddressableOffsetVector
58
61
  compatible_module_types: List[SharedDataModuleType]
59
62
  features: LocatingFeatures
63
+ orientation: ModuleOrientation
60
64
 
61
65
 
62
66
  # TODO make the below some sort of better type
@@ -1,5 +1,5 @@
1
1
  """Protocol Engine types to do with instruments."""
2
-
2
+ import enum
3
3
  from typing import Union
4
4
 
5
5
  from dataclasses import dataclass
@@ -45,3 +45,10 @@ class InstrumentOffsetVector(BaseModel):
45
45
  x: float
46
46
  y: float
47
47
  z: float
48
+
49
+
50
+ class GripperMoveType(enum.Enum):
51
+ """Types of gripper movement."""
52
+
53
+ PICK_UP_LABWARE = enum.auto()
54
+ DROP_LABWARE = enum.auto()
@@ -2,16 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Optional, Union
5
+ from typing import Optional
6
6
  from dataclasses import dataclass
7
7
  from datetime import datetime
8
8
 
9
9
  from pydantic import BaseModel, Field
10
10
 
11
- from opentrons_shared_data.labware.labware_definition import (
12
- LabwareDefinition,
13
- )
14
-
15
11
  from .location import LabwareLocation
16
12
  from .labware_offset_location import (
17
13
  LegacyLabwareOffsetLocation,
@@ -19,8 +15,6 @@ from .labware_offset_location import (
19
15
  )
20
16
  from .labware_offset_vector import LabwareOffsetVector
21
17
  from .util import Vec3f
22
- from .module import ModuleDefinition
23
- from .deck_configuration import DeckLocationDefinition
24
18
 
25
19
 
26
20
  @dataclass(frozen=True)
@@ -126,12 +120,6 @@ class LoadedLabware(BaseModel):
126
120
  )
127
121
 
128
122
 
129
- LabwareParentDefinition = Union[
130
- DeckLocationDefinition, ModuleDefinition, LabwareDefinition
131
- ]
132
- """Information pertaining to a labware's parent (deck slot, module, or another labware) location."""
133
-
134
-
135
123
  @dataclass(frozen=True)
136
124
  class LabwareWellId:
137
125
  """Designates a well in a labware."""
@@ -21,6 +21,7 @@ from opentrons_shared_data.labware.types import LocatingFeatures
21
21
 
22
22
  from opentrons.hardware_control.modules import (
23
23
  ModuleType as ModuleType,
24
+ ModuleModel as HardwareModuleModel,
24
25
  )
25
26
 
26
27
  from .location import DeckSlotLocation
@@ -43,6 +44,11 @@ class ModuleModel(str, Enum):
43
44
  ABSORBANCE_READER_V1 = "absorbanceReaderV1"
44
45
  FLEX_STACKER_MODULE_V1 = "flexStackerModuleV1"
45
46
 
47
+ @classmethod
48
+ def from_hardware(cls, hardware_model: HardwareModuleModel) -> "ModuleModel":
49
+ """Convert from the hardware model representation."""
50
+ return cls(hardware_model.value)
51
+
46
52
  def as_type(self) -> ModuleType:
47
53
  """Get the ModuleType of this model."""
48
54
  if ModuleModel.is_temperature_module_model(self):
@@ -193,6 +199,10 @@ class ModuleDefinition(BaseModel):
193
199
  ...,
194
200
  )
195
201
 
202
+ orientation: Dict[str, str] = Field(
203
+ ...,
204
+ )
205
+
196
206
  compatibleWith: List[ModuleModel] = Field(
197
207
  ...,
198
208
  )
@@ -0,0 +1,38 @@
1
+ """Types for Tasks."""
2
+ from datetime import datetime
3
+ from opentrons.protocol_engine.errors import ErrorOccurrence
4
+ from dataclasses import dataclass
5
+ import asyncio
6
+
7
+
8
+ @dataclass
9
+ class _BaseTask:
10
+ """A base task representation."""
11
+
12
+ id: str
13
+ createdAt: datetime
14
+
15
+
16
+ @dataclass
17
+ class Task(_BaseTask):
18
+ """A task representation."""
19
+
20
+ asyncioTask: asyncio.Task[None]
21
+
22
+
23
+ @dataclass
24
+ class FinishedTask(_BaseTask):
25
+ """A finished task representation."""
26
+
27
+ finishedAt: datetime
28
+ error: ErrorOccurrence | None
29
+
30
+
31
+ @dataclass
32
+ class TaskSummary:
33
+ """Task info for use in summary lists."""
34
+
35
+ id: str
36
+ createdAt: datetime
37
+ finishedAt: datetime | None = None
38
+ error: ErrorOccurrence | None = None
@@ -1,6 +1,7 @@
1
1
  """Protocol Engine types to deal with tips."""
2
2
 
3
3
  from dataclasses import dataclass
4
+ from enum import Enum
4
5
 
5
6
 
6
7
  @dataclass(frozen=True)
@@ -16,3 +17,11 @@ class TipGeometry:
16
17
  length: float
17
18
  diameter: float
18
19
  volume: float
20
+
21
+
22
+ class TipRackWellState(Enum):
23
+ """The state of a single tip in a tip rack's well."""
24
+
25
+ CLEAN = "clean"
26
+ USED = "used"
27
+ EMPTY = "empty"
@@ -16,11 +16,38 @@ from opentrons_shared_data.robot.types import RobotType
16
16
  from .python_protocol_wrappers import SimulatingContextCreator
17
17
  from .run_orchestrator import RunOrchestrator
18
18
  from .protocol_runner import create_protocol_runner, LiveRunner
19
+ from ..protocol_engine.types import (
20
+ PostRunHardwareState,
21
+ )
22
+
23
+
24
+ class SimulatingRunOrchestrator(RunOrchestrator):
25
+ """A RunOrchestrator that cleans up its simulating hardware controller.
26
+
27
+ This should only be created and returned by create_simulating_orchestrator, unless
28
+ you are sure it's appropriate to clean up the hardware controller.
29
+ """
30
+
31
+ async def finish(
32
+ self,
33
+ error: Exception | None = None,
34
+ drop_tips_after_run: bool = True,
35
+ set_run_status: bool = True,
36
+ post_run_hardware_state: PostRunHardwareState = PostRunHardwareState.HOME_AND_STAY_ENGAGED,
37
+ ) -> None:
38
+ """Finish the run and clean up the simulating hardware controller."""
39
+ await super().finish(
40
+ error=error,
41
+ drop_tips_after_run=drop_tips_after_run,
42
+ set_run_status=set_run_status,
43
+ post_run_hardware_state=post_run_hardware_state,
44
+ )
45
+ await self._hardware_api.clean_up()
19
46
 
20
47
 
21
48
  async def create_simulating_orchestrator(
22
49
  robot_type: RobotType, protocol_config: ProtocolConfig
23
- ) -> RunOrchestrator:
50
+ ) -> SimulatingRunOrchestrator:
24
51
  """Create a RunOrchestrator wired to a simulating HardwareControlAPI.
25
52
 
26
53
  Example:
@@ -96,7 +123,7 @@ async def create_simulating_orchestrator(
96
123
  hardware_api=simulating_hardware_api,
97
124
  )
98
125
 
99
- return RunOrchestrator(
126
+ return SimulatingRunOrchestrator(
100
127
  hardware_api=simulating_hardware_api,
101
128
  json_or_python_protocol_runner=runner,
102
129
  protocol_engine=protocol_engine,
@@ -15,7 +15,10 @@ from opentrons_shared_data.robot.types import RobotType
15
15
 
16
16
  from . import protocol_runner, RunResult, JsonRunner, PythonAndLegacyRunner
17
17
  from ..hardware_control import HardwareControlAPI
18
- from ..hardware_control.modules import AbstractModule as HardwareModuleAPI
18
+ from ..hardware_control.modules import (
19
+ AbstractModule as HardwareModuleAPI,
20
+ ModuleModel as HardwareModuleModel,
21
+ )
19
22
  from ..protocol_engine import (
20
23
  ProtocolEngine,
21
24
  CommandCreate,
@@ -85,7 +88,6 @@ class RunOrchestrator:
85
88
  def __init__(
86
89
  self,
87
90
  protocol_engine: ProtocolEngine,
88
- # todo(mm, 2024-07-05): This hardware_api param looks unused?
89
91
  hardware_api: HardwareControlAPI,
90
92
  fixit_runner: protocol_runner.LiveRunner,
91
93
  setup_runner: protocol_runner.LiveRunner,
@@ -115,6 +117,8 @@ class RunOrchestrator:
115
117
  self._fixit_runner.prepare()
116
118
  self._setup_runner.prepare()
117
119
  self._protocol_engine.set_and_start_queue_worker(self.command_generator)
120
+ # used by SimulatingRunOrchestrator to clean up the simulating hardware controller
121
+ self._hardware_api = hardware_api
118
122
 
119
123
  @property
120
124
  def run_id(self) -> str:
@@ -380,6 +384,18 @@ class RunOrchestrator:
380
384
  """Handle an E-stop event from the hardware API."""
381
385
  return self._protocol_engine.estop()
382
386
 
387
+ async def asynchronous_module_error(
388
+ self, module_model: HardwareModuleModel, module_serial: str | None
389
+ ) -> bool:
390
+ """Handle an asynchronous module error reported by hardware.
391
+
392
+ If this function returns true, the caller should call finish() immediately; if it returns
393
+ False, the caller should not call finish() until it otherwise would.
394
+ """
395
+ return await self._protocol_engine.async_module_error(
396
+ module_model=ModuleModel.from_hardware(module_model), serial=module_serial
397
+ )
398
+
383
399
  async def use_attached_modules(
384
400
  self, modules_by_id: Dict[str, HardwareModuleAPI]
385
401
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  from .types import APIVersion
2
2
 
3
- MAX_SUPPORTED_VERSION = APIVersion(2, 26)
3
+ MAX_SUPPORTED_VERSION = APIVersion(2, 27)
4
4
  """The maximum supported protocol API version in this release."""
5
5
 
6
6
  MIN_SUPPORTED_VERSION = APIVersion(2, 0)
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import NamedTuple, TypedDict
2
+ from typing import NamedTuple, TypedDict, Optional
3
3
 
4
4
 
5
5
  class APIVersion(NamedTuple):
@@ -30,3 +30,4 @@ class ThermocyclerStep(ThermocyclerStepBase, total=False):
30
30
 
31
31
  hold_time_seconds: float
32
32
  hold_time_minutes: float
33
+ ramp_rate: Optional[float]
opentrons/simulate.py CHANGED
@@ -3,6 +3,7 @@
3
3
  This module has functions that provide a console entrypoint for simulating
4
4
  a protocol from the command line.
5
5
  """
6
+
6
7
  import argparse
7
8
  import asyncio
8
9
  import atexit
@@ -24,6 +25,7 @@ from typing import (
24
25
  BinaryIO,
25
26
  Optional,
26
27
  Union,
28
+ Iterator,
27
29
  )
28
30
  from typing_extensions import Literal
29
31
 
@@ -81,6 +83,7 @@ if TYPE_CHECKING:
81
83
  from opentrons_shared_data.labware.types import (
82
84
  LabwareDefinition as LabwareDefinitionDict,
83
85
  )
86
+ from opentrons.protocol_engine import ProtocolEngine
84
87
 
85
88
 
86
89
  # See Jira RCORE-535.
@@ -309,6 +312,7 @@ def get_protocol_api(
309
312
  bundled_labware=bundled_labware,
310
313
  bundled_data=bundled_data,
311
314
  extra_labware=extra_labware,
315
+ clean_up_hardware=(hardware_simulator is None),
312
316
  )
313
317
  else:
314
318
  if bundled_labware is not None:
@@ -326,6 +330,7 @@ def get_protocol_api(
326
330
  bundled_data=bundled_data,
327
331
  extra_labware=extra_labware,
328
332
  use_pe_virtual_hardware=use_virtual_hardware,
333
+ clean_up_hardware=(hardware_simulator is None),
329
334
  )
330
335
 
331
336
  # Intentional difference from execute.get_protocol_api():
@@ -781,13 +786,15 @@ def _create_live_context_non_pe(
781
786
  extra_labware: Optional[Dict[str, "LabwareDefinitionDict"]],
782
787
  bundled_labware: Optional[Dict[str, "LabwareDefinitionDict"]],
783
788
  bundled_data: Optional[Dict[str, bytes]],
789
+ clean_up_hardware: bool,
784
790
  ) -> ProtocolContext:
785
791
  """Return a live ProtocolContext.
786
792
 
787
793
  This controls the robot through the older infrastructure, instead of through Protocol Engine.
788
794
  """
789
795
  assert api_version < ENGINE_CORE_API_VERSION
790
- return protocol_api.create_protocol_context(
796
+
797
+ ctx = protocol_api.create_protocol_context(
791
798
  api_version=api_version,
792
799
  deck_type=deck_type,
793
800
  hardware_api=hardware_api,
@@ -795,6 +802,17 @@ def _create_live_context_non_pe(
795
802
  bundled_data=bundled_data,
796
803
  extra_labware=extra_labware,
797
804
  )
805
+ # Hack: we need to hook the protocol context cleanup in a way that isn't safe to put
806
+ # in the context generally, so we can do it like this and feel sad
807
+ original_cleanup = ctx.cleanup
808
+
809
+ def _cleanup_hook() -> None:
810
+ if clean_up_hardware:
811
+ ctx._hw_manager.hardware.clean_up()
812
+ original_cleanup()
813
+
814
+ ctx.cleanup = _cleanup_hook # type: ignore[method-assign]
815
+ return ctx
798
816
 
799
817
 
800
818
  def _create_live_context_pe(
@@ -805,26 +823,40 @@ def _create_live_context_pe(
805
823
  extra_labware: Dict[str, "LabwareDefinitionDict"],
806
824
  bundled_data: Optional[Dict[str, bytes]],
807
825
  use_pe_virtual_hardware: bool = True,
826
+ clean_up_hardware: bool = True,
808
827
  ) -> ProtocolContext:
809
828
  """Return a live ProtocolContext that controls the robot through ProtocolEngine."""
810
829
  assert api_version >= ENGINE_CORE_API_VERSION
811
830
  hardware_api_wrapped = hardware_api.wrapped()
812
831
  global _LIVE_PROTOCOL_ENGINE_CONTEXTS
832
+
833
+ @contextmanager
834
+ def _cleanup_hardware_with_engine() -> (
835
+ Iterator[tuple["ProtocolEngine", asyncio.AbstractEventLoop]]
836
+ ):
837
+
838
+ try:
839
+ with create_protocol_engine_in_thread(
840
+ hardware_api=hardware_api_wrapped,
841
+ config=_get_protocol_engine_config(
842
+ robot_type, use_pe_virtual_hardware=use_pe_virtual_hardware
843
+ ),
844
+ deck_configuration=None,
845
+ file_provider=None,
846
+ error_recovery_policy=error_recovery_policy.never_recover,
847
+ drop_tips_after_run=False,
848
+ post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
849
+ load_fixed_trash=should_load_fixed_trash_labware_for_python_protocol(
850
+ api_version
851
+ ),
852
+ ) as (engine, loop):
853
+ yield engine, loop
854
+ finally:
855
+ if clean_up_hardware:
856
+ hardware_api.clean_up()
857
+
813
858
  pe, loop = _LIVE_PROTOCOL_ENGINE_CONTEXTS.enter_context(
814
- create_protocol_engine_in_thread(
815
- hardware_api=hardware_api_wrapped,
816
- config=_get_protocol_engine_config(
817
- robot_type, use_pe_virtual_hardware=use_pe_virtual_hardware
818
- ),
819
- deck_configuration=None,
820
- file_provider=None,
821
- error_recovery_policy=error_recovery_policy.never_recover,
822
- drop_tips_after_run=False,
823
- post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
824
- load_fixed_trash=should_load_fixed_trash_labware_for_python_protocol(
825
- api_version
826
- ),
827
- )
859
+ _cleanup_hardware_with_engine()
828
860
  )
829
861
 
830
862
  # `async def` so we can use loop.run_coroutine_threadsafe() to wait for its completion.
@@ -879,6 +911,7 @@ def _run_file_non_pe(
879
911
  extra_labware=extra_labware,
880
912
  bundled_labware=bundled_labware,
881
913
  bundled_data=bundled_data,
914
+ clean_up_hardware=False,
882
915
  )
883
916
 
884
917
  scraper = _CommandScraper(logger=logger, level=level, broker=context.broker)
@@ -30,7 +30,7 @@ async def take_picture(filename: Path) -> None:
30
30
  pass
31
31
 
32
32
  if ARCHITECTURE == SystemArchitecture.YOCTO:
33
- cmd = f"v4l2-ctl --device /dev/video2 --set-fmt-video=width=1280,height=720,pixelformat=MJPG --stream-mmap --stream-to={str(filename)} --stream-count=1"
33
+ cmd = f"v4l2-ctl --device /dev/ot_system_camera --set-fmt-video=width=1280,height=720,pixelformat=MJPG --stream-mmap --stream-to={str(filename)} --stream-count=1"
34
34
  elif ARCHITECTURE == SystemArchitecture.BUILDROOT:
35
35
  cmd = f"ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -ss 0:0:1 -frames 1 {str(filename)}"
36
36
  else: # HOST
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opentrons
3
- Version: 8.7.0a6
3
+ Version: 8.7.0a7
4
4
  Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
5
5
  Project-URL: opentrons.com, https://www.opentrons.com
6
6
  Project-URL: Source Code On Github, https://github.com/Opentrons/opentrons/tree/edge/api
@@ -24,7 +24,7 @@ Requires-Dist: click<9,>=8.0.0
24
24
  Requires-Dist: importlib-metadata>=1.0; python_version < '3.8'
25
25
  Requires-Dist: jsonschema<4.18.0,>=3.0.1
26
26
  Requires-Dist: numpy<2,>=1.20.0
27
- Requires-Dist: opentrons-shared-data==8.7.0a6
27
+ Requires-Dist: opentrons-shared-data==8.7.0a7
28
28
  Requires-Dist: packaging>=21.0
29
29
  Requires-Dist: pydantic-settings<3,>=2
30
30
  Requires-Dist: pydantic<3,>=2.0.0
@@ -32,6 +32,6 @@ Requires-Dist: pyserial>=3.5
32
32
  Requires-Dist: pyusb==1.2.1
33
33
  Requires-Dist: typing-extensions<5,>=4.0.0
34
34
  Provides-Extra: flex-hardware
35
- Requires-Dist: opentrons-hardware[flex]==8.7.0a6; extra == 'flex-hardware'
35
+ Requires-Dist: opentrons-hardware[flex]==8.7.0a7; extra == 'flex-hardware'
36
36
  Provides-Extra: ot2-hardware
37
- Requires-Dist: opentrons-hardware==8.7.0a6; extra == 'ot2-hardware'
37
+ Requires-Dist: opentrons-hardware==8.7.0a7; extra == 'ot2-hardware'