isar 1.26.0__py3-none-any.whl → 1.26.2__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 +0 -4
- isar/apis/schedule/scheduling_controller.py +5 -28
- isar/apis/security/authentication.py +1 -1
- isar/config/settings.py +1 -1
- isar/models/communication/queues/events.py +57 -0
- isar/models/communication/queues/queue_utils.py +18 -13
- isar/models/communication/queues/status_queue.py +7 -5
- isar/modules.py +27 -14
- isar/robot/robot.py +46 -13
- isar/robot/robot_start_mission.py +10 -10
- isar/robot/robot_status.py +7 -4
- isar/robot/robot_stop_mission.py +72 -0
- isar/robot/robot_task_status.py +6 -6
- isar/script.py +8 -8
- isar/services/service_connections/mqtt/mqtt_client.py +2 -2
- isar/services/utilities/scheduling_utilities.py +10 -8
- isar/state_machine/state_machine.py +22 -75
- isar/state_machine/states/blocked_protective_stop.py +4 -12
- isar/state_machine/states/idle.py +45 -25
- isar/state_machine/states/monitor.py +127 -90
- isar/state_machine/states/off.py +1 -1
- isar/state_machine/states/offline.py +5 -1
- isar/state_machine/states/paused.py +5 -2
- isar/state_machine/states/stop.py +29 -58
- isar/state_machine/transitions/pause.py +2 -2
- isar/state_machine/transitions/resume.py +2 -2
- isar/state_machine/transitions/start_mission.py +3 -3
- isar/state_machine/transitions/stop.py +9 -2
- isar/storage/blob_storage.py +2 -2
- isar/storage/slimm_storage.py +1 -1
- isar/storage/uploader.py +5 -5
- {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/METADATA +4 -17
- {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/RECORD +37 -36
- {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/WHEEL +1 -1
- isar/models/communication/queues/queues.py +0 -37
- {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/entry_points.txt +0 -0
- {isar-1.26.0.dist-info → isar-1.26.2.dist-info/licenses}/LICENSE +0 -0
- {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/top_level.txt +0 -0
|
@@ -61,7 +61,6 @@ class StartMissionDefinition(BaseModel):
|
|
|
61
61
|
|
|
62
62
|
def to_isar_mission(
|
|
63
63
|
start_mission_definition: StartMissionDefinition,
|
|
64
|
-
return_pose: Optional[InputPose] = None,
|
|
65
64
|
) -> Mission:
|
|
66
65
|
isar_tasks: List[TASKS] = []
|
|
67
66
|
|
|
@@ -69,9 +68,6 @@ def to_isar_mission(
|
|
|
69
68
|
task: TASKS = to_isar_task(task_definition)
|
|
70
69
|
isar_tasks.append(task)
|
|
71
70
|
|
|
72
|
-
if return_pose:
|
|
73
|
-
isar_tasks.append(ReturnToHome(pose=return_pose.to_alitra_pose()))
|
|
74
|
-
|
|
75
71
|
if not isar_tasks:
|
|
76
72
|
raise MissionPlannerError("Mission does not contain any valid tasks")
|
|
77
73
|
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from http import HTTPStatus
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
|
-
from alitra import Pose
|
|
6
4
|
from fastapi import Body, HTTPException, Path
|
|
7
5
|
from injector import inject
|
|
8
6
|
|
|
9
7
|
from isar.apis.models.models import (
|
|
10
8
|
ControlMissionResponse,
|
|
11
|
-
InputPose,
|
|
12
9
|
StartMissionResponse,
|
|
13
10
|
TaskResponse,
|
|
14
11
|
)
|
|
@@ -21,12 +18,7 @@ from isar.mission_planner.mission_planner_interface import MissionPlannerError
|
|
|
21
18
|
from isar.services.utilities.scheduling_utilities import SchedulingUtilities
|
|
22
19
|
from isar.state_machine.states_enum import States
|
|
23
20
|
from robot_interface.models.mission.mission import Mission
|
|
24
|
-
from robot_interface.models.mission.task import
|
|
25
|
-
TASKS,
|
|
26
|
-
InspectionTask,
|
|
27
|
-
MoveArm,
|
|
28
|
-
ReturnToHome,
|
|
29
|
-
)
|
|
21
|
+
from robot_interface.models.mission.task import TASKS, InspectionTask, MoveArm
|
|
30
22
|
|
|
31
23
|
|
|
32
24
|
class SchedulingController:
|
|
@@ -45,28 +37,19 @@ class SchedulingController:
|
|
|
45
37
|
title="Mission ID",
|
|
46
38
|
description="ID-number for predefined mission",
|
|
47
39
|
),
|
|
48
|
-
return_pose: Optional[InputPose] = Body(
|
|
49
|
-
default=None,
|
|
50
|
-
description="End pose of the mission. The robot return to the specified "
|
|
51
|
-
"pose after finishing all inspections",
|
|
52
|
-
embed=True,
|
|
53
|
-
),
|
|
54
40
|
) -> StartMissionResponse:
|
|
55
|
-
self.logger.info(
|
|
41
|
+
self.logger.info("Received request to start mission with id %s", mission_id)
|
|
56
42
|
|
|
57
43
|
state: States = self.scheduling_utilities.get_state()
|
|
58
44
|
self.scheduling_utilities.verify_state_machine_ready_to_receive_mission(state)
|
|
59
45
|
|
|
60
46
|
mission: Mission = self.scheduling_utilities.get_mission(mission_id)
|
|
61
|
-
if return_pose:
|
|
62
|
-
pose: Pose = return_pose.to_alitra_pose()
|
|
63
|
-
mission.tasks.append(ReturnToHome(pose=pose))
|
|
64
47
|
|
|
65
48
|
self.scheduling_utilities.verify_robot_capable_of_mission(
|
|
66
49
|
mission=mission, robot_capabilities=robot_settings.CAPABILITIES
|
|
67
50
|
)
|
|
68
51
|
|
|
69
|
-
self.logger.info(
|
|
52
|
+
self.logger.info("Starting mission with ISAR Mission ID: '%s'", mission.id)
|
|
70
53
|
|
|
71
54
|
self.scheduling_utilities.start_mission(mission=mission)
|
|
72
55
|
|
|
@@ -80,12 +63,6 @@ class SchedulingController:
|
|
|
80
63
|
title="Mission Definition",
|
|
81
64
|
description="Description of the mission in json format",
|
|
82
65
|
),
|
|
83
|
-
return_pose: Optional[InputPose] = Body(
|
|
84
|
-
default=None,
|
|
85
|
-
description="End pose of the mission. The robot return to the specified "
|
|
86
|
-
"pose after finishing all inspections",
|
|
87
|
-
embed=True,
|
|
88
|
-
),
|
|
89
66
|
) -> StartMissionResponse:
|
|
90
67
|
self.logger.info("Received request to start new mission")
|
|
91
68
|
|
|
@@ -103,7 +80,7 @@ class SchedulingController:
|
|
|
103
80
|
|
|
104
81
|
try:
|
|
105
82
|
mission: Mission = to_isar_mission(
|
|
106
|
-
start_mission_definition=mission_definition
|
|
83
|
+
start_mission_definition=mission_definition
|
|
107
84
|
)
|
|
108
85
|
except MissionPlannerError as e:
|
|
109
86
|
error_message = f"Bad Request - Cannot create ISAR mission: {e}"
|
|
@@ -117,7 +94,7 @@ class SchedulingController:
|
|
|
117
94
|
mission=mission, robot_capabilities=robot_settings.CAPABILITIES
|
|
118
95
|
)
|
|
119
96
|
|
|
120
|
-
self.logger.info(
|
|
97
|
+
self.logger.info("Starting mission: %s", mission.id)
|
|
121
98
|
self.scheduling_utilities.start_mission(mission=mission)
|
|
122
99
|
return self._api_response(mission)
|
|
123
100
|
|
|
@@ -48,7 +48,7 @@ class Authenticator:
|
|
|
48
48
|
self.logger = logging.getLogger("api")
|
|
49
49
|
self.authentication_enabled: bool = authentication_enabled
|
|
50
50
|
enabled_string = "enabled" if self.authentication_enabled else "disabled"
|
|
51
|
-
self.logger.info(
|
|
51
|
+
self.logger.info("API authentication is %s", enabled_string)
|
|
52
52
|
|
|
53
53
|
def should_authenticate(self) -> bool:
|
|
54
54
|
return self.authentication_enabled
|
isar/config/settings.py
CHANGED
|
@@ -51,7 +51,7 @@ class Settings(BaseSettings):
|
|
|
51
51
|
# Determines the number of state transitions that are kept in the log
|
|
52
52
|
STATE_TRANSITIONS_LOG_LENGTH: int = Field(default=20)
|
|
53
53
|
|
|
54
|
-
# Number of attempts to initiate a
|
|
54
|
+
# Number of attempts to initiate a mission before cancelling
|
|
55
55
|
INITIATE_FAILURE_COUNTER_LIMIT: int = Field(default=10)
|
|
56
56
|
|
|
57
57
|
# Number of attempts to request a task status in monitor before cancelling
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from queue import Queue
|
|
2
|
+
|
|
3
|
+
from transitions import State
|
|
4
|
+
|
|
5
|
+
from isar.config.settings import settings
|
|
6
|
+
from isar.models.communication.queues.queue_io import QueueIO
|
|
7
|
+
from isar.models.communication.queues.status_queue import StatusQueue
|
|
8
|
+
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
9
|
+
from robot_interface.models.mission.mission import Mission
|
|
10
|
+
from robot_interface.models.mission.status import RobotStatus, TaskStatus
|
|
11
|
+
from robot_interface.models.mission.task import TASKS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Events:
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self.api_requests: APIRequests = APIRequests()
|
|
17
|
+
self.state_machine_events: StateMachineEvents = StateMachineEvents()
|
|
18
|
+
self.robot_service_events: RobotServiceEvents = RobotServiceEvents()
|
|
19
|
+
|
|
20
|
+
self.upload_queue: Queue = Queue(maxsize=10)
|
|
21
|
+
|
|
22
|
+
if settings.MQTT_ENABLED:
|
|
23
|
+
self.mqtt_queue: Queue = Queue()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class APIRequests:
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self.start_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
29
|
+
self.stop_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
30
|
+
self.pause_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
31
|
+
self.resume_mission: QueueIO = QueueIO(input_size=1, output_size=1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StateMachineEvents:
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
self.start_mission: Queue[Mission] = Queue(maxsize=1)
|
|
37
|
+
self.stop_mission: Queue[bool] = Queue(maxsize=1)
|
|
38
|
+
self.pause_mission: Queue[bool] = Queue(maxsize=1)
|
|
39
|
+
self.task_status_request: Queue[str] = Queue(maxsize=1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RobotServiceEvents:
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
self.task_status_updated: Queue[TaskStatus] = Queue(maxsize=1)
|
|
45
|
+
self.task_status_failed: Queue[ErrorMessage] = Queue(maxsize=1)
|
|
46
|
+
self.mission_started: Queue[bool] = Queue(maxsize=1)
|
|
47
|
+
self.mission_failed: Queue[ErrorMessage] = Queue(maxsize=1)
|
|
48
|
+
self.robot_status_changed: Queue[bool] = Queue(maxsize=1)
|
|
49
|
+
self.mission_failed_to_stop: Queue[ErrorMessage] = Queue(maxsize=1)
|
|
50
|
+
self.mission_successfully_stopped: Queue[bool] = Queue(maxsize=1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SharedState:
|
|
54
|
+
def __init__(self) -> None:
|
|
55
|
+
self.state: StatusQueue[State] = StatusQueue()
|
|
56
|
+
self.robot_status: StatusQueue[RobotStatus] = StatusQueue()
|
|
57
|
+
self.state_machine_current_task: StatusQueue[TASKS] = StatusQueue()
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
from typing import
|
|
1
|
+
from queue import Empty, Queue
|
|
2
|
+
from typing import Optional, TypeVar
|
|
3
3
|
|
|
4
|
-
from isar.models.communication.queues.queue_io import QueueIO
|
|
5
4
|
from isar.models.communication.queues.status_queue import StatusQueue
|
|
6
5
|
|
|
6
|
+
T = TypeVar("T")
|
|
7
7
|
|
|
8
|
-
def trigger_event(queueio: QueueIO, data: Any = None) -> None:
|
|
9
|
-
queueio.input.put(data if data is not None else True)
|
|
10
8
|
|
|
9
|
+
def trigger_event_without_data(queue: Queue[bool]) -> None:
|
|
10
|
+
queue.put(True)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
def trigger_event(queue: Queue[T], data: T) -> None:
|
|
14
|
+
queue.put(data)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_shared_state(queue: StatusQueue[T]) -> Optional[T]:
|
|
13
18
|
try:
|
|
14
|
-
return
|
|
15
|
-
except
|
|
19
|
+
return queue.check()
|
|
20
|
+
except Empty:
|
|
16
21
|
return None
|
|
17
22
|
|
|
18
23
|
|
|
19
|
-
def update_shared_state(
|
|
20
|
-
|
|
24
|
+
def update_shared_state(queue: StatusQueue[T], data: T) -> None:
|
|
25
|
+
queue.update(data)
|
|
21
26
|
|
|
22
27
|
|
|
23
|
-
def check_for_event(
|
|
28
|
+
def check_for_event(queue: Queue[T]) -> Optional[T]:
|
|
24
29
|
try:
|
|
25
|
-
return
|
|
26
|
-
except
|
|
30
|
+
return queue.get(block=False)
|
|
31
|
+
except Empty:
|
|
27
32
|
return None
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
from collections import deque
|
|
2
2
|
from queue import Empty, Queue
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import TypeVar
|
|
4
4
|
|
|
5
|
+
T = TypeVar("T")
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
class StatusQueue(Queue[T]):
|
|
7
9
|
def __init__(self) -> None:
|
|
8
10
|
super().__init__()
|
|
9
11
|
|
|
10
|
-
def check(self) ->
|
|
12
|
+
def check(self) -> T:
|
|
11
13
|
if not self._qsize():
|
|
12
14
|
raise Empty
|
|
13
15
|
with self.mutex:
|
|
14
16
|
queueList = list(self.queue)
|
|
15
17
|
return queueList.pop()
|
|
16
18
|
|
|
17
|
-
def update(self, item:
|
|
19
|
+
def update(self, item: T):
|
|
18
20
|
with self.mutex:
|
|
19
|
-
self.queue = deque()
|
|
21
|
+
self.queue: deque[T] = deque()
|
|
20
22
|
self.queue.append(item)
|
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),
|
|
@@ -229,6 +242,6 @@ def get_injector() -> Injector:
|
|
|
229
242
|
)
|
|
230
243
|
|
|
231
244
|
logger: Logger = logging.getLogger("modules")
|
|
232
|
-
logger.info(
|
|
245
|
+
logger.info("Loaded the following module configurations: %s", module_overview)
|
|
233
246
|
|
|
234
247
|
return Injector(injector_modules)
|
isar/robot/robot.py
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from queue import Queue
|
|
2
3
|
from threading import Event
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
from injector import inject
|
|
6
7
|
|
|
7
|
-
from isar.models.communication.queues.
|
|
8
|
+
from isar.models.communication.queues.events import (
|
|
9
|
+
Events,
|
|
10
|
+
RobotServiceEvents,
|
|
11
|
+
SharedState,
|
|
12
|
+
StateMachineEvents,
|
|
13
|
+
)
|
|
8
14
|
from isar.models.communication.queues.queue_utils import check_for_event
|
|
9
|
-
from isar.models.communication.queues.queues import Queues
|
|
10
15
|
from isar.robot.robot_start_mission import RobotStartMissionThread
|
|
11
16
|
from isar.robot.robot_status import RobotStatusThread
|
|
17
|
+
from isar.robot.robot_stop_mission import RobotStopMissionThread
|
|
12
18
|
from isar.robot.robot_task_status import RobotTaskStatusThread
|
|
13
19
|
from robot_interface.robot_interface import RobotInterface
|
|
14
20
|
|
|
@@ -16,13 +22,18 @@ from robot_interface.robot_interface import RobotInterface
|
|
|
16
22
|
class Robot(object):
|
|
17
23
|
|
|
18
24
|
@inject
|
|
19
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self, events: Events, robot: RobotInterface, shared_state: SharedState
|
|
27
|
+
):
|
|
20
28
|
self.logger = logging.getLogger("robot")
|
|
21
|
-
self.
|
|
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
|
|
22
32
|
self.robot: RobotInterface = robot
|
|
23
33
|
self.start_mission_thread: Optional[RobotStartMissionThread] = None
|
|
24
34
|
self.robot_status_thread: Optional[RobotStatusThread] = None
|
|
25
35
|
self.robot_task_status_thread: Optional[RobotTaskStatusThread] = None
|
|
36
|
+
self.stop_mission_thread_thread: Optional[RobotStopMissionThread] = None
|
|
26
37
|
self.signal_thread_quitting: Event = Event()
|
|
27
38
|
|
|
28
39
|
def stop(self) -> None:
|
|
@@ -39,12 +50,17 @@ class Robot(object):
|
|
|
39
50
|
and self.robot_status_thread.is_alive()
|
|
40
51
|
):
|
|
41
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()
|
|
42
58
|
self.robot_status_thread = None
|
|
43
59
|
self.robot_task_status_thread = None
|
|
44
60
|
self.start_mission_thread = None
|
|
45
61
|
|
|
46
|
-
def _check_and_handle_start_mission(self,
|
|
47
|
-
start_mission = check_for_event(
|
|
62
|
+
def _check_and_handle_start_mission(self, event: Queue) -> None:
|
|
63
|
+
start_mission = check_for_event(event)
|
|
48
64
|
if start_mission is not None:
|
|
49
65
|
if (
|
|
50
66
|
self.start_mission_thread is not None
|
|
@@ -55,24 +71,39 @@ class Robot(object):
|
|
|
55
71
|
)
|
|
56
72
|
self.start_mission_thread.join()
|
|
57
73
|
self.start_mission_thread = RobotStartMissionThread(
|
|
58
|
-
self.
|
|
74
|
+
self.robot_service_events,
|
|
59
75
|
self.robot,
|
|
60
76
|
self.signal_thread_quitting,
|
|
61
77
|
start_mission,
|
|
62
78
|
)
|
|
63
79
|
self.start_mission_thread.start()
|
|
64
80
|
|
|
65
|
-
def _check_and_handle_task_status_request(self,
|
|
66
|
-
task_id = check_for_event(
|
|
81
|
+
def _check_and_handle_task_status_request(self, event: Queue[str]) -> None:
|
|
82
|
+
task_id: str = check_for_event(event)
|
|
67
83
|
if task_id:
|
|
68
84
|
self.robot_task_status_thread = RobotTaskStatusThread(
|
|
69
|
-
self.
|
|
85
|
+
self.robot_service_events,
|
|
86
|
+
self.robot,
|
|
87
|
+
self.signal_thread_quitting,
|
|
88
|
+
task_id,
|
|
70
89
|
)
|
|
71
90
|
self.robot_task_status_thread.start()
|
|
72
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
|
+
|
|
73
104
|
def run(self) -> None:
|
|
74
105
|
self.robot_status_thread = RobotStatusThread(
|
|
75
|
-
self.
|
|
106
|
+
self.robot, self.signal_thread_quitting, self.shared_state
|
|
76
107
|
)
|
|
77
108
|
self.robot_status_thread.start()
|
|
78
109
|
|
|
@@ -81,11 +112,13 @@ class Robot(object):
|
|
|
81
112
|
break
|
|
82
113
|
|
|
83
114
|
self._check_and_handle_start_mission(
|
|
84
|
-
self.
|
|
115
|
+
self.state_machine_events.start_mission
|
|
85
116
|
)
|
|
86
117
|
|
|
87
118
|
self._check_and_handle_task_status_request(
|
|
88
|
-
self.
|
|
119
|
+
self.state_machine_events.task_status_request
|
|
89
120
|
)
|
|
90
121
|
|
|
122
|
+
self._check_and_handle_stop_mission(self.state_machine_events.stop_mission)
|
|
123
|
+
|
|
91
124
|
self.logger.info("Exiting robot service main thread")
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from threading import Event, Thread
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from isar.config.settings import settings
|
|
6
|
-
from isar.models.communication.queues.
|
|
7
|
-
from isar.models.communication.queues.
|
|
8
|
-
|
|
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
|
+
)
|
|
9
10
|
from robot_interface.models.exceptions.robot_exceptions import (
|
|
10
11
|
ErrorMessage,
|
|
11
12
|
RobotException,
|
|
@@ -19,15 +20,14 @@ class RobotStartMissionThread(Thread):
|
|
|
19
20
|
|
|
20
21
|
def __init__(
|
|
21
22
|
self,
|
|
22
|
-
|
|
23
|
+
robot_service_events: RobotServiceEvents,
|
|
23
24
|
robot: RobotInterface,
|
|
24
25
|
signal_thread_quitting: Event,
|
|
25
26
|
mission: Mission,
|
|
26
27
|
):
|
|
27
28
|
self.logger = logging.getLogger("robot")
|
|
28
|
-
self.
|
|
29
|
+
self.robot_service_events: RobotServiceEvents = robot_service_events
|
|
29
30
|
self.robot: RobotInterface = robot
|
|
30
|
-
self.start_mission_thread: Optional[ThreadedRequest] = None
|
|
31
31
|
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
32
32
|
self.mission = mission
|
|
33
33
|
Thread.__init__(self, name="Robot start mission thread")
|
|
@@ -56,7 +56,7 @@ class RobotStartMissionThread(Thread):
|
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
trigger_event(
|
|
59
|
-
self.
|
|
59
|
+
self.robot_service_events.mission_failed,
|
|
60
60
|
ErrorMessage(
|
|
61
61
|
error_reason=e.error_reason,
|
|
62
62
|
error_description=error_description,
|
|
@@ -65,9 +65,9 @@ class RobotStartMissionThread(Thread):
|
|
|
65
65
|
started_mission = True
|
|
66
66
|
except RobotInfeasibleMissionException as e:
|
|
67
67
|
trigger_event(
|
|
68
|
-
self.
|
|
68
|
+
self.robot_service_events.mission_failed,
|
|
69
69
|
ErrorMessage(
|
|
70
70
|
error_reason=e.error_reason, error_description=e.error_description
|
|
71
71
|
),
|
|
72
72
|
)
|
|
73
|
-
|
|
73
|
+
trigger_event_without_data(self.robot_service_events.mission_started)
|
isar/robot/robot_status.py
CHANGED
|
@@ -3,18 +3,21 @@ import time
|
|
|
3
3
|
from threading import Event, Thread
|
|
4
4
|
|
|
5
5
|
from isar.config.settings import settings
|
|
6
|
+
from isar.models.communication.queues.events import SharedState
|
|
6
7
|
from isar.models.communication.queues.queue_utils import update_shared_state
|
|
7
|
-
from isar.models.communication.queues.queues import Queues
|
|
8
8
|
from robot_interface.robot_interface import RobotInterface
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class RobotStatusThread(Thread):
|
|
12
12
|
|
|
13
13
|
def __init__(
|
|
14
|
-
self,
|
|
14
|
+
self,
|
|
15
|
+
robot: RobotInterface,
|
|
16
|
+
signal_thread_quitting: Event,
|
|
17
|
+
shared_state: SharedState,
|
|
15
18
|
):
|
|
16
19
|
self.logger = logging.getLogger("robot")
|
|
17
|
-
self.
|
|
20
|
+
self.shared_state: SharedState = shared_state
|
|
18
21
|
self.robot: RobotInterface = robot
|
|
19
22
|
self.signal_thread_quitting: Event = signal_thread_quitting
|
|
20
23
|
self.last_robot_status_poll_time: float = time.time()
|
|
@@ -41,6 +44,6 @@ class RobotStatusThread(Thread):
|
|
|
41
44
|
|
|
42
45
|
robot_status = self.robot.robot_status()
|
|
43
46
|
|
|
44
|
-
update_shared_state(self.
|
|
47
|
+
update_shared_state(self.shared_state.robot_status, robot_status)
|
|
45
48
|
self.last_robot_status_poll_time = time.time()
|
|
46
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)
|