isar 1.30.5__py3-none-any.whl → 1.31.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of isar might be problematic. Click here for more details.
- isar/apis/api.py +7 -51
- isar/apis/models/models.py +1 -0
- isar/apis/models/start_mission_definition.py +4 -0
- isar/apis/robot_control/robot_controller.py +0 -2
- isar/apis/schedule/scheduling_controller.py +12 -4
- isar/config/log.py +8 -29
- isar/config/open_telemetry.py +62 -0
- isar/config/settings.py +12 -0
- isar/eventhandlers/eventhandler.py +93 -0
- isar/mission_planner/local_planner.py +0 -3
- isar/models/events.py +118 -0
- isar/modules.py +1 -1
- isar/robot/robot.py +16 -21
- isar/robot/robot_start_mission.py +8 -14
- isar/robot/robot_status.py +2 -3
- isar/robot/robot_stop_mission.py +3 -9
- isar/robot/robot_task_status.py +3 -7
- isar/script.py +4 -1
- isar/services/utilities/robot_utilities.py +0 -3
- isar/services/utilities/scheduling_utilities.py +45 -35
- isar/state_machine/state_machine.py +79 -11
- isar/state_machine/states/await_next_mission.py +46 -11
- isar/state_machine/states/blocked_protective_stop.py +24 -15
- isar/state_machine/states/home.py +40 -9
- isar/state_machine/states/monitor.py +83 -14
- isar/state_machine/states/offline.py +25 -13
- isar/state_machine/states/paused.py +24 -38
- isar/state_machine/states/returning_home.py +75 -14
- isar/state_machine/states/robot_standing_still.py +41 -11
- isar/state_machine/states/stopping.py +52 -67
- isar/state_machine/states/unknown_status.py +37 -64
- isar/state_machine/transitions/functions/pause.py +39 -10
- isar/state_machine/transitions/functions/resume.py +44 -15
- isar/state_machine/transitions/functions/robot_status.py +4 -5
- isar/state_machine/transitions/functions/stop.py +3 -12
- isar/state_machine/transitions/mission.py +12 -2
- isar/state_machine/transitions/return_home.py +1 -1
- isar/state_machine/utils/common_event_handlers.py +166 -0
- isar/storage/blob_storage.py +0 -2
- isar/storage/uploader.py +1 -4
- {isar-1.30.5.dist-info → isar-1.31.1.dist-info}/METADATA +7 -4
- {isar-1.30.5.dist-info → isar-1.31.1.dist-info}/RECORD +48 -58
- robot_interface/models/mission/task.py +0 -3
- robot_interface/robot_interface.py +1 -4
- isar/models/communication/__init__.py +0 -0
- isar/models/communication/message.py +0 -8
- isar/models/communication/queues/__init__.py +0 -0
- isar/models/communication/queues/events.py +0 -58
- isar/models/communication/queues/queue_io.py +0 -12
- isar/models/communication/queues/queue_timeout_error.py +0 -2
- isar/models/communication/queues/queue_utils.py +0 -38
- isar/models/communication/queues/status_queue.py +0 -22
- isar/models/mission_metadata/__init__.py +0 -0
- isar/services/service_connections/stid/__init__.py +0 -0
- isar/services/utilities/queue_utilities.py +0 -39
- isar/state_machine/generic_states/idle.py +0 -133
- isar/state_machine/generic_states/ongoing_mission.py +0 -294
- isar/state_machine/generic_states/robot_unavailable.py +0 -61
- {isar-1.30.5.dist-info → isar-1.31.1.dist-info}/WHEEL +0 -0
- {isar-1.30.5.dist-info → isar-1.31.1.dist-info}/entry_points.txt +0 -0
- {isar-1.30.5.dist-info → isar-1.31.1.dist-info}/licenses/LICENSE +0 -0
- {isar-1.30.5.dist-info → isar-1.31.1.dist-info}/top_level.txt +0 -0
isar/robot/robot.py
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
3
|
-
from threading import Event
|
|
2
|
+
from threading import Event as ThreadEvent
|
|
4
3
|
from typing import Optional
|
|
5
4
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from isar.models.communication.queues.events import (
|
|
5
|
+
from isar.models.events import (
|
|
6
|
+
Event,
|
|
9
7
|
Events,
|
|
10
8
|
RobotServiceEvents,
|
|
11
9
|
SharedState,
|
|
12
10
|
StateMachineEvents,
|
|
13
11
|
)
|
|
14
|
-
from isar.models.communication.queues.queue_utils import check_for_event, trigger_event
|
|
15
12
|
from isar.robot.robot_start_mission import RobotStartMissionThread
|
|
16
13
|
from isar.robot.robot_status import RobotStatusThread
|
|
17
14
|
from isar.robot.robot_stop_mission import RobotStopMissionThread
|
|
18
15
|
from isar.robot.robot_task_status import RobotTaskStatusThread
|
|
19
16
|
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage, ErrorReason
|
|
17
|
+
from robot_interface.models.mission.mission import Mission
|
|
20
18
|
from robot_interface.robot_interface import RobotInterface
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
class Robot(object):
|
|
24
|
-
@inject
|
|
25
22
|
def __init__(
|
|
26
23
|
self, events: Events, robot: RobotInterface, shared_state: SharedState
|
|
27
24
|
) -> None:
|
|
@@ -34,7 +31,7 @@ class Robot(object):
|
|
|
34
31
|
self.robot_status_thread: Optional[RobotStatusThread] = None
|
|
35
32
|
self.robot_task_status_thread: Optional[RobotTaskStatusThread] = None
|
|
36
33
|
self.stop_mission_thread: Optional[RobotStopMissionThread] = None
|
|
37
|
-
self.signal_thread_quitting:
|
|
34
|
+
self.signal_thread_quitting: ThreadEvent = ThreadEvent()
|
|
38
35
|
|
|
39
36
|
def stop(self) -> None:
|
|
40
37
|
self.signal_thread_quitting.set()
|
|
@@ -56,8 +53,8 @@ class Robot(object):
|
|
|
56
53
|
self.robot_task_status_thread = None
|
|
57
54
|
self.start_mission_thread = None
|
|
58
55
|
|
|
59
|
-
def
|
|
60
|
-
start_mission =
|
|
56
|
+
def _start_mission_event_handler(self, event: Event[Mission]) -> None:
|
|
57
|
+
start_mission = event.consume_event()
|
|
61
58
|
if start_mission is not None:
|
|
62
59
|
if (
|
|
63
60
|
self.start_mission_thread is not None
|
|
@@ -75,8 +72,8 @@ class Robot(object):
|
|
|
75
72
|
)
|
|
76
73
|
self.start_mission_thread.start()
|
|
77
74
|
|
|
78
|
-
def
|
|
79
|
-
task_id: str =
|
|
75
|
+
def _task_status_request_handler(self, event: Event[str]) -> None:
|
|
76
|
+
task_id: str = event.consume_event()
|
|
80
77
|
if task_id:
|
|
81
78
|
self.robot_task_status_thread = RobotTaskStatusThread(
|
|
82
79
|
self.robot_service_events,
|
|
@@ -86,8 +83,8 @@ class Robot(object):
|
|
|
86
83
|
)
|
|
87
84
|
self.robot_task_status_thread.start()
|
|
88
85
|
|
|
89
|
-
def
|
|
90
|
-
if
|
|
86
|
+
def _stop_mission_request_handler(self, event: Event[bool]) -> None:
|
|
87
|
+
if event.consume_event():
|
|
91
88
|
if (
|
|
92
89
|
self.stop_mission_thread is not None
|
|
93
90
|
and self.stop_mission_thread.is_alive()
|
|
@@ -105,8 +102,8 @@ class Robot(object):
|
|
|
105
102
|
error_reason=ErrorReason.RobotStillStartingMissionException,
|
|
106
103
|
error_description=error_description,
|
|
107
104
|
)
|
|
108
|
-
trigger_event(
|
|
109
|
-
|
|
105
|
+
self.robot_service_events.mission_failed_to_stop.trigger_event(
|
|
106
|
+
error_message
|
|
110
107
|
)
|
|
111
108
|
return
|
|
112
109
|
self.stop_mission_thread = RobotStopMissionThread(
|
|
@@ -121,14 +118,12 @@ class Robot(object):
|
|
|
121
118
|
self.robot_status_thread.start()
|
|
122
119
|
|
|
123
120
|
while not self.signal_thread_quitting.wait(0):
|
|
124
|
-
self.
|
|
125
|
-
self.state_machine_events.start_mission
|
|
126
|
-
)
|
|
121
|
+
self._start_mission_event_handler(self.state_machine_events.start_mission)
|
|
127
122
|
|
|
128
|
-
self.
|
|
123
|
+
self._task_status_request_handler(
|
|
129
124
|
self.state_machine_events.task_status_request
|
|
130
125
|
)
|
|
131
126
|
|
|
132
|
-
self.
|
|
127
|
+
self._stop_mission_request_handler(self.state_machine_events.stop_mission)
|
|
133
128
|
|
|
134
129
|
self.logger.info("Exiting robot service main thread")
|
|
@@ -2,11 +2,7 @@ import logging
|
|
|
2
2
|
from threading import Event, Thread
|
|
3
3
|
|
|
4
4
|
from isar.config.settings import settings
|
|
5
|
-
from isar.models.
|
|
6
|
-
from isar.models.communication.queues.queue_utils import (
|
|
7
|
-
trigger_event,
|
|
8
|
-
trigger_event_without_data,
|
|
9
|
-
)
|
|
5
|
+
from isar.models.events import RobotServiceEvents
|
|
10
6
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
11
7
|
ErrorMessage,
|
|
12
8
|
RobotException,
|
|
@@ -44,13 +40,13 @@ class RobotStartMissionThread(Thread):
|
|
|
44
40
|
self.logger.error(
|
|
45
41
|
f"Mission is infeasible and cannot be scheduled because: {e.error_description}"
|
|
46
42
|
)
|
|
47
|
-
trigger_event(
|
|
48
|
-
self.robot_service_events.mission_failed,
|
|
43
|
+
self.robot_service_events.mission_failed.trigger_event(
|
|
49
44
|
ErrorMessage(
|
|
50
45
|
error_reason=e.error_reason,
|
|
51
46
|
error_description=e.error_description,
|
|
52
|
-
)
|
|
47
|
+
)
|
|
53
48
|
)
|
|
49
|
+
|
|
54
50
|
break
|
|
55
51
|
except RobotException as e:
|
|
56
52
|
retries += 1
|
|
@@ -66,12 +62,11 @@ class RobotStartMissionThread(Thread):
|
|
|
66
62
|
f"{e.error_description}"
|
|
67
63
|
)
|
|
68
64
|
|
|
69
|
-
trigger_event(
|
|
70
|
-
self.robot_service_events.mission_failed,
|
|
65
|
+
self.robot_service_events.mission_failed.trigger_event(
|
|
71
66
|
ErrorMessage(
|
|
72
67
|
error_reason=e.error_reason,
|
|
73
68
|
error_description=e.error_description,
|
|
74
|
-
)
|
|
69
|
+
)
|
|
75
70
|
)
|
|
76
71
|
break
|
|
77
72
|
|
|
@@ -79,12 +74,11 @@ class RobotStartMissionThread(Thread):
|
|
|
79
74
|
|
|
80
75
|
started_mission = True
|
|
81
76
|
except RobotInfeasibleMissionException as e:
|
|
82
|
-
trigger_event(
|
|
83
|
-
self.robot_service_events.mission_failed,
|
|
77
|
+
self.robot_service_events.mission_failed.trigger_event(
|
|
84
78
|
ErrorMessage(
|
|
85
79
|
error_reason=e.error_reason, error_description=e.error_description
|
|
86
80
|
),
|
|
87
81
|
)
|
|
88
82
|
|
|
89
83
|
if started_mission:
|
|
90
|
-
|
|
84
|
+
self.robot_service_events.mission_started.trigger_event(True)
|
isar/robot/robot_status.py
CHANGED
|
@@ -3,8 +3,7 @@ import time
|
|
|
3
3
|
from threading import Event, Thread
|
|
4
4
|
|
|
5
5
|
from isar.config.settings import settings
|
|
6
|
-
from isar.models.
|
|
7
|
-
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
6
|
+
from isar.models.events import SharedState
|
|
8
7
|
from robot_interface.models.exceptions.robot_exceptions import RobotException
|
|
9
8
|
from robot_interface.robot_interface import RobotInterface
|
|
10
9
|
|
|
@@ -49,7 +48,7 @@ class RobotStatusThread(Thread):
|
|
|
49
48
|
self.last_robot_status_poll_time = time.time()
|
|
50
49
|
|
|
51
50
|
robot_status = self.robot.robot_status()
|
|
52
|
-
|
|
51
|
+
self.shared_state.robot_status.update(robot_status)
|
|
53
52
|
except RobotException as e:
|
|
54
53
|
self.logger.error(f"Failed to retrieve robot status: {e}")
|
|
55
54
|
continue
|
isar/robot/robot_stop_mission.py
CHANGED
|
@@ -4,11 +4,7 @@ from threading import Event, Thread
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from isar.config.settings import settings
|
|
7
|
-
from isar.models.
|
|
8
|
-
from isar.models.communication.queues.queue_utils import (
|
|
9
|
-
trigger_event,
|
|
10
|
-
trigger_event_without_data,
|
|
11
|
-
)
|
|
7
|
+
from isar.models.events import RobotServiceEvents
|
|
12
8
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
13
9
|
ErrorMessage,
|
|
14
10
|
RobotActionException,
|
|
@@ -51,9 +47,7 @@ class RobotStopMissionThread(Thread):
|
|
|
51
47
|
time.sleep(settings.FSM_SLEEP_TIME)
|
|
52
48
|
continue
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
self.robot_service_events.mission_successfully_stopped
|
|
56
|
-
)
|
|
50
|
+
self.robot_service_events.mission_successfully_stopped.trigger_event(True)
|
|
57
51
|
return
|
|
58
52
|
|
|
59
53
|
error_description = (
|
|
@@ -68,4 +62,4 @@ class RobotStopMissionThread(Thread):
|
|
|
68
62
|
error_description=error_description,
|
|
69
63
|
)
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
self.robot_service_events.mission_failed_to_stop.trigger_event(error_message)
|
isar/robot/robot_task_status.py
CHANGED
|
@@ -4,8 +4,7 @@ from threading import Event, Thread
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from isar.config.settings import settings
|
|
7
|
-
from isar.models.
|
|
8
|
-
from isar.models.communication.queues.queue_utils import trigger_event
|
|
7
|
+
from isar.models.events import RobotServiceEvents
|
|
9
8
|
from isar.services.utilities.threaded_request import ThreadedRequest
|
|
10
9
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
11
10
|
ErrorMessage,
|
|
@@ -82,10 +81,7 @@ class RobotTaskStatusThread(Thread):
|
|
|
82
81
|
)
|
|
83
82
|
break
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
self.robot_service_events.task_status_updated.trigger_event(task_status)
|
|
86
85
|
return
|
|
87
86
|
|
|
88
|
-
trigger_event(
|
|
89
|
-
self.robot_service_events.task_status_failed,
|
|
90
|
-
failed_task_error,
|
|
91
|
-
)
|
|
87
|
+
self.robot_service_events.task_status_failed.trigger_event(failed_task_error)
|
isar/script.py
CHANGED
|
@@ -8,8 +8,9 @@ from typing import Any, List, Tuple
|
|
|
8
8
|
import isar
|
|
9
9
|
from isar.apis.api import API
|
|
10
10
|
from isar.config.log import setup_loggers
|
|
11
|
+
from isar.config.open_telemetry import setup_open_telemetry
|
|
11
12
|
from isar.config.settings import robot_settings, settings
|
|
12
|
-
from isar.models.
|
|
13
|
+
from isar.models.events import Events
|
|
13
14
|
from isar.modules import ApplicationContainer, get_injector
|
|
14
15
|
from isar.robot.robot import Robot
|
|
15
16
|
from isar.services.service_connections.mqtt.mqtt_client import MqttClient
|
|
@@ -68,6 +69,7 @@ def print_startup_info():
|
|
|
68
69
|
print_setting("Mission planner", settings.MISSION_PLANNER)
|
|
69
70
|
print_setting("Using local storage", settings.STORAGE_LOCAL_ENABLED)
|
|
70
71
|
print_setting("Using blob storage", settings.STORAGE_BLOB_ENABLED)
|
|
72
|
+
print_setting("Blob storage account", settings.BLOB_STORAGE_ACCOUNT)
|
|
71
73
|
print_setting("Using async inspection uploading", settings.UPLOAD_INSPECTIONS_ASYNC)
|
|
72
74
|
print_setting("Plant code", settings.PLANT_CODE)
|
|
73
75
|
print_setting("Plant name", settings.PLANT_NAME)
|
|
@@ -83,6 +85,7 @@ def start() -> None:
|
|
|
83
85
|
|
|
84
86
|
keyvault = injector.keyvault()
|
|
85
87
|
setup_loggers(keyvault=keyvault)
|
|
88
|
+
setup_open_telemetry(app=injector.api().app)
|
|
86
89
|
logger: Logger = logging.getLogger("main")
|
|
87
90
|
|
|
88
91
|
print_startup_info()
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from dependency_injector.wiring import inject
|
|
4
|
-
|
|
5
3
|
from robot_interface.models.robots.media import MediaConfig
|
|
6
4
|
from robot_interface.robot_interface import RobotInterface
|
|
7
5
|
|
|
@@ -11,7 +9,6 @@ class RobotUtilities:
|
|
|
11
9
|
Contains utility functions for getting robot information from the API.
|
|
12
10
|
"""
|
|
13
11
|
|
|
14
|
-
@inject
|
|
15
12
|
def __init__(
|
|
16
13
|
self,
|
|
17
14
|
robot: RobotInterface,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from http import HTTPStatus
|
|
4
|
-
from
|
|
5
|
-
from typing import Any, List
|
|
4
|
+
from typing import List, TypeVar
|
|
6
5
|
|
|
7
|
-
from dependency_injector.wiring import inject
|
|
8
6
|
from fastapi import HTTPException
|
|
9
7
|
from requests import HTTPError
|
|
10
8
|
|
|
@@ -15,15 +13,20 @@ from isar.mission_planner.mission_planner_interface import (
|
|
|
15
13
|
MissionPlannerError,
|
|
16
14
|
MissionPlannerInterface,
|
|
17
15
|
)
|
|
18
|
-
from isar.models.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
from isar.models.events import (
|
|
17
|
+
APIEvent,
|
|
18
|
+
APIRequests,
|
|
19
|
+
Events,
|
|
20
|
+
EventTimeoutError,
|
|
21
|
+
SharedState,
|
|
22
|
+
)
|
|
23
23
|
from isar.state_machine.states_enum import States
|
|
24
24
|
from robot_interface.models.mission.mission import Mission
|
|
25
25
|
from robot_interface.models.mission.status import MissionStatus
|
|
26
26
|
|
|
27
|
+
T1 = TypeVar("T1")
|
|
28
|
+
T2 = TypeVar("T2")
|
|
29
|
+
|
|
27
30
|
|
|
28
31
|
class SchedulingUtilities:
|
|
29
32
|
"""
|
|
@@ -31,7 +34,6 @@ class SchedulingUtilities:
|
|
|
31
34
|
required thread communication through queues to the state machine.
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
|
-
@inject
|
|
35
37
|
def __init__(
|
|
36
38
|
self,
|
|
37
39
|
events: Events,
|
|
@@ -53,9 +55,8 @@ class SchedulingUtilities:
|
|
|
53
55
|
HTTPException 500 Internal Server Error
|
|
54
56
|
If the current state is not available on the queue
|
|
55
57
|
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
except Empty:
|
|
58
|
+
current_state = self.shared_state.state.check()
|
|
59
|
+
if current_state is None:
|
|
59
60
|
error_message: str = (
|
|
60
61
|
"Internal Server Error - Current state of the state machine is unknown"
|
|
61
62
|
)
|
|
@@ -63,6 +64,7 @@ class SchedulingUtilities:
|
|
|
63
64
|
raise HTTPException(
|
|
64
65
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
65
66
|
)
|
|
67
|
+
return current_state
|
|
66
68
|
|
|
67
69
|
def get_mission(self, mission_id: str) -> Mission:
|
|
68
70
|
"""Get the mission with mission_id from the current mission planner
|
|
@@ -175,10 +177,10 @@ class SchedulingUtilities:
|
|
|
175
177
|
"""
|
|
176
178
|
try:
|
|
177
179
|
self._send_command(
|
|
178
|
-
|
|
180
|
+
deepcopy(mission),
|
|
179
181
|
self.api_events.start_mission,
|
|
180
182
|
)
|
|
181
|
-
except
|
|
183
|
+
except EventTimeoutError:
|
|
182
184
|
error_message = "Internal Server Error - Failed to start mission in ISAR"
|
|
183
185
|
self.logger.error(error_message)
|
|
184
186
|
raise HTTPException(
|
|
@@ -201,7 +203,7 @@ class SchedulingUtilities:
|
|
|
201
203
|
True,
|
|
202
204
|
self.api_events.return_home,
|
|
203
205
|
)
|
|
204
|
-
except
|
|
206
|
+
except EventTimeoutError:
|
|
205
207
|
error_message = (
|
|
206
208
|
"Internal Server Error - Failed to start return home mission in ISAR"
|
|
207
209
|
)
|
|
@@ -220,15 +222,15 @@ class SchedulingUtilities:
|
|
|
220
222
|
If there is a timeout while communicating with the state machine
|
|
221
223
|
"""
|
|
222
224
|
try:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
response = self._send_command(True, self.api_events.pause_mission)
|
|
226
|
+
self.logger.info("OK - Mission successfully paused")
|
|
227
|
+
return response
|
|
228
|
+
except EventTimeoutError:
|
|
225
229
|
error_message = "Internal Server Error - Failed to pause mission"
|
|
226
230
|
self.logger.error(error_message)
|
|
227
231
|
raise HTTPException(
|
|
228
232
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
229
233
|
)
|
|
230
|
-
finally:
|
|
231
|
-
self.logger.info("OK - Mission successfully paused")
|
|
232
234
|
|
|
233
235
|
def resume_mission(self) -> ControlMissionResponse:
|
|
234
236
|
"""Resume mission
|
|
@@ -239,21 +241,23 @@ class SchedulingUtilities:
|
|
|
239
241
|
If there is a timeout while communicating with the state machine
|
|
240
242
|
"""
|
|
241
243
|
try:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
response = self._send_command(True, self.api_events.resume_mission)
|
|
245
|
+
self.logger.info("OK - Mission successfully resumed")
|
|
246
|
+
return response
|
|
247
|
+
except EventTimeoutError:
|
|
244
248
|
error_message = "Internal Server Error - Failed to resume mission"
|
|
245
249
|
self.logger.error(error_message)
|
|
246
250
|
raise HTTPException(
|
|
247
251
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=error_message
|
|
248
252
|
)
|
|
249
|
-
finally:
|
|
250
|
-
self.logger.info("OK - Mission successfully resumed")
|
|
251
253
|
|
|
252
|
-
def stop_mission(self) -> ControlMissionResponse:
|
|
254
|
+
def stop_mission(self, mission_id: str = "") -> ControlMissionResponse:
|
|
253
255
|
"""Stop mission
|
|
254
256
|
|
|
255
257
|
Raises
|
|
256
258
|
------
|
|
259
|
+
HTTPException 404 Not Found
|
|
260
|
+
If the mission_id was not known to Isar
|
|
257
261
|
HTTPException 503 Service Unavailable
|
|
258
262
|
The request was understood, but attempting to stop the mission failed
|
|
259
263
|
HTTPException 408 Request timeout
|
|
@@ -261,15 +265,23 @@ class SchedulingUtilities:
|
|
|
261
265
|
"""
|
|
262
266
|
try:
|
|
263
267
|
stop_mission_response: ControlMissionResponse = self._send_command(
|
|
264
|
-
|
|
268
|
+
mission_id, self.api_events.stop_mission
|
|
265
269
|
)
|
|
270
|
+
|
|
271
|
+
if stop_mission_response.mission_not_found:
|
|
272
|
+
error_message = f"Mission ID {stop_mission_response.mission_id} is not currently running"
|
|
273
|
+
self.logger.error(error_message)
|
|
274
|
+
raise HTTPException(
|
|
275
|
+
status_code=HTTPStatus.NOT_FOUND, detail=error_message
|
|
276
|
+
)
|
|
277
|
+
|
|
266
278
|
if stop_mission_response.mission_status != MissionStatus.Cancelled.value:
|
|
267
279
|
error_message = "Failed to stop mission"
|
|
268
280
|
self.logger.error(error_message)
|
|
269
281
|
raise HTTPException(
|
|
270
|
-
status_code=HTTPStatus.
|
|
282
|
+
status_code=HTTPStatus.CONFLICT, detail=error_message
|
|
271
283
|
)
|
|
272
|
-
except
|
|
284
|
+
except EventTimeoutError:
|
|
273
285
|
error_message = "Internal Server Error - Failed to stop mission"
|
|
274
286
|
self.logger.error(error_message)
|
|
275
287
|
raise HTTPException(
|
|
@@ -278,14 +290,12 @@ class SchedulingUtilities:
|
|
|
278
290
|
self.logger.info("OK - Mission successfully stopped")
|
|
279
291
|
return stop_mission_response
|
|
280
292
|
|
|
281
|
-
def _send_command(self, input:
|
|
282
|
-
|
|
293
|
+
def _send_command(self, input: T1, api_event: APIEvent[T1, T2]) -> T2:
|
|
294
|
+
api_event.input.trigger_event(input)
|
|
283
295
|
try:
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
)
|
|
288
|
-
except QueueTimeoutError as e:
|
|
289
|
-
QueueUtilities.clear_queue(queueio.input)
|
|
296
|
+
return api_event.output.consume_event(timeout=self.queue_timeout)
|
|
297
|
+
except EventTimeoutError as e:
|
|
298
|
+
self.logger.error("Queue timed out")
|
|
299
|
+
api_event.input.clear_event()
|
|
290
300
|
self.logger.error("No output received for command to state machine")
|
|
291
301
|
raise e
|
|
@@ -3,9 +3,8 @@ import logging
|
|
|
3
3
|
from collections import deque
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
5
|
from threading import Event
|
|
6
|
-
from typing import Deque, List, Optional
|
|
6
|
+
from typing import Deque, List, Optional, Tuple
|
|
7
7
|
|
|
8
|
-
from dependency_injector.wiring import inject
|
|
9
8
|
from transitions import Machine
|
|
10
9
|
from transitions.core import State
|
|
11
10
|
|
|
@@ -15,8 +14,7 @@ from isar.mission_planner.task_selector_interface import (
|
|
|
15
14
|
TaskSelectorInterface,
|
|
16
15
|
TaskSelectorStop,
|
|
17
16
|
)
|
|
18
|
-
from isar.models.
|
|
19
|
-
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
17
|
+
from isar.models.events import Events, SharedState
|
|
20
18
|
from isar.state_machine.states.await_next_mission import AwaitNextMission
|
|
21
19
|
from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
|
|
22
20
|
from isar.state_machine.states.home import Home
|
|
@@ -31,10 +29,15 @@ from isar.state_machine.states_enum import States
|
|
|
31
29
|
from isar.state_machine.transitions.mission import get_mission_transitions
|
|
32
30
|
from isar.state_machine.transitions.return_home import get_return_home_transitions
|
|
33
31
|
from isar.state_machine.transitions.robot_status import get_robot_status_transitions
|
|
34
|
-
from robot_interface.models.exceptions.robot_exceptions import
|
|
32
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
33
|
+
ErrorMessage,
|
|
34
|
+
RobotException,
|
|
35
|
+
RobotRetrieveInspectionException,
|
|
36
|
+
)
|
|
37
|
+
from robot_interface.models.inspection.inspection import Inspection
|
|
35
38
|
from robot_interface.models.mission.mission import Mission
|
|
36
39
|
from robot_interface.models.mission.status import RobotStatus, TaskStatus
|
|
37
|
-
from robot_interface.models.mission.task import TASKS
|
|
40
|
+
from robot_interface.models.mission.task import TASKS, InspectionTask, Task
|
|
38
41
|
from robot_interface.robot_interface import RobotInterface
|
|
39
42
|
from robot_interface.telemetry.mqtt_client import MqttClientInterface
|
|
40
43
|
from robot_interface.telemetry.payloads import (
|
|
@@ -48,7 +51,6 @@ from robot_interface.utilities.json_service import EnhancedJSONEncoder
|
|
|
48
51
|
class StateMachine(object):
|
|
49
52
|
"""Handles state transitions for supervisory robot control."""
|
|
50
53
|
|
|
51
|
-
@inject
|
|
52
54
|
def __init__(
|
|
53
55
|
self,
|
|
54
56
|
events: Events,
|
|
@@ -142,6 +144,8 @@ class StateMachine(object):
|
|
|
142
144
|
|
|
143
145
|
self.current_state: State = States(self.state) # type: ignore
|
|
144
146
|
|
|
147
|
+
self.awaiting_task_status: bool = False
|
|
148
|
+
|
|
145
149
|
self.transitions_log_length: int = transitions_log_length
|
|
146
150
|
self.transitions_list: Deque[States] = deque([], self.transitions_log_length)
|
|
147
151
|
|
|
@@ -184,7 +188,7 @@ class StateMachine(object):
|
|
|
184
188
|
def update_state(self):
|
|
185
189
|
"""Updates the current state of the state machine."""
|
|
186
190
|
self.current_state = States(self.state) # type: ignore
|
|
187
|
-
|
|
191
|
+
self.shared_state.state.update(self.current_state)
|
|
188
192
|
self._log_state_transition(self.current_state)
|
|
189
193
|
self.logger.info("State: %s", self.current_state)
|
|
190
194
|
self.publish_status()
|
|
@@ -203,9 +207,21 @@ class StateMachine(object):
|
|
|
203
207
|
self.task_selector.initialize(tasks=self.current_mission.tasks)
|
|
204
208
|
|
|
205
209
|
def send_task_status(self):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
210
|
+
self.shared_state.state_machine_current_task.update(self.current_task)
|
|
211
|
+
|
|
212
|
+
def report_task_status(self, task: Task) -> None:
|
|
213
|
+
if task.status == TaskStatus.Failed:
|
|
214
|
+
self.logger.warning(
|
|
215
|
+
f"Task: {str(task.id)[:8]} was reported as failed by the robot"
|
|
216
|
+
)
|
|
217
|
+
elif task.status == TaskStatus.Successful:
|
|
218
|
+
self.logger.info(
|
|
219
|
+
f"{type(task).__name__} task: {str(task.id)[:8]} completed"
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
self.logger.info(
|
|
223
|
+
f"Task: {str(task.id)[:8]} was reported as task.status by the robot"
|
|
224
|
+
)
|
|
209
225
|
|
|
210
226
|
def publish_mission_status(self) -> None:
|
|
211
227
|
if not self.mqtt_publisher:
|
|
@@ -336,6 +352,58 @@ class StateMachine(object):
|
|
|
336
352
|
)
|
|
337
353
|
)
|
|
338
354
|
|
|
355
|
+
def should_upload_inspections(self) -> bool:
|
|
356
|
+
if settings.UPLOAD_INSPECTIONS_ASYNC:
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
self.current_task.is_finished()
|
|
361
|
+
and self.current_task.status == TaskStatus.Successful
|
|
362
|
+
and isinstance(self.current_task, InspectionTask)
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def queue_inspections_for_upload(
|
|
366
|
+
self, mission: Mission, current_task: InspectionTask, logger: logging.Logger
|
|
367
|
+
) -> None:
|
|
368
|
+
try:
|
|
369
|
+
inspection: Inspection = self.robot.get_inspection(task=current_task)
|
|
370
|
+
if current_task.inspection_id != inspection.id:
|
|
371
|
+
logger.warning(
|
|
372
|
+
f"The inspection_id of task ({current_task.inspection_id}) "
|
|
373
|
+
f"and result ({inspection.id}) is not matching. "
|
|
374
|
+
f"This may lead to confusions when accessing the inspection later"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
except (RobotRetrieveInspectionException, RobotException) as e:
|
|
378
|
+
error_message: ErrorMessage = ErrorMessage(
|
|
379
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
380
|
+
)
|
|
381
|
+
self.current_task.error_message = error_message
|
|
382
|
+
logger.error(
|
|
383
|
+
f"Failed to retrieve inspections because: {e.error_description}"
|
|
384
|
+
)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
except Exception as e:
|
|
388
|
+
logger.error(
|
|
389
|
+
f"Failed to retrieve inspections because of unexpected error: {e}"
|
|
390
|
+
)
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
if not inspection:
|
|
394
|
+
logger.warning(
|
|
395
|
+
f"No inspection result data retrieved for task {str(current_task.id)[:8]}"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
inspection.metadata.tag_id = current_task.tag_id
|
|
399
|
+
|
|
400
|
+
message: Tuple[Inspection, Mission] = (
|
|
401
|
+
inspection,
|
|
402
|
+
mission,
|
|
403
|
+
)
|
|
404
|
+
self.events.upload_queue.put(message)
|
|
405
|
+
logger.info(f"Inspection result: {str(inspection.id)[:8]} queued for upload")
|
|
406
|
+
|
|
339
407
|
|
|
340
408
|
def main(state_machine: StateMachine):
|
|
341
409
|
"""Starts a state machine instance."""
|
|
@@ -1,20 +1,55 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING, List
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from isar.config.settings import settings
|
|
4
|
+
from isar.eventhandlers.eventhandler import (
|
|
5
|
+
EventHandlerBase,
|
|
6
|
+
EventHandlerMapping,
|
|
7
|
+
TimeoutHandlerMapping,
|
|
8
|
+
)
|
|
9
|
+
from isar.state_machine.utils.common_event_handlers import (
|
|
10
|
+
return_home_event_handler,
|
|
11
|
+
start_mission_event_handler,
|
|
12
|
+
stop_mission_event_handler,
|
|
13
|
+
)
|
|
4
14
|
|
|
5
15
|
if TYPE_CHECKING:
|
|
6
16
|
from isar.state_machine.state_machine import StateMachine
|
|
7
17
|
|
|
8
|
-
from isar.state_machine.generic_states.idle import Idle, IdleStates
|
|
9
18
|
|
|
19
|
+
class AwaitNextMission(EventHandlerBase):
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
def __init__(self, state_machine: "StateMachine"):
|
|
22
|
+
events = state_machine.events
|
|
23
|
+
|
|
24
|
+
event_handlers: List[EventHandlerMapping] = [
|
|
25
|
+
EventHandlerMapping(
|
|
26
|
+
name="start_mission_event",
|
|
27
|
+
event=events.api_requests.start_mission.input,
|
|
28
|
+
handler=lambda event: start_mission_event_handler(state_machine, event),
|
|
29
|
+
),
|
|
30
|
+
EventHandlerMapping(
|
|
31
|
+
name="return_home_event",
|
|
32
|
+
event=events.api_requests.return_home.input,
|
|
33
|
+
handler=lambda event: return_home_event_handler(state_machine, event),
|
|
34
|
+
),
|
|
35
|
+
EventHandlerMapping(
|
|
36
|
+
name="stop_mission_event",
|
|
37
|
+
event=events.api_requests.return_home.input,
|
|
38
|
+
handler=lambda event: stop_mission_event_handler(state_machine, event),
|
|
39
|
+
),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
timers: List[TimeoutHandlerMapping] = [
|
|
43
|
+
TimeoutHandlerMapping(
|
|
44
|
+
name="should_return_home_timer",
|
|
45
|
+
timeout_in_seconds=settings.RETURN_HOME_DELAY,
|
|
46
|
+
handler=lambda: state_machine.request_return_home, # type: ignore
|
|
47
|
+
)
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
super().__init__(
|
|
51
|
+
state_name="await_next_mission",
|
|
18
52
|
state_machine=state_machine,
|
|
19
|
-
|
|
53
|
+
event_handler_mappings=event_handlers,
|
|
54
|
+
timers=timers,
|
|
20
55
|
)
|