opentrons 8.7.0a1__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.
Files changed (119) 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/labware.py +8 -1
  34. opentrons/protocol_api/core/engine/module_core.py +4 -0
  35. opentrons/protocol_api/core/engine/protocol.py +18 -1
  36. opentrons/protocol_api/core/engine/tasks.py +35 -0
  37. opentrons/protocol_api/core/legacy/legacy_module_core.py +2 -0
  38. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
  39. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  40. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  41. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  42. opentrons/protocol_api/core/module.py +1 -0
  43. opentrons/protocol_api/core/protocol.py +11 -2
  44. opentrons/protocol_api/core/tasks.py +31 -0
  45. opentrons/protocol_api/module_contexts.py +1 -0
  46. opentrons/protocol_api/protocol_context.py +26 -4
  47. opentrons/protocol_api/robot_context.py +38 -21
  48. opentrons/protocol_api/tasks.py +48 -0
  49. opentrons/protocol_api/validation.py +6 -1
  50. opentrons/protocol_engine/actions/__init__.py +4 -2
  51. opentrons/protocol_engine/actions/actions.py +22 -9
  52. opentrons/protocol_engine/clients/sync_client.py +6 -7
  53. opentrons/protocol_engine/commands/__init__.py +42 -0
  54. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  55. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  56. opentrons/protocol_engine/commands/aspirate.py +1 -0
  57. opentrons/protocol_engine/commands/command.py +1 -0
  58. opentrons/protocol_engine/commands/command_unions.py +39 -0
  59. opentrons/protocol_engine/commands/create_timer.py +83 -0
  60. opentrons/protocol_engine/commands/dispense.py +1 -0
  61. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  62. opentrons/protocol_engine/commands/movement_common.py +2 -0
  63. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  64. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  65. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  66. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  67. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +17 -1
  68. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  69. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  70. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  71. opentrons/protocol_engine/errors/__init__.py +4 -0
  72. opentrons/protocol_engine/errors/exceptions.py +55 -0
  73. opentrons/protocol_engine/execution/__init__.py +2 -0
  74. opentrons/protocol_engine/execution/command_executor.py +8 -0
  75. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  76. opentrons/protocol_engine/execution/labware_movement.py +9 -12
  77. opentrons/protocol_engine/execution/movement.py +2 -0
  78. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  79. opentrons/protocol_engine/execution/run_control.py +8 -0
  80. opentrons/protocol_engine/execution/task_handler.py +157 -0
  81. opentrons/protocol_engine/protocol_engine.py +67 -33
  82. opentrons/protocol_engine/resources/__init__.py +2 -0
  83. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  84. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  85. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  86. opentrons/protocol_engine/state/_well_math.py +60 -18
  87. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  88. opentrons/protocol_engine/state/commands.py +7 -7
  89. opentrons/protocol_engine/state/geometry.py +204 -374
  90. opentrons/protocol_engine/state/labware.py +52 -102
  91. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  92. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
  93. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  94. opentrons/protocol_engine/state/modules.py +21 -8
  95. opentrons/protocol_engine/state/motion.py +44 -0
  96. opentrons/protocol_engine/state/state.py +14 -0
  97. opentrons/protocol_engine/state/state_summary.py +2 -0
  98. opentrons/protocol_engine/state/tasks.py +139 -0
  99. opentrons/protocol_engine/state/tips.py +177 -258
  100. opentrons/protocol_engine/state/update_types.py +16 -9
  101. opentrons/protocol_engine/types/__init__.py +9 -3
  102. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  103. opentrons/protocol_engine/types/instrument.py +8 -1
  104. opentrons/protocol_engine/types/labware.py +1 -13
  105. opentrons/protocol_engine/types/module.py +10 -0
  106. opentrons/protocol_engine/types/tasks.py +38 -0
  107. opentrons/protocol_engine/types/tip.py +9 -0
  108. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  109. opentrons/protocol_runner/run_orchestrator.py +18 -2
  110. opentrons/protocols/api_support/definitions.py +1 -1
  111. opentrons/protocols/api_support/types.py +2 -1
  112. opentrons/simulate.py +48 -15
  113. opentrons/system/camera.py +1 -1
  114. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/METADATA +4 -4
  115. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/RECORD +118 -105
  116. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  117. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/WHEEL +0 -0
  118. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/entry_points.txt +0 -0
  119. {opentrons-8.7.0a1.dist-info → opentrons-8.7.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -11,6 +11,7 @@ from .labware_data_provider import LabwareDataProvider
11
11
  from .module_data_provider import ModuleDataProvider
12
12
  from .file_provider import FileProvider
13
13
  from .ot3_validation import ensure_ot3_hardware
14
+ from .concurrency_provider import ConcurrencyProvider
14
15
 
15
16
 
16
17
  __all__ = [
@@ -18,6 +19,7 @@ __all__ = [
18
19
  "LabwareDataProvider",
19
20
  "DeckDataProvider",
20
21
  "DeckFixedLabware",
22
+ "ConcurrencyProvider",
21
23
  "ModuleDataProvider",
22
24
  "FileProvider",
23
25
  "ensure_ot3_hardware",
@@ -0,0 +1,27 @@
1
+ """Concurrency primitives providers."""
2
+ import asyncio
3
+
4
+
5
+ class ConcurrencyProvider:
6
+ """Concurrency primitives for engine tasks."""
7
+
8
+ def __init__(self) -> None:
9
+ """Build a concurrency provider."""
10
+ self._locks: dict[str, asyncio.Lock] = {}
11
+ self._queues: dict[str, "asyncio.Queue[asyncio.Task[None]]"] = {}
12
+
13
+ def lock_for_group(self, group_id: str) -> asyncio.Lock:
14
+ """Returns the lock for specified group id."""
15
+ try:
16
+ return self._locks[group_id]
17
+ except KeyError:
18
+ self._locks[group_id] = asyncio.Lock()
19
+ return self._locks[group_id]
20
+
21
+ def queue_for_group(self, group_id: str) -> "asyncio.Queue[asyncio.Task[None]]":
22
+ """Returns the queue for specified group id."""
23
+ try:
24
+ return self._queues[group_id]
25
+ except KeyError:
26
+ self._queues[group_id] = asyncio.Queue()
27
+ return self._queues[group_id]
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import List, Set, Tuple
4
4
 
5
+ from opentrons_shared_data.module.types import ModuleOrientation
5
6
  from opentrons_shared_data.deck.types import (
6
7
  DeckDefinitionV5,
7
8
  CutoutFixture,
@@ -124,6 +125,11 @@ def get_addressable_area_from_name(
124
125
  z=addressable_area["boundingBox"]["zDimension"],
125
126
  )
126
127
  features = addressable_area["features"]
128
+ orientation = (
129
+ addressable_area["orientation"]
130
+ if addressable_area["orientation"]
131
+ else ModuleOrientation.NOT_APPLICABLE
132
+ )
127
133
  mating_surface_unit_vector = addressable_area.get("matingSurfaceUnitVector")
128
134
 
129
135
  return AddressableArea(
@@ -138,6 +144,7 @@ def get_addressable_area_from_name(
138
144
  "compatibleModuleTypes", []
139
145
  ),
140
146
  features=features,
147
+ orientation=orientation,
141
148
  )
142
149
  raise AddressableAreaDoesNotExistError(
143
150
  f"Could not find addressable area with name {addressable_area_name}"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from opentrons_shared_data.labware.labware_definition import (
4
4
  LabwareDefinition,
5
+ LabwareDefinition2,
5
6
  LabwareRole,
6
7
  )
7
8
 
@@ -44,15 +45,18 @@ def validate_definition_is_system(definition: LabwareDefinition) -> bool:
44
45
  return LabwareRole.system in definition.allowedRoles
45
46
 
46
47
 
47
- def validate_labware_can_be_stacked(
48
- top_labware_definition: LabwareDefinition, below_labware_load_name: str
48
+ def validate_legacy_labware_can_be_stacked(
49
+ child_labware_definition: LabwareDefinition2, parent_labware_load_name: str
49
50
  ) -> bool:
50
- """Validate that the labware being loaded onto is in the above labware's stackingOffsetWithLabware definition."""
51
+ """Validate that the parent labware is in the child labware's stackingOffsetWithLabware definition.
52
+
53
+ Schema 3 Labware stacking validation is handled in locating features.
54
+ """
51
55
  return (
52
- below_labware_load_name in top_labware_definition.stackingOffsetWithLabware
56
+ parent_labware_load_name in child_labware_definition.stackingOffsetWithLabware
53
57
  or (
54
- "default" in top_labware_definition.stackingOffsetWithLabware
55
- and top_labware_definition.compatibleParentLabware is None
58
+ "default" in child_labware_definition.stackingOffsetWithLabware
59
+ and child_labware_definition.compatibleParentLabware is None
56
60
  )
57
61
  )
58
62
 
@@ -17,13 +17,47 @@ def wells_covered_by_pipette_configuration(
17
17
  """Compute the wells covered by a pipette nozzle configuration."""
18
18
  if len(labware_wells_by_column) >= 12 and len(labware_wells_by_column[0]) >= 8:
19
19
  yield from wells_covered_dense(
20
- nozzle_map,
20
+ nozzle_map.columns,
21
+ nozzle_map.rows,
22
+ nozzle_map.starting_nozzle,
21
23
  target_well,
22
24
  labware_wells_by_column,
23
25
  )
24
26
  elif len(labware_wells_by_column) < 12 and len(labware_wells_by_column[0]) < 8:
25
27
  yield from wells_covered_sparse(
26
- nozzle_map, target_well, labware_wells_by_column
28
+ nozzle_map.columns,
29
+ nozzle_map.rows,
30
+ nozzle_map.starting_nozzle,
31
+ target_well,
32
+ labware_wells_by_column,
33
+ )
34
+ else:
35
+ raise InvalidStoredData(
36
+ "Labware of non-SBS and non-reservoir format cannot be handled"
37
+ )
38
+
39
+
40
+ def wells_covered_by_physical_pipette(
41
+ nozzle_map: NozzleMap,
42
+ target_well: str,
43
+ labware_wells_by_column: list[list[str]],
44
+ ) -> Iterator[str]:
45
+ """Compute the wells covered by a pipette nozzle configuration."""
46
+ if len(labware_wells_by_column) >= 12 and len(labware_wells_by_column[0]) >= 8:
47
+ yield from wells_covered_dense(
48
+ nozzle_map.full_instrument_columns,
49
+ nozzle_map.full_instrument_rows,
50
+ nozzle_map.starting_nozzle,
51
+ target_well,
52
+ labware_wells_by_column,
53
+ )
54
+ elif len(labware_wells_by_column) < 12 and len(labware_wells_by_column[0]) < 8:
55
+ yield from wells_covered_sparse(
56
+ nozzle_map.full_instrument_columns,
57
+ nozzle_map.full_instrument_rows,
58
+ nozzle_map.starting_nozzle,
59
+ target_well,
60
+ labware_wells_by_column,
27
61
  )
28
62
  else:
29
63
  raise InvalidStoredData(
@@ -42,7 +76,11 @@ def row_col_ordinals_from_column_major_map(
42
76
 
43
77
 
44
78
  def wells_covered_dense( # noqa: C901
45
- nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
79
+ columns: dict[str, list[str]],
80
+ rows: dict[str, list[str]],
81
+ starting_nozzle: str,
82
+ target_well: str,
83
+ target_wells_by_column: list[list[str]],
46
84
  ) -> Iterator[str]:
47
85
  """Get the list of wells covered by a nozzle map on an SBS format labware with a specified multiplier of 96 into the number of wells.
48
86
 
@@ -66,11 +104,11 @@ def wells_covered_dense( # noqa: C901
66
104
  "This labware cannot be used with wells_covered_dense() because it is less dense than an SBS 96 standard"
67
105
  )
68
106
 
69
- for nozzle_column in range(len(nozzle_map.columns)):
107
+ for nozzle_column in range(len(columns)):
70
108
  target_column_offset = nozzle_column * column_downsample
71
- for nozzle_row in range(len(nozzle_map.rows)):
109
+ for nozzle_row in range(len(rows)):
72
110
  target_row_offset = nozzle_row * row_downsample
73
- if nozzle_map.starting_nozzle == "A1":
111
+ if starting_nozzle == "A1":
74
112
  if (
75
113
  target_column_index + target_column_offset
76
114
  < len(target_wells_by_column)
@@ -81,7 +119,7 @@ def wells_covered_dense( # noqa: C901
81
119
  yield target_wells_by_column[
82
120
  target_column_index + target_column_offset
83
121
  ][target_row_index + target_row_offset]
84
- elif nozzle_map.starting_nozzle == "A12":
122
+ elif starting_nozzle == "A12":
85
123
  if (target_column_index - target_column_offset >= 0) and (
86
124
  target_row_index + target_row_offset
87
125
  < len(target_wells_by_column[target_column_index])
@@ -89,7 +127,7 @@ def wells_covered_dense( # noqa: C901
89
127
  yield target_wells_by_column[
90
128
  target_column_index - target_column_offset
91
129
  ][target_row_index + target_row_offset]
92
- elif nozzle_map.starting_nozzle == "H1":
130
+ elif starting_nozzle == "H1":
93
131
  if (
94
132
  target_column_index + target_column_offset
95
133
  < len(target_wells_by_column)
@@ -97,7 +135,7 @@ def wells_covered_dense( # noqa: C901
97
135
  yield target_wells_by_column[
98
136
  target_column_index + target_column_offset
99
137
  ][target_row_index - target_row_offset]
100
- elif nozzle_map.starting_nozzle == "H12":
138
+ elif starting_nozzle == "H12":
101
139
  if (target_column_index - target_column_offset >= 0) and (
102
140
  target_row_index - target_row_offset >= 0
103
141
  ):
@@ -106,12 +144,16 @@ def wells_covered_dense( # noqa: C901
106
144
  ][target_row_index - target_row_offset]
107
145
  else:
108
146
  raise InvalidProtocolData(
109
- f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}"
147
+ f"A pipette nozzle configuration may not having a starting nozzle of {starting_nozzle}"
110
148
  )
111
149
 
112
150
 
113
151
  def wells_covered_sparse( # noqa: C901
114
- nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
152
+ columns: dict[str, list[str]],
153
+ rows: dict[str, list[str]],
154
+ starting_nozzle: str,
155
+ target_well: str,
156
+ target_wells_by_column: list[list[str]],
115
157
  ) -> Iterator[str]:
116
158
  """Get the list of wells covered by a nozzle map on a column-oriented reservoir.
117
159
 
@@ -128,9 +170,9 @@ def wells_covered_sparse( # noqa: C901
128
170
  raise InvalidStoredData(
129
171
  "This labware cannot be used with wells_covered_sparse() because it is more dense than an SBS 96 standard."
130
172
  )
131
- for nozzle_column in range(max(1, len(nozzle_map.columns) // column_upsample)):
132
- for nozzle_row in range(max(1, len(nozzle_map.rows) // row_upsample)):
133
- if nozzle_map.starting_nozzle == "A1":
173
+ for nozzle_column in range(max(1, len(columns) // column_upsample)):
174
+ for nozzle_row in range(max(1, len(rows) // row_upsample)):
175
+ if starting_nozzle == "A1":
134
176
  if (
135
177
  target_column_index + nozzle_column < len(target_wells_by_column)
136
178
  ) and (
@@ -140,7 +182,7 @@ def wells_covered_sparse( # noqa: C901
140
182
  yield target_wells_by_column[target_column_index + nozzle_column][
141
183
  target_row_index + nozzle_row
142
184
  ]
143
- elif nozzle_map.starting_nozzle == "A12":
185
+ elif starting_nozzle == "A12":
144
186
  if (target_column_index - nozzle_column >= 0) and (
145
187
  target_row_index + nozzle_row
146
188
  < len(target_wells_by_column[target_column_index])
@@ -148,7 +190,7 @@ def wells_covered_sparse( # noqa: C901
148
190
  yield target_wells_by_column[target_column_index - nozzle_column][
149
191
  target_row_index + nozzle_row
150
192
  ]
151
- elif nozzle_map.starting_nozzle == "H1":
193
+ elif starting_nozzle == "H1":
152
194
  if (
153
195
  target_column_index + nozzle_column
154
196
  < len(target_wells_by_column[target_column_index])
@@ -156,7 +198,7 @@ def wells_covered_sparse( # noqa: C901
156
198
  yield target_wells_by_column[target_column_index + nozzle_column][
157
199
  target_row_index - nozzle_row
158
200
  ]
159
- elif nozzle_map.starting_nozzle == "H12":
201
+ elif starting_nozzle == "H12":
160
202
  if (target_column_index - nozzle_column >= 0) and (
161
203
  target_row_index - nozzle_row >= 0
162
204
  ):
@@ -165,7 +207,7 @@ def wells_covered_sparse( # noqa: C901
165
207
  ]
166
208
  else:
167
209
  raise InvalidProtocolData(
168
- f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}"
210
+ f"A pipette nozzle configuration may not having a starting nozzle of {starting_nozzle}"
169
211
  )
170
212
 
171
213
 
@@ -5,6 +5,7 @@ from functools import cached_property
5
5
  from typing import Dict, List, Optional, Set
6
6
 
7
7
  from opentrons_shared_data.robot.types import RobotType, RobotDefinition
8
+ from opentrons_shared_data.module.types import ModuleOrientation
8
9
  from opentrons_shared_data.deck.types import (
9
10
  DeckDefinitionV5,
10
11
  SlotDefV3,
@@ -614,6 +615,7 @@ class AddressableAreaView:
614
615
  "displayName": addressable_area.display_name,
615
616
  "compatibleModuleTypes": addressable_area.compatible_module_types,
616
617
  "features": addressable_area.features,
618
+ "orientation": ModuleOrientation.NOT_APPLICABLE,
617
619
  }
618
620
 
619
621
  def get_deck_slot_definitions(self) -> Dict[str, SlotDefV3]:
@@ -238,7 +238,7 @@ class CommandState:
238
238
  has_entered_error_recovery: bool
239
239
  """Whether the run has entered error recovery."""
240
240
 
241
- stopped_by_estop: bool
241
+ stopped_by_async_error: bool
242
242
  """If this is set to True, the engine was stopped by an estop event."""
243
243
 
244
244
  error_recovery_policy: ErrorRecoveryPolicy
@@ -272,7 +272,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
272
272
  run_completed_at=None,
273
273
  run_started_at=None,
274
274
  latest_protocol_command_hash=None,
275
- stopped_by_estop=False,
275
+ stopped_by_async_error=False,
276
276
  error_recovery_policy=error_recovery_policy,
277
277
  has_entered_error_recovery=False,
278
278
  )
@@ -472,8 +472,8 @@ class CommandStore(HasState[CommandState], HandlesActions):
472
472
  self._state.recovery_target = None
473
473
  self._state.queue_status = QueueStatus.PAUSED
474
474
 
475
- if action.from_estop:
476
- self._state.stopped_by_estop = True
475
+ if action.from_asynchronous_error:
476
+ self._state.stopped_by_async_error = True
477
477
  self._state.run_result = RunResult.FAILED
478
478
  else:
479
479
  self._state.run_result = RunResult.STOPPED
@@ -501,7 +501,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
501
501
  else:
502
502
  # HACK(sf): There needs to be a better way to set
503
503
  # an estop error than this else clause
504
- if self._state.stopped_by_estop and action.error_details:
504
+ if self._state.stopped_by_async_error and action.error_details:
505
505
  self._state.run_error = self._map_run_exception_to_error_occurrence(
506
506
  action.error_details.error_id,
507
507
  action.error_details.created_at,
@@ -952,9 +952,9 @@ class CommandView:
952
952
  """Get whether an engine stop has completed."""
953
953
  return self._state.run_completed_at is not None
954
954
 
955
- def get_is_stopped_by_estop(self) -> bool:
955
+ def get_is_stopped_by_async_error(self) -> bool:
956
956
  """Return whether the engine was stopped specifically by an E-stop."""
957
- return self._state.stopped_by_estop
957
+ return self._state.stopped_by_async_error
958
958
 
959
959
  def has_been_played(self) -> bool:
960
960
  """Get whether engine has started."""