isar 1.33.9__py3-none-any.whl → 1.34.1__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 isar might be problematic. Click here for more details.

Files changed (44) hide show
  1. isar/apis/models/models.py +2 -5
  2. isar/config/settings.py +0 -7
  3. isar/models/events.py +9 -10
  4. isar/modules.py +5 -11
  5. isar/robot/robot.py +109 -24
  6. isar/robot/robot_monitor_mission.py +399 -0
  7. isar/robot/robot_status.py +0 -10
  8. isar/robot/robot_stop_mission.py +7 -7
  9. isar/robot/robot_upload_inspection.py +80 -0
  10. isar/services/utilities/scheduling_utilities.py +30 -10
  11. isar/state_machine/state_machine.py +7 -223
  12. isar/state_machine/states/going_to_lockdown.py +23 -34
  13. isar/state_machine/states/home.py +4 -9
  14. isar/state_machine/states/monitor.py +20 -38
  15. isar/state_machine/states/paused.py +2 -2
  16. isar/state_machine/states/pausing.py +6 -22
  17. isar/state_machine/states/pausing_return_home.py +6 -22
  18. isar/state_machine/states/returning_home.py +40 -37
  19. isar/state_machine/states/stopping.py +2 -22
  20. isar/state_machine/states/stopping_go_to_lockdown.py +1 -21
  21. isar/state_machine/states/stopping_return_home.py +0 -2
  22. isar/state_machine/transitions/functions/fail_mission.py +1 -9
  23. isar/state_machine/transitions/functions/finish_mission.py +2 -32
  24. isar/state_machine/transitions/functions/pause.py +8 -7
  25. isar/state_machine/transitions/functions/resume.py +3 -12
  26. isar/state_machine/transitions/functions/return_home.py +1 -16
  27. isar/state_machine/transitions/functions/robot_status.py +7 -12
  28. isar/state_machine/transitions/functions/start_mission.py +2 -44
  29. isar/state_machine/transitions/functions/stop.py +4 -33
  30. isar/state_machine/transitions/mission.py +2 -17
  31. isar/state_machine/transitions/return_home.py +3 -24
  32. isar/state_machine/transitions/robot_status.py +9 -0
  33. isar/state_machine/utils/common_event_handlers.py +4 -96
  34. {isar-1.33.9.dist-info → isar-1.34.1.dist-info}/METADATA +1 -63
  35. {isar-1.33.9.dist-info → isar-1.34.1.dist-info}/RECORD +41 -42
  36. robot_interface/models/mission/task.py +0 -10
  37. robot_interface/robot_interface.py +25 -1
  38. isar/mission_planner/sequential_task_selector.py +0 -23
  39. isar/mission_planner/task_selector_interface.py +0 -31
  40. isar/robot/robot_task_status.py +0 -87
  41. {isar-1.33.9.dist-info → isar-1.34.1.dist-info}/WHEEL +0 -0
  42. {isar-1.33.9.dist-info → isar-1.34.1.dist-info}/entry_points.txt +0 -0
  43. {isar-1.33.9.dist-info → isar-1.34.1.dist-info}/licenses/LICENSE +0 -0
  44. {isar-1.33.9.dist-info → isar-1.34.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,399 @@
1
+ import json
2
+ import logging
3
+ import time
4
+ from datetime import datetime, timezone
5
+ from threading import Event, Thread
6
+ from typing import Iterator, Optional
7
+
8
+ from isar.config.settings import settings
9
+ from isar.models.events import RobotServiceEvents, SharedState
10
+ from isar.services.service_connections.mqtt.mqtt_client import props_expiry
11
+ from robot_interface.models.exceptions.robot_exceptions import (
12
+ ErrorMessage,
13
+ ErrorReason,
14
+ RobotCommunicationException,
15
+ RobotCommunicationTimeoutException,
16
+ RobotException,
17
+ RobotMissionStatusException,
18
+ RobotTaskStatusException,
19
+ )
20
+ from robot_interface.models.mission.mission import Mission
21
+ from robot_interface.models.mission.status import MissionStatus, TaskStatus
22
+ from robot_interface.models.mission.task import TASKS, InspectionTask
23
+ from robot_interface.robot_interface import RobotInterface
24
+ from robot_interface.telemetry.mqtt_client import MqttClientInterface
25
+ from robot_interface.telemetry.payloads import MissionPayload, TaskPayload
26
+ from robot_interface.utilities.json_service import EnhancedJSONEncoder
27
+
28
+
29
+ def get_next_task(task_iterator: Iterator[TASKS]) -> Optional[TASKS]:
30
+ try:
31
+ return next(task_iterator)
32
+ except StopIteration:
33
+ return None
34
+
35
+
36
+ def is_finished(task_status: TaskStatus) -> bool:
37
+ if (
38
+ task_status == TaskStatus.Successful
39
+ or task_status == TaskStatus.PartiallySuccessful
40
+ or task_status == TaskStatus.Cancelled
41
+ or task_status == TaskStatus.Failed
42
+ ):
43
+ return True
44
+ return False
45
+
46
+
47
+ def should_upload_inspections(task: TASKS) -> bool:
48
+ if settings.UPLOAD_INSPECTIONS_ASYNC:
49
+ return False
50
+
51
+ return task.status == TaskStatus.Successful and isinstance(task, InspectionTask)
52
+
53
+
54
+ class RobotMonitorMissionThread(Thread):
55
+ def __init__(
56
+ self,
57
+ robot_service_events: RobotServiceEvents,
58
+ shared_state: SharedState,
59
+ robot: RobotInterface,
60
+ mqtt_publisher: MqttClientInterface,
61
+ signal_thread_quitting: Event,
62
+ signal_mission_stopped: Event,
63
+ mission: Mission,
64
+ ):
65
+ self.logger = logging.getLogger("robot")
66
+ self.robot_service_events: RobotServiceEvents = robot_service_events
67
+ self.robot: RobotInterface = robot
68
+ self.signal_thread_quitting: Event = signal_thread_quitting
69
+ self.signal_mission_stopped: Event = signal_mission_stopped
70
+ self.mqtt_publisher = mqtt_publisher
71
+ self.current_mission: Optional[Mission] = mission
72
+ self.shared_state: SharedState = shared_state
73
+ self.shared_state.mission_id.trigger_event(mission.id)
74
+
75
+ Thread.__init__(self, name="Robot mission monitoring thread")
76
+
77
+ def _get_task_status(self, task_id) -> TaskStatus:
78
+ task_status: TaskStatus = TaskStatus.NotStarted
79
+ failed_task_error: Optional[ErrorMessage] = None
80
+ request_status_failure_counter: int = 0
81
+
82
+ while (
83
+ request_status_failure_counter
84
+ < settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
85
+ ):
86
+ if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
87
+ 0
88
+ ):
89
+ break
90
+ if request_status_failure_counter > 0:
91
+ time.sleep(settings.REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY)
92
+
93
+ try:
94
+ task_status = self.robot.task_status(task_id)
95
+ request_status_failure_counter = 0
96
+ except (
97
+ RobotCommunicationTimeoutException,
98
+ RobotCommunicationException,
99
+ ) as e:
100
+ request_status_failure_counter += 1
101
+ self.logger.error(
102
+ f"Failed to get task status "
103
+ f"{request_status_failure_counter} times because: "
104
+ f"{e.error_description}"
105
+ )
106
+
107
+ failed_task_error = ErrorMessage(
108
+ error_reason=e.error_reason,
109
+ error_description=e.error_description,
110
+ )
111
+ continue
112
+
113
+ except RobotTaskStatusException as e:
114
+ failed_task_error = ErrorMessage(
115
+ error_reason=e.error_reason,
116
+ error_description=e.error_description,
117
+ )
118
+ break
119
+
120
+ except RobotException as e:
121
+ failed_task_error = ErrorMessage(
122
+ error_reason=e.error_reason,
123
+ error_description=e.error_description,
124
+ )
125
+ break
126
+
127
+ except Exception as e:
128
+ failed_task_error = ErrorMessage(
129
+ error_reason=ErrorReason.RobotUnknownErrorException,
130
+ error_description=str(e),
131
+ )
132
+ break
133
+
134
+ return task_status
135
+
136
+ raise RobotTaskStatusException(
137
+ error_description=failed_task_error.error_description
138
+ )
139
+
140
+ def _get_mission_status(self, mission_id) -> MissionStatus:
141
+ mission_status: MissionStatus = MissionStatus.NotStarted
142
+ failed_mission_error: Optional[ErrorMessage] = None
143
+ request_status_failure_counter: int = 0
144
+
145
+ while (
146
+ request_status_failure_counter
147
+ < settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
148
+ ):
149
+ if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
150
+ 0
151
+ ):
152
+ break
153
+ if request_status_failure_counter > 0:
154
+ time.sleep(settings.REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY)
155
+
156
+ try:
157
+ mission_status = self.robot.mission_status(mission_id)
158
+ request_status_failure_counter = 0
159
+ except (
160
+ RobotCommunicationTimeoutException,
161
+ RobotCommunicationException,
162
+ ) as e:
163
+ request_status_failure_counter += 1
164
+ self.logger.error(
165
+ f"Failed to get task status "
166
+ f"{request_status_failure_counter} times because: "
167
+ f"{e.error_description}"
168
+ )
169
+
170
+ failed_mission_error = ErrorMessage(
171
+ error_reason=e.error_reason,
172
+ error_description=e.error_description,
173
+ )
174
+ continue
175
+
176
+ except RobotMissionStatusException as e:
177
+ failed_mission_error = ErrorMessage(
178
+ error_reason=e.error_reason,
179
+ error_description=e.error_description,
180
+ )
181
+ break
182
+
183
+ except RobotException as e:
184
+ failed_mission_error = ErrorMessage(
185
+ error_reason=e.error_reason,
186
+ error_description=e.error_description,
187
+ )
188
+ break
189
+
190
+ except Exception as e:
191
+ failed_mission_error = ErrorMessage(
192
+ error_reason=ErrorReason.RobotUnknownErrorException,
193
+ error_description=str(e),
194
+ )
195
+ break
196
+
197
+ return mission_status
198
+
199
+ raise RobotMissionStatusException(
200
+ error_description=(
201
+ failed_mission_error.error_description
202
+ if failed_mission_error
203
+ else "Mission status thread cancelled"
204
+ )
205
+ )
206
+
207
+ def publish_task_status(self, task: TASKS) -> None:
208
+ """Publishes the task status to the MQTT Broker"""
209
+ if not self.mqtt_publisher:
210
+ return
211
+
212
+ error_message: Optional[ErrorMessage] = None
213
+ if task:
214
+ if task.error_message:
215
+ error_message = task.error_message
216
+
217
+ payload: TaskPayload = TaskPayload(
218
+ isar_id=settings.ISAR_ID,
219
+ robot_name=settings.ROBOT_NAME,
220
+ mission_id=self.current_mission.id if self.current_mission else None,
221
+ task_id=task.id if task else None,
222
+ status=task.status if task else None,
223
+ task_type=task.type if task else None,
224
+ error_reason=error_message.error_reason if error_message else None,
225
+ error_description=(
226
+ error_message.error_description if error_message else None
227
+ ),
228
+ timestamp=datetime.now(timezone.utc),
229
+ )
230
+
231
+ self.mqtt_publisher.publish(
232
+ topic=settings.TOPIC_ISAR_TASK + f"/{task.id}",
233
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
234
+ qos=1,
235
+ retain=True,
236
+ properties=props_expiry(settings.MQTT_MISSION_AND_TASK_EXPIRY),
237
+ )
238
+
239
+ def _report_task_status(self, task: TASKS) -> None:
240
+ if task.status == TaskStatus.Failed:
241
+ self.logger.warning(
242
+ f"Task: {str(task.id)[:8]} was reported as failed by the robot"
243
+ )
244
+ elif task.status == TaskStatus.Successful:
245
+ self.logger.info(
246
+ f"{type(task).__name__} task: {str(task.id)[:8]} completed"
247
+ )
248
+ else:
249
+ self.logger.info(
250
+ f"Task: {str(task.id)[:8]} was reported as {task.status} by the robot"
251
+ )
252
+
253
+ def publish_mission_status(self) -> None:
254
+ if not self.mqtt_publisher:
255
+ return
256
+
257
+ error_message: Optional[ErrorMessage] = None
258
+ if self.current_mission:
259
+ if self.current_mission.error_message:
260
+ error_message = self.current_mission.error_message
261
+
262
+ payload: MissionPayload = MissionPayload(
263
+ isar_id=settings.ISAR_ID,
264
+ robot_name=settings.ROBOT_NAME,
265
+ mission_id=self.current_mission.id if self.current_mission else None,
266
+ status=self.current_mission.status if self.current_mission else None,
267
+ error_reason=error_message.error_reason if error_message else None,
268
+ error_description=(
269
+ error_message.error_description if error_message else None
270
+ ),
271
+ timestamp=datetime.now(timezone.utc),
272
+ )
273
+
274
+ self.mqtt_publisher.publish(
275
+ topic=settings.TOPIC_ISAR_MISSION + f"/{self.current_mission.id}",
276
+ payload=json.dumps(payload, cls=EnhancedJSONEncoder),
277
+ qos=1,
278
+ retain=True,
279
+ properties=props_expiry(settings.MQTT_MISSION_AND_TASK_EXPIRY),
280
+ )
281
+
282
+ def _finalize_mission_status(self):
283
+ fail_statuses = [
284
+ TaskStatus.Cancelled,
285
+ TaskStatus.Failed,
286
+ ]
287
+ partially_fail_statuses = fail_statuses + [TaskStatus.PartiallySuccessful]
288
+
289
+ if len(self.current_mission.tasks) == 0:
290
+ self.current_mission.status = MissionStatus.Successful
291
+ elif all(task.status in fail_statuses for task in self.current_mission.tasks):
292
+ self.current_mission.error_message = ErrorMessage(
293
+ error_reason=None,
294
+ error_description="The mission failed because all tasks in the mission "
295
+ "failed",
296
+ )
297
+ self.current_mission.status = MissionStatus.Failed
298
+ elif any(
299
+ task.status in partially_fail_statuses
300
+ for task in self.current_mission.tasks
301
+ ):
302
+ self.current_mission.status = MissionStatus.PartiallySuccessful
303
+ else:
304
+ self.current_mission.status = MissionStatus.Successful
305
+
306
+ def run(self) -> None:
307
+
308
+ self.task_iterator: Iterator[TASKS] = iter(self.current_mission.tasks)
309
+ current_task: Optional[TASKS] = get_next_task(self.task_iterator)
310
+ current_task.status = TaskStatus.NotStarted
311
+
312
+ last_mission_status: MissionStatus = MissionStatus.NotStarted
313
+
314
+ while not self.signal_thread_quitting.wait(
315
+ 0
316
+ ) or self.signal_mission_stopped.wait(0):
317
+
318
+ if current_task:
319
+ try:
320
+ new_task_status = self._get_task_status(current_task.id)
321
+ except RobotTaskStatusException as e:
322
+ self.logger.error(
323
+ "Failed to collect task status", e.error_description
324
+ )
325
+ break
326
+ except Exception:
327
+ break
328
+
329
+ if current_task.status != new_task_status:
330
+ current_task.status = new_task_status
331
+ self._report_task_status(current_task)
332
+ self.publish_task_status(task=current_task)
333
+
334
+ if is_finished(new_task_status):
335
+ if should_upload_inspections(current_task):
336
+ self.robot_service_events.request_inspection_upload.trigger_event(
337
+ (current_task, self.current_mission)
338
+ )
339
+ current_task = get_next_task(self.task_iterator)
340
+ if current_task is not None:
341
+ # This is not required, but does make reporting more responsive
342
+ current_task.status = TaskStatus.InProgress
343
+ self._report_task_status(current_task)
344
+ self.publish_task_status(task=current_task)
345
+
346
+ if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
347
+ 0
348
+ ):
349
+ break
350
+
351
+ try:
352
+ new_mission_status = self._get_mission_status(self.current_mission.id)
353
+ except RobotMissionStatusException as e:
354
+ self.logger.error("Failed to collect mission status", e)
355
+ break
356
+ if new_mission_status != last_mission_status:
357
+ self.current_mission.status = new_mission_status
358
+ last_mission_status = new_mission_status
359
+ self.publish_mission_status()
360
+ self.robot_service_events.mission_status_updated.trigger_event(
361
+ new_mission_status
362
+ )
363
+
364
+ if new_mission_status == MissionStatus.Cancelled or (
365
+ new_mission_status
366
+ not in [MissionStatus.NotStarted, MissionStatus.InProgress]
367
+ and current_task is None
368
+ ):
369
+ # Standardises final mission status report
370
+ mission_status = self.current_mission.status
371
+ self._finalize_mission_status()
372
+ if mission_status != self.current_mission.status:
373
+ self.publish_mission_status()
374
+ break
375
+
376
+ if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
377
+ 0
378
+ ):
379
+ break
380
+
381
+ time.sleep(settings.FSM_SLEEP_TIME)
382
+ self.shared_state.mission_id.trigger_event(None)
383
+
384
+ if not self.signal_mission_stopped.wait(0):
385
+ self.logger.info("Done monitoring current mission")
386
+ return
387
+
388
+ if current_task:
389
+ current_task.status = TaskStatus.Cancelled
390
+ self.publish_task_status(task=current_task)
391
+ if new_mission_status not in [
392
+ MissionStatus.Cancelled,
393
+ MissionStatus.PartiallySuccessful,
394
+ MissionStatus.Failed,
395
+ MissionStatus.Successful,
396
+ ]:
397
+ self.current_mission.status = MissionStatus.Cancelled
398
+ self.publish_mission_status()
399
+ self.logger.info("Stopped monitoring mission due to mission stop")
@@ -25,17 +25,12 @@ class RobotStatusThread(Thread):
25
25
  self.signal_thread_quitting: Event = signal_thread_quitting
