isar 1.25.8__py3-none-any.whl → 1.26.0__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 -108
- isar/apis/robot_control/robot_controller.py +5 -4
- isar/apis/schedule/scheduling_controller.py +4 -32
- isar/apis/security/authentication.py +2 -2
- isar/config/settings.env +1 -3
- isar/config/settings.py +5 -5
- isar/models/communication/message.py +0 -4
- isar/models/communication/queues/queue_utils.py +27 -0
- isar/models/communication/queues/queues.py +23 -5
- isar/robot/robot.py +91 -0
- isar/robot/robot_start_mission.py +73 -0
- isar/robot/robot_status.py +46 -0
- isar/robot/robot_task_status.py +92 -0
- isar/script.py +7 -0
- isar/services/utilities/scheduling_utilities.py +15 -26
- isar/state_machine/state_machine.py +94 -187
- isar/state_machine/states/blocked_protective_stop.py +7 -19
- isar/state_machine/states/idle.py +12 -54
- isar/state_machine/states/monitor.py +43 -90
- isar/state_machine/states/offline.py +8 -33
- isar/state_machine/states/paused.py +1 -1
- 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 +33 -0
- isar/state_machine/transitions/utils.py +10 -0
- isar/storage/slimm_storage.py +2 -2
- {isar-1.25.8.dist-info → isar-1.26.0.dist-info}/METADATA +2 -3
- {isar-1.25.8.dist-info → isar-1.26.0.dist-info}/RECORD +42 -33
- robot_interface/models/exceptions/robot_exceptions.py +0 -24
- robot_interface/models/mission/task.py +1 -1
- robot_interface/robot_interface.py +1 -6
- robot_interface/telemetry/mqtt_client.py +0 -1
- robot_interface/telemetry/payloads.py +3 -3
- robot_interface/utilities/json_service.py +1 -1
- isar/state_machine/states/initialize.py +0 -71
- isar/state_machine/states/initiate.py +0 -111
- robot_interface/models/initialize/initialize_params.py +0 -9
- {isar-1.25.8.dist-info → isar-1.26.0.dist-info}/LICENSE +0 -0
- {isar-1.25.8.dist-info → isar-1.26.0.dist-info}/WHEEL +0 -0
- {isar-1.25.8.dist-info → isar-1.26.0.dist-info}/entry_points.txt +0 -0
- {isar-1.25.8.dist-info → isar-1.26.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
|
|
5
|
+
from isar.config.settings import settings
|
|
6
|
+
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
7
|
+
from isar.models.communication.queues.queues import Queues
|
|
8
|
+
from robot_interface.robot_interface import RobotInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RobotStatusThread(Thread):
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self, queues: Queues, robot: RobotInterface, signal_thread_quitting: Event
|
|
15
|
+
):
|
|
16
|
+
self.logger = logging.getLogger("robot")
|
|
17
|
+
self.queues: Queues = queues
|
|
18
|
+
self.robot: RobotInterface = robot
|
|
19
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
20
|
+
self.last_robot_status_poll_time: float = time.time()
|
|
21
|
+
Thread.__init__(self, name="Robot status thread", daemon=True)
|
|
22
|
+
|
|
23
|
+
def stop(self) -> None:
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
def _is_ready_to_poll_for_status(self) -> bool:
|
|
27
|
+
time_since_last_robot_status_poll = (
|
|
28
|
+
time.time() - self.last_robot_status_poll_time
|
|
29
|
+
)
|
|
30
|
+
return (
|
|
31
|
+
time_since_last_robot_status_poll > settings.ROBOT_API_STATUS_POLL_INTERVAL
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def run(self):
|
|
35
|
+
while True:
|
|
36
|
+
if self.signal_thread_quitting.is_set():
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
if not self._is_ready_to_poll_for_status():
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
robot_status = self.robot.robot_status()
|
|
43
|
+
|
|
44
|
+
update_shared_state(self.queues.robot_status, robot_status)
|
|
45
|
+
self.last_robot_status_poll_time = time.time()
|
|
46
|
+
self.logger.info("Exiting robot status thread")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from threading import Event, Thread
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from isar.config.settings import settings
|
|
7
|
+
from isar.models.communication.queues.queue_utils import trigger_event
|
|
8
|
+
from isar.models.communication.queues.queues import Queues
|
|
9
|
+
from isar.services.utilities.threaded_request import ThreadedRequest
|
|
10
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
11
|
+
ErrorMessage,
|
|
12
|
+
RobotCommunicationException,
|
|
13
|
+
RobotCommunicationTimeoutException,
|
|
14
|
+
RobotException,
|
|
15
|
+
RobotTaskStatusException,
|
|
16
|
+
)
|
|
17
|
+
from robot_interface.models.mission.status import TaskStatus
|
|
18
|
+
from robot_interface.robot_interface import RobotInterface
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RobotTaskStatusThread(Thread):
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
queues: Queues,
|
|
26
|
+
robot: RobotInterface,
|
|
27
|
+
signal_thread_quitting: Event,
|
|
28
|
+
task_id: str,
|
|
29
|
+
):
|
|
30
|
+
self.logger = logging.getLogger("robot")
|
|
31
|
+
self.queues: Queues = queues
|
|
32
|
+
self.robot: RobotInterface = robot
|
|
33
|
+
self.start_mission_thread: Optional[ThreadedRequest] = None
|
|
34
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
35
|
+
self.task_id: str = task_id
|
|
36
|
+
Thread.__init__(self, name="Robot task status thread")
|
|
37
|
+
|
|
38
|
+
def run(self):
|
|
39
|
+
task_status: TaskStatus = TaskStatus.NotStarted
|
|
40
|
+
failed_task_error: Optional[ErrorMessage] = None
|
|
41
|
+
request_status_failure_counter: int = 0
|
|
42
|
+
|
|
43
|
+
while (
|
|
44
|
+
request_status_failure_counter
|
|
45
|
+
< settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
|
|
46
|
+
):
|
|
47
|
+
if self.signal_thread_quitting.wait(0):
|
|
48
|
+
return
|
|
49
|
+
if request_status_failure_counter > 0:
|
|
50
|
+
time.sleep(settings.REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
task_status = self.robot.task_status(self.task_id)
|
|
54
|
+
self.request_status_failure_counter = 0
|
|
55
|
+
except (
|
|
56
|
+
RobotCommunicationTimeoutException,
|
|
57
|
+
RobotCommunicationException,
|
|
58
|
+
) as e:
|
|
59
|
+
self.request_status_failure_counter += 1
|
|
60
|
+
self.logger.error(
|
|
61
|
+
f"Failed to get task status "
|
|
62
|
+
f"{self.request_status_failure_counter} times because: "
|
|
63
|
+
f"{e.error_description}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
failed_task_error = ErrorMessage(
|
|
67
|
+
error_reason=e.error_reason,
|
|
68
|
+
error_description=e.error_description,
|
|
69
|
+
)
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
except RobotTaskStatusException as e:
|
|
73
|
+
failed_task_error = ErrorMessage(
|
|
74
|
+
error_reason=e.error_reason,
|
|
75
|
+
error_description=e.error_description,
|
|
76
|
+
)
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
except RobotException as e:
|
|
80
|
+
failed_task_error = ErrorMessage(
|
|
81
|
+
error_reason=e.error_reason,
|
|
82
|
+
error_description=e.error_description,
|
|
83
|
+
)
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
trigger_event(self.queues.robot_task_status, task_status)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
trigger_event(
|
|
90
|
+
self.queues.robot_task_status_failed,
|
|
91
|
+
failed_task_error,
|
|
92
|
+
)
|
isar/script.py
CHANGED
|
@@ -14,6 +14,7 @@ from isar.config.log import setup_loggers
|
|
|
14
14
|
from isar.config.settings import robot_settings, settings
|
|
15
15
|
from isar.models.communication.queues.queues import Queues
|
|
16
16
|
from isar.modules import get_injector
|
|
17
|
+
from isar.robot.robot import Robot
|
|
17
18
|
from isar.services.service_connections.mqtt.mqtt_client import MqttClient
|
|
18
19
|
from isar.services.service_connections.mqtt.robot_heartbeat_publisher import (
|
|
19
20
|
RobotHeartbeatPublisher,
|
|
@@ -94,6 +95,7 @@ def start() -> None:
|
|
|
94
95
|
uploader: Uploader = injector.get(Uploader)
|
|
95
96
|
robot: RobotInterface = injector.get(RobotInterface)
|
|
96
97
|
queues: Queues = injector.get(Queues)
|
|
98
|
+
robot_service: Robot = injector.get(Robot)
|
|
97
99
|
|
|
98
100
|
threads: List[Thread] = []
|
|
99
101
|
|
|
@@ -107,6 +109,11 @@ def start() -> None:
|
|
|
107
109
|
)
|
|
108
110
|
threads.append(uploader_thread)
|
|
109
111
|
|
|
112
|
+
robot_service_thread: Thread = Thread(
|
|
113
|
+
target=robot_service.run, name="Robot service", daemon=True
|
|
114
|
+
)
|
|
115
|
+
threads.append(robot_service_thread)
|
|
116
|
+
|
|
110
117
|
if settings.UPLOAD_INSPECTIONS_ASYNC:
|
|
111
118
|
|
|
112
119
|
def inspections_callback(inspection: Inspection, mission: Mission):
|
|
@@ -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
|
|
@@ -18,10 +17,8 @@ from isar.mission_planner.mission_planner_interface import (
|
|
|
18
17
|
)
|
|
19
18
|
from isar.models.communication.message import StartMissionMessage
|
|
20
19
|
from isar.models.communication.queues.queue_io import QueueIO
|
|
20
|
+
from isar.models.communication.queues.queue_timeout_error import QueueTimeoutError
|
|
21
21
|
from isar.models.communication.queues.queues import Queues
|
|
22
|
-
from isar.models.communication.queues.queue_timeout_error import (
|
|
23
|
-
QueueTimeoutError,
|
|
24
|
-
)
|
|
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
|
|
@@ -102,14 +99,11 @@ class SchedulingUtilities:
|
|
|
102
99
|
HTTPException 400 Bad request
|
|
103
100
|
If the robot is not capable of performing mission
|
|
104
101
|
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
missing_capabilities.add(task.type)
|
|
111
|
-
|
|
112
|
-
if not is_capable:
|
|
102
|
+
missing_capabilities = {
|
|
103
|
+
task.type for task in mission.tasks if task.type not in robot_capabilities
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if missing_capabilities:
|
|
113
107
|
error_message = (
|
|
114
108
|
f"Bad Request - Robot is not capable of performing mission."
|
|
115
109
|
f" Missing functionalities: {missing_capabilities}."
|
|
@@ -120,7 +114,7 @@ class SchedulingUtilities:
|
|
|
120
114
|
detail=error_message,
|
|
121
115
|
)
|
|
122
116
|
|
|
123
|
-
return
|
|
117
|
+
return True
|
|
124
118
|
|
|
125
119
|
def verify_state_machine_ready_to_receive_mission(self, state: States) -> bool:
|
|
126
120
|
"""Verify that the state machine is idle and ready to receive a mission
|
|
@@ -130,18 +124,16 @@ class SchedulingUtilities:
|
|
|
130
124
|
HTTPException 409 Conflict
|
|
131
125
|
If state machine is not idle and therefore can not start a new mission
|
|
132
126
|
"""
|
|
133
|
-
|
|
134
|
-
if not is_state_machine_ready_to_receive_mission:
|
|
127
|
+
if state != States.Idle:
|
|
135
128
|
error_message = f"Conflict - Robot is not idle - State: {state}"
|
|
136
129
|
self.logger.warning(error_message)
|
|
137
130
|
raise HTTPException(status_code=HTTPStatus.CONFLICT, detail=error_message)
|
|
138
131
|
|
|
139
|
-
return
|
|
132
|
+
return True
|
|
140
133
|
|
|
141
134
|
def start_mission(
|
|
142
135
|
self,
|
|
143
136
|
mission: Mission,
|
|
144
|
-
initial_pose: Optional[Pose],
|
|
145
137
|
) -> None:
|
|
146
138
|
"""Start mission
|
|
147
139
|
|
|
@@ -152,11 +144,8 @@ class SchedulingUtilities:
|
|
|
152
144
|
"""
|
|
153
145
|
try:
|
|
154
146
|
self._send_command(
|
|
155
|
-
StartMissionMessage(
|
|
156
|
-
|
|
157
|
-
initial_pose=initial_pose,
|
|
158
|
-
),
|
|
159
|
-
self.queues.start_mission,
|
|
147
|
+
StartMissionMessage(mission=deepcopy(mission)),
|
|
148
|
+
self.queues.api_start_mission,
|
|
160
149
|
)
|
|
161
150
|
except QueueTimeoutError:
|
|
162
151
|
error_message = "Internal Server Error - Failed to start mission in ISAR"
|
|
@@ -175,7 +164,7 @@ class SchedulingUtilities:
|
|
|
175
164
|
If there is a timeout while communicating with the state machine
|
|
176
165
|
"""
|
|
177
166
|
try:
|
|
178
|
-
return self._send_command(True, self.queues.
|
|
167
|
+
return self._send_command(True, self.queues.api_pause_mission)
|
|
179
168
|
except QueueTimeoutError:
|
|
180
169
|
error_message = "Internal Server Error - Failed to pause mission"
|
|
181
170
|
self.logger.error(error_message)
|
|
@@ -194,7 +183,7 @@ class SchedulingUtilities:
|
|
|
194
183
|
If there is a timeout while communicating with the state machine
|
|
195
184
|
"""
|
|
196
185
|
try:
|
|
197
|
-
return self._send_command(True, self.queues.
|
|
186
|
+
return self._send_command(True, self.queues.api_resume_mission)
|
|
198
187
|
except QueueTimeoutError:
|
|
199
188
|
error_message = "Internal Server Error - Failed to resume mission"
|
|
200
189
|
self.logger.error(error_message)
|
|
@@ -214,7 +203,7 @@ class SchedulingUtilities:
|
|
|
214
203
|
"""
|
|
215
204
|
try:
|
|
216
205
|
stop_mission_response: ControlMissionResponse = self._send_command(
|
|
217
|
-
True, self.queues.
|
|
206
|
+
True, self.queues.api_stop_mission
|
|
218
207
|
)
|
|
219
208
|
except QueueTimeoutError:
|
|
220
209
|
error_message = "Internal Server Error - Failed to stop mission"
|