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
isar/modules.py
CHANGED
|
@@ -16,7 +16,7 @@ from isar.mission_planner.local_planner import LocalPlanner
|
|
|
16
16
|
from isar.mission_planner.mission_planner_interface import MissionPlannerInterface
|
|
17
17
|
from isar.mission_planner.sequential_task_selector import SequentialTaskSelector
|
|
18
18
|
from isar.mission_planner.task_selector_interface import TaskSelectorInterface
|
|
19
|
-
from isar.models.communication.queues.
|
|
19
|
+
from isar.models.communication.queues.events import Events, SharedState
|
|
20
20
|
from isar.services.service_connections.request_handler import RequestHandler
|
|
21
21
|
from isar.services.utilities.robot_utilities import RobotUtilities
|
|
22
22
|
from isar.services.utilities.scheduling_utilities import SchedulingUtilities
|
|
@@ -76,11 +76,18 @@ class RobotModule(Module):
|
|
|
76
76
|
return robot_interface.Robot() # type: ignore
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
class
|
|
79
|
+
class EventsModule(Module):
|
|
80
80
|
@provider
|
|
81
81
|
@singleton
|
|
82
|
-
def
|
|
83
|
-
return
|
|
82
|
+
def provide_events(self) -> Events:
|
|
83
|
+
return Events()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SharedStateModule(Module):
|
|
87
|
+
@provider
|
|
88
|
+
@singleton
|
|
89
|
+
def provide_shared_state(self) -> SharedState:
|
|
90
|
+
return SharedState()
|
|
84
91
|
|
|
85
92
|
|
|
86
93
|
class RequestHandlerModule(Module):
|
|
@@ -125,13 +132,15 @@ class StateMachineModule(Module):
|
|
|
125
132
|
@singleton
|
|
126
133
|
def provide_state_machine(
|
|
127
134
|
self,
|
|
128
|
-
|
|
135
|
+
events: Events,
|
|
136
|
+
shared_state: SharedState,
|
|
129
137
|
robot: RobotInterface,
|
|
130
138
|
mqtt_client: MqttClientInterface,
|
|
131
139
|
task_selector: TaskSelectorInterface,
|
|
132
140
|
) -> StateMachine:
|
|
133
141
|
return StateMachine(
|
|
134
|
-
|
|
142
|
+
events=events,
|
|
143
|
+
shared_state=shared_state,
|
|
135
144
|
robot=robot,
|
|
136
145
|
mqtt_publisher=mqtt_client,
|
|
137
146
|
task_selector=task_selector,
|
|
@@ -143,12 +152,12 @@ class UploaderModule(Module):
|
|
|
143
152
|
@singleton
|
|
144
153
|
def provide_uploader(
|
|
145
154
|
self,
|
|
146
|
-
|
|
155
|
+
events: Events,
|
|
147
156
|
storage_handlers: List[StorageInterface],
|
|
148
157
|
mqtt_client: MqttClientInterface,
|
|
149
158
|
) -> Uploader:
|
|
150
159
|
return Uploader(
|
|
151
|
-
|
|
160
|
+
events=events,
|
|
152
161
|
storage_handlers=storage_handlers,
|
|
153
162
|
mqtt_publisher=mqtt_client,
|
|
154
163
|
)
|
|
@@ -158,9 +167,12 @@ class SchedulingUtilitiesModule(Module):
|
|
|
158
167
|
@provider
|
|
159
168
|
@singleton
|
|
160
169
|
def provide_scheduling_utilities(
|
|
161
|
-
self,
|
|
170
|
+
self,
|
|
171
|
+
events: Events,
|
|
172
|
+
shared_state: SharedState,
|
|
173
|
+
mission_planner: MissionPlannerInterface,
|
|
162
174
|
) -> SchedulingUtilities:
|
|
163
|
-
return SchedulingUtilities(
|
|
175
|
+
return SchedulingUtilities(events, shared_state, mission_planner)
|
|
164
176
|
|
|
165
177
|
|
|
166
178
|
class RobotUtilitiesModule(Module):
|
|
@@ -180,9 +192,9 @@ class ServiceModule(Module):
|
|
|
180
192
|
class MqttModule(Module):
|
|
181
193
|
@provider
|
|
182
194
|
@singleton
|
|
183
|
-
def provide_mqtt_client(self,
|
|
195
|
+
def provide_mqtt_client(self, events: Events) -> MqttClientInterface:
|
|
184
196
|
if settings.MQTT_ENABLED:
|
|
185
|
-
return MqttPublisher(mqtt_queue=
|
|
197
|
+
return MqttPublisher(mqtt_queue=events.mqtt_queue)
|
|
186
198
|
return None
|
|
187
199
|
|
|
188
200
|
|
|
@@ -196,7 +208,8 @@ class SequentialTaskSelectorModule(Module):
|
|
|
196
208
|
modules: Dict[str, Tuple[Module, Union[str, bool]]] = {
|
|
197
209
|
"api": (APIModule, "required"),
|
|
198
210
|
"authentication": (AuthenticationModule, "required"),
|
|
199
|
-
"
|
|
211
|
+
"events": (EventsModule, "required"),
|
|
212
|
+
"shared_state": (SharedStateModule, "required"),
|
|
200
213
|
"request_handler": (RequestHandlerModule, "required"),
|
|
201
214
|
"robot_package": (RobotModule, settings.ROBOT_PACKAGE),
|
|
202
215
|
"isar_id": (RobotModule, settings.ISAR_ID),
|
isar/robot/robot.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from queue import Queue
|
|
3
|
+
from threading import Event
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from injector import inject
|
|
7
|
+
|
|
8
|
+
from isar.models.communication.queues.events import (
|
|
9
|
+
Events,
|
|
10
|
+
RobotServiceEvents,
|
|
11
|
+
SharedState,
|
|
12
|
+
StateMachineEvents,
|
|
13
|
+
)
|
|
14
|
+
from isar.models.communication.queues.queue_utils import check_for_event
|
|
15
|
+
from isar.robot.robot_start_mission import RobotStartMissionThread
|
|
16
|
+
from isar.robot.robot_status import RobotStatusThread
|
|
17
|
+
from isar.robot.robot_stop_mission import RobotStopMissionThread
|
|
18
|
+
from isar.robot.robot_task_status import RobotTaskStatusThread
|
|
19
|
+
from robot_interface.robot_interface import RobotInterface
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Robot(object):
|
|
23
|
+
|
|
24
|
+
@inject
|
|
25
|
+
def __init__(
|
|
26
|
+
self, events: Events, robot: RobotInterface, shared_state: SharedState
|
|
27
|
+
):
|
|
28
|
+
self.logger = logging.getLogger("robot")
|
|
29
|
+
self.state_machine_events: StateMachineEvents = events.state_machine_events
|
|
30
|
+
self.robot_service_events: RobotServiceEvents = events.robot_service_events
|
|
31
|
+
self.shared_state: SharedState = shared_state
|
|
32
|
+
self.robot: RobotInterface = robot
|
|
33
|
+
self.start_mission_thread: Optional[RobotStartMissionThread] = None
|
|
34
|
+
self.robot_status_thread: Optional[RobotStatusThread] = None
|
|
35
|
+
self.robot_task_status_thread: Optional[RobotTaskStatusThread] = None
|
|
36
|
+
self.stop_mission_thread_thread: Optional[RobotStopMissionThread] = None
|
|
37
|
+
self.signal_thread_quitting: Event = Event()
|
|
38
|
+
|
|
39
|
+
def stop(self) -> None:
|
|
40
|
+
self.signal_thread_quitting.set()
|
|
41
|
+
if self.robot_status_thread is not None and self.robot_status_thread.is_alive():
|
|
42
|
+
self.robot_status_thread.join()
|
|
43
|
+
if (
|
|
44
|
+
self.robot_task_status_thread is not None
|
|
45
|
+
and self.robot_status_thread.is_alive()
|
|
46
|
+
):
|
|
47
|
+
self.robot_task_status_thread.join()
|
|
48
|
+
if (
|
|
49
|
+
self.start_mission_thread is not None
|
|
50
|
+
and self.robot_status_thread.is_alive()
|
|
51
|
+
):
|
|
52
|
+
self.start_mission_thread.join()
|
|
53
|
+
if (
|
|
54
|
+
self.stop_mission_thread_thread is not None
|
|
55
|
+
and self.stop_mission_thread_thread.is_alive()
|
|
56
|
+
):
|
|
57
|
+
self.stop_mission_thread_thread.join()
|
|
58
|
+
self.robot_status_thread = None
|
|
59
|
+
self.robot_task_status_thread = None
|
|
60
|
+
self.start_mission_thread = None
|
|
61
|
+
|
|
62
|
+
def _check_and_handle_start_mission(self, event: Queue) -> None:
|
|
63
|
+
start_mission = check_for_event(event)
|
|
64
|
+
if start_mission is not None:
|
|
65
|
+
if (
|
|
66
|
+
self.start_mission_thread is not None
|
|
67
|
+
and self.start_mission_thread.is_alive()
|
|
68
|
+
):
|
|
69
|
+
self.logger.warning(
|
|
70
|
+
"Attempted to start mission while another mission was starting."
|
|
71
|
+
)
|
|
72
|
+
self.start_mission_thread.join()
|
|
73
|
+
self.start_mission_thread = RobotStartMissionThread(
|
|
74
|
+
self.robot_service_events,
|
|
75
|
+
self.robot,
|
|
76
|
+
self.signal_thread_quitting,
|
|
77
|
+
start_mission,
|
|
78
|
+
)
|
|
79
|
+
self.start_mission_thread.start()
|
|
80
|
+
|
|
81
|
+
def _check_and_handle_task_status_request(self, event: Queue[str]) -> None:
|
|
82
|
+
task_id: str = check_for_event(event)
|
|
83
|
+
if task_id:
|
|
84
|
+
self.robot_task_status_thread = RobotTaskStatusThread(
|
|
85
|
+
self.robot_service_events,
|
|
86
|
+
self.robot,
|
|
87
|
+
self.signal_thread_quitting,
|
|
88
|
+
task_id,
|
|
89
|
+
)
|
|
90
|
+
self.robot_task_status_thread.start()
|
|
91
|
+
|
|
92
|
+
def _check_and_handle_stop_mission(self, event: Queue) -> None:
|
|
93
|
+
if check_for_event(event):
|
|
94
|
+
if (
|
|
95
|
+
self.stop_mission_thread_thread is not None
|
|
96
|
+
and self.start_mission_thread.is_alive()
|
|
97
|
+
):
|
|
98
|
+
return
|
|
99
|
+
self.stop_mission_thread_thread = RobotStopMissionThread(
|
|
100
|
+
self.robot_service_events, self.robot, self.signal_thread_quitting
|
|
101
|
+
)
|
|
102
|
+
self.stop_mission_thread_thread.start()
|
|
103
|
+
|
|
104
|
+
def run(self) -> None:
|
|
105
|
+
self.robot_status_thread = RobotStatusThread(
|
|
106
|
+
self.robot, self.signal_thread_quitting, self.shared_state
|
|
107
|
+
)
|
|
108
|
+
self.robot_status_thread.start()
|
|
109
|
+
|
|
110
|
+
while True:
|
|
111
|
+
if self.signal_thread_quitting.is_set():
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
self._check_and_handle_start_mission(
|
|
115
|
+
self.state_machine_events.start_mission
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self._check_and_handle_task_status_request(
|
|
119
|
+
self.state_machine_events.task_status_request
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
self._check_and_handle_stop_mission(self.state_machine_events.stop_mission)
|
|
123
|
+
|
|
124
|
+
self.logger.info("Exiting robot service main thread")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from threading import Event, Thread
|
|
3
|
+
|
|
4
|
+
from isar.config.settings import settings
|
|
5
|
+
from isar.models.communication.queues.events import RobotServiceEvents
|
|
6
|
+
from isar.models.communication.queues.queue_utils import (
|
|
7
|
+
trigger_event,
|
|
8
|
+
trigger_event_without_data,
|
|
9
|
+
)
|
|
10
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
11
|
+
ErrorMessage,
|
|
12
|
+
RobotException,
|
|
13
|
+
RobotInfeasibleMissionException,
|
|
14
|
+
)
|
|
15
|
+
from robot_interface.models.mission.mission import Mission
|
|
16
|
+
from robot_interface.robot_interface import RobotInterface
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RobotStartMissionThread(Thread):
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
robot_service_events: RobotServiceEvents,
|
|
24
|
+
robot: RobotInterface,
|
|
25
|
+
signal_thread_quitting: Event,
|
|
26
|
+
mission: Mission,
|
|
27
|
+
):
|
|
28
|
+
self.logger = logging.getLogger("robot")
|
|
29
|
+
self.robot_service_events: RobotServiceEvents = robot_service_events
|
|
30
|
+
self.robot: RobotInterface = robot
|
|
31
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
32
|
+
self.mission = mission
|
|
33
|
+
Thread.__init__(self, name="Robot start mission thread")
|
|
34
|
+
|
|
35
|
+
def run(self):
|
|
36
|
+
retries = 0
|
|
37
|
+
started_mission = False
|
|
38
|
+
try:
|
|
39
|
+
while not started_mission:
|
|
40
|
+
if self.signal_thread_quitting.wait(0):
|
|
41
|
+
return
|
|
42
|
+
try:
|
|
43
|
+
self.robot.initiate_mission(self.mission)
|
|
44
|
+
except RobotException as e:
|
|
45
|
+
retries += 1
|
|
46
|
+
self.logger.warning(
|
|
47
|
+
f"Initiating failed #: {str(retries)} "
|
|
48
|
+
f"because: {e.error_description}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if retries >= settings.INITIATE_FAILURE_COUNTER_LIMIT:
|
|
52
|
+
error_description = (
|
|
53
|
+
f"Mission will be cancelled after failing to initiate "
|
|
54
|
+
f"{settings.INITIATE_FAILURE_COUNTER_LIMIT} times because: "
|
|
55
|
+
f"{e.error_description}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
trigger_event(
|
|
59
|
+
self.robot_service_events.mission_failed,
|
|
60
|
+
ErrorMessage(
|
|
61
|
+
error_reason=e.error_reason,
|
|
62
|
+
error_description=error_description,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
started_mission = True
|
|
66
|
+
except RobotInfeasibleMissionException as e:
|
|
67
|
+
trigger_event(
|
|
68
|
+
self.robot_service_events.mission_failed,
|
|
69
|
+
ErrorMessage(
|
|
70
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
trigger_event_without_data(self.robot_service_events.mission_started)
|
|
@@ -0,0 +1,49 @@
|
|
|
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.events import SharedState
|
|
7
|
+
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
8
|
+
from robot_interface.robot_interface import RobotInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RobotStatusThread(Thread):
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
robot: RobotInterface,
|
|
16
|
+
signal_thread_quitting: Event,
|
|
17
|
+
shared_state: SharedState,
|
|
18
|
+
):
|
|
19
|
+
self.logger = logging.getLogger("robot")
|
|
20
|
+
self.shared_state: SharedState = shared_state
|
|
21
|
+
self.robot: RobotInterface = robot
|
|
22
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
23
|
+
self.last_robot_status_poll_time: float = time.time()
|
|
24
|
+
Thread.__init__(self, name="Robot status thread", daemon=True)
|
|
25
|
+
|
|
26
|
+
def stop(self) -> None:
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
def _is_ready_to_poll_for_status(self) -> bool:
|
|
30
|
+
time_since_last_robot_status_poll = (
|
|
31
|
+
time.time() - self.last_robot_status_poll_time
|
|
32
|
+
)
|
|
33
|
+
return (
|
|
34
|
+
time_since_last_robot_status_poll > settings.ROBOT_API_STATUS_POLL_INTERVAL
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def run(self):
|
|
38
|
+
while True:
|
|
39
|
+
if self.signal_thread_quitting.is_set():
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
if not self._is_ready_to_poll_for_status():
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
robot_status = self.robot.robot_status()
|
|
46
|
+
|
|
47
|
+
update_shared_state(self.shared_state.robot_status, robot_status)
|
|
48
|
+
self.last_robot_status_poll_time = time.time()
|
|
49
|
+
self.logger.info("Exiting robot status thread")
|
|
@@ -0,0 +1,72 @@
|
|
|
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.events import RobotServiceEvents
|
|
8
|
+
from isar.models.communication.queues.queue_utils import (
|
|
9
|
+
trigger_event,
|
|
10
|
+
trigger_event_without_data,
|
|
11
|
+
)
|
|
12
|
+
from robot_interface.models.exceptions.robot_exceptions import (
|
|
13
|
+
ErrorMessage,
|
|
14
|
+
RobotActionException,
|
|
15
|
+
RobotException,
|
|
16
|
+
)
|
|
17
|
+
from robot_interface.robot_interface import RobotInterface
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RobotStopMissionThread(Thread):
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
robot_service_events: RobotServiceEvents,
|
|
25
|
+
robot: RobotInterface,
|
|
26
|
+
signal_thread_quitting: Event,
|
|
27
|
+
):
|
|
28
|
+
self.logger = logging.getLogger("robot")
|
|
29
|
+
self.robot_service_events: RobotServiceEvents = robot_service_events
|
|
30
|
+
self.robot: RobotInterface = robot
|
|
31
|
+
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
32
|
+
Thread.__init__(self, name="Robot start mission thread")
|
|
33
|
+
|
|
34
|
+
def run(self) -> None:
|
|
35
|
+
retries = 0
|
|
36
|
+
error: Optional[ErrorMessage] = None
|
|
37
|
+
while retries < settings.STOP_ROBOT_ATTEMPTS_LIMIT:
|
|
38
|
+
if self.signal_thread_quitting.wait(0):
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
self.robot.stop()
|
|
43
|
+
except (RobotActionException, RobotException) as e:
|
|
44
|
+
self.logger.warning(
|
|
45
|
+
f"\nFailed to stop robot because: {e.error_description}"
|
|
46
|
+
f"\nAttempting to stop the robot again"
|
|
47
|
+
)
|
|
48
|
+
retries += 1
|
|
49
|
+
error = ErrorMessage(
|
|
50
|
+
error_reason=e.error_reason, error_description=e.error_description
|
|
51
|
+
)
|
|
52
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
trigger_event_without_data(
|
|
56
|
+
self.robot_service_events.mission_successfully_stopped
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
error_description = (
|
|
61
|
+
f"\nFailed to stop the robot after {retries} attempts because: "
|
|
62
|
+
f"{error.error_description}"
|
|
63
|
+
f"\nBe aware that the robot may still be moving even though a stop has "
|
|
64
|
+
"been attempted"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
error_message = ErrorMessage(
|
|
68
|
+
error_reason=error.error_reason,
|
|
69
|
+
error_description=error_description,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
trigger_event(self.robot_service_events.mission_failed_to_stop, error_message)
|
|
@@ -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.events import RobotServiceEvents
|
|
8
|
+
from isar.models.communication.queues.queue_utils import trigger_event
|
|
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
|
+
robot_service_events: RobotServiceEvents,
|
|
26
|
+
robot: RobotInterface,
|
|
27
|
+
signal_thread_quitting: Event,
|
|
28
|
+
task_id: str,
|
|
29
|
+
):
|
|
30
|
+
self.logger = logging.getLogger("robot")
|
|
31
|
+
self.robot_service_events: RobotServiceEvents = robot_service_events
|
|
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) -> None:
|
|
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.robot_service_events.task_status_updated, task_status)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
trigger_event(
|
|
90
|
+
self.robot_service_events.task_status_failed,
|
|
91
|
+
failed_task_error,
|
|
92
|
+
)
|
isar/script.py
CHANGED
|
@@ -12,8 +12,9 @@ from isar.apis.api import API
|
|
|
12
12
|
from isar.config.keyvault.keyvault_service import Keyvault
|
|
13
13
|
from isar.config.log import setup_loggers
|
|
14
14
|
from isar.config.settings import robot_settings, settings
|
|
15
|
-
from isar.models.communication.queues.
|
|
15
|
+
from isar.models.communication.queues.events import Events
|
|
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,
|
|
@@ -93,7 +94,8 @@ def start() -> None:
|
|
|
93
94
|
state_machine: StateMachine = injector.get(StateMachine)
|
|
94
95
|
uploader: Uploader = injector.get(Uploader)
|
|
95
96
|
robot: RobotInterface = injector.get(RobotInterface)
|
|
96
|
-
|
|
97
|
+
events: Events = injector.get(Events)
|
|
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):
|
|
@@ -114,12 +121,12 @@ def start() -> None:
|
|
|
114
121
|
inspection,
|
|
115
122
|
mission,
|
|
116
123
|
)
|
|
117
|
-
state_machine.
|
|
124
|
+
state_machine.events.upload_queue.put(message)
|
|
118
125
|
|
|
119
126
|
robot.register_inspection_callback(inspections_callback)
|
|
120
127
|
|
|
121
128
|
if settings.MQTT_ENABLED:
|
|
122
|
-
mqtt_client: MqttClient = MqttClient(mqtt_queue=
|
|
129
|
+
mqtt_client: MqttClient = MqttClient(mqtt_queue=events.mqtt_queue)
|
|
123
130
|
|
|
124
131
|
mqtt_thread: Thread = Thread(
|
|
125
132
|
target=mqtt_client.run, name="ISAR MQTT Client", daemon=True
|
|
@@ -127,7 +134,7 @@ def start() -> None:
|
|
|
127
134
|
threads.append(mqtt_thread)
|
|
128
135
|
|
|
129
136
|
robot_info_publisher: RobotInfoPublisher = RobotInfoPublisher(
|
|
130
|
-
mqtt_queue=
|
|
137
|
+
mqtt_queue=events.mqtt_queue
|
|
131
138
|
)
|
|
132
139
|
robot_info_thread: Thread = Thread(
|
|
133
140
|
target=robot_info_publisher.run,
|
|
@@ -137,7 +144,7 @@ def start() -> None:
|
|
|
137
144
|
threads.append(robot_info_thread)
|
|
138
145
|
|
|
139
146
|
robot_heartbeat_publisher: RobotHeartbeatPublisher = RobotHeartbeatPublisher(
|
|
140
|
-
mqtt_queue=
|
|
147
|
+
mqtt_queue=events.mqtt_queue
|
|
141
148
|
)
|
|
142
149
|
|
|
143
150
|
robot_heartbeat_thread: Thread = Thread(
|
|
@@ -148,7 +155,7 @@ def start() -> None:
|
|
|
148
155
|
threads.append(robot_heartbeat_thread)
|
|
149
156
|
|
|
150
157
|
publishers: List[Thread] = robot.get_telemetry_publishers(
|
|
151
|
-
queue=
|
|
158
|
+
queue=events.mqtt_queue,
|
|
152
159
|
robot_name=settings.ROBOT_NAME,
|
|
153
160
|
isar_id=settings.ISAR_ID,
|
|
154
161
|
)
|