26
26
  self.robot_status_poll_interval: float = settings.ROBOT_API_STATUS_POLL_INTERVAL
27
27
  self.last_robot_status_poll_time: float = time.time()
28
- self.force_status_poll_next_iteration: bool = True
29
28
  Thread.__init__(self, name="Robot status thread")
30
29
 
31
30
  def stop(self) -> None:
32
31
  return
33
32
 
34
33
  def _is_ready_to_poll_for_status(self) -> bool:
35
- if self.force_status_poll_next_iteration:
36
- self.force_status_poll_next_iteration = False
37
- return True
38
-
39
34
  time_since_last_robot_status_poll = (
40
35
  time.time() - self.last_robot_status_poll_time
41
36
  )
@@ -48,11 +43,6 @@ class RobotStatusThread(Thread):
48
43
  thread_check_interval = settings.THREAD_CHECK_INTERVAL
49
44
 
50
45
  while not self.signal_thread_quitting.wait(thread_check_interval):
51
- if self.state_machine_events.clear_robot_status.consume_event() is not None:
52
- self.shared_state.robot_status.clear_event()
53
- self.robot_service_events.robot_status_changed.clear_event()
54
- self.robot_service_events.robot_status_cleared.trigger_event(True)
55
- self.force_status_poll_next_iteration = True
56
46
 
