isar 1.20.2__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/apis/api.py +148 -76
- isar/apis/models/__init__.py +0 -1
- isar/apis/models/models.py +21 -11
- isar/apis/models/start_mission_definition.py +110 -168
- isar/apis/robot_control/robot_controller.py +41 -0
- isar/apis/schedule/scheduling_controller.py +124 -162
- isar/apis/security/authentication.py +5 -5
- isar/config/certs/ca-cert.pem +33 -31
- isar/config/keyvault/keyvault_service.py +1 -1
- isar/config/log.py +45 -40
- isar/config/logging.conf +16 -31
- isar/config/open_telemetry.py +102 -0
- isar/config/predefined_mission_definition/default_exr.json +0 -2
- 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 +76 -111
- 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 -199
- 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 +57 -40
- isar/services/service_connections/mqtt/mqtt_client.py +47 -11
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
- isar/services/service_connections/mqtt/robot_info_publisher.py +3 -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 +219 -538
- isar/state_machine/states/__init__.py +0 -8
- 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 -247
- isar/state_machine/states/offline.py +51 -53
- 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 +21 -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 +70 -52
- isar/storage/local_storage.py +25 -12
- isar/storage/storage_interface.py +28 -7
- isar/storage/uploader.py +174 -55
- isar/storage/utilities.py +32 -29
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/METADATA +73 -110
- isar-1.34.9.dist-info/RECORD +135 -0
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/entry_points.txt +1 -0
- robot_interface/models/exceptions/robot_exceptions.py +91 -41
- robot_interface/models/initialize/__init__.py +0 -1
- robot_interface/models/inspection/__init__.py +0 -13
- robot_interface/models/inspection/inspection.py +42 -33
- robot_interface/models/mission/mission.py +14 -15
- robot_interface/models/mission/status.py +20 -26
- robot_interface/models/mission/task.py +154 -121
- 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 +119 -84
- robot_interface/telemetry/mqtt_client.py +74 -12
- robot_interface/telemetry/payloads.py +91 -13
- 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 -25
- 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/stid/__init__.py +0 -0
- isar/services/utilities/queue_utilities.py +0 -39
- isar/state_machine/states/idle.py +0 -85
- isar/state_machine/states/initialize.py +0 -71
- isar/state_machine/states/initiate.py +0 -142
- isar/state_machine/states/off.py +0 -18
- isar/state_machine/states/stop.py +0 -95
- isar/storage/slimm_storage.py +0 -191
- isar-1.20.2.dist-info/RECORD +0 -116
- robot_interface/models/initialize/initialize_params.py +0 -9
- robot_interface/models/mission/step.py +0 -234
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
- {isar-1.20.2.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from http import HTTPStatus
|
|
4
|
-
from
|
|
5
|
-
from typing import Any, List, Optional, Set
|
|
4
|
+
from typing import List, TypeVar
|
|
6
5
|
|
|
7
|
-
from alitra import Pose
|
|
8
6
|
from fastapi import HTTPException
|
|
9
|
-
from injector import inject
|
|
10
7
|
from requests import HTTPError
|
|
11
8
|
|
|
12
|
-
from isar.apis.models.models import ControlMissionResponse
|
|
9
|
+
from isar.apis.models.models import ControlMissionResponse, MaintenanceResponse
|
|
13
10
|
from isar.config.settings import settings
|
|
14
11
|
from isar.mission_planner.mission_planner_interface import (
|
|
15
12
|
MissionNotFoundError,
|
|
16
13
|
MissionPlannerError,
|
|
17
14
|
MissionPlannerInterface,
|
|
18
15
|
)
|
|
19
|
-
from isar.models.
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
from isar.models.events import (
|
|
17
|
+
APIEvent,
|
|
18
|
+
APIRequests,
|
|
19
|
+
EventConflictError,
|
|
20
|
+
Events,
|
|
21
|
+
EventTimeoutError,
|
|
22
|
+
SharedState,
|
|
23
|
+
)
|
|
24
|
+
from isar.services.service_connections.persistent_memory import (
|
|
25
|
+
change_persistent_robot_state_is_maintenance_mode,
|
|
26
|
+
)
|
|
22
27
|
from isar.state_machine.states_enum import States
|
|
23
28
|
from robot_interface.models.mission.mission import Mission
|
|
24
29
|
|
|
30
|
+
T1 = TypeVar("T1")
|
|
31
|
+
T2 = TypeVar("T2")
|
|
32
|
+
|
|
25
33
|
|
|
26
34
|
class SchedulingUtilities:
|
|
27
35
|
"""
|
|
@@ -29,14 +37,15 @@ class SchedulingUtilities:
|
|
|
29
37
|
required thread communication through queues to the state machine.
|
|
30
38
|
"""
|
|
31
39
|
|
|
32
|
-
@inject
|
|
33
40
|
def __init__(
|
|
34
41
|
self,
|
|
35
|
-
|
|
42
|
+
events: Events,
|
|
43
|
+
shared_state: SharedState,
|
|
36
44
|
mission_planner: MissionPlannerInterface,
|
|
37
45
|
queue_timeout: int = settings.QUEUE_TIMEOUT,
|
|
38
46
|
):
|
|
39
|
-
self.
|
|
47
|
+
self.api_events: APIRequests = events.api_requests
|
|
48
|
+
self.shared_state: SharedState = shared_state
|
|
40
49
|
self.mission_planner: MissionPlannerInterface = mission_planner
|
|
41
50
|
self.queue_timeout: int = queue_timeout
|
|
42
51
|
self.logger = logging.getLogger("api")
|
|
@@ -49,9 +58,8 @@ class SchedulingUtilities:
|
|
|
49
58
|
HTTPException 500 Internal Server Error
|
|
50
59
|
If the current state is not available on the queue
|
|
51
60
|
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
except Empty:
|
|
61
|
+
current_state = self.shared_state.state.check()
|
|
62
|
+
if current_state is None:
|
|
55
63
|
error_message: str = (
|
|
56
64
|
"Internal Server Error - Current state of the state machine is unknown"
|
|
57
65
|
)
|
|
@@ -59,6 +67,7 @@ class SchedulingUtilities:
|
|
|
59
67
|
raise HTTPException(
|
|
60
68
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
61
69
|
)
|
|
70
|
+
return current_state
|
|
62
71
|
|
|
63
72
|
def get_mission(self, mission_id: str) -> Mission:
|
|
64
73
|
"""Get the mission with mission_id from the current mission planner
|
|
@@ -87,6 +96,12 @@ class SchedulingUtilities:
|
|
|
87
96
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
88
97
|
detail="Could not plan mission",
|
|
89
98
|
)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self.logger.error(e)
|
|
101
|
+
raise HTTPException(
|
|
102
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
103
|
+
detail="Could not return mission",
|
|
104
|
+
)
|
|
90
105
|
|
|
91
106
|
def verify_robot_capable_of_mission(
|
|
92
107
|
self, mission: Mission, robot_capabilities: List[str]
|
|
@@ -98,15 +113,11 @@ class SchedulingUtilities:
|
|
|
98
113
|
HTTPException 400 Bad request
|
|
99
114
|
If the robot is not capable of performing mission
|
|
100
115
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for step in task.steps:
|
|
105
|
-
if not step.type in robot_capabilities:
|
|
106
|
-
is_capable = False
|
|
107
|
-
missing_capabilities.add(step.type)
|
|
116
|
+
missing_capabilities = {
|
|
117
|
+
task.type for task in mission.tasks if task.type not in robot_capabilities
|
|
118
|
+
}
|
|
108
119
|
|
|
109
|
-
if
|
|
120
|
+
if missing_capabilities:
|
|
110
121
|
error_message = (
|
|
111
122
|
f"Bad Request - Robot is not capable of performing mission."
|
|
112
123
|
f" Missing functionalities: {missing_capabilities}."
|
|
@@ -117,28 +128,61 @@ class SchedulingUtilities:
|
|
|
117
128
|
detail=error_message,
|
|
118
129
|
)
|
|
119
130
|
|
|
120
|
-
return
|
|
131
|
+
return True
|
|
121
132
|
|
|
122
133
|
def verify_state_machine_ready_to_receive_mission(self, state: States) -> bool:
|
|
123
|
-
"""Verify that the state machine is
|
|
134
|
+
"""Verify that the state machine is ready to receive a mission
|
|
124
135
|
|
|
125
136
|
Raises
|
|
126
137
|
------
|
|
127
138
|
HTTPException 409 Conflict
|
|
128
|
-
If state machine is not
|
|
139
|
+
If state machine is not home, robot standing still, awaiting next mission
|
|
140
|
+
return home paused or returning home and therefore cannot start a new mission
|
|
129
141
|
"""
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
if (
|
|
143
|
+
state == States.Home
|
|
144
|
+
or state == States.AwaitNextMission
|
|
145
|
+
or state == States.ReturningHome
|
|
146
|
+
or state == States.ReturnHomePaused
|
|
147
|
+
):
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
error_message = f"Conflict - Robot is not home, robot standing still, awaiting next mission or returning home - State: {state}"
|
|
151
|
+
self.logger.warning(error_message)
|
|
152
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
153
|
+
|
|
154
|
+
def verify_state_machine_ready_to_receive_return_home_mission(
|
|
155
|
+
self, state: States
|
|
156
|
+
) -> bool:
|
|
157
|
+
"""Verify that the state machine is ready to receive a return home mission
|
|
158
|
+
|
|
159
|
+
Raises
|
|
160
|
+
------
|
|
161
|
+
HTTPException 409 Conflict
|
|
162
|
+
If state machine is not home, robot standing still or awaiting next mission
|
|
163
|
+
and therefore cannot start a new return home mission
|
|
164
|
+
"""
|
|
165
|
+
if state == States.Home or state == States.AwaitNextMission:
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
error_message = f"Conflict - Robot is not home, robot standing still or awaiting next mission - State: {state}"
|
|
169
|
+
self.logger.warning(error_message)
|
|
170
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
171
|
+
|
|
172
|
+
def log_mission_overview(self, mission: Mission) -> None:
|
|
173
|
+
"""Log an overview of the tasks in a mission"""
|
|
174
|
+
log_statements: List[str] = []
|
|
175
|
+
for task in mission.tasks:
|
|
176
|
+
log_statements.append(
|
|
177
|
+
f"{type(task).__name__:<20} {str(task.id)[:8]:<32} -- {task.status}"
|
|
178
|
+
)
|
|
179
|
+
log_statement: str = "\n".join(log_statements)
|
|
135
180
|
|
|
136
|
-
|
|
181
|
+
self.logger.info("Started mission:\n%s", log_statement)
|
|
137
182
|
|
|
138
183
|
def start_mission(
|
|
139
184
|
self,
|
|
140
185
|
mission: Mission,
|
|
141
|
-
initial_pose: Optional[Pose],
|
|
142
186
|
) -> None:
|
|
143
187
|
"""Start mission
|
|
144
188
|
|
|
@@ -146,23 +190,84 @@ class SchedulingUtilities:
|
|
|
146
190
|
------
|
|
147
191
|
HTTTPException 408 Request timeout
|
|
148
192
|
If there is a timeout while communicating with the state machine
|
|
193
|
+
HTTPException 409 Conflict
|
|
194
|
+
If the state machine is not ready to receive a mission
|
|
195
|
+
HTTPException 500 Internal Server Error
|
|
196
|
+
If there is an unexpected error while sending the mission to the state machine
|
|
149
197
|
"""
|
|
150
198
|
try:
|
|
151
|
-
self.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
199
|
+
self.logger.info(
|
|
200
|
+
"Requesting to start mission:\n"
|
|
201
|
+
f" Mission ID: {mission.id}\n"
|
|
202
|
+
f" Mission Name: {mission.name}\n"
|
|
203
|
+
f" Number of Tasks: {len(mission.tasks)}"
|
|
204
|
+
)
|
|
205
|
+
mission_start_response = self._send_command(
|
|
206
|
+
deepcopy(mission),
|
|
207
|
+
self.api_events.start_mission,
|
|
208
|
+
)
|
|
209
|
+
if not mission_start_response.mission_started:
|
|
210
|
+
self.logger.warning(
|
|
211
|
+
f"Mission failed to start - {mission_start_response.mission_not_started_reason}"
|
|
212
|
+
)
|
|
213
|
+
raise HTTPException(
|
|
214
|
+
status_code=HTTPStatus.CONFLICT,
|
|
215
|
+
detail=mission_start_response.mission_not_started_reason,
|
|
216
|
+
)
|
|
217
|
+
except EventConflictError:
|
|
218
|
+
error_message = "Previous mission request is still being processed"
|
|
219
|
+
self.logger.warning(error_message)
|
|
220
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
221
|
+
except EventTimeoutError:
|
|
222
|
+
error_message = (
|
|
223
|
+
"State machine has entered a state which cannot start a mission"
|
|
224
|
+
)
|
|
225
|
+
self.logger.warning(error_message)
|
|
226
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
error_message = "Unexpected error while sending mission to state machine"
|
|
229
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
161
230
|
raise HTTPException(
|
|
162
231
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
163
232
|
)
|
|
233
|
+
self.log_mission_overview(mission)
|
|
164
234
|
self.logger.info("OK - Mission started in ISAR")
|
|
165
235
|
|
|
236
|
+
def return_home(
|
|
237
|
+
self,
|
|
238
|
+
) -> None:
|
|
239
|
+
"""Start return home mission
|
|
240
|
+
|
|
241
|
+
Raises
|
|
242
|
+
------
|
|
243
|
+
HTTTPException 408 Request timeout
|
|
244
|
+
If there is a timeout while communicating with the state machine
|
|
245
|
+
HTTPException 409 Conflict
|
|
246
|
+
If the state machine is not ready to receive a return home mission
|
|
247
|
+
HTTPException 500 Internal Server Error
|
|
248
|
+
If there is an unexpected error while sending the return home command
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
self._send_command(
|
|
252
|
+
True,
|
|
253
|
+
self.api_events.return_home,
|
|
254
|
+
)
|
|
255
|
+
except EventConflictError:
|
|
256
|
+
error_message = "Previous return home request is still being processed"
|
|
257
|
+
self.logger.warning(error_message)
|
|
258
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
259
|
+
except EventTimeoutError:
|
|
260
|
+
error_message = "State machine has entered a state which cannot start a return home mission"
|
|
261
|
+
self.logger.warning(error_message)
|
|
262
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
error_message = "Unexpected error while sending return home command"
|
|
265
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
266
|
+
raise HTTPException(
|
|
267
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
268
|
+
)
|
|
269
|
+
self.logger.info("OK - Return home mission started in ISAR")
|
|
270
|
+
|
|
166
271
|
def pause_mission(self) -> ControlMissionResponse:
|
|
167
272
|
"""Pause mission
|
|
168
273
|
|
|
@@ -170,17 +275,37 @@ class SchedulingUtilities:
|
|
|
170
275
|
------
|
|
171
276
|
HTTTPException 408 Request timeout
|
|
172
277
|
If there is a timeout while communicating with the state machine
|
|
278
|
+
HTTPException 409 Conflict
|
|
279
|
+
If the state machine is not in a state which can pause a mission
|
|
173
280
|
"""
|
|
174
281
|
try:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
282
|
+
response = self._send_command(True, self.api_events.pause_mission)
|
|
283
|
+
if not response.success:
|
|
284
|
+
self.logger.warning(
|
|
285
|
+
f"Mission failed to pause - {response.failure_reason}"
|
|
286
|
+
)
|
|
287
|
+
raise HTTPException(
|
|
288
|
+
status_code=HTTPStatus.CONFLICT,
|
|
289
|
+
detail=response.failure_reason,
|
|
290
|
+
)
|
|
291
|
+
self.logger.info("OK - Mission successfully paused")
|
|
292
|
+
return response
|
|
293
|
+
except EventConflictError:
|
|
294
|
+
error_message = "Previous pause mission request is still being processed"
|
|
295
|
+
self.logger.warning(error_message)
|
|
296
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
297
|
+
except EventTimeoutError:
|
|
298
|
+
error_message = (
|
|
299
|
+
"State machine has entered a state which cannot pause a mission"
|
|
300
|
+
)
|
|
301
|
+
self.logger.warning(error_message)
|
|
302
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
error_message = "Unexpected error while pausing mission"
|
|
305
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
179
306
|
raise HTTPException(
|
|
180
307
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
181
308
|
)
|
|
182
|
-
finally:
|
|
183
|
-
self.logger.info("OK - Mission successfully paused")
|
|
184
309
|
|
|
185
310
|
def resume_mission(self) -> ControlMissionResponse:
|
|
186
311
|
"""Resume mission
|
|
@@ -189,47 +314,250 @@ class SchedulingUtilities:
|
|
|
189
314
|
------
|
|
190
315
|
HTTTPException 408 Request timeout
|
|
191
316
|
If there is a timeout while communicating with the state machine
|
|
317
|
+
HTTPException 409 Conflict
|
|
318
|
+
If the state machine is not in a state which can resume a mission
|
|
319
|
+
HTTPException 500 Internal Server Error
|
|
320
|
+
If there is an unexpected error while resuming the mission
|
|
192
321
|
"""
|
|
193
322
|
try:
|
|
194
|
-
|
|
195
|
-
|
|
323
|
+
response = self._send_command(True, self.api_events.resume_mission)
|
|
324
|
+
self.logger.info("OK - Mission successfully resumed")
|
|
325
|
+
return response
|
|
326
|
+
except EventConflictError:
|
|
327
|
+
error_message = "Previous resume mission request is still being processed"
|
|
328
|
+
self.logger.warning(error_message)
|
|
329
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
330
|
+
except EventTimeoutError:
|
|
196
331
|
error_message = "Internal Server Error - Failed to resume mission"
|
|
197
332
|
self.logger.error(error_message)
|
|
198
333
|
raise HTTPException(
|
|
199
334
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
200
335
|
)
|
|
201
|
-
|
|
202
|
-
|
|
336
|
+
except Exception as e:
|
|
337
|
+
error_message = "Unexpected error while resuming mission"
|
|
338
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
339
|
+
raise HTTPException(
|
|
340
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
341
|
+
)
|
|
203
342
|
|
|
204
|
-
def stop_mission(self) -> ControlMissionResponse:
|
|
343
|
+
def stop_mission(self, mission_id: str = "") -> ControlMissionResponse:
|
|
205
344
|
"""Stop mission
|
|
206
345
|
|
|
207
346
|
Raises
|
|
208
347
|
------
|
|
209
|
-
|
|
348
|
+
HTTPException 404 Not Found
|
|
349
|
+
If the mission_id was not known to Isar
|
|
350
|
+
HTTPException 503 Service Unavailable
|
|
351
|
+
The request was understood, but attempting to stop the mission failed
|
|
352
|
+
HTTPException 408 Request timeout
|
|
210
353
|
If there is a timeout while communicating with the state machine
|
|
354
|
+
HTTPException 409 Conflict
|
|
355
|
+
If the state machine is not in a state which can stop a mission
|
|
356
|
+
HTTPException 500 Internal Server Error
|
|
357
|
+
If there is an unexpected error while stopping the mission
|
|
211
358
|
"""
|
|
212
359
|
try:
|
|
213
360
|
stop_mission_response: ControlMissionResponse = self._send_command(
|
|
214
|
-
|
|
361
|
+
mission_id, self.api_events.stop_mission
|
|
215
362
|
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
363
|
+
|
|
364
|
+
if not stop_mission_response.success:
|
|
365
|
+
error_message = (
|
|
366
|
+
f"Failed to stop mission: {stop_mission_response.failure_reason}"
|
|
367
|
+
)
|
|
368
|
+
self.logger.error(error_message)
|
|
369
|
+
raise HTTPException(
|
|
370
|
+
status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=error_message
|
|
371
|
+
)
|
|
372
|
+
except EventConflictError:
|
|
373
|
+
error_message = "Previous stop mission request is still being processed"
|
|
374
|
+
self.logger.warning(error_message)
|
|
375
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
376
|
+
except EventTimeoutError:
|
|
377
|
+
error_message = (
|
|
378
|
+
"State machine has entered a state which cannot stop a mission"
|
|
379
|
+
)
|
|
380
|
+
self.logger.warning(error_message)
|
|
381
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
382
|
+
except HTTPException as e:
|
|
383
|
+
raise e
|
|
384
|
+
except Exception as e:
|
|
385
|
+
error_message = "Unexpected error while stopping mission"
|
|
386
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
219
387
|
raise HTTPException(
|
|
220
388
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
221
389
|
)
|
|
222
390
|
self.logger.info("OK - Mission successfully stopped")
|
|
223
391
|
return stop_mission_response
|
|
224
392
|
|
|
225
|
-
def
|
|
226
|
-
|
|
393
|
+
def release_intervention_needed(self) -> None:
|
|
394
|
+
"""Release intervention needed state
|
|
395
|
+
|
|
396
|
+
Raises
|
|
397
|
+
------
|
|
398
|
+
HTTPException 409 Conflict
|
|
399
|
+
If the state machine is not in intervention needed state
|
|
400
|
+
HTTPException 408 Request timeout
|
|
401
|
+
If there is a timeout while communicating with the state machine
|
|
402
|
+
HTTPException 500 Internal Server Error
|
|
403
|
+
If the intervention needed state could not be released
|
|
404
|
+
"""
|
|
405
|
+
try:
|
|
406
|
+
self._send_command(True, self.api_events.release_intervention_needed)
|
|
407
|
+
self.logger.info("OK - Intervention needed state released")
|
|
408
|
+
except EventConflictError:
|
|
409
|
+
error_message = (
|
|
410
|
+
"Previous release intervention needed request is still being processed"
|
|
411
|
+
)
|
|
412
|
+
self.logger.warning(error_message)
|
|
413
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
414
|
+
except EventTimeoutError:
|
|
415
|
+
error_message = "Cannot release intervention needed as it is not in intervention needed state"
|
|
416
|
+
self.logger.warning(error_message)
|
|
417
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
418
|
+
except Exception as e:
|
|
419
|
+
error_message = "Unexpected error while releasing intervention needed state"
|
|
420
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
421
|
+
raise HTTPException(
|
|
422
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
def lock_down_robot(self) -> None:
|
|
426
|
+
"""Lock down robot
|
|
427
|
+
|
|
428
|
+
Raises
|
|
429
|
+
------
|
|
430
|
+
HTTPException 409 Conflict
|
|
431
|
+
If the state machine is not in a state which can be locked down
|
|
432
|
+
HTTPException 500 Internal Server Error
|
|
433
|
+
If the robot could not be locked down
|
|
434
|
+
"""
|
|
227
435
|
try:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
436
|
+
self._send_command(True, self.api_events.send_to_lockdown)
|
|
437
|
+
self.logger.info("OK - Robot sent into lockdown")
|
|
438
|
+
except EventConflictError:
|
|
439
|
+
error_message = "Previous lockdown request is still being processed"
|
|
440
|
+
self.logger.warning(error_message)
|
|
441
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
442
|
+
except EventTimeoutError:
|
|
443
|
+
error_message = "Cannot send robot to lockdown as it is already in lockdown"
|
|
444
|
+
self.logger.warning(error_message)
|
|
445
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
error_message = "Unexpected error while locking down robot"
|
|
448
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
449
|
+
raise HTTPException(
|
|
450
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
231
451
|
)
|
|
232
|
-
|
|
233
|
-
|
|
452
|
+
|
|
453
|
+
def release_robot_lockdown(self) -> None:
|
|
454
|
+
"""Release robot from lockdown
|
|
455
|
+
|
|
456
|
+
Raises
|
|
457
|
+
------
|
|
458
|
+
HTTPException 409 Conflict
|
|
459
|
+
If the state machine is not in lockdown
|
|
460
|
+
HTTPException 500 Internal Server Error
|
|
461
|
+
If the robot could not be released from lockdown
|
|
462
|
+
"""
|
|
463
|
+
try:
|
|
464
|
+
self._send_command(True, self.api_events.release_from_lockdown)
|
|
465
|
+
self.logger.info("OK - Robot released form lockdown")
|
|
466
|
+
except EventConflictError:
|
|
467
|
+
error_message = (
|
|
468
|
+
"Previous release robot from lockdown request is still being processed"
|
|
469
|
+
)
|
|
470
|
+
self.logger.warning(error_message)
|
|
471
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
472
|
+
except EventTimeoutError:
|
|
473
|
+
error_message = (
|
|
474
|
+
"Cannot release robot from lockdown as it is not in lockdown"
|
|
475
|
+
)
|
|
476
|
+
self.logger.warning(error_message)
|
|
477
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
478
|
+
except Exception as e:
|
|
479
|
+
error_message = "Unexpected error while releasing robot from lockdown"
|
|
480
|
+
self.logger.error(f"{error_message}. Exception: {e}")
|
|
481
|
+
raise HTTPException(
|
|
482
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def set_maintenance_mode(self) -> None:
|
|
486
|
+
"""Set maintenance mode"""
|
|
487
|
+
try:
|
|
488
|
+
if settings.PERSISTENT_STORAGE_CONNECTION_STRING != "":
|
|
489
|
+
change_persistent_robot_state_is_maintenance_mode(
|
|
490
|
+
settings.PERSISTENT_STORAGE_CONNECTION_STRING,
|
|
491
|
+
settings.ISAR_ID,
|
|
492
|
+
value=True,
|
|
493
|
+
)
|
|
494
|
+
response: MaintenanceResponse = self._send_command(
|
|
495
|
+
True, self.api_events.set_maintenance_mode
|
|
496
|
+
)
|
|
497
|
+
if response.failure_reason is not None:
|
|
498
|
+
self.logger.warning(response.failure_reason)
|
|
499
|
+
raise HTTPException(
|
|
500
|
+
status_code=HTTPStatus.CONFLICT,
|
|
501
|
+
detail="Conflict attempting to set maintenance mode",
|
|
502
|
+
)
|
|
503
|
+
self.logger.info("OK - Robot sent into maintenance mode")
|
|
504
|
+
except EventConflictError:
|
|
505
|
+
error_message = "Previous maintenance request is still being processed"
|
|
506
|
+
self.logger.warning(error_message)
|
|
507
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
508
|
+
except EventTimeoutError:
|
|
509
|
+
error_message = (
|
|
510
|
+
"Cannot send robot to maintenance as it is already in maintenance"
|
|
511
|
+
)
|
|
512
|
+
self.logger.warning(error_message)
|
|
513
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
514
|
+
except Exception as e:
|
|
515
|
+
error_message = "Unexpected error while setting maintenance mode"
|
|
516
|
+
self.logger.error(f"{error_message} Exception: {e}")
|
|
517
|
+
raise HTTPException(
|
|
518
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def release_maintenance_mode(self) -> None:
|
|
522
|
+
"""Release robot from maintenance mode"""
|
|
523
|
+
try:
|
|
524
|
+
self._send_command(True, self.api_events.release_from_maintenance_mode)
|
|
525
|
+
if settings.PERSISTENT_STORAGE_CONNECTION_STRING != "":
|
|
526
|
+
change_persistent_robot_state_is_maintenance_mode(
|
|
527
|
+
settings.PERSISTENT_STORAGE_CONNECTION_STRING,
|
|
528
|
+
settings.ISAR_ID,
|
|
529
|
+
value=False,
|
|
530
|
+
)
|
|
531
|
+
self.logger.info("OK - Robot released form maintenance mode")
|
|
532
|
+
except EventConflictError:
|
|
533
|
+
error_message = "Previous release robot from maintenance request is still being processed"
|
|
534
|
+
self.logger.warning(error_message)
|
|
535
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
536
|
+
except EventTimeoutError:
|
|
537
|
+
error_message = (
|
|
538
|
+
"Cannot release robot from maintenance as it is not in maintenance"
|
|
539
|
+
)
|
|
540
|
+
self.logger.warning(error_message)
|
|
541
|
+
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
542
|
+
except Exception as e:
|
|
543
|
+
error_message = "Unexpected error while releasing maintenance mode"
|
|
544
|
+
self.logger.error(f"{error_message} Exception: {e}")
|
|
545
|
+
raise HTTPException(
|
|
546
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
def _send_command(self, input: T1, api_event: APIEvent[T1, T2]) -> T2:
|
|
550
|
+
if api_event.request.has_event():
|
|
551
|
+
raise EventConflictError("API event has already been sent")
|
|
552
|
+
|
|
553
|
+
try:
|
|
554
|
+
api_event.request.trigger_event(input, timeout=1)
|
|
555
|
+
return api_event.response.consume_event(timeout=self.queue_timeout)
|
|
556
|
+
except EventTimeoutError as e:
|
|
557
|
+
self.logger.error("Queue timed out")
|
|
558
|
+
api_event.request.clear_event()
|
|
234
559
|
self.logger.error("No output received for command to state machine")
|
|
235
560
|
raise e
|
|
561
|
+
finally:
|
|
562
|
+
api_event.request.clear_event()
|
|
563
|
+
api_event.response.clear_event()
|