petal-leafsdk 0.2.4__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.
- petal_leafsdk/__init__.py +11 -0
- petal_leafsdk/data_model.py +290 -0
- petal_leafsdk/mavlink_helpers.py +71 -0
- petal_leafsdk/mission_plan_executor.py +434 -0
- petal_leafsdk/mission_step_executor.py +583 -0
- petal_leafsdk/plugin.py +409 -0
- petal_leafsdk/redis_helpers.py +33 -0
- petal_leafsdk-0.2.4.dist-info/METADATA +12 -0
- petal_leafsdk-0.2.4.dist-info/RECORD +11 -0
- petal_leafsdk-0.2.4.dist-info/WHEEL +4 -0
- petal_leafsdk-0.2.4.dist-info/entry_points.txt +7 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# petal_leafsdk/mission_plan_executor.py
|
|
2
|
+
import json
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional, Literal
|
|
5
|
+
import traceback
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
import networkx as nx
|
|
8
|
+
from enum import Enum, auto
|
|
9
|
+
|
|
10
|
+
from leafsdk.core.mission.mission_plan import MissionPlan
|
|
11
|
+
from leafsdk.utils.logstyle import LogIcons
|
|
12
|
+
from leafsdk import logger
|
|
13
|
+
|
|
14
|
+
from petal_leafsdk.mission_step_executor import get_mission_step_executor
|
|
15
|
+
from petal_leafsdk.mission_step_executor import get_mission_step_executor
|
|
16
|
+
|
|
17
|
+
from petal_app_manager.proxies.external import MavLinkExternalProxy
|
|
18
|
+
from petal_app_manager.proxies.redis import RedisProxy
|
|
19
|
+
from pymavlink.dialects.v20 import droneleaf_mav_msgs as leafMAV
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MissionPlanExecutor:
|
|
24
|
+
def __init__(self, plan: MissionPlan, mav_proxy: MavLinkExternalProxy, redis_proxy: RedisProxy):
|
|
25
|
+
self.plan = plan
|
|
26
|
+
self.execution_graph = self.get_execution_graph(plan)
|
|
27
|
+
self.mav_proxy = mav_proxy
|
|
28
|
+
self.redis_proxy = redis_proxy
|
|
29
|
+
|
|
30
|
+
self._mission_status = MissionStatus()
|
|
31
|
+
self._current_step = None
|
|
32
|
+
self._current_node = None
|
|
33
|
+
self._mission_control_cmd: MissionControlCommand = MissionControlCommand.NONE
|
|
34
|
+
|
|
35
|
+
def get_execution_graph(self, plan: MissionPlan) -> nx.MultiDiGraph:
|
|
36
|
+
"""Generate the execution graph from the mission plan."""
|
|
37
|
+
graph = plan.mission_graph.copy()
|
|
38
|
+
# Traverse and replace steps with their executors
|
|
39
|
+
for name, data in graph.nodes(data=True):
|
|
40
|
+
step = data['step']
|
|
41
|
+
executor = get_mission_step_executor(step)
|
|
42
|
+
graph.nodes[name]['step'] = executor
|
|
43
|
+
return graph
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_plan(self, data: dict | str):
|
|
47
|
+
"""Load a new mission plan."""
|
|
48
|
+
self.plan.load(data)
|
|
49
|
+
self.execution_graph = self.get_execution_graph(self.plan)
|
|
50
|
+
logger.info(f"{LogIcons.SUCCESS} Mission plan loaded successfully.")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_step(self):
|
|
54
|
+
"""Execute the current mission step.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
dict: Current mission status after executing the step.
|
|
58
|
+
Keys include:
|
|
59
|
+
- step_id
|
|
60
|
+
- step_description
|
|
61
|
+
- next_step_id
|
|
62
|
+
- next_step_description
|
|
63
|
+
- step_completed
|
|
64
|
+
- state
|
|
65
|
+
"""
|
|
66
|
+
if self._mission_status.state == MissionState.CANCELLED:
|
|
67
|
+
return self._mission_status.as_dict()
|
|
68
|
+
elif self._mission_control_cmd == MissionControlCommand.CANCEL:
|
|
69
|
+
if self._mission_status.state in [MissionState.RUNNING, MissionState.PAUSED]:
|
|
70
|
+
self._canceled()
|
|
71
|
+
return self._mission_status.as_dict()
|
|
72
|
+
|
|
73
|
+
elif self._mission_control_cmd == MissionControlCommand.RESUME:
|
|
74
|
+
if self._mission_status.state == MissionState.PAUSED:
|
|
75
|
+
self._mission_status.state = MissionState.RUNNING
|
|
76
|
+
self._mission_control_cmd = MissionControlCommand.NONE
|
|
77
|
+
|
|
78
|
+
elif self._mission_status.state == MissionState.PAUSED:
|
|
79
|
+
return self._mission_status.as_dict()
|
|
80
|
+
elif self._mission_control_cmd == MissionControlCommand.PAUSE_NOW:
|
|
81
|
+
if self._mission_status.state == MissionState.RUNNING:
|
|
82
|
+
self._paused()
|
|
83
|
+
return self._mission_status.as_dict()
|
|
84
|
+
|
|
85
|
+
if self._current_step is None:
|
|
86
|
+
logger.warning(f"{LogIcons.WARNING} Cannot run step: no current step available")
|
|
87
|
+
self._mission_status.reset()
|
|
88
|
+
return self._mission_status.as_dict()
|
|
89
|
+
|
|
90
|
+
if self._current_step.first_exec():
|
|
91
|
+
logger.info(f"{LogIcons.RUN} Executing step: {self._current_node}")
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
result, completed = self._current_step.execute_step(mav_proxy=self.mav_proxy, redis_proxy=self.redis_proxy)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self._failed(e)
|
|
97
|
+
return self._mission_status.as_dict()
|
|
98
|
+
|
|
99
|
+
if completed:
|
|
100
|
+
logger.info(f"Step {self._current_step.description()} Completed")
|
|
101
|
+
prev_node = self._current_node
|
|
102
|
+
self._current_node = self._get_next_node(result)
|
|
103
|
+
|
|
104
|
+
if self._current_node is None:
|
|
105
|
+
self._completed(prev_node)
|
|
106
|
+
return self._mission_status.as_dict()
|
|
107
|
+
else:
|
|
108
|
+
if self._mission_control_cmd == MissionControlCommand.PAUSE_LATER:
|
|
109
|
+
self._paused()
|
|
110
|
+
logger.info(f"Mission paused after completetion of step: {self._current_node}")
|
|
111
|
+
return self._mission_status.as_dict()
|
|
112
|
+
|
|
113
|
+
# Update current step to the next node immediately
|
|
114
|
+
self._current_step = self.execution_graph.nodes[self._current_node]['step']
|
|
115
|
+
self._mission_status.step_transition(prev_node, self._current_node, self.execution_graph)
|
|
116
|
+
return self._mission_status.as_dict()
|
|
117
|
+
|
|
118
|
+
self._mission_status.running(self._current_node, self.execution_graph)
|
|
119
|
+
return self._mission_status.as_dict()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _get_next_node(self, result) -> Optional[str]:
|
|
123
|
+
"""Determine the next node based on current node and conditions."""
|
|
124
|
+
next_node = None
|
|
125
|
+
for successor in self.execution_graph.successors(self._current_node):
|
|
126
|
+
condition = self.execution_graph.edges[self._current_node, successor, 0].get("condition")
|
|
127
|
+
if condition is None or condition == result:
|
|
128
|
+
next_node = successor
|
|
129
|
+
break
|
|
130
|
+
return next_node
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def prepare(self) -> bool:
|
|
134
|
+
"""Prepare the mission plan for execution."""
|
|
135
|
+
try:
|
|
136
|
+
self.plan.validate()
|
|
137
|
+
self._current_node = self.plan._head_node
|
|
138
|
+
self._current_step = self.execution_graph.nodes[self._current_node]['step']
|
|
139
|
+
for name, _ in self.plan._get_steps():
|
|
140
|
+
step = self.execution_graph.nodes[name]['step']
|
|
141
|
+
step.setup(mav_proxy=self.mav_proxy, redis_proxy=self.redis_proxy)
|
|
142
|
+
self._mission_control_cmd = MissionState.RUNNING
|
|
143
|
+
|
|
144
|
+
# Send joystick enable/disable command
|
|
145
|
+
joystick_mode_map = {
|
|
146
|
+
"disabled": 0,
|
|
147
|
+
"enabled": 1,
|
|
148
|
+
"enabled_on_pause": 2
|
|
149
|
+
}
|
|
150
|
+
joystick_cmd = joystick_mode_map.get(self.plan.config.joystick_mode.value, 1) # Default to ENABLED if unknown
|
|
151
|
+
self.redis_proxy.publish(
|
|
152
|
+
channel="/FlightLogic/joystick_mode",
|
|
153
|
+
message=json.dumps({"payload": joystick_cmd})
|
|
154
|
+
)
|
|
155
|
+
logger.info(f"{LogIcons.SUCCESS} Joystick control set to {self.plan.config.joystick_mode.value.upper()}.")
|
|
156
|
+
logger.info(f"{LogIcons.SUCCESS} Mission plan has been prepared and ready for execution.")
|
|
157
|
+
return True
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"{LogIcons.ERROR} Mission plan preparation failed: {e}")
|
|
160
|
+
logger.error(traceback.format_exc())
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def pause(self, action: Optional[Literal["NONE"]] = "NONE"):
|
|
165
|
+
"""Pause the mission execution."""
|
|
166
|
+
logger.info(f"{LogIcons.RUN} Mission pause commanded.")
|
|
167
|
+
|
|
168
|
+
if self._mission_status.state != MissionState.RUNNING:
|
|
169
|
+
logger.warning(f"{LogIcons.WARNING} Mission cannot be paused, current state: {self._mission_status.state.name}.")
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
if self._current_step is None:
|
|
173
|
+
logger.warning(f"{LogIcons.WARNING} Cannot pause, no current step to pause.")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
if self.mav_proxy is None:
|
|
177
|
+
logger.warning(f"{LogIcons.WARNING} Cannot pause, MAVLink proxy is required.")
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
if self._current_step.is_pausable():
|
|
181
|
+
self._mission_control_cmd = MissionControlCommand.PAUSE_NOW
|
|
182
|
+
logger.info(f"{LogIcons.RUN} Mission will be paused Immediately")
|
|
183
|
+
else:
|
|
184
|
+
self._mission_control_cmd = MissionControlCommand.PAUSE_LATER
|
|
185
|
+
logger.info(f"{LogIcons.RUN} Mission will pause after the step is completed.")
|
|
186
|
+
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def resume(self):
|
|
191
|
+
"""Resume the mission execution."""
|
|
192
|
+
if self._mission_status.state != MissionState.PAUSED:
|
|
193
|
+
logger.warning(f"{LogIcons.WARNING} Mission cannot be resumed, current state: {self._mission_status.state.name}.")
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
if self._current_step is None:
|
|
197
|
+
logger.warning(f"{LogIcons.WARNING} Cannot resume, no current step to resume.")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
if self.mav_proxy is None:
|
|
201
|
+
logger.warning(f"{LogIcons.WARNING} Cannot resume, MAVLink proxy is required.")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
self._current_step.resume()
|
|
205
|
+
self._mission_control_cmd = MissionControlCommand.RESUME
|
|
206
|
+
|
|
207
|
+
msg = leafMAV.MAVLink_leaf_control_cmd_message(
|
|
208
|
+
target_system=self.mav_proxy.target_system,
|
|
209
|
+
cmd=1,
|
|
210
|
+
action=0,
|
|
211
|
+
mission_id=self.plan.id.encode('ascii') # Use unique mission ID for tracking
|
|
212
|
+
)
|
|
213
|
+
self.mav_proxy.send(key='mav', msg=msg, burst_count=4, burst_interval=0.1)
|
|
214
|
+
logger.info(f"{LogIcons.RUN} Mission resume commanded.")
|
|
215
|
+
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def cancel(self, action: Optional[Literal["NONE", "HOVER", "RETURN_TO_HOME", "LAND_IMMEDIATELY"]] = "HOVER"):
|
|
220
|
+
# Map action strings to command codes
|
|
221
|
+
action_map = {
|
|
222
|
+
"NONE": 0,
|
|
223
|
+
"HOVER": 1,
|
|
224
|
+
"RETURN_TO_HOME": 2,
|
|
225
|
+
"LAND_IMMEDIATELY": 3
|
|
226
|
+
}
|
|
227
|
+
action_code = action_map.get(action, 1) # Default to HOVER if invalid action
|
|
228
|
+
|
|
229
|
+
"""Cancel the mission execution completely."""
|
|
230
|
+
if self._mission_status.state != MissionState.RUNNING and self._mission_status.state != MissionState.PAUSED:
|
|
231
|
+
logger.warning(f"{LogIcons.WARNING} Mission cannot be canceled, current state: {self._mission_status.state.name}.")
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
if self._current_step is None:
|
|
235
|
+
logger.warning(f"{LogIcons.WARNING} Cannot cancel, no current step to cancel.")
|
|
236
|
+
return False
|
|
237
|
+
|
|
238
|
+
if self._current_step.is_cancelable() is False:
|
|
239
|
+
logger.warning(f"{LogIcons.WARNING} Current step does not support cancellation: {self._current_node}.")
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
if self.mav_proxy is None:
|
|
243
|
+
logger.warning(f"{LogIcons.WARNING} Cannot cancel, MAVLink proxy is required.")
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
self._mission_control_cmd = MissionControlCommand.CANCEL
|
|
247
|
+
|
|
248
|
+
# Send MAVLink cancel command
|
|
249
|
+
msg = leafMAV.MAVLink_leaf_control_cmd_message(
|
|
250
|
+
target_system=self.mav_proxy.target_system,
|
|
251
|
+
cmd=2,
|
|
252
|
+
action=action_code,
|
|
253
|
+
mission_id=self.plan.id.encode('ascii') # Use unique mission ID for tracking
|
|
254
|
+
)
|
|
255
|
+
self.mav_proxy.send(key='mav', msg=msg, burst_count=4, burst_interval=0.1)
|
|
256
|
+
logger.info(f"{LogIcons.RUN} Mission cancel commanded with action: {action}.")
|
|
257
|
+
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def abort(self):
|
|
262
|
+
"""Abort the mission immediately without any graceful shutdown."""
|
|
263
|
+
self._mission_control_cmd = MissionControlCommand.CANCEL
|
|
264
|
+
|
|
265
|
+
logger.info(f"{LogIcons.CANCEL} Mission aborted immediately.")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _completed(self, prev_node: str=None):
|
|
269
|
+
"""Handle mission completion procedures."""
|
|
270
|
+
state_change_flag = self._mission_status.completed(prev_node, self.execution_graph)
|
|
271
|
+
self._mission_control_cmd = MissionState.COMPLETED
|
|
272
|
+
self._current_step = None
|
|
273
|
+
if state_change_flag:
|
|
274
|
+
logger.info(f"{LogIcons.SUCCESS} Mission complete.")
|
|
275
|
+
|
|
276
|
+
def _failed(self, e: Exception):
|
|
277
|
+
"""Handle mission failure procedures."""
|
|
278
|
+
state_change_flag = self._mission_status.failed(self._current_node, self.execution_graph)
|
|
279
|
+
self._mission_control_cmd = MissionState.FAILED
|
|
280
|
+
self._current_node = None
|
|
281
|
+
self._current_step = None
|
|
282
|
+
if state_change_flag:
|
|
283
|
+
logger.error(f"{LogIcons.ERROR} Step {self._current_node} failed: {e}\n{traceback.format_exc()}")
|
|
284
|
+
|
|
285
|
+
def _send_pause_to_FC(self) -> None:
|
|
286
|
+
action_map = {
|
|
287
|
+
"NONE": 0
|
|
288
|
+
}
|
|
289
|
+
action_code = action_map.get("NONE", 0) # Default to HOVER if invalid action
|
|
290
|
+
msg = leafMAV.MAVLink_leaf_control_cmd_message(
|
|
291
|
+
target_system=self.mav_proxy.target_system,
|
|
292
|
+
cmd=0,
|
|
293
|
+
action=action_code,
|
|
294
|
+
mission_id=self.plan.id.encode('ascii')
|
|
295
|
+
)
|
|
296
|
+
self.mav_proxy.send(key='mav', msg=msg, burst_count=4, burst_interval=0.1)
|
|
297
|
+
|
|
298
|
+
def _paused(self):
|
|
299
|
+
"""Internal method to perform pause procedures."""
|
|
300
|
+
state_change_flag = self._mission_status.paused(self._current_node, self.execution_graph)
|
|
301
|
+
if state_change_flag:
|
|
302
|
+
self._current_step.pause(self.plan.id.encode('ascii'), mav_proxy=self.mav_proxy, redis_proxy=self.redis_proxy)
|
|
303
|
+
self._send_pause_to_FC()
|
|
304
|
+
logger.info(f"{LogIcons.PAUSE} Mission paused at step: {self._current_node}")
|
|
305
|
+
|
|
306
|
+
def _canceled(self):
|
|
307
|
+
"""Internal method to perform cancellation procedures."""
|
|
308
|
+
state_change_flag = self._mission_status.canceled(self._current_node, self.execution_graph)
|
|
309
|
+
if state_change_flag:
|
|
310
|
+
self._current_step.cancel(mav_proxy=self.mav_proxy, redis_proxy=self.redis_proxy)
|
|
311
|
+
logger.info(f"{LogIcons.CANCEL} Mission cancelled at step: {self._current_node}")
|
|
312
|
+
self._current_node = None
|
|
313
|
+
self._current_step = None
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def reset(self):
|
|
317
|
+
"""Reset the mission plan executor to its initial state."""
|
|
318
|
+
self.plan.reset()
|
|
319
|
+
self._mission_status.reset()
|
|
320
|
+
self._current_step = None
|
|
321
|
+
self._current_node = None
|
|
322
|
+
self._mission_control_cmd = None
|
|
323
|
+
logger.info(f"{LogIcons.SUCCESS} MissionPlanExecutor has been reset.")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class MissionState(Enum):
|
|
327
|
+
IDLE = auto()
|
|
328
|
+
RUNNING = auto()
|
|
329
|
+
PAUSED = auto()
|
|
330
|
+
CANCELLED = auto()
|
|
331
|
+
COMPLETED = auto()
|
|
332
|
+
FAILED = auto()
|
|
333
|
+
|
|
334
|
+
def __str__(self):
|
|
335
|
+
return self.name
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class MissionControlCommand(Enum):
|
|
339
|
+
NONE = auto()
|
|
340
|
+
PAUSE_NOW = auto()
|
|
341
|
+
PAUSE_LATER = auto()
|
|
342
|
+
RESUME = auto()
|
|
343
|
+
CANCEL = auto()
|
|
344
|
+
|
|
345
|
+
def __str__(self):
|
|
346
|
+
return self.name
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass
|
|
350
|
+
class MissionStatus:
|
|
351
|
+
step_id: Optional[str] = None
|
|
352
|
+
step_description: Optional[str] = None
|
|
353
|
+
next_step_id: Optional[str] = None
|
|
354
|
+
next_step_description: Optional[str] = None
|
|
355
|
+
step_completed: bool = False
|
|
356
|
+
state: MissionState = MissionState.IDLE
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def as_dict(self) -> dict:
|
|
360
|
+
"""Return a serializable dictionary for external systems."""
|
|
361
|
+
data = self.__dict__.copy()
|
|
362
|
+
data["state"] = str(self.state) # convert enum to string
|
|
363
|
+
return data
|
|
364
|
+
|
|
365
|
+
def reset(self):
|
|
366
|
+
self.step_id = None
|
|
367
|
+
self.step_description = None
|
|
368
|
+
self.next_step_id = None
|
|
369
|
+
self.next_step_description = None
|
|
370
|
+
self.step_completed = False
|
|
371
|
+
self.state = MissionState.IDLE
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def set_step(self, node: str, graph: nx.MultiDiGraph):
|
|
375
|
+
self.step_id = str(node)
|
|
376
|
+
self.step_description = graph.nodes[node]['step'].description()
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def set_next_step(self, node: str, graph: nx.MultiDiGraph):
|
|
380
|
+
self.next_step_id = str(node)
|
|
381
|
+
self.next_step_description = graph.nodes[node]['step'].description()
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def completed(self, node: str, graph: nx.MultiDiGraph) -> bool:
|
|
385
|
+
_state = self.state
|
|
386
|
+
self.reset()
|
|
387
|
+
self.state = MissionState.COMPLETED
|
|
388
|
+
self.step_completed = True
|
|
389
|
+
self.set_step(node, graph)
|
|
390
|
+
return _state != self.state
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def step_transition(self, prev_node: str, node: str, graph: nx.MultiDiGraph) -> bool:
|
|
394
|
+
_state = self.state
|
|
395
|
+
self.reset()
|
|
396
|
+
self.state = MissionState.RUNNING
|
|
397
|
+
self.step_completed = True
|
|
398
|
+
self.set_step(prev_node, graph)
|
|
399
|
+
self.set_next_step(node, graph)
|
|
400
|
+
return _state != self.state
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def running(self, node: str, graph: nx.MultiDiGraph) -> bool:
|
|
404
|
+
_state = self.state
|
|
405
|
+
self.reset()
|
|
406
|
+
self.state = MissionState.RUNNING
|
|
407
|
+
self.set_step(node, graph)
|
|
408
|
+
return _state != self.state
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def paused(self, node: str, graph: nx.MultiDiGraph) -> bool:
|
|
412
|
+
_state = self.state
|
|
413
|
+
self.reset()
|
|
414
|
+
self.state = MissionState.PAUSED
|
|
415
|
+
self.step_completed = True
|
|
416
|
+
self.set_step(node, graph)
|
|
417
|
+
return _state != self.state
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def canceled(self, node: str, graph: nx.MultiDiGraph) -> bool:
|
|
421
|
+
_state = self.state
|
|
422
|
+
self.reset()
|
|
423
|
+
self.state = MissionState.CANCELLED
|
|
424
|
+
self.step_completed = True
|
|
425
|
+
self.set_step(node, graph)
|
|
426
|
+
return _state != self.state
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def failed(self, node: str, graph: nx.MultiDiGraph) -> bool:
|
|
430
|
+
_state = self.state
|
|
431
|
+
self.reset()
|
|
432
|
+
self.state = MissionState.FAILED
|
|
433
|
+
self.set_step(node, graph)
|
|
434
|
+
return _state != self.state
|