57
47
  if not self._is_ready_to_poll_for_status():
58
48
  continue
@@ -4,9 +4,9 @@ from threading import Event, Thread
4
4
  from typing import Optional
5
5
 
6
6
  from isar.config.settings import settings
7
- from isar.models.events import RobotServiceEvents
8
7
  from robot_interface.models.exceptions.robot_exceptions import (
9
8
  ErrorMessage,
9
+ ErrorReason,
10
10
  RobotActionException,
11
11
  RobotException,
12
12
  )
@@ -16,14 +16,13 @@ from robot_interface.robot_interface import RobotInterface
16
16
  class RobotStopMissionThread(Thread):
17
17
  def __init__(
18
18
  self,
19
- robot_service_events: RobotServiceEvents,
20
19
  robot: RobotInterface,
21
20
  signal_thread_quitting: Event,
22
21
  ):
23
22
  self.logger = logging.getLogger("robot")
24
- self.robot_service_events: RobotServiceEvents = robot_service_events
25
23
  self.robot: RobotInterface = robot
26
24
  self.signal_thread_quitting: Event = signal_thread_quitting
25
+ self.error_message: Optional[ErrorMessage] = None
27
26
  Thread.__init__(self, name="Robot stop mission thread")
28
27
 
29
28
  def run(self) -> None:
