isar 1.25.9__py3-none-any.whl → 1.26.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/models/start_mission_definition.py +55 -112
- isar/apis/robot_control/robot_controller.py +5 -4
- isar/apis/schedule/scheduling_controller.py +6 -57
- isar/apis/security/authentication.py +2 -2
- isar/config/settings.env +1 -3
- isar/config/settings.py +6 -6
- isar/models/communication/message.py +0 -4
- isar/models/communication/queues/events.py +57 -0
- isar/models/communication/queues/queue_utils.py +32 -0
- isar/models/communication/queues/status_queue.py +7 -5
- isar/modules.py +26 -13
- isar/robot/robot.py +124 -0
- isar/robot/robot_start_mission.py +73 -0
- isar/robot/robot_status.py +49 -0
- isar/robot/robot_stop_mission.py +72 -0
- isar/robot/robot_task_status.py +92 -0
- isar/script.py +14 -7
- isar/services/utilities/scheduling_utilities.py +21 -30
- isar/state_machine/state_machine.py +70 -212
- isar/state_machine/states/blocked_protective_stop.py +10 -30
- isar/state_machine/states/idle.py +45 -67
- isar/state_machine/states/monitor.py +129 -139
- isar/state_machine/states/offline.py +12 -33
- isar/state_machine/states/paused.py +6 -3
- isar/state_machine/states/stop.py +29 -58
- isar/state_machine/states_enum.py +0 -2
- isar/state_machine/transitions/fail_mission.py +13 -0
- isar/state_machine/transitions/finish_mission.py +39 -0
- isar/state_machine/transitions/pause.py +24 -0
- isar/state_machine/transitions/resume.py +27 -0
- isar/state_machine/transitions/start_mission.py +73 -0
- isar/state_machine/transitions/stop.py +40 -0
- isar/state_machine/transitions/utils.py +10 -0
- isar/storage/slimm_storage.py +2 -2
- isar/storage/uploader.py +5 -5
- {isar-1.25.9.dist-info → isar-1.26.1.dist-info}/METADATA +5 -19
- {isar-1.25.9.dist-info → isar-1.26.1.dist-info}/RECORD +45 -34
- {isar-1.25.9.dist-info → isar-1.26.1.dist-info}/WHEEL +1 -1
- robot_interface/models/mission/task.py +1 -1
- robot_interface/telemetry/mqtt_client.py +0 -1
- robot_interface/telemetry/payloads.py +3 -3
- robot_interface/utilities/json_service.py +1 -1
- isar/models/communication/queues/queues.py +0 -19
- isar/state_machine/states/initialize.py +0 -70
- isar/state_machine/states/initiate.py +0 -111
- {isar-1.25.9.dist-info → isar-1.26.1.dist-info}/entry_points.txt +0 -0
- {isar-1.25.9.dist-info → isar-1.26.1.dist-info/licenses}/LICENSE +0 -0
- {isar-1.25.9.dist-info → isar-1.26.1.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
+
from queue import Queue
|
|
3
4
|
from typing import TYPE_CHECKING, Optional
|
|
4
5
|
|
|
5
6
|
from transitions import State
|
|
6
7
|
|
|
7
|
-
from isar.config.settings import settings
|
|
8
8
|
from isar.models.communication.message import StartMissionMessage
|
|
9
|
-
from isar.
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from isar.models.communication.queues.queue_utils import (
|
|
10
|
+
check_for_event,
|
|
11
|
+
check_shared_state,
|
|
12
12
|
)
|
|
13
|
-
from
|
|
13
|
+
from isar.models.communication.queues.status_queue import StatusQueue
|
|
14
14
|
from robot_interface.models.mission.status import RobotStatus
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
@@ -22,82 +22,60 @@ class Idle(State):
|
|
|
22
22
|
super().__init__(name="idle", on_enter=self.start, on_exit=self.stop)
|
|
23
23
|
self.state_machine: "StateMachine" = state_machine
|
|
24
24
|
self.logger = logging.getLogger("state_machine")
|
|
25
|
-
self.robot_status_thread: Optional[ThreadedRequest] = None
|
|
26
25
|
self.last_robot_status_poll_time: float = time.time()
|
|
27
|
-
self.
|
|
26
|
+
self.events = self.state_machine.events
|
|
27
|
+
self.shared_state = self.state_machine.shared_state
|
|
28
28
|
|
|
29
29
|
def start(self) -> None:
|
|
30
30
|
self.state_machine.update_state()
|
|
31
31
|
self._run()
|
|
32
32
|
|
|
33
33
|
def stop(self) -> None:
|
|
34
|
-
|
|
35
|
-
self.robot_status_thread.wait_for_thread()
|
|
36
|
-
self.robot_status_thread = None
|
|
37
|
-
self.status_checked_at_least_once = False
|
|
34
|
+
return
|
|
38
35
|
|
|
39
|
-
def
|
|
40
|
-
if
|
|
36
|
+
def _check_and_handle_stop_mission_event(self, event: Queue) -> bool:
|
|
37
|
+
if check_for_event(event):
|
|
38
|
+
self.state_machine.stop() # type: ignore
|
|
41
39
|
return True
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def _check_and_handle_start_mission_event(
|
|
43
|
+
self, event: Queue[StartMissionMessage]
|
|
44
|
+
) -> bool:
|
|
45
|
+
start_mission: Optional[StartMissionMessage] = check_for_event(event)
|
|
46
|
+
if start_mission:
|
|
47
|
+
self.state_machine.start_mission(mission=start_mission.mission)
|
|
48
|
+
self.state_machine.request_mission_start() # type: ignore
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def _check_and_handle_robot_status_event(
|
|
53
|
+
self, event: StatusQueue[RobotStatus]
|
|
54
|
+
) -> bool:
|
|
55
|
+
robot_status: RobotStatus = check_shared_state(event)
|
|
56
|
+
if robot_status == RobotStatus.Offline:
|
|
57
|
+
self.state_machine.robot_turned_offline() # type: ignore
|
|
58
|
+
return True
|
|
59
|
+
elif robot_status == RobotStatus.BlockedProtectiveStop:
|
|
60
|
+
self.state_machine.robot_protective_stop_engaged() # type: ignore
|
|
61
|
+
return True
|
|
62
|
+
return False
|
|
49
63
|
|
|
50
64
|
def _run(self) -> None:
|
|
51
65
|
while True:
|
|
52
|
-
if self.
|
|
53
|
-
|
|
66
|
+
if self._check_and_handle_stop_mission_event(
|
|
67
|
+
self.events.api_requests.stop_mission.input
|
|
68
|
+
):
|
|
54
69
|
break
|
|
55
70
|
|
|
56
|
-
if self.
|
|
57
|
-
start_mission
|
|
58
|
-
|
|
59
|
-
)
|
|
60
|
-
if start_mission:
|
|
61
|
-
self.state_machine.start_mission(
|
|
62
|
-
mission=start_mission.mission,
|
|
63
|
-
initial_pose=start_mission.initial_pose,
|
|
64
|
-
)
|
|
65
|
-
transition = self.state_machine.mission_started # type: ignore
|
|
66
|
-
break
|
|
67
|
-
time.sleep(self.state_machine.sleep_time)
|
|
68
|
-
|
|
69
|
-
if not self._is_ready_to_poll_for_status():
|
|
70
|
-
continue
|
|
71
|
-
|
|
72
|
-
if not self.robot_status_thread:
|
|
73
|
-
self.robot_status_thread = ThreadedRequest(
|
|
74
|
-
request_func=self.state_machine.robot.robot_status
|
|
75
|
-
)
|
|
76
|
-
self.robot_status_thread.start_thread(
|
|
77
|
-
name="State Machine Offline Get Robot Status"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
robot_status: RobotStatus = self.robot_status_thread.get_output()
|
|
82
|
-
self.status_checked_at_least_once = True
|
|
83
|
-
except ThreadedRequestNotFinishedError:
|
|
84
|
-
time.sleep(self.state_machine.sleep_time)
|
|
85
|
-
continue
|
|
86
|
-
|
|
87
|
-
except RobotException as e:
|
|
88
|
-
self.logger.error(
|
|
89
|
-
f"Failed to get robot status because: {e.error_description}"
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
self.last_robot_status_poll_time = time.time()
|
|
93
|
-
|
|
94
|
-
if robot_status == RobotStatus.Offline:
|
|
95
|
-
transition = self.state_machine.robot_turned_offline # type: ignore
|
|
96
|
-
break
|
|
97
|
-
elif robot_status == RobotStatus.BlockedProtectiveStop:
|
|
98
|
-
transition = self.state_machine.robot_protective_stop_engaged # type: ignore
|
|
71
|
+
if self._check_and_handle_start_mission_event(
|
|
72
|
+
self.events.api_requests.start_mission.input
|
|
73
|
+
):
|
|
99
74
|
break
|
|
100
75
|
|
|
101
|
-
self.
|
|
76
|
+
if self._check_and_handle_robot_status_event(
|
|
77
|
+
self.shared_state.robot_status
|
|
78
|
+
):
|
|
79
|
+
break
|
|
102
80
|
|
|
103
|
-
|
|
81
|
+
time.sleep(self.state_machine.sleep_time)
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
3
|
from copy import deepcopy
|
|
4
|
-
from
|
|
4
|
+
from queue import Queue
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, Tuple
|
|
5
6
|
|
|
6
7
|
from injector import inject
|
|
7
8
|
from transitions import State
|
|
8
9
|
|
|
9
10
|
from isar.config.settings import settings
|
|
10
|
-
from isar.
|
|
11
|
-
|
|
12
|
-
ThreadedRequestNotFinishedError,
|
|
13
|
-
)
|
|
11
|
+
from isar.models.communication.queues.queue_utils import check_for_event, trigger_event
|
|
12
|
+
from isar.services.utilities.threaded_request import ThreadedRequest
|
|
14
13
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
15
14
|
ErrorMessage,
|
|
16
|
-
RobotCommunicationException,
|
|
17
|
-
RobotCommunicationTimeoutException,
|
|
18
15
|
RobotException,
|
|
19
16
|
RobotRetrieveInspectionException,
|
|
20
|
-
RobotTaskStatusException,
|
|
21
17
|
)
|
|
22
18
|
from robot_interface.models.inspection.inspection import Inspection
|
|
23
19
|
from robot_interface.models.mission.mission import Mission
|
|
@@ -33,131 +29,146 @@ class Monitor(State):
|
|
|
33
29
|
def __init__(self, state_machine: "StateMachine") -> None:
|
|
34
30
|
super().__init__(name="monitor", on_enter=self.start, on_exit=self.stop)
|
|
35
31
|
self.state_machine: "StateMachine" = state_machine
|
|
36
|
-
self.request_status_failure_counter: int = 0
|
|
37
|
-
self.request_status_failure_counter_limit: int = (
|
|
38
|
-
settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
|
|
39
|
-
)
|
|
40
32
|
|
|
41
33
|
self.logger = logging.getLogger("state_machine")
|
|
42
|
-
self.
|
|
34
|
+
self.events = self.state_machine.events
|
|
35
|
+
|
|
36
|
+
self.awaiting_task_status: bool = False
|
|
43
37
|
|
|
44
38
|
def start(self) -> None:
|
|
45
39
|
self.state_machine.update_state()
|
|
46
40
|
self._run()
|
|
47
41
|
|
|
48
42
|
def stop(self) -> None:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
self.task_status_thread = None
|
|
43
|
+
self.state_machine.mission_ongoing = False
|
|
44
|
+
return
|
|
52
45
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
break
|
|
46
|
+
def _check_and_handle_stop_mission_event(self, event: Queue) -> bool:
|
|
47
|
+
if check_for_event(event):
|
|
48
|
+
self.state_machine.stop() # type: ignore
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
def _check_and_handle_pause_mission_event(self, event: Queue) -> bool:
|
|
53
|
+
if check_for_event(event):
|
|
54
|
+
self.state_machine.pause() # type: ignore
|
|
55
|
+
return True
|
|
56
|
+
return False
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if retry_limit_exceeded:
|
|
82
|
-
self.logger.error(
|
|
83
|
-
f"Monitoring task {self.state_machine.current_task.id[:8]} failed "
|
|
84
|
-
f"because: {e.error_description}"
|
|
85
|
-
)
|
|
86
|
-
status = TaskStatus.Failed
|
|
87
|
-
else:
|
|
88
|
-
time.sleep(settings.REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY)
|
|
89
|
-
continue
|
|
90
|
-
|
|
91
|
-
except RobotTaskStatusException as e:
|
|
92
|
-
self.state_machine.current_task.error_message = ErrorMessage(
|
|
93
|
-
error_reason=e.error_reason, error_description=e.error_description
|
|
94
|
-
)
|
|
95
|
-
self.logger.error(
|
|
96
|
-
f"Monitoring task {self.state_machine.current_task.id[:8]} failed "
|
|
97
|
-
f"because: {e.error_description}"
|
|
98
|
-
)
|
|
99
|
-
status = TaskStatus.Failed
|
|
58
|
+
def _check_and_handle_mission_started_event(self, event: Queue) -> None:
|
|
59
|
+
if not self.state_machine.mission_ongoing:
|
|
60
|
+
if check_for_event(event):
|
|
61
|
+
self.state_machine.mission_ongoing = True
|
|
62
|
+
|
|
63
|
+
def _check_and_handle_mission_failed_event(self, event: Queue) -> bool:
|
|
64
|
+
mission_failed: Optional[ErrorMessage] = check_for_event(event)
|
|
65
|
+
if mission_failed is not None:
|
|
66
|
+
self.state_machine.logger.warning(
|
|
67
|
+
f"Failed to initiate mission "
|
|
68
|
+
f"{str(self.state_machine.current_mission.id)[:8]} because: "
|
|
69
|
+
f"{mission_failed.error_description}"
|
|
70
|
+
)
|
|
71
|
+
self.state_machine.current_mission.error_message = ErrorMessage(
|
|
72
|
+
error_reason=mission_failed.error_reason,
|
|
73
|
+
error_description=mission_failed.error_description,
|
|
74
|
+
)
|
|
100
75
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
76
|
+
self.state_machine.mission_failed_to_start() # type: ignore
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
104
79
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
80
|
+
def _check_and_handle_task_status_failed_event(self, event: Queue) -> bool:
|
|
81
|
+
task_failure: Optional[ErrorMessage] = check_for_event(event)
|
|
82
|
+
if task_failure is not None:
|
|
83
|
+
self.awaiting_task_status = False
|
|
84
|
+
self.state_machine.current_task.error_message = task_failure
|
|
85
|
+
self.logger.error(
|
|
86
|
+
f"Monitoring task {self.state_machine.current_task.id[:8]} failed "
|
|
87
|
+
f"because: {task_failure.error_description}"
|
|
88
|
+
)
|
|
89
|
+
return self._handle_new_task_status(TaskStatus.Failed)
|
|
90
|
+
elif not self.awaiting_task_status:
|
|
91
|
+
trigger_event(
|
|
92
|
+
self.events.state_machine_events.task_status_request,
|
|
93
|
+
self.state_machine.current_task.id,
|
|
94
|
+
)
|
|
95
|
+
self.awaiting_task_status = True
|
|
96
|
+
return False
|
|
108
97
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
def _check_and_handle_task_status_event(self, event: Queue) -> bool:
|
|
99
|
+
status: Optional[TaskStatus] = check_for_event(event)
|
|
100
|
+
if status is not None:
|
|
101
|
+
self.awaiting_task_status = False
|
|
102
|
+
return self._handle_new_task_status(status)
|
|
103
|
+
elif not self.awaiting_task_status:
|
|
104
|
+
trigger_event(
|
|
105
|
+
self.events.state_machine_events.task_status_request,
|
|
106
|
+
self.state_machine.current_task.id,
|
|
107
|
+
)
|
|
108
|
+
self.awaiting_task_status = True
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def _handle_new_task_status(self, status: TaskStatus) -> bool:
|
|
112
|
+
if self.state_machine.current_task is None:
|
|
113
|
+
self.state_machine.iterate_current_task()
|
|
114
|
+
|
|
115
|
+
self.state_machine.current_task.status = status
|
|
115
116
|
|
|
117
|
+
if self._should_upload_inspections():
|
|
118
|
+
get_inspection_thread = ThreadedRequest(self._queue_inspections_for_upload)
|
|
119
|
+
get_inspection_thread.start_thread(
|
|
120
|
+
deepcopy(self.state_machine.current_mission),
|
|
121
|
+
deepcopy(self.state_machine.current_task),
|
|
122
|
+
name="State Machine Get Inspections",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if self.state_machine.current_task.is_finished():
|
|
126
|
+
self._report_task_status(self.state_machine.current_task)
|
|
127
|
+
self.state_machine.publish_task_status(task=self.state_machine.current_task)
|
|
128
|
+
|
|
129
|
+
self.state_machine.iterate_current_task()
|
|
116
130
|
if self.state_machine.current_task is None:
|
|
117
|
-
self.state_machine.
|
|
131
|
+
self.state_machine.mission_finished() # type: ignore
|
|
132
|
+
return True
|
|
118
133
|
|
|
119
|
-
|
|
134
|
+
# Report and update next task
|
|
135
|
+
self.state_machine.current_task.update_task_status()
|
|
136
|
+
self.state_machine.publish_task_status(task=self.state_machine.current_task)
|
|
137
|
+
return False
|
|
120
138
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
def _run(self) -> None:
|
|
140
|
+
self.awaiting_task_status = False
|
|
141
|
+
while True:
|
|
142
|
+
if self._check_and_handle_stop_mission_event(
|
|
143
|
+
self.events.api_requests.stop_mission.input
|
|
124
144
|
):
|
|
125
|
-
|
|
126
|
-
self._queue_inspections_for_upload
|
|
127
|
-
)
|
|
128
|
-
get_inspection_thread.start_thread(
|
|
129
|
-
deepcopy(self.state_machine.current_mission),
|
|
130
|
-
deepcopy(self.state_machine.current_task),
|
|
131
|
-
name="State Machine Get Inspections",
|
|
132
|
-
)
|
|
145
|
+
break
|
|
133
146
|
|
|
134
|
-
if self.
|
|
135
|
-
self.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
147
|
+
if self._check_and_handle_pause_mission_event(
|
|
148
|
+
self.events.api_requests.pause_mission.input
|
|
149
|
+
):
|
|
150
|
+
break
|
|
139
151
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
break
|
|
152
|
+
self._check_and_handle_mission_started_event(
|
|
153
|
+
self.events.robot_service_events.mission_started
|
|
154
|
+
)
|
|
144
155
|
|
|
145
|
-
|
|
146
|
-
self.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
156
|
+
if self._check_and_handle_mission_failed_event(
|
|
157
|
+
self.events.robot_service_events.mission_failed
|
|
158
|
+
):
|
|
159
|
+
break
|
|
150
160
|
|
|
151
|
-
self.
|
|
152
|
-
|
|
161
|
+
if self._check_and_handle_task_status_failed_event(
|
|
162
|
+
self.events.robot_service_events.task_status_failed
|
|
163
|
+
):
|
|
164
|
+
break
|
|
153
165
|
|
|
154
|
-
|
|
166
|
+
if self._check_and_handle_task_status_event(
|
|
167
|
+
self.events.robot_service_events.task_status_updated
|
|
168
|
+
):
|
|
169
|
+
break
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
self, status_function: Callable, function_argument: str, thread_name: str
|
|
158
|
-
) -> None:
|
|
159
|
-
self.task_status_thread = ThreadedRequest(request_func=status_function)
|
|
160
|
-
self.task_status_thread.start_thread(function_argument, name=thread_name)
|
|
171
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
161
172
|
|
|
162
173
|
def _queue_inspections_for_upload(
|
|
163
174
|
self, mission: Mission, current_task: InspectionTask
|
|
@@ -180,6 +191,12 @@ class Monitor(State):
|
|
|
180
191
|
)
|
|
181
192
|
return
|
|
182
193
|
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self.logger.error(
|
|
196
|
+
f"Failed to retrieve inspections because of unexpected error: {e}"
|
|
197
|
+
)
|
|
198
|
+
return
|
|
199
|
+
|
|
183
200
|
if not inspection:
|
|
184
201
|
self.logger.warning(
|
|
185
202
|
f"No inspection result data retrieved for task {str(current_task.id)[:8]}"
|
|
@@ -191,7 +208,7 @@ class Monitor(State):
|
|
|
191
208
|
inspection,
|
|
192
209
|
mission,
|
|
193
210
|
)
|
|
194
|
-
self.state_machine.
|
|
211
|
+
self.state_machine.events.upload_queue.put(message)
|
|
195
212
|
self.logger.info(
|
|
196
213
|
f"Inspection result: {str(inspection.id)[:8]} queued for upload"
|
|
197
214
|
)
|
|
@@ -207,6 +224,9 @@ class Monitor(State):
|
|
|
207
224
|
)
|
|
208
225
|
|
|
209
226
|
def _should_upload_inspections(self) -> bool:
|
|
227
|
+
if settings.UPLOAD_INSPECTIONS_ASYNC:
|
|
228
|
+
return False
|
|
229
|
+
|
|
210
230
|
return (
|
|
211
231
|
self.state_machine.current_task.is_finished()
|
|
212
232
|
and self.state_machine.current_task.status == TaskStatus.Successful
|
|
@@ -218,33 +238,3 @@ class Monitor(State):
|
|
|
218
238
|
error_reason=e.error_reason, error_description=e.error_description
|
|
219
239
|
)
|
|
220
240
|
self.state_machine.current_task.error_message = error_message
|
|
221
|
-
|
|
222
|
-
def _check_if_exceeded_retry_limit(
|
|
223
|
-
self, e: Union[RobotCommunicationTimeoutException, RobotCommunicationException]
|
|
224
|
-
) -> bool:
|
|
225
|
-
self.state_machine.current_mission.error_message = ErrorMessage(
|
|
226
|
-
error_reason=e.error_reason, error_description=e.error_description
|
|
227
|
-
)
|
|
228
|
-
self.task_status_thread = None
|
|
229
|
-
self.request_status_failure_counter += 1
|
|
230
|
-
self.logger.warning(
|
|
231
|
-
f"Monitoring task {self.state_machine.current_task.id} failed #: "
|
|
232
|
-
f"{self.request_status_failure_counter} failed because: {e.error_description}"
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
self.request_status_failure_counter
|
|
237
|
-
>= self.request_status_failure_counter_limit
|
|
238
|
-
):
|
|
239
|
-
self.state_machine.current_task.error_message = ErrorMessage(
|
|
240
|
-
error_reason=e.error_reason,
|
|
241
|
-
error_description=e.error_description,
|
|
242
|
-
)
|
|
243
|
-
self.logger.error(
|
|
244
|
-
f"Task will be cancelled after failing to get task status "
|
|
245
|
-
f"{self.request_status_failure_counter} times because: "
|
|
246
|
-
f"{e.error_description}"
|
|
247
|
-
)
|
|
248
|
-
return True
|
|
249
|
-
|
|
250
|
-
return False
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
7
|
-
from isar.
|
|
8
|
-
from isar.services.utilities.threaded_request import (
|
|
9
|
-
ThreadedRequest,
|
|
10
|
-
ThreadedRequestNotFinishedError,
|
|
11
|
-
)
|
|
12
|
-
from robot_interface.models.exceptions.robot_exceptions import RobotException
|
|
7
|
+
from isar.models.communication.queues.queue_utils import check_shared_state
|
|
13
8
|
from robot_interface.models.mission.status import RobotStatus
|
|
14
9
|
|
|
15
10
|
if TYPE_CHECKING:
|
|
@@ -21,43 +16,27 @@ class Offline(State):
|
|
|
21
16
|
super().__init__(name="offline", on_enter=self.start, on_exit=self.stop)
|
|
22
17
|
self.state_machine: "StateMachine" = state_machine
|
|
23
18
|
self.logger = logging.getLogger("state_machine")
|
|
24
|
-
self.
|
|
19
|
+
self.shared_state = self.state_machine.shared_state
|
|
25
20
|
|
|
26
21
|
def start(self) -> None:
|
|
27
22
|
self.state_machine.update_state()
|
|
28
23
|
self._run()
|
|
29
24
|
|
|
30
25
|
def stop(self) -> None:
|
|
31
|
-
|
|
32
|
-
self.robot_status_thread.wait_for_thread()
|
|
33
|
-
self.robot_status_thread = None
|
|
26
|
+
return
|
|
34
27
|
|
|
35
28
|
def _run(self) -> None:
|
|
36
29
|
while True:
|
|
37
|
-
|
|
38
|
-
self.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
robot_status: RobotStatus = self.robot_status_thread.get_output()
|
|
47
|
-
except ThreadedRequestNotFinishedError:
|
|
48
|
-
time.sleep(self.state_machine.sleep_time)
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
except RobotException as e:
|
|
52
|
-
self.logger.error(
|
|
53
|
-
f"Failed to get robot status because: {e.error_description}"
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
if robot_status != RobotStatus.Offline:
|
|
30
|
+
robot_status: RobotStatus = check_shared_state(
|
|
31
|
+
self.shared_state.robot_status
|
|
32
|
+
)
|
|
33
|
+
if robot_status == RobotStatus.BlockedProtectiveStop:
|
|
34
|
+
transition = self.state_machine.robot_protective_stop_engaged # type: ignore
|
|
35
|
+
break
|
|
36
|
+
elif robot_status != RobotStatus.Offline:
|
|
57
37
|
transition = self.state_machine.robot_turned_online # type: ignore
|
|
58
38
|
break
|
|
59
39
|
|
|
60
|
-
self.
|
|
61
|
-
time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
|
|
40
|
+
time.sleep(self.state_machine.sleep_time)
|
|
62
41
|
|
|
63
42
|
transition()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import TYPE_CHECKING, Callable
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
7
|
+
from isar.models.communication.queues.queue_utils import check_for_event
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from isar.state_machine.state_machine import StateMachine
|
|
9
11
|
|
|
@@ -13,6 +15,7 @@ class Paused(State):
|
|
|
13
15
|
super().__init__(name="paused", on_enter=self.start)
|
|
14
16
|
self.state_machine: "StateMachine" = state_machine
|
|
15
17
|
self.logger = logging.getLogger("state_machine")
|
|
18
|
+
self.events = self.state_machine.events
|
|
16
19
|
|
|
17
20
|
def start(self) -> None:
|
|
18
21
|
self.state_machine.update_state()
|
|
@@ -21,11 +24,11 @@ class Paused(State):
|
|
|
21
24
|
def _run(self) -> None:
|
|
22
25
|
transition: Callable
|
|
23
26
|
while True:
|
|
24
|
-
if self.
|
|
27
|
+
if check_for_event(self.events.api_requests.pause_mission.input):
|
|
25
28
|
transition = self.state_machine.stop # type: ignore
|
|
26
29
|
break
|
|
27
30
|
|
|
28
|
-
if self.
|
|
31
|
+
if check_for_event(self.events.api_requests.resume_mission.input):
|
|
29
32
|
transition = self.state_machine.resume # type: ignore
|
|
30
33
|
break
|
|
31
34
|
|