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
|
@@ -2,9 +2,8 @@ import logging
|
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from http import HTTPStatus
|
|
4
4
|
from queue import Empty
|
|
5
|
-
from typing import Any, List
|
|
5
|
+
from typing import Any, List
|
|
6
6
|
|
|
7
|
-
from alitra import Pose
|
|
8
7
|
from fastapi import HTTPException
|
|
9
8
|
from injector import inject
|
|
10
9
|
from requests import HTTPError
|
|
@@ -17,11 +16,9 @@ from isar.mission_planner.mission_planner_interface import (
|
|
|
17
16
|
MissionPlannerInterface,
|
|
18
17
|
)
|
|
19
18
|
from isar.models.communication.message import StartMissionMessage
|
|
19
|
+
from isar.models.communication.queues.events import APIRequests, Events, SharedState
|
|
20
20
|
from isar.models.communication.queues.queue_io import QueueIO
|
|
21
|
-
from isar.models.communication.queues.
|
|
22
|
-
from isar.models.communication.queues.queue_timeout_error import (
|
|
23
|
-
QueueTimeoutError,
|
|
24
|
-
)
|
|
21
|
+
from isar.models.communication.queues.queue_timeout_error import QueueTimeoutError
|
|
25
22
|
from isar.services.utilities.queue_utilities import QueueUtilities
|
|
26
23
|
from isar.state_machine.states_enum import States
|
|
27
24
|
from robot_interface.models.mission.mission import Mission
|
|
@@ -36,11 +33,13 @@ class SchedulingUtilities:
|
|
|
36
33
|
@inject
|
|
37
34
|
def __init__(
|
|
38
35
|
self,
|
|
39
|
-
|
|
36
|
+
events: Events,
|
|
37
|
+
shared_state: SharedState,
|
|
40
38
|
mission_planner: MissionPlannerInterface,
|
|
41
39
|
queue_timeout: int = settings.QUEUE_TIMEOUT,
|
|
42
40
|
):
|
|
43
|
-
self.
|
|
41
|
+
self.api_events: APIRequests = events.api_requests
|
|
42
|
+
self.shared_state: SharedState = shared_state
|
|
44
43
|
self.mission_planner: MissionPlannerInterface = mission_planner
|
|
45
44
|
self.queue_timeout: int = queue_timeout
|
|
46
45
|
self.logger = logging.getLogger("api")
|
|
@@ -54,7 +53,7 @@ class SchedulingUtilities:
|
|
|
54
53
|
If the current state is not available on the queue
|
|
55
54
|
"""
|
|
56
55
|
try:
|
|
57
|
-
return self.
|
|
56
|
+
return self.shared_state.state.check()
|
|
58
57
|
except Empty:
|
|
59
58
|
error_message: str = (
|
|
60
59
|
"Internal Server Error - Current state of the state machine is unknown"
|
|
@@ -102,14 +101,11 @@ class SchedulingUtilities:
|
|
|
102
101
|
HTTPException 400 Bad request
|
|
103
102
|
If the robot is not capable of performing mission
|
|
104
103
|
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
missing_capabilities.add(task.type)
|
|
111
|
-
|
|
112
|
-
if not is_capable:
|
|
104
|
+
missing_capabilities = {
|
|
105
|
+
task.type for task in mission.tasks if task.type not in robot_capabilities
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if missing_capabilities:
|
|
113
109
|
error_message = (
|
|
114
110
|
f"Bad Request - Robot is not capable of performing mission."
|
|
115
111
|
f" Missing functionalities: {missing_capabilities}."
|
|
@@ -120,7 +116,7 @@ class SchedulingUtilities:
|
|
|
120
116
|
detail=error_message,
|
|
121
117
|
)
|
|
122
118
|
|
|
123
|
-
return
|
|
119
|
+
return True
|
|
124
120
|
|
|
125
121
|
def verify_state_machine_ready_to_receive_mission(self, state: States) -> bool:
|
|
126
122
|
"""Verify that the state machine is idle and ready to receive a mission
|
|
@@ -130,18 +126,16 @@ class SchedulingUtilities:
|
|
|
130
126
|
HTTPException 409 Conflict
|
|
131
127
|
If state machine is not idle and therefore can not start a new mission
|
|
132
128
|
"""
|
|
133
|
-
|
|
134
|
-
if not is_state_machine_ready_to_receive_mission:
|
|
129
|
+
if state != States.Idle:
|
|
135
130
|
error_message = f"Conflict - Robot is not idle - State: {state}"
|
|
136
131
|
self.logger.warning(error_message)
|
|
137
132
|
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
138
133
|
|
|
139
|
-
return
|
|
134
|
+
return True
|
|
140
135
|
|
|
141
136
|
def start_mission(
|
|
142
137
|
self,
|
|
143
138
|
mission: Mission,
|
|
144
|
-
initial_pose: Optional[Pose],
|
|
145
139
|
) -> None:
|
|
146
140
|
"""Start mission
|
|
147
141
|
|
|
@@ -152,11 +146,8 @@ class SchedulingUtilities:
|
|
|
152
146
|
"""
|
|
153
147
|
try:
|
|
154
148
|
self._send_command(
|
|
155
|
-
StartMissionMessage(
|
|
156
|
-
|
|
157
|
-
initial_pose=initial_pose,
|
|
158
|
-
),
|
|
159
|
-
self.queues.start_mission,
|
|
149
|
+
StartMissionMessage(mission=deepcopy(mission)),
|
|
150
|
+
self.api_events.start_mission,
|
|
160
151
|
)
|
|
161
152
|
except QueueTimeoutError:
|
|
162
153
|
error_message = "Internal Server Error - Failed to start mission in ISAR"
|
|
@@ -175,7 +166,7 @@ class SchedulingUtilities:
|
|
|
175
166
|
If there is a timeout while communicating with the state machine
|
|
176
167
|
"""
|
|
177
168
|
try:
|
|
178
|
-
return self._send_command(True, self.
|
|
169
|
+
return self._send_command(True, self.api_events.pause_mission)
|
|
179
170
|
except QueueTimeoutError:
|
|
180
171
|
error_message = "Internal Server Error - Failed to pause mission"
|
|
181
172
|
self.logger.error(error_message)
|
|
@@ -194,7 +185,7 @@ class SchedulingUtilities:
|
|
|
194
185
|
If there is a timeout while communicating with the state machine
|
|
195
186
|
"""
|
|
196
187
|
try:
|
|
197
|
-
return self._send_command(True, self.
|
|
188
|
+
return self._send_command(True, self.api_events.resume_mission)
|
|
198
189
|
except QueueTimeoutError:
|
|
199
190
|
error_message = "Internal Server Error - Failed to resume mission"
|
|
200
191
|
self.logger.error(error_message)
|
|
@@ -214,7 +205,7 @@ class SchedulingUtilities:
|
|
|
214
205
|
"""
|
|
215
206
|
try:
|
|
216
207
|
stop_mission_response: ControlMissionResponse = self._send_command(
|
|
217
|
-
True, self.
|
|
208
|
+
True, self.api_events.stop_mission
|
|
218
209
|
)
|
|
219
210
|
except QueueTimeoutError:
|
|
220
211
|
error_message = "Internal Server Error - Failed to stop mission"
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
import queue
|
|
4
3
|
from collections import deque
|
|
5
4
|
from datetime import datetime, timezone
|
|
6
5
|
from typing import Deque, List, Optional
|
|
7
6
|
|
|
8
|
-
from alitra import Pose
|
|
9
7
|
from injector import inject
|
|
10
8
|
from transitions import Machine
|
|
11
9
|
from transitions.core import State
|
|
@@ -16,27 +14,43 @@ from isar.mission_planner.task_selector_interface import (
|
|
|
16
14
|
TaskSelectorInterface,
|
|
17
15
|
TaskSelectorStop,
|
|
18
16
|
)
|
|
19
|
-
from isar.models.communication.
|
|
20
|
-
from isar.models.communication.queues.
|
|
17
|
+
from isar.models.communication.queues.events import Events, SharedState
|
|
18
|
+
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
19
|
+
from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
|
|
21
20
|
from isar.state_machine.states.idle import Idle
|
|
22
|
-
from isar.state_machine.states.initialize import Initialize
|
|
23
|
-
from isar.state_machine.states.initiate import Initiate
|
|
24
21
|
from isar.state_machine.states.monitor import Monitor
|
|
25
22
|
from isar.state_machine.states.off import Off
|
|
26
23
|
from isar.state_machine.states.offline import Offline
|
|
27
|
-
from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
|
|
28
24
|
from isar.state_machine.states.paused import Paused
|
|
29
25
|
from isar.state_machine.states.stop import Stop
|
|
30
26
|
from isar.state_machine.states_enum import States
|
|
27
|
+
from isar.state_machine.transitions.fail_mission import (
|
|
28
|
+
report_failed_mission_and_finalize,
|
|
29
|
+
)
|
|
30
|
+
from isar.state_machine.transitions.finish_mission import finish_mission
|
|
31
|
+
from isar.state_machine.transitions.pause import pause_mission
|
|
32
|
+
from isar.state_machine.transitions.resume import resume_mission
|
|
33
|
+
from isar.state_machine.transitions.start_mission import (
|
|
34
|
+
initialize_robot,
|
|
35
|
+
initiate_mission,
|
|
36
|
+
put_start_mission_on_queue,
|
|
37
|
+
set_mission_to_in_progress,
|
|
38
|
+
trigger_start_mission_or_task_event,
|
|
39
|
+
)
|
|
40
|
+
from isar.state_machine.transitions.stop import (
|
|
41
|
+
stop_mission_cleanup,
|
|
42
|
+
trigger_stop_mission_event,
|
|
43
|
+
)
|
|
44
|
+
from isar.state_machine.transitions.utils import def_transition
|
|
31
45
|
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
32
46
|
from robot_interface.models.mission.mission import Mission
|
|
33
|
-
from robot_interface.models.mission.status import
|
|
47
|
+
from robot_interface.models.mission.status import RobotStatus, TaskStatus
|
|
34
48
|
from robot_interface.models.mission.task import TASKS
|
|
35
49
|
from robot_interface.robot_interface import RobotInterface
|
|
36
50
|
from robot_interface.telemetry.mqtt_client import MqttClientInterface
|
|
37
51
|
from robot_interface.telemetry.payloads import (
|
|
38
|
-
RobotStatusPayload,
|
|
39
52
|
MissionPayload,
|
|
53
|
+
RobotStatusPayload,
|
|
40
54
|
TaskPayload,
|
|
41
55
|
)
|
|
42
56
|
from robot_interface.utilities.json_service import EnhancedJSONEncoder
|
|
@@ -48,7 +62,8 @@ class StateMachine(object):
|
|
|
48
62
|
@inject
|
|
49
63
|
def __init__(
|
|
50
64
|
self,
|
|
51
|
-
|
|
65
|
+
events: Events,
|
|
66
|
+
shared_state: SharedState,
|
|
52
67
|
robot: RobotInterface,
|
|
53
68
|
mqtt_publisher: MqttClientInterface,
|
|
54
69
|
task_selector: TaskSelectorInterface,
|
|
@@ -60,8 +75,8 @@ class StateMachine(object):
|
|
|
60
75
|
|
|
61
76
|
Parameters
|
|
62
77
|
----------
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
events : Events
|
|
79
|
+
Events used for API and robot service communication.
|
|
65
80
|
robot : RobotInterface
|
|
66
81
|
Instance of robot interface.
|
|
67
82
|
mqtt_publisher : MqttClientInterface
|
|
@@ -76,7 +91,8 @@ class StateMachine(object):
|
|
|
76
91
|
"""
|
|
77
92
|
self.logger = logging.getLogger("state_machine")
|
|
78
93
|
|
|
79
|
-
self.
|
|
94
|
+
self.events: Events = events
|
|
95
|
+
self.shared_state: SharedState = shared_state
|
|
80
96
|
self.robot: RobotInterface = robot
|
|
81
97
|
self.mqtt_publisher: Optional[MqttClientInterface] = mqtt_publisher
|
|
82
98
|
self.task_selector: TaskSelectorInterface = task_selector
|
|
@@ -85,9 +101,7 @@ class StateMachine(object):
|
|
|
85
101
|
self.stop_state: State = Stop(self)
|
|
86
102
|
self.paused_state: State = Paused(self)
|
|
87
103
|
self.idle_state: State = Idle(self)
|
|
88
|
-
self.initialize_state: State = Initialize(self)
|
|
89
104
|
self.monitor_state: State = Monitor(self)
|
|
90
|
-
self.initiate_state: State = Initiate(self)
|
|
91
105
|
self.off_state: State = Off(self)
|
|
92
106
|
self.offline_state: State = Offline(self)
|
|
93
107
|
self.blocked_protective_stop: State = BlockedProtectiveStop(self)
|
|
@@ -95,8 +109,6 @@ class StateMachine(object):
|
|
|
95
109
|
self.states: List[State] = [
|
|
96
110
|
self.off_state,
|
|
97
111
|
self.idle_state,
|
|
98
|
-
self.initialize_state,
|
|
99
|
-
self.initiate_state,
|
|
100
112
|
self.monitor_state,
|
|
101
113
|
self.stop_state,
|
|
102
114
|
self.paused_state,
|
|
@@ -112,73 +124,68 @@ class StateMachine(object):
|
|
|
112
124
|
"source": self.off_state,
|
|
113
125
|
"dest": self.idle_state,
|
|
114
126
|
},
|
|
115
|
-
{
|
|
116
|
-
"trigger": "initiated",
|
|
117
|
-
"source": self.initiate_state,
|
|
118
|
-
"dest": self.monitor_state,
|
|
119
|
-
"before": self._initiated,
|
|
120
|
-
},
|
|
121
127
|
{
|
|
122
128
|
"trigger": "pause",
|
|
123
129
|
"source": self.monitor_state,
|
|
124
130
|
"dest": self.paused_state,
|
|
125
|
-
"before": self
|
|
131
|
+
"before": def_transition(self, pause_mission),
|
|
126
132
|
},
|
|
127
133
|
{
|
|
128
134
|
"trigger": "stop",
|
|
129
135
|
"source": [
|
|
130
136
|
self.idle_state,
|
|
131
|
-
self.initiate_state,
|
|
132
137
|
self.monitor_state,
|
|
133
138
|
self.paused_state,
|
|
134
139
|
],
|
|
135
140
|
"dest": self.stop_state,
|
|
141
|
+
"before": def_transition(self, trigger_stop_mission_event),
|
|
136
142
|
},
|
|
137
143
|
{
|
|
138
|
-
"trigger": "
|
|
139
|
-
"source": self.monitor_state,
|
|
140
|
-
"dest": self.idle_state,
|
|
141
|
-
"before": self._mission_finished,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
"trigger": "mission_started",
|
|
144
|
+
"trigger": "request_mission_start",
|
|
145
145
|
"source": self.idle_state,
|
|
146
|
-
"dest": self.
|
|
147
|
-
"
|
|
146
|
+
"dest": self.monitor_state,
|
|
147
|
+
"prepare": def_transition(self, put_start_mission_on_queue),
|
|
148
|
+
"conditions": [
|
|
149
|
+
def_transition(self, initiate_mission),
|
|
150
|
+
def_transition(self, initialize_robot),
|
|
151
|
+
],
|
|
152
|
+
"before": [
|
|
153
|
+
def_transition(self, set_mission_to_in_progress),
|
|
154
|
+
def_transition(self, trigger_start_mission_or_task_event),
|
|
155
|
+
],
|
|
148
156
|
},
|
|
149
157
|
{
|
|
150
|
-
"trigger": "
|
|
151
|
-
"source": self.
|
|
152
|
-
"dest": self.
|
|
153
|
-
"before": self._initialization_successful,
|
|
158
|
+
"trigger": "request_mission_start",
|
|
159
|
+
"source": self.idle_state,
|
|
160
|
+
"dest": self.idle_state,
|
|
154
161
|
},
|
|
155
162
|
{
|
|
156
|
-
"trigger": "
|
|
157
|
-
"source": self.
|
|
163
|
+
"trigger": "mission_failed_to_start",
|
|
164
|
+
"source": self.monitor_state,
|
|
158
165
|
"dest": self.idle_state,
|
|
159
|
-
"before": self
|
|
166
|
+
"before": def_transition(self, report_failed_mission_and_finalize),
|
|
160
167
|
},
|
|
161
168
|
{
|
|
162
169
|
"trigger": "resume",
|
|
163
170
|
"source": self.paused_state,
|
|
164
171
|
"dest": self.monitor_state,
|
|
165
|
-
"before": self
|
|
172
|
+
"before": def_transition(self, resume_mission),
|
|
166
173
|
},
|
|
167
174
|
{
|
|
168
|
-
"trigger": "
|
|
169
|
-
"source": self.
|
|
175
|
+
"trigger": "mission_finished",
|
|
176
|
+
"source": self.monitor_state,
|
|
170
177
|
"dest": self.idle_state,
|
|
171
|
-
"before": self
|
|
178
|
+
"before": def_transition(self, finish_mission),
|
|
172
179
|
},
|
|
173
180
|
{
|
|
174
181
|
"trigger": "mission_stopped",
|
|
175
182
|
"source": self.stop_state,
|
|
176
183
|
"dest": self.idle_state,
|
|
177
|
-
"before": self
|
|
184
|
+
"before": def_transition(self, stop_mission_cleanup),
|
|
178
185
|
},
|
|
179
186
|
{
|
|
180
187
|
"trigger": "robot_turned_offline",
|
|
181
|
-
"source": self.idle_state,
|
|
188
|
+
"source": [self.idle_state, self.blocked_protective_stop],
|
|
182
189
|
"dest": self.offline_state,
|
|
183
190
|
},
|
|
184
191
|
{
|
|
@@ -188,7 +195,7 @@ class StateMachine(object):
|
|
|
188
195
|
},
|
|
189
196
|
{
|
|
190
197
|
"trigger": "robot_protective_stop_engaged",
|
|
191
|
-
"source": self.idle_state,
|
|
198
|
+
"source": [self.idle_state, self.offline_state],
|
|
192
199
|
"dest": self.blocked_protective_stop,
|
|
193
200
|
},
|
|
194
201
|
{
|
|
@@ -204,137 +211,14 @@ class StateMachine(object):
|
|
|
204
211
|
|
|
205
212
|
self.current_mission: Optional[Mission] = None
|
|
206
213
|
self.current_task: Optional[TASKS] = None
|
|
207
|
-
|
|
214
|
+
|
|
215
|
+
self.mission_ongoing: bool = False
|
|
208
216
|
|
|
209
217
|
self.current_state: State = States(self.state) # type: ignore
|
|
210
218
|
|
|
211
219
|
self.transitions_log_length: int = transitions_log_length
|
|
212
220
|
self.transitions_list: Deque[States] = deque([], self.transitions_log_length)
|
|
213
221
|
|
|
214
|
-
#################################################################################
|
|
215
|
-
# Transition Callbacks
|
|
216
|
-
def _initialization_successful(self) -> None:
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
def _initialization_failed(self) -> None:
|
|
220
|
-
self.queues.start_mission.output.put(False)
|
|
221
|
-
self._finalize()
|
|
222
|
-
|
|
223
|
-
def _initiated(self) -> None:
|
|
224
|
-
self.current_mission.status = MissionStatus.InProgress
|
|
225
|
-
self.publish_task_status(task=self.current_task)
|
|
226
|
-
self.logger.info(
|
|
227
|
-
f"Successfully initiated "
|
|
228
|
-
f"{type(self.current_task).__name__} "
|
|
229
|
-
f"task: {str(self.current_task.id)[:8]}"
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
def _resume(self) -> None:
|
|
233
|
-
self.logger.info(f"Resuming mission: {self.current_mission.id}")
|
|
234
|
-
self.current_mission.status = MissionStatus.InProgress
|
|
235
|
-
self.current_mission.error_message = None
|
|
236
|
-
self.current_task.status = TaskStatus.InProgress
|
|
237
|
-
|
|
238
|
-
self.publish_mission_status()
|
|
239
|
-
self.publish_task_status(task=self.current_task)
|
|
240
|
-
|
|
241
|
-
resume_mission_response: ControlMissionResponse = (
|
|
242
|
-
self._make_control_mission_response()
|
|
243
|
-
)
|
|
244
|
-
self.queues.resume_mission.output.put(resume_mission_response)
|
|
245
|
-
|
|
246
|
-
self.robot.resume()
|
|
247
|
-
|
|
248
|
-
def _mission_finished(self) -> None:
|
|
249
|
-
fail_statuses: List[TaskStatus] = [
|
|
250
|
-
TaskStatus.Cancelled,
|
|
251
|
-
TaskStatus.Failed,
|
|
252
|
-
]
|
|
253
|
-
partially_fail_statuses = fail_statuses + [TaskStatus.PartiallySuccessful]
|
|
254
|
-
|
|
255
|
-
if len(self.current_mission.tasks) == 0:
|
|
256
|
-
self.current_mission.status = MissionStatus.Successful
|
|
257
|
-
elif all(task.status in fail_statuses for task in self.current_mission.tasks):
|
|
258
|
-
self.current_mission.error_message = ErrorMessage(
|
|
259
|
-
error_reason=None,
|
|
260
|
-
error_description="The mission failed because all tasks in the mission "
|
|
261
|
-
"failed",
|
|
262
|
-
)
|
|
263
|
-
self.current_mission.status = MissionStatus.Failed
|
|
264
|
-
elif any(
|
|
265
|
-
task.status in partially_fail_statuses
|
|
266
|
-
for task in self.current_mission.tasks
|
|
267
|
-
):
|
|
268
|
-
self.current_mission.status = MissionStatus.PartiallySuccessful
|
|
269
|
-
else:
|
|
270
|
-
self.current_mission.status = MissionStatus.Successful
|
|
271
|
-
self._finalize()
|
|
272
|
-
|
|
273
|
-
def _mission_started(self) -> None:
|
|
274
|
-
self.queues.start_mission.output.put(True)
|
|
275
|
-
self.logger.info(
|
|
276
|
-
f"Initialization successful. Starting new mission: "
|
|
277
|
-
f"{self.current_mission.id}"
|
|
278
|
-
)
|
|
279
|
-
self.log_mission_overview(mission=self.current_mission)
|
|
280
|
-
|
|
281
|
-
self.current_mission.status = MissionStatus.InProgress
|
|
282
|
-
self.publish_mission_status()
|
|
283
|
-
self.current_task = self.task_selector.next_task()
|
|
284
|
-
if self.current_task is None:
|
|
285
|
-
self._mission_finished()
|
|
286
|
-
else:
|
|
287
|
-
self.current_task.status = TaskStatus.InProgress
|
|
288
|
-
self.publish_task_status(task=self.current_task)
|
|
289
|
-
|
|
290
|
-
def _full_mission_finished(self) -> None:
|
|
291
|
-
self.current_task = None
|
|
292
|
-
|
|
293
|
-
def _mission_paused(self) -> None:
|
|
294
|
-
self.logger.info(f"Pausing mission: {self.current_mission.id}")
|
|
295
|
-
self.current_mission.status = MissionStatus.Paused
|
|
296
|
-
self.current_task.status = TaskStatus.Paused
|
|
297
|
-
|
|
298
|
-
paused_mission_response: ControlMissionResponse = (
|
|
299
|
-
self._make_control_mission_response()
|
|
300
|
-
)
|
|
301
|
-
self.queues.pause_mission.output.put(paused_mission_response)
|
|
302
|
-
|
|
303
|
-
self.publish_mission_status()
|
|
304
|
-
self.publish_task_status(task=self.current_task)
|
|
305
|
-
|
|
306
|
-
self.robot.pause()
|
|
307
|
-
|
|
308
|
-
def _initiate_failed(self) -> None:
|
|
309
|
-
self.current_task.status = TaskStatus.Failed
|
|
310
|
-
self.current_mission.status = MissionStatus.Failed
|
|
311
|
-
self.publish_task_status(task=self.current_task)
|
|
312
|
-
self._finalize()
|
|
313
|
-
|
|
314
|
-
def _mission_stopped(self) -> None:
|
|
315
|
-
if self.current_mission is None:
|
|
316
|
-
self._queue_empty_response()
|
|
317
|
-
self.reset_state_machine()
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
self.current_mission.status = MissionStatus.Cancelled
|
|
321
|
-
|
|
322
|
-
for task in self.current_mission.tasks:
|
|
323
|
-
if task.status in [
|
|
324
|
-
TaskStatus.NotStarted,
|
|
325
|
-
TaskStatus.InProgress,
|
|
326
|
-
TaskStatus.Paused,
|
|
327
|
-
]:
|
|
328
|
-
task.status = TaskStatus.Cancelled
|
|
329
|
-
|
|
330
|
-
stopped_mission_response: ControlMissionResponse = (
|
|
331
|
-
self._make_control_mission_response()
|
|
332
|
-
)
|
|
333
|
-
self.queues.stop_mission.output.put(stopped_mission_response)
|
|
334
|
-
|
|
335
|
-
self.publish_task_status(task=self.current_task)
|
|
336
|
-
self._finalize()
|
|
337
|
-
|
|
338
222
|
#################################################################################
|
|
339
223
|
|
|
340
224
|
def _finalize(self) -> None:
|
|
@@ -350,11 +234,7 @@ class StateMachine(object):
|
|
|
350
234
|
self.reset_state_machine()
|
|
351
235
|
|
|
352
236
|
def begin(self):
|
|
353
|
-
"""Starts the state machine.
|
|
354
|
-
|
|
355
|
-
Transitions into idle state.
|
|
356
|
-
|
|
357
|
-
"""
|
|
237
|
+
"""Starts the state machine. Transitions into idle state."""
|
|
358
238
|
self.to_idle() # type: ignore
|
|
359
239
|
|
|
360
240
|
def iterate_current_task(self):
|
|
@@ -366,11 +246,12 @@ class StateMachine(object):
|
|
|
366
246
|
except TaskSelectorStop:
|
|
367
247
|
# Indicates that all tasks are finished
|
|
368
248
|
self.current_task = None
|
|
249
|
+
self.send_task_status()
|
|
369
250
|
|
|
370
251
|
def update_state(self):
|
|
371
252
|
"""Updates the current state of the state machine."""
|
|
372
253
|
self.current_state = States(self.state) # type: ignore
|
|
373
|
-
self.
|
|
254
|
+
update_shared_state(self.shared_state.state, self.current_state)
|
|
374
255
|
self._log_state_transition(self.current_state)
|
|
375
256
|
self.logger.info(f"State: {self.current_state}")
|
|
376
257
|
self.publish_status()
|
|
@@ -378,42 +259,19 @@ class StateMachine(object):
|
|
|
378
259
|
def reset_state_machine(self) -> None:
|
|
379
260
|
self.logger.info("Resetting state machine")
|
|
380
261
|
self.current_task = None
|
|
262
|
+
self.send_task_status()
|
|
381
263
|
self.current_mission = None
|
|
382
|
-
self.initial_pose = None
|
|
383
264
|
|
|
384
|
-
def start_mission(self, mission: Mission
|
|
265
|
+
def start_mission(self, mission: Mission):
|
|
385
266
|
"""Starts a scheduled mission."""
|
|
386
267
|
self.current_mission = mission
|
|
387
|
-
self.initial_pose = initial_pose
|
|
388
268
|
|
|
389
269
|
self.task_selector.initialize(tasks=self.current_mission.tasks)
|
|
390
270
|
|
|
391
|
-
def
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return None
|
|
396
|
-
|
|
397
|
-
def should_stop_mission(self) -> bool:
|
|
398
|
-
try:
|
|
399
|
-
return self.queues.stop_mission.input.get(block=False)
|
|
400
|
-
except queue.Empty:
|
|
401
|
-
return False
|
|
402
|
-
|
|
403
|
-
def should_pause_mission(self) -> bool:
|
|
404
|
-
try:
|
|
405
|
-
return self.queues.pause_mission.input.get(block=False)
|
|
406
|
-
except queue.Empty:
|
|
407
|
-
return False
|
|
408
|
-
|
|
409
|
-
def should_resume_mission(self) -> bool:
|
|
410
|
-
try:
|
|
411
|
-
return self.queues.resume_mission.input.get(block=False)
|
|
412
|
-
except queue.Empty:
|
|
413
|
-
return False
|
|
414
|
-
|
|
415
|
-
def send_state_status(self):
|
|
416
|
-
self.queues.state.update(self.current_state)
|
|
271
|
+
def send_task_status(self):
|
|
272
|
+
update_shared_state(
|
|
273
|
+
self.shared_state.state_machine_current_task, self.current_task
|
|
274
|
+
)
|
|
417
275
|
|
|
418
276
|
def publish_mission_status(self) -> None:
|
|
419
277
|
if not self.mqtt_publisher:
|
|
@@ -502,11 +360,11 @@ class StateMachine(object):
|
|
|
502
360
|
else:
|
|
503
361
|
return RobotStatus.Busy
|
|
504
362
|
|
|
505
|
-
def _log_state_transition(self, next_state):
|
|
363
|
+
def _log_state_transition(self, next_state) -> None:
|
|
506
364
|
"""Logs all state transitions that are not self-transitions."""
|
|
507
365
|
self.transitions_list.append(next_state)
|
|
508
366
|
|
|
509
|
-
def log_mission_overview(self, mission: Mission):
|
|
367
|
+
def log_mission_overview(self, mission: Mission) -> None:
|
|
510
368
|
"""Log an overview of the tasks in a mission"""
|
|
511
369
|
log_statements: List[str] = []
|
|
512
370
|
for task in mission.tasks:
|
|
@@ -525,8 +383,8 @@ class StateMachine(object):
|
|
|
525
383
|
task_status=self.current_task.status,
|
|
526
384
|
)
|
|
527
385
|
|
|
528
|
-
def _queue_empty_response(self):
|
|
529
|
-
self.
|
|
386
|
+
def _queue_empty_response(self) -> None:
|
|
387
|
+
self.events.api_requests.stop_mission.output.put(
|
|
530
388
|
ControlMissionResponse(
|
|
531
389
|
mission_id="None",
|
|
532
390
|
mission_status="None",
|
|
@@ -4,12 +4,8 @@ from typing import TYPE_CHECKING, Optional
|
|
|
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
|
|
8
|
+
from isar.services.utilities.threaded_request import ThreadedRequest
|
|
13
9
|
from robot_interface.models.mission.status import RobotStatus
|
|
14
10
|
|
|
15
11
|
if TYPE_CHECKING:
|
|
@@ -24,42 +20,26 @@ class BlockedProtectiveStop(State):
|
|
|
24
20
|
self.state_machine: "StateMachine" = state_machine
|
|
25
21
|
self.logger = logging.getLogger("state_machine")
|
|
26
22
|
self.robot_status_thread: Optional[ThreadedRequest] = None
|
|
23
|
+
self.shared_state = self.state_machine.shared_state
|
|
27
24
|
|
|
28
25
|
def start(self) -> None:
|
|
29
26
|
self.state_machine.update_state()
|
|
30
27
|
self._run()
|
|
31
28
|
|
|
32
29
|
def stop(self) -> None:
|
|
33
|
-
|
|
34
|
-
self.robot_status_thread.wait_for_thread()
|
|
35
|
-
self.robot_status_thread = None
|
|
30
|
+
return
|
|
36
31
|
|
|
37
32
|
def _run(self) -> None:
|
|
38
33
|
while True:
|
|
39
|
-
|
|
40
|
-
self.robot_status_thread = ThreadedRequest(
|
|
41
|
-
request_func=self.state_machine.robot.robot_status
|
|
42
|
-
)
|
|
43
|
-
self.robot_status_thread.start_thread(
|
|
44
|
-
name="State Machine BlockedProtectiveStop Get Robot Status"
|
|
45
|
-
)
|
|
34
|
+
robot_status = check_shared_state(self.shared_state.robot_status)
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
continue
|
|
52
|
-
|
|
53
|
-
except RobotException as e:
|
|
54
|
-
self.logger.error(
|
|
55
|
-
f"Failed to get robot status because: {e.error_description}"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if robot_status != RobotStatus.BlockedProtectiveStop:
|
|
36
|
+
if robot_status == RobotStatus.Offline:
|
|
37
|
+
transition = self.state_machine.robot_turned_offline # type: ignore
|
|
38
|
+
break
|
|
39
|
+
elif robot_status != RobotStatus.BlockedProtectiveStop:
|
|
59
40
|
transition = self.state_machine.robot_protective_stop_disengaged # type: ignore
|
|
60
41
|
break
|
|
61
42
|
|
|
62
|
-
self.
|
|
63
|
-
time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
|
|
43
|
+
time.sleep(self.state_machine.sleep_time)
|
|
64
44
|
|
|
65
45
|
transition()
|