@@ -31,6 +30,10 @@ class RobotStopMissionThread(Thread):
31
30
  error: Optional[ErrorMessage] = None
32
31
  while retries < settings.STOP_ROBOT_ATTEMPTS_LIMIT:
33
32
  if self.signal_thread_quitting.wait(0):
33
+ self.error_message = ErrorMessage(
34
+ error_reason=ErrorReason.RobotUnknownErrorException,
35
+ error_description="Stop mission thread cancelled",
36
+ )
34
37
  return
35
38
 
36
39
  try:
@@ -47,7 +50,6 @@ class RobotStopMissionThread(Thread):
47
50
  time.sleep(settings.FSM_SLEEP_TIME)
48
51
  continue
49
52
 
50
- self.robot_service_events.mission_successfully_stopped.trigger_event(True)
51
53
  return
52
54
 
53
55
  error_description = (
@@ -57,9 +59,7 @@ class RobotStopMissionThread(Thread):
57
59
  "been attempted"
58
60
  )
59
61
 
60
- error_message = ErrorMessage(
62
+ self.error_message = ErrorMessage(
61
63
  error_reason=error.error_reason,
62
64
  error_description=error_description,
63
65
  )
64
-
65
- self.robot_service_events.mission_failed_to_stop.trigger_event(error_message)
@@ -0,0 +1,80 @@
1
+ import logging
2
+ from queue import Queue
3
+ from threading import Thread
4
+ from typing import Tuple
5
+
6
+ from robot_interface.models.exceptions.robot_exceptions import (
7
+ ErrorMessage,
8
+ RobotException,
9
+ RobotRetrieveInspectionException,
10
+ )
11
+ from robot_interface.models.inspection.inspection import Inspection
12
+ from robot_interface.models.mission.mission import Mission
13
+ from robot_interface.models.mission.task import TASKS
14
+ from robot_interface.robot_interface import RobotInterface
15
+
16
+
17
+ class RobotUploadInspectionThread(Thread):
18
+ def __init__(
19
+ self,
20
+ upload_queue: Queue,
21
+ robot: RobotInterface,
22
+ task: TASKS,
23
+ mission: Mission,
24
+ ):
25
+ self.logger = logging.getLogger("robot")
26
+ self.robot: RobotInterface = robot
27
+ self.task: TASKS = task
28
+ self.upload_queue = upload_queue
29
+ self.mission: Mission = mission
30
+ self._is_done = False
31
+ Thread.__init__(self, name=f"Robot inspection upload thread - {task.id}")
32
+
33
+ def stop(self) -> None:
34
+ return
35
+
36
+ def is_done(self) -> bool:
37
+ return self._is_done
38
+
39
+ def run(self):
40
+ try:
41
+ inspection: Inspection = self.robot.get_inspection(task=self.task)
42
+ if self.task.inspection_id != inspection.id:
43
+ self.logger.warning(
44
+ f"The inspection_id of task ({self.task.inspection_id}) "
45
+ f"and result ({inspection.id}) is not matching. "
46
+ f"This may lead to confusions when accessing the inspection later"
47
+ )
48
+
49
+ except (RobotRetrieveInspectionException, RobotException) as e:
50
+ error_message: ErrorMessage = ErrorMessage(
51
+ error_reason=e.error_reason, error_description=e.error_description
52
+ )
53
+ self.task.error_message = error_message
54
+ self.logger.error(
55
+ f"Failed to retrieve inspections because: {e.error_description}"
56
+ )
57
+ return
58
+
59
+ except Exception as e:
60
+ self.logger.error(
61
+ f"Failed to retrieve inspections because of unexpected error: {e}"
62
+ )
63
+ return
64
+
65
+ if not inspection:
66
+ self.logger.warning(
67
+ f"No inspection result data retrieved for task {str(self.task.id)[:8]}"
68
+ )
69
+
70
+ inspection.metadata.tag_id = self.task.tag_id
71
+
72
+ message: Tuple[Inspection, Mission] = (
73
+ inspection,
74
+ self.mission,
75
+ )
76
+ self.upload_queue.put(message)
77
+ self.logger.info(
78
+ f"Inspection result: {str(inspection.id)[:8]} queued for upload"
79
+ )
80
+ self._is_done = True
@@ -23,7 +23,6 @@ from isar.models.events import (
23
23
  )
24
24
  from isar.state_machine.states_enum import States
25
25
  from robot_interface.models.mission.mission import Mission
26
- from robot_interface.models.mission.status import MissionStatus
27
26
 
28
27
  T1 = TypeVar("T1")
29
28
  T2 = TypeVar("T2")
@@ -167,6 +166,17 @@ class SchedulingUtilities:
167
166
  self.logger.warning(error_message)
168
167
  raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
169
168
 
169
+ def log_mission_overview(self, mission: Mission) -> None:
170
+ """Log an overview of the tasks in a mission"""
171
+ log_statements: List[str] = []
172
+ for task in mission.tasks:
173
+ log_statements.append(
174
+ f"{type(task).__name__:<20} {str(task.id)[:8]:<32} -- {task.status}"
175
+ )
176
+ log_statement: str = "\n".join(log_statements)
177
+
178
+ self.logger.info("Started mission:\n%s", log_statement)
179
+
170
180
  def start_mission(
171
181
  self,
172
182
  mission: Mission,
@@ -183,6 +193,12 @@ class SchedulingUtilities:
183
193
  If there is an unexpected error while sending the mission to the state machine
184
194
  """
185
195
  try:
196
+ self.logger.info(
197
+ "Requesting to start mission:\n"
198
+ f" Mission ID: {mission.id}\n"
199
+ f" Mission Name: {mission.name}\n"
200
+ f" Number of Tasks: {len(mission.tasks)}"
201
+ )
186
202
  mission_start_response = self._send_command(
187
203
  deepcopy(mission),
188
204
  self.api_events.start_mission,
@@ -213,6 +229,7 @@ class SchedulingUtilities:
213
229
  raise HTTPException(
214
230
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
215
231
  )
232
+ self.log_mission_overview(mission)
216
233
  self.logger.info("OK - Mission started in ISAR")
217
234
 
218
235
  def return_home(
@@ -262,6 +279,14 @@ class SchedulingUtilities:
262
279
  """
263
280
  try:
264
281
  response = self._send_command(True, self.api_events.pause_mission)
282
+ if not response.success:
283
+ self.logger.warning(
284
+ f"Mission failed to pause - {response.failure_reason}"
285
+ )
286
+ raise HTTPException(
287
+ status_code=HTTPStatus.CONFLICT,
288
+ detail=response.failure_reason,
289
+ )
265
290
  self.logger.info("OK - Mission successfully paused")
266
291
  return response
267
292
  except EventConflictError:
@@ -335,18 +360,13 @@ class SchedulingUtilities:
335
360
  mission_id, self.api_events.stop_mission
336
361
  )
337
362
 
338
- if stop_mission_response.mission_not_found:
339
- error_message = f"Mission ID {stop_mission_response.mission_id} is not currently running"
340
- self.logger.error(error_message)
341
- raise HTTPException(
342
- status_code=HTTPStatus.NOT_FOUND, detail=error_message
363
+ if not stop_mission_response.success:
364
+ error_message = (
365
+ f"Failed to stop mission: {stop_mission_response.failure_reason}"
343
366
  )
344
-
345
- if stop_mission_response.mission_status != MissionStatus.Cancelled.value:
346
- error_message = f"Failed to stop mission, mission status is {stop_mission_response.mission_status}"
347
367
  self.logger.error(error_message)
348
368
  raise HTTPException(
349
- status_code=HTTPStatus.CONFLICT, detail=error_message
369
+ status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=error_message
350
370
  )
351
371
  except EventConflictError:
352
372
  error_message = "Previous stop mission request is still being processed"