isar 1.15.0__py3-none-any.whl → 1.34.9__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.
- isar/__init__.py +2 -5
- isar/apis/api.py +159 -66
- isar/apis/models/__init__.py +0 -1
- isar/apis/models/models.py +22 -12
- isar/apis/models/start_mission_definition.py +128 -123
- isar/apis/robot_control/robot_controller.py +41 -0
- isar/apis/schedule/scheduling_controller.py +135 -121
- isar/apis/security/authentication.py +5 -5
- isar/config/certs/ca-cert.pem +32 -32
- isar/config/keyvault/keyvault_service.py +1 -2
- isar/config/log.py +47 -39
- isar/config/logging.conf +16 -31
- isar/config/open_telemetry.py +102 -0
- isar/config/predefined_mission_definition/default_exr.json +49 -0
- isar/config/predefined_mission_definition/default_mission.json +1 -5
- isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
- isar/config/predefined_missions/default.json +67 -87
- isar/config/predefined_missions/default_extra_capabilities.json +107 -0
- isar/config/settings.py +119 -142
- isar/eventhandlers/eventhandler.py +123 -0
- isar/mission_planner/local_planner.py +6 -20
- isar/mission_planner/mission_planner_interface.py +1 -1
- isar/models/events.py +184 -0
- isar/models/status.py +18 -0
- isar/modules.py +118 -205
- isar/robot/robot.py +377 -0
- isar/robot/robot_battery.py +60 -0
- isar/robot/robot_monitor_mission.py +357 -0
- isar/robot/robot_pause_mission.py +74 -0
- isar/robot/robot_resume_mission.py +67 -0
- isar/robot/robot_start_mission.py +66 -0
- isar/robot/robot_status.py +61 -0
- isar/robot/robot_stop_mission.py +68 -0
- isar/robot/robot_upload_inspection.py +75 -0
- isar/script.py +171 -0
- isar/services/service_connections/mqtt/mqtt_client.py +47 -11
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +32 -0
- isar/services/service_connections/mqtt/robot_info_publisher.py +4 -3
- isar/services/service_connections/persistent_memory.py +69 -0
- isar/services/utilities/mqtt_utilities.py +93 -0
- isar/services/utilities/robot_utilities.py +20 -0
- isar/services/utilities/scheduling_utilities.py +393 -65
- isar/state_machine/state_machine.py +227 -486
- isar/state_machine/states/__init__.py +0 -7
- isar/state_machine/states/await_next_mission.py +114 -0
- isar/state_machine/states/blocked_protective_stop.py +60 -0
- isar/state_machine/states/going_to_lockdown.py +95 -0
- isar/state_machine/states/going_to_recharging.py +92 -0
- isar/state_machine/states/home.py +115 -0
- isar/state_machine/states/intervention_needed.py +77 -0
- isar/state_machine/states/lockdown.py +38 -0
- isar/state_machine/states/maintenance.py +36 -0
- isar/state_machine/states/monitor.py +137 -166
- isar/state_machine/states/offline.py +60 -0
- isar/state_machine/states/paused.py +92 -23
- isar/state_machine/states/pausing.py +48 -0
- isar/state_machine/states/pausing_return_home.py +48 -0
- isar/state_machine/states/recharging.py +80 -0
- isar/state_machine/states/resuming.py +57 -0
- isar/state_machine/states/resuming_return_home.py +64 -0
- isar/state_machine/states/return_home_paused.py +109 -0
- isar/state_machine/states/returning_home.py +217 -0
- isar/state_machine/states/stopping.py +61 -0
- isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
- isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
- isar/state_machine/states/stopping_go_to_recharge.py +51 -0
- isar/state_machine/states/stopping_return_home.py +77 -0
- isar/state_machine/states/unknown_status.py +72 -0
- isar/state_machine/states_enum.py +22 -5
- isar/state_machine/transitions/mission.py +192 -0
- isar/state_machine/transitions/return_home.py +106 -0
- isar/state_machine/transitions/robot_status.py +80 -0
- isar/state_machine/utils/common_event_handlers.py +73 -0
- isar/storage/blob_storage.py +71 -45
- isar/storage/local_storage.py +28 -14
- isar/storage/storage_interface.py +28 -6
- isar/storage/uploader.py +184 -55
- isar/storage/utilities.py +35 -27
- isar-1.34.9.dist-info/METADATA +496 -0
- isar-1.34.9.dist-info/RECORD +135 -0
- {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
- isar-1.34.9.dist-info/entry_points.txt +3 -0
- robot_interface/models/exceptions/__init__.py +0 -7
- robot_interface/models/exceptions/robot_exceptions.py +274 -4
- robot_interface/models/initialize/__init__.py +0 -1
- robot_interface/models/inspection/__init__.py +0 -13
- robot_interface/models/inspection/inspection.py +43 -34
- robot_interface/models/mission/mission.py +18 -14
- robot_interface/models/mission/status.py +20 -25
- robot_interface/models/mission/task.py +156 -92
- robot_interface/models/robots/battery_state.py +6 -0
- robot_interface/models/robots/media.py +13 -0
- robot_interface/models/robots/robot_model.py +7 -7
- robot_interface/robot_interface.py +135 -66
- robot_interface/telemetry/mqtt_client.py +84 -12
- robot_interface/telemetry/payloads.py +111 -12
- robot_interface/utilities/json_service.py +7 -1
- isar/config/predefined_missions/default_turtlebot.json +0 -110
- isar/config/predefined_poses/__init__.py +0 -0
- isar/config/predefined_poses/predefined_poses.py +0 -616
- isar/config/settings.env +0 -26
- isar/mission_planner/sequential_task_selector.py +0 -23
- isar/mission_planner/task_selector_interface.py +0 -31
- isar/models/communication/__init__.py +0 -0
- isar/models/communication/message.py +0 -12
- isar/models/communication/queues/__init__.py +0 -4
- isar/models/communication/queues/queue_io.py +0 -12
- isar/models/communication/queues/queue_timeout_error.py +0 -2
- isar/models/communication/queues/queues.py +0 -19
- isar/models/communication/queues/status_queue.py +0 -20
- isar/models/mission_metadata/__init__.py +0 -0
- isar/services/readers/__init__.py +0 -0
- isar/services/readers/base_reader.py +0 -37
- isar/services/service_connections/mqtt/robot_status_publisher.py +0 -93
- isar/services/service_connections/stid/__init__.py +0 -0
- isar/services/service_connections/stid/stid_service.py +0 -45
- isar/services/utilities/queue_utilities.py +0 -39
- isar/state_machine/states/idle.py +0 -40
- isar/state_machine/states/initialize.py +0 -60
- isar/state_machine/states/initiate.py +0 -129
- isar/state_machine/states/off.py +0 -18
- isar/state_machine/states/stop.py +0 -78
- isar/storage/slimm_storage.py +0 -181
- isar-1.15.0.dist-info/METADATA +0 -417
- isar-1.15.0.dist-info/RECORD +0 -113
- robot_interface/models/initialize/initialize_params.py +0 -9
- robot_interface/models/mission/step.py +0 -211
- {isar-1.15.0.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
- {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
from typing import Iterator, Optional
|
|
5
|
+
|
|
6
|
+
from isar.config.settings import settings
|
|
7
|
+
from isar.models.events import RobotServiceEvents, SharedState
|
|
8
|
+
from isar.services.utilities.mqtt_utilities import (
|
|
9
|
+
publish_mission_status,
|
|
10
|
+
publish_task_status,
|
|
11
|
+
)
|
|
12
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
13
|
+
ErrorMessage,
|
|
14
|
+
ErrorReason,
|
|
15
|
+
RobotCommunicationException,
|
|
16
|
+
RobotCommunicationTimeoutException,
|
|
17
|
+
RobotException,
|
|
18
|
+
RobotMissionStatusException,
|
|
19
|
+
RobotTaskStatusException,
|
|
20
|
+
)
|
|
21
|
+
from robot_interface.models.mission.mission import Mission
|
|
22
|
+
from robot_interface.models.mission.status import MissionStatus, TaskStatus
|
|
23
|
+
from robot_interface.models.mission.task import TASKS, InspectionTask
|
|
24
|
+
from robot_interface.robot_interface import RobotInterface
|
|
25
|
+
from robot_interface.telemetry.mqtt_client import MqttClientInterface
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_next_task(task_iterator: Iterator[TASKS]) -> Optional[TASKS]:
|
|
29
|
+
try:
|
|
30
|
+
return next(task_iterator)
|
|
31
|
+
except StopIteration:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_finished(task_status: TaskStatus) -> bool:
|
|
36
|
+
if (
|
|
37
|
+
task_status == TaskStatus.Successful
|
|
38
|
+
or task_status == TaskStatus.PartiallySuccessful
|
|
39
|
+
or task_status == TaskStatus.Cancelled
|
|
40
|
+
or task_status == TaskStatus.Failed
|
|
41
|
+
):
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def should_upload_inspections(task: TASKS) -> bool:
|
|
47
|
+
if settings.UPLOAD_INSPECTIONS_ASYNC:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
return task.status == TaskStatus.Successful and isinstance(task, InspectionTask)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RobotMonitorMissionThread(Thread):
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
robot_service_events: RobotServiceEvents,
|
|
57
|
+
shared_state: SharedState,
|
|
58
|
+
robot: RobotInterface,
|
|
59
|
+
mqtt_publisher: MqttClientInterface,
|
|
60
|
+
signal_thread_quitting: Event,
|
|
61
|
+
signal_mission_stopped: Event,
|
|
62
|
+
mission: Mission,
|
|
63
|
+
):
|
|
64
|
+
self.logger = logging.getLogger("robot")
|
|
65
|
+
self.robot_service_events: RobotServiceEvents = robot_service_events
|
|
66
|
+
self.robot: RobotInterface = robot
|
|
67
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
68
|
+
self.signal_mission_stopped: Event = signal_mission_stopped
|
|
69
|
+
self.mqtt_publisher = mqtt_publisher
|
|
70
|
+
self.current_mission: Optional[Mission] = mission
|
|
71
|
+
self.shared_state: SharedState = shared_state
|
|
72
|
+
self.shared_state.mission_id.trigger_event(mission.id)
|
|
73
|
+
|
|
74
|
+
Thread.__init__(self, name="Robot mission monitoring thread")
|
|
75
|
+
|
|
76
|
+
def _get_task_status(self, task_id) -> TaskStatus:
|
|
77
|
+
task_status: TaskStatus = TaskStatus.NotStarted
|
|
78
|
+
failed_task_error: Optional[ErrorMessage] = None
|
|
79
|
+
request_status_failure_counter: int = 0
|
|
80
|
+
|
|
81
|
+
while (
|
|
82
|
+
request_status_failure_counter
|
|
83
|
+
< settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
|
|
84
|
+
):
|
|
85
|
+
if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
|
|
86
|
+
0
|
|
87
|
+
):
|
|
88
|
+
failed_task_error = ErrorMessage(
|
|
89
|
+
error_reason=ErrorReason.RobotTaskStatusException,
|
|
90
|
+
error_description="Task status collection was cancelled by monitor thread exit",
|
|
91
|
+
)
|
|
92
|
+
break
|
|
93
|
+
if request_status_failure_counter > 0:
|
|
94
|
+
time.sleep(settings.REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
task_status = self.robot.task_status(task_id)
|
|
98
|
+
request_status_failure_counter = 0
|
|
99
|
+
except (
|
|
100
|
+
RobotCommunicationTimeoutException,
|
|
101
|
+
RobotCommunicationException,
|
|
102
|
+
) as e:
|
|
103
|
+
request_status_failure_counter += 1
|
|
104
|
+
self.logger.error(
|
|
105
|
+
f"Failed to get task status "
|
|
106
|
+
f"{request_status_failure_counter} times because: "
|
|
107
|
+
f"{e.error_description}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
failed_task_error = ErrorMessage(
|
|
111
|
+
error_reason=e.error_reason,
|
|
112
|
+
error_description=e.error_description,
|
|
113
|
+
)
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
except RobotTaskStatusException as e:
|
|
117
|
+
failed_task_error = ErrorMessage(
|
|
118
|
+
error_reason=e.error_reason,
|
|
119
|
+
error_description=e.error_description,
|
|
120
|
+
)
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
except RobotException as e:
|
|
124
|
+
failed_task_error = ErrorMessage(
|
|
125
|
+
error_reason=e.error_reason,
|
|
126
|
+
error_description=e.error_description,
|
|
127
|
+
)
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
failed_task_error = ErrorMessage(
|
|
132
|
+
error_reason=ErrorReason.RobotUnknownErrorException,
|
|
133
|
+
error_description=str(e),
|
|
134
|
+
)
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
return task_status
|
|
138
|
+
|
|
139
|
+
raise RobotTaskStatusException(
|
|
140
|
+
error_description=failed_task_error.error_description
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _get_mission_status(self, mission_id) -> MissionStatus:
|
|
144
|
+
mission_status: MissionStatus = MissionStatus.NotStarted
|
|
145
|
+
failed_mission_error: Optional[ErrorMessage] = None
|
|
146
|
+
request_status_failure_counter: int = 0
|
|
147
|
+
|
|
148
|
+
while (
|
|
149
|
+
request_status_failure_counter
|
|
150
|
+
< settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
|
|
151
|
+
):
|
|
152
|
+
if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
|
|
153
|
+
0
|
|
154
|
+
):
|
|
155
|
+
break
|
|
156
|
+
if request_status_failure_counter > 0:
|
|
157
|
+
time.sleep(settings.REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
mission_status = self.robot.mission_status(mission_id)
|
|
161
|
+
request_status_failure_counter = 0
|
|
162
|
+
except (
|
|
163
|
+
RobotCommunicationTimeoutException,
|
|
164
|
+
RobotCommunicationException,
|
|
165
|
+
) as e:
|
|
166
|
+
request_status_failure_counter += 1
|
|
167
|
+
self.logger.error(
|
|
168
|
+
f"Failed to get task status "
|
|
169
|
+
f"{request_status_failure_counter} times because: "
|
|
170
|
+
f"{e.error_description}"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
failed_mission_error = ErrorMessage(
|
|
174
|
+
error_reason=e.error_reason,
|
|
175
|
+
error_description=e.error_description,
|
|
176
|
+
)
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
except RobotMissionStatusException as e:
|
|
180
|
+
failed_mission_error = ErrorMessage(
|
|
181
|
+
error_reason=e.error_reason,
|
|
182
|
+
error_description=e.error_description,
|
|
183
|
+
)
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
except RobotException as e:
|
|
187
|
+
failed_mission_error = ErrorMessage(
|
|
188
|
+
error_reason=e.error_reason,
|
|
189
|
+
error_description=e.error_description,
|
|
190
|
+
)
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
failed_mission_error = ErrorMessage(
|
|
195
|
+
error_reason=ErrorReason.RobotUnknownErrorException,
|
|
196
|
+
error_description=str(e),
|
|
197
|
+
)
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
return mission_status
|
|
201
|
+
|
|
202
|
+
raise RobotMissionStatusException(
|
|
203
|
+
error_description=(
|
|
204
|
+
failed_mission_error.error_description
|
|
205
|
+
if failed_mission_error
|
|
206
|
+
else "Mission status thread cancelled"
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _report_task_status(self, task: TASKS) -> None:
|
|
211
|
+
if task.status == TaskStatus.Failed:
|
|
212
|
+
self.logger.warning(
|
|
213
|
+
f"Task: {str(task.id)[:8]} was reported as failed by the robot"
|
|
214
|
+
)
|
|
215
|
+
elif task.status == TaskStatus.Successful:
|
|
216
|
+
self.logger.info(
|
|
217
|
+
f"{type(task).__name__} task: {str(task.id)[:8]} completed"
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
self.logger.info(
|
|
221
|
+
f"Task: {str(task.id)[:8]} was reported as {task.status} by the robot"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _finalize_mission_status(self):
|
|
225
|
+
fail_statuses = [
|
|
226
|
+
TaskStatus.Cancelled,
|
|
227
|
+
TaskStatus.Failed,
|
|
228
|
+
]
|
|
229
|
+
partially_fail_statuses = fail_statuses + [TaskStatus.PartiallySuccessful]
|
|
230
|
+
|
|
231
|
+
if len(self.current_mission.tasks) == 0:
|
|
232
|
+
self.current_mission.status = MissionStatus.Successful
|
|
233
|
+
elif all(task.status in fail_statuses for task in self.current_mission.tasks):
|
|
234
|
+
self.current_mission.error_message = ErrorMessage(
|
|
235
|
+
error_reason=None,
|
|
236
|
+
error_description="The mission failed because all tasks in the mission "
|
|
237
|
+
"failed",
|
|
238
|
+
)
|
|
239
|
+
self.current_mission.status = MissionStatus.Failed
|
|
240
|
+
elif any(
|
|
241
|
+
task.status in partially_fail_statuses
|
|
242
|
+
for task in self.current_mission.tasks
|
|
243
|
+
):
|
|
244
|
+
self.current_mission.status = MissionStatus.PartiallySuccessful
|
|
245
|
+
else:
|
|
246
|
+
self.current_mission.status = MissionStatus.Successful
|
|
247
|
+
|
|
248
|
+
def run(self) -> None:
|
|
249
|
+
|
|
250
|
+
self.task_iterator: Iterator[TASKS] = iter(self.current_mission.tasks)
|
|
251
|
+
current_task: Optional[TASKS] = get_next_task(self.task_iterator)
|
|
252
|
+
current_task.status = TaskStatus.NotStarted
|
|
253
|
+
|
|
254
|
+
last_mission_status: MissionStatus = MissionStatus.NotStarted
|
|
255
|
+
|
|
256
|
+
while not self.signal_thread_quitting.wait(
|
|
257
|
+
0
|
|
258
|
+
) or self.signal_mission_stopped.wait(0):
|
|
259
|
+
|
|
260
|
+
if current_task:
|
|
261
|
+
try:
|
|
262
|
+
new_task_status = self._get_task_status(current_task.id)
|
|
263
|
+
except RobotTaskStatusException as e:
|
|
264
|
+
self.logger.error(
|
|
265
|
+
"Failed to collect task status. Error description: %s",
|
|
266
|
+
e.error_description,
|
|
267
|
+
)
|
|
268
|
+
break
|
|
269
|
+
except Exception:
|
|
270
|
+
self.logger.exception("Failed to collect task status")
|
|
271
|
+
break
|
|
272
|
+
|
|
273
|
+
if current_task.status != new_task_status:
|
|
274
|
+
current_task.status = new_task_status
|
|
275
|
+
self._report_task_status(current_task)
|
|
276
|
+
publish_task_status(
|
|
277
|
+
self.mqtt_publisher, current_task, self.current_mission
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if is_finished(new_task_status):
|
|
281
|
+
if should_upload_inspections(current_task):
|
|
282
|
+
self.robot_service_events.request_inspection_upload.trigger_event(
|
|
283
|
+
(current_task, self.current_mission)
|
|
284
|
+
)
|
|
285
|
+
current_task = get_next_task(self.task_iterator)
|
|
286
|
+
if current_task is not None:
|
|
287
|
+
# This is not required, but does make reporting more responsive
|
|
288
|
+
current_task.status = TaskStatus.InProgress
|
|
289
|
+
self._report_task_status(current_task)
|
|
290
|
+
publish_task_status(
|
|
291
|
+
self.mqtt_publisher, current_task, self.current_mission
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
|
|
295
|
+
0
|
|
296
|
+
):
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
new_mission_status = self._get_mission_status(self.current_mission.id)
|
|
301
|
+
except RobotMissionStatusException as e:
|
|
302
|
+
self.logger.exception("Failed to collect mission status")
|
|
303
|
+
self.robot_service_events.mission_failed.trigger_event(
|
|
304
|
+
ErrorMessage(
|
|
305
|
+
error_reason=e.error_reason,
|
|
306
|
+
error_description=e.error_description,
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
break
|
|
310
|
+
if new_mission_status != last_mission_status:
|
|
311
|
+
self.current_mission.status = new_mission_status
|
|
312
|
+
last_mission_status = new_mission_status
|
|
313
|
+
publish_mission_status(self.mqtt_publisher, self.current_mission)
|
|
314
|
+
self.robot_service_events.mission_status_updated.trigger_event(
|
|
315
|
+
new_mission_status
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if new_mission_status == MissionStatus.Cancelled or (
|
|
319
|
+
new_mission_status
|
|
320
|
+
not in [MissionStatus.NotStarted, MissionStatus.InProgress]
|
|
321
|
+
and current_task is None
|
|
322
|
+
):
|
|
323
|
+
# Standardises final mission status report
|
|
324
|
+
mission_status = self.current_mission.status
|
|
325
|
+
self._finalize_mission_status()
|
|
326
|
+
if mission_status != self.current_mission.status:
|
|
327
|
+
publish_mission_status(self.mqtt_publisher, self.current_mission)
|
|
328
|
+
break
|
|
329
|
+
|
|
330
|
+
if self.signal_thread_quitting.wait(0) or self.signal_mission_stopped.wait(
|
|
331
|
+
0
|
|
332
|
+
):
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
336
|
+
self.shared_state.mission_id.trigger_event(None)
|
|
337
|
+
|
|
338
|
+
mission_stopped = self.signal_mission_stopped.wait(0)
|
|
339
|
+
|
|
340
|
+
if current_task:
|
|
341
|
+
current_task.status = (
|
|
342
|
+
TaskStatus.Cancelled if mission_stopped else TaskStatus.Failed
|
|
343
|
+
)
|
|
344
|
+
publish_task_status(self.mqtt_publisher, current_task, self.current_mission)
|
|
345
|
+
if self.current_mission.status not in [
|
|
346
|
+
MissionStatus.Cancelled,
|
|
347
|
+
MissionStatus.PartiallySuccessful,
|
|
348
|
+
MissionStatus.Failed,
|
|
349
|
+
MissionStatus.Successful,
|
|
350
|
+
]:
|
|
351
|
+
self.current_mission.status = MissionStatus.Cancelled
|
|
352
|
+
publish_mission_status(self.mqtt_publisher, self.current_mission)
|
|
353
|
+
if not mission_stopped:
|
|
354
|
+
self.robot_service_events.mission_status_updated.trigger_event(
|
|
355
|
+
self.current_mission.status
|
|
356
|
+
)
|
|
357
|
+
self.logger.info("Stopped monitoring mission")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from isar.config.settings import settings
|
|
7
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
8
|
+
ErrorMessage,
|
|
9
|
+
ErrorReason,
|
|
10
|
+
RobotActionException,
|
|
11
|
+
RobotException,
|
|
12
|
+
RobotNoMissionRunningException,
|
|
13
|
+
)
|
|
14
|
+
from robot_interface.robot_interface import RobotInterface
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RobotPauseMissionThread(Thread):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
robot: RobotInterface,
|
|
21
|
+
signal_thread_quitting: Event,
|
|
22
|
+
):
|
|
23
|
+
self.logger = logging.getLogger("robot")
|
|
24
|
+
self.robot: RobotInterface = robot
|
|
25
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
26
|
+
self.error_message: Optional[ErrorMessage] = None
|
|
27
|
+
Thread.__init__(self, name="Robot pause mission thread")
|
|
28
|
+
|
|
29
|
+
def run(self) -> None:
|
|
30
|
+
retries = 0
|
|
31
|
+
error: Optional[ErrorMessage] = None
|
|
32
|
+
while retries < settings.STATE_TRANSITION_NUM_RETIRES:
|
|
33
|
+
if self.signal_thread_quitting.wait(0):
|
|
34
|
+
return
|
|
35
|
+
try:
|
|
36
|
+
self.robot.pause()
|
|
37
|
+
except RobotNoMissionRunningException as e:
|
|
38
|
+
error = ErrorMessage(
|
|
39
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
40
|
+
)
|
|
41
|
+
break
|
|
42
|
+
except (RobotActionException, RobotException) as e:
|
|
43
|
+
self.logger.warning(
|
|
44
|
+
f"\nFailed to pause robot because: {e.error_description}"
|
|
45
|
+
f"\nAttempting to pause the robot again"
|
|
46
|
+
)
|
|
47
|
+
retries += 1
|
|
48
|
+
error = ErrorMessage(
|
|
49
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
50
|
+
)
|
|
51
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
52
|
+
continue
|
|
53
|
+
except Exception as e:
|
|
54
|
+
self.logger.error(
|
|
55
|
+
f"\nAn unexpected error occurred while pausing the robot: {e}"
|
|
56
|
+
)
|
|
57
|
+
error = ErrorMessage(
|
|
58
|
+
error_reason=ErrorReason.RobotUnknownErrorException,
|
|
59
|
+
error_description=str(e),
|
|
60
|
+
)
|
|
61
|
+
break
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
error_description = (
|
|
65
|
+
f"\nFailed to pause the robot after {retries} attempts because: "
|
|
66
|
+
f"{error.error_description}"
|
|
67
|
+
f"\nBe aware that the robot may still be moving even though a pause has "
|
|
68
|
+
"been attempted"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self.error_message = ErrorMessage(
|
|
72
|
+
error_reason=error.error_reason,
|
|
73
|
+
error_description=error_description,
|
|
74
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from isar.config.settings import settings
|
|
7
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
8
|
+
ErrorMessage,
|
|
9
|
+
RobotActionException,
|
|
10
|
+
RobotException,
|
|
11
|
+
RobotNoMissionRunningException,
|
|
12
|
+
)
|
|
13
|
+
from robot_interface.robot_interface import RobotInterface
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RobotResumeMissionThread(Thread):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
robot: RobotInterface,
|
|
20
|
+
signal_thread_quitting: Event,
|
|
21
|
+
):
|
|
22
|
+
self.logger = logging.getLogger("robot")
|
|
23
|
+
self.robot: RobotInterface = robot
|
|
24
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
25
|
+
self.error_message: Optional[ErrorMessage] = None
|
|
26
|
+
Thread.__init__(self, name="Robot resume mission thread")
|
|
27
|
+
|
|
28
|
+
def run(self) -> None:
|
|
29
|
+
retries = 0
|
|
30
|
+
error: Optional[ErrorMessage] = None
|
|
31
|
+
while retries < settings.STATE_TRANSITION_NUM_RETIRES:
|
|
32
|
+
if self.signal_thread_quitting.wait(0):
|
|
33
|
+
return
|
|
34
|
+
try:
|
|
35
|
+
self.robot.resume()
|
|
36
|
+
return
|
|
37
|
+
except (RobotActionException, RobotException) as e:
|
|
38
|
+
self.logger.warning(
|
|
39
|
+
f"Attempt {retries + 1} to resume mission failed: {e.error_description}"
|
|
40
|
+
f"\nAttempting to resume the robot again"
|
|
41
|
+
)
|
|
42
|
+
error = ErrorMessage(
|
|
43
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
44
|
+
)
|
|
45
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
46
|
+
retries += 1
|
|
47
|
+
continue
|
|
48
|
+
except RobotNoMissionRunningException as e:
|
|
49
|
+
self.logger.error(
|
|
50
|
+
f"Failed to resume mission: {e.error_reason}. {e.error_description}"
|
|
51
|
+
)
|
|
52
|
+
error = ErrorMessage(
|
|
53
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
54
|
+
)
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
error_description = (
|
|
58
|
+
f"\nFailed to resume the robot after {retries + 1} attempts because: "
|
|
59
|
+
f"{error.error_description}"
|
|
60
|
+
f"\nBe aware that the robot may still be moving even though a resume has "
|
|
61
|
+
"been attempted"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
self.error_message = ErrorMessage(
|
|
65
|
+
error_reason=error.error_reason,
|
|
66
|
+
error_description=error_description,
|
|
67
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from threading import Event, Thread
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
6
|
+
ErrorMessage,
|
|
7
|
+
ErrorReason,
|
|
8
|
+
RobotAlreadyHomeException,
|
|
9
|
+
RobotException,
|
|
10
|
+
RobotInfeasibleMissionException,
|
|
11
|
+
)
|
|
12
|
+
from robot_interface.models.mission.mission import Mission
|
|
13
|
+
from robot_interface.robot_interface import RobotInterface
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RobotStartMissionThread(Thread):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
robot: RobotInterface,
|
|
20
|
+
signal_thread_quitting: Event,
|
|
21
|
+
mission: Mission,
|
|
22
|
+
):
|
|
23
|
+
self.logger = logging.getLogger("robot")
|
|
24
|
+
self.robot: RobotInterface = robot
|
|
25
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
26
|
+
self.mission = mission
|
|
27
|
+
self.error_message: Optional[ErrorMessage] = None
|
|
28
|
+
Thread.__init__(self, name="Robot start mission thread")
|
|
29
|
+
|
|
30
|
+
def run(self):
|
|
31
|
+
if self.signal_thread_quitting.wait(0):
|
|
32
|
+
return
|
|
33
|
+
try:
|
|
34
|
+
self.robot.initiate_mission(self.mission)
|
|
35
|
+
except RobotAlreadyHomeException as e:
|
|
36
|
+
self.logger.info(
|
|
37
|
+
"Robot disregarded return to home mission as its already at home. Return home mission will be assumed successful without running."
|
|
38
|
+
)
|
|
39
|
+
self.error_message = ErrorMessage(
|
|
40
|
+
error_reason=e.error_reason,
|
|
41
|
+
error_description=e.error_description,
|
|
42
|
+
)
|
|
43
|
+
except RobotInfeasibleMissionException as e:
|
|
44
|
+
self.logger.error(
|
|
45
|
+
f"Mission is infeasible and cannot be scheduled because: {e.error_description}"
|
|
46
|
+
)
|
|
47
|
+
self.error_message = ErrorMessage(
|
|
48
|
+
error_reason=e.error_reason,
|
|
49
|
+
error_description=e.error_description,
|
|
50
|
+
)
|
|
51
|
+
except RobotException as e:
|
|
52
|
+
self.logger.warning(
|
|
53
|
+
f"Initiating mission failed " f"because: {e.error_description}"
|
|
54
|
+
)
|
|
55
|
+
self.error_message = ErrorMessage(
|
|
56
|
+
error_reason=e.error_reason,
|
|
57
|
+
error_description=e.error_description,
|
|
58
|
+
)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
self.logger.warning(
|
|
61
|
+
f"Initiating mission failed due to unknown exception: {e}"
|
|
62
|
+
)
|
|
63
|
+
self.error_message = ErrorMessage(
|
|
64
|
+
error_reason=ErrorReason.RobotUnknownErrorException,
|
|
65
|
+
error_description=str(e),
|
|
66
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
|
|
5
|
+
from isar.config.settings import settings
|
|
6
|
+
from isar.models.events import RobotServiceEvents, SharedState, StateMachineEvents
|
|
7
|
+
from robot_interface.models.exceptions.robot_exceptions import RobotException
|
|
8
|
+
from robot_interface.robot_interface import RobotInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RobotStatusThread(Thread):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
robot: RobotInterface,
|
|
15
|
+
signal_thread_quitting: Event,
|
|
16
|
+
shared_state: SharedState,
|
|
17
|
+
robot_service_events: RobotServiceEvents,
|
|
18
|
+
state_machine_events: StateMachineEvents,
|
|
19
|
+
):
|
|
20
|
+
self.logger = logging.getLogger("robot")
|
|
21
|
+
self.shared_state: SharedState = shared_state
|
|
22
|
+
self.robot_service_events: RobotServiceEvents = robot_service_events
|
|
23
|
+
self.state_machine_events: StateMachineEvents = state_machine_events
|
|
24
|
+
self.robot: RobotInterface = robot
|
|
25
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
26
|
+
self.robot_status_poll_interval: float = settings.ROBOT_API_STATUS_POLL_INTERVAL
|
|
27
|
+
self.last_robot_status_poll_time: float = time.time()
|
|
28
|
+
Thread.__init__(self, name="Robot status thread")
|
|
29
|
+
|
|
30
|
+
def stop(self) -> None:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
def _is_ready_to_poll_for_status(self) -> bool:
|
|
34
|
+
time_since_last_robot_status_poll = (
|
|
35
|
+
time.time() - self.last_robot_status_poll_time
|
|
36
|
+
)
|
|
37
|
+
return time_since_last_robot_status_poll > self.robot_status_poll_interval
|
|
38
|
+
|
|
39
|
+
def run(self):
|
|
40
|
+
if self.signal_thread_quitting.is_set():
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
thread_check_interval = settings.THREAD_CHECK_INTERVAL
|
|
44
|
+
|
|
45
|
+
while not self.signal_thread_quitting.wait(thread_check_interval):
|
|
46
|
+
|
|
47
|
+
if not self._is_ready_to_poll_for_status():
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
self.last_robot_status_poll_time = time.time()
|
|
52
|
+
|
|
53
|
+
robot_status = self.robot.robot_status()
|
|
54
|
+
|
|
55
|
+
if robot_status is not self.shared_state.robot_status.check():
|
|
56
|
+
self.shared_state.robot_status.update(robot_status)
|
|
57
|
+
self.robot_service_events.robot_status_changed.trigger_event(True)
|
|
58
|
+
except RobotException as e:
|
|
59
|
+
self.logger.error(f"Failed to retrieve robot status: {e}")
|
|
60
|
+
continue
|
|
61
|
+
self.logger.info("Exiting robot status thread")
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from isar.config.settings import settings
|
|
7
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
8
|
+
ErrorMessage,
|
|
9
|
+
ErrorReason,
|
|
10
|
+
RobotActionException,
|
|
11
|
+
RobotException,
|
|
12
|
+
RobotNoMissionRunningException,
|
|
13
|
+
)
|
|
14
|
+
from robot_interface.robot_interface import RobotInterface
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RobotStopMissionThread(Thread):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
robot: RobotInterface,
|
|
21
|
+
signal_thread_quitting: Event,
|
|
22
|
+
):
|
|
23
|
+
self.logger = logging.getLogger("robot")
|
|
24
|
+
self.robot: RobotInterface = robot
|
|
25
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
26
|
+
self.error_message: Optional[ErrorMessage] = None
|
|
27
|
+
Thread.__init__(self, name="Robot stop mission thread")
|
|
28
|
+
|
|
29
|
+
def run(self) -> None:
|
|
30
|
+
retries = 0
|
|
31
|
+
error: Optional[ErrorMessage] = None
|
|
32
|
+
while retries < settings.STOP_ROBOT_ATTEMPTS_LIMIT:
|
|
33
|
+
if self.signal_thread_quitting.wait(0):
|
|
34
|
+
self.error_message = ErrorMessage(
|
|
35
|
+
error_reason=ErrorReason.RobotUnknownErrorException,
|
|
36
|
+
error_description="Stop mission thread cancelled",
|
|
37
|
+
)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
self.robot.stop()
|
|
42
|
+
except RobotNoMissionRunningException:
|
|
43
|
+
return
|
|
44
|
+
except (RobotActionException, RobotException) as e:
|
|
45
|
+
self.logger.warning(
|
|
46
|
+
f"\nFailed to stop robot because: {e.error_description}"
|
|
47
|
+
f"\nAttempting to stop the robot again"
|
|
48
|
+
)
|
|
49
|
+
retries += 1
|
|
50
|
+
error = ErrorMessage(
|
|
51
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
52
|
+
)
|
|
53
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
error_description = (
|
|
59
|
+
f"\nFailed to stop the robot after {retries} attempts because: "
|
|
60
|
+
f"{error.error_description}"
|
|
61
|
+
f"\nBe aware that the robot may still be moving even though a stop has "
|
|
62
|
+
"been attempted"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.error_message = ErrorMessage(
|
|
66
|
+
error_reason=error.error_reason,
|
|
67
|
+
error_description=error_description,
|
|
68
|
+
)
|