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