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.

Files changed (38) hide show
  1. isar/apis/models/start_mission_definition.py +0 -4
  2. isar/apis/schedule/scheduling_controller.py +5 -28
  3. isar/apis/security/authentication.py +1 -1
  4. isar/config/settings.py +1 -1
  5. isar/models/communication/queues/events.py +57 -0
  6. isar/models/communication/queues/queue_utils.py +18 -13
  7. isar/models/communication/queues/status_queue.py +7 -5
  8. isar/modules.py +27 -14
  9. isar/robot/robot.py +46 -13
  10. isar/robot/robot_start_mission.py +10 -10
  11. isar/robot/robot_status.py +7 -4
  12. isar/robot/robot_stop_mission.py +72 -0
  13. isar/robot/robot_task_status.py +6 -6
  14. isar/script.py +8 -8
  15. isar/services/service_connections/mqtt/mqtt_client.py +2 -2
  16. isar/services/utilities/scheduling_utilities.py +10 -8
  17. isar/state_machine/state_machine.py +22 -75
  18. isar/state_machine/states/blocked_protective_stop.py +4 -12
  19. isar/state_machine/states/idle.py +45 -25
  20. isar/state_machine/states/monitor.py +127 -90
  21. isar/state_machine/states/off.py +1 -1
  22. isar/state_machine/states/offline.py +5 -1
  23. isar/state_machine/states/paused.py +5 -2
  24. isar/state_machine/states/stop.py +29 -58
  25. isar/state_machine/transitions/pause.py +2 -2
  26. isar/state_machine/transitions/resume.py +2 -2
  27. isar/state_machine/transitions/start_mission.py +3 -3
  28. isar/state_machine/transitions/stop.py +9 -2
  29. isar/storage/blob_storage.py +2 -2
  30. isar/storage/slimm_storage.py +1 -1
  31. isar/storage/uploader.py +5 -5
  32. {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/METADATA +4 -17
  33. {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/RECORD +37 -36
  34. {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/WHEEL +1 -1
  35. isar/models/communication/queues/queues.py +0 -37
  36. {isar-1.26.0.dist-info → isar-1.26.2.dist-info}/entry_points.txt +0 -0
  37. {isar-1.26.0.dist-info → isar-1.26.2.dist-info/licenses}/LICENSE +0 -0
  38. {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(f"Received request to start mission with id {mission_id}")
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(f"Starting mission with ISAR Mission ID: '{mission.id}'")
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, return_pose=return_pose
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(f"Starting mission: {mission.id}")
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(f"API authentication is {enabled_string}")
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 task or mission before cancelling
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 queue
2
- from typing import Any
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
- def check_shared_state(queueio: StatusQueue) -> Any:
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 queueio.check()
15
- except queue.Empty:
19
+ return queue.check()
20
+ except Empty:
16
21
  return None
17
22
 
18
23
 
19
- def update_shared_state(queueio: StatusQueue, data: Any = None) -> None:
20
- queueio.update(data if data is not None else True)
24
+ def update_shared_state(queue: StatusQueue[T], data: T) -> None:
25
+ queue.update(data)
21
26
 
22
27
 
23
- def check_for_event(queueio: QueueIO) -> Any:
28
+ def check_for_event(queue: Queue[T]) -> Optional[T]:
24
29
  try:
25
- return queueio.input.get(block=False)
26
- except queue.Empty:
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 Any
3
+ from typing import TypeVar
4
4
 
5
+ T = TypeVar("T")
5
6
 
6
- class StatusQueue(Queue):
7
+
8
+ class StatusQueue(Queue[T]):
7
9
  def __init__(self) -> None:
8
10
  super().__init__()
9
11
 
10
- def check(self) -> Any:
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: Any):
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.queues import 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 QueuesModule(Module):
79
+ class EventsModule(Module):
80
80
  @provider
81
81
  @singleton
82
- def provide_queues(self) -> Queues:
83
- return Queues()
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
- queues: Queues,
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
- queues=queues,
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
- queues: Queues,
155
+ events: Events,
147
156
  storage_handlers: List[StorageInterface],
148
157
  mqtt_client: MqttClientInterface,
149
158
  ) -> Uploader:
150
159
  return Uploader(
151
- queues=queues,
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, queues: Queues, mission_planner: MissionPlannerInterface
170
+ self,
171
+ events: Events,
172
+ shared_state: SharedState,
173
+ mission_planner: MissionPlannerInterface,
162
174
  ) -> SchedulingUtilities:
163
- return SchedulingUtilities(queues, mission_planner)
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, queues: Queues) -> MqttClientInterface:
195
+ def provide_mqtt_client(self, events: Events) -> MqttClientInterface:
184
196
  if settings.MQTT_ENABLED:
185
- return MqttPublisher(mqtt_queue=queues.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
- "queues": (QueuesModule, "required"),
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(f"Loaded the following module configurations:{module_overview}")
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.queue_io import QueueIO
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__(self, queues: Queues, robot: RobotInterface):
25
+ def __init__(
26
+ self, events: Events, robot: RobotInterface, shared_state: SharedState
27
+ ):
20
28
  self.logger = logging.getLogger("robot")
21
- self.queues: Queues = queues
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, queue: QueueIO) -> None:
47
- start_mission = check_for_event(queue)
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.queues,
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, queue: QueueIO) -> None:
66
- task_id = check_for_event(queue)
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.queues, self.robot, self.signal_thread_quitting, task_id
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.queues, self.robot, self.signal_thread_quitting
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.queues.state_machine_start_mission
115
+ self.state_machine_events.start_mission
85
116
  )
86
117
 
87
118
  self._check_and_handle_task_status_request(
88
- self.queues.state_machine_task_status_request
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.queue_utils import trigger_event
7
- from isar.models.communication.queues.queues import Queues
8
- from isar.services.utilities.threaded_request import ThreadedRequest
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
- queues: Queues,
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.queues: Queues = queues
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.queues.robot_mission_failed,
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.queues.robot_mission_failed,
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
- trigger_event(self.queues.robot_mission_started)
73
+ trigger_event_without_data(self.robot_service_events.mission_started)
@@ -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, queues: Queues, robot: RobotInterface, signal_thread_quitting: Event
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.queues: Queues = queues
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.queues.robot_status, robot_status)
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)