isar 1.26.0__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.

Files changed (33) hide show
  1. isar/apis/models/start_mission_definition.py +0 -4
  2. isar/apis/schedule/scheduling_controller.py +2 -25
  3. isar/config/settings.py +1 -1
  4. isar/models/communication/queues/events.py +57 -0
  5. isar/models/communication/queues/queue_utils.py +18 -13
  6. isar/models/communication/queues/status_queue.py +7 -5
  7. isar/modules.py +26 -13
  8. isar/robot/robot.py +46 -13
  9. isar/robot/robot_start_mission.py +10 -10
  10. isar/robot/robot_status.py +7 -4
  11. isar/robot/robot_stop_mission.py +72 -0
  12. isar/robot/robot_task_status.py +6 -6
  13. isar/script.py +7 -7
  14. isar/services/utilities/scheduling_utilities.py +10 -8
  15. isar/state_machine/state_machine.py +19 -72
  16. isar/state_machine/states/blocked_protective_stop.py +4 -12
  17. isar/state_machine/states/idle.py +45 -25
  18. isar/state_machine/states/monitor.py +127 -90
  19. isar/state_machine/states/offline.py +5 -1
  20. isar/state_machine/states/paused.py +5 -2
  21. isar/state_machine/states/stop.py +29 -58
  22. isar/state_machine/transitions/pause.py +1 -1
  23. isar/state_machine/transitions/resume.py +1 -1
  24. isar/state_machine/transitions/start_mission.py +3 -3
  25. isar/state_machine/transitions/stop.py +9 -2
  26. isar/storage/uploader.py +5 -5
  27. {isar-1.26.0.dist-info → isar-1.26.1.dist-info}/METADATA +4 -17
  28. {isar-1.26.0.dist-info → isar-1.26.1.dist-info}/RECORD +32 -31
  29. {isar-1.26.0.dist-info → isar-1.26.1.dist-info}/WHEEL +1 -1
  30. isar/models/communication/queues/queues.py +0 -37
  31. {isar-1.26.0.dist-info → isar-1.26.1.dist-info}/entry_points.txt +0 -0
  32. {isar-1.26.0.dist-info → isar-1.26.1.dist-info/licenses}/LICENSE +0 -0
  33. {isar-1.26.0.dist-info → isar-1.26.1.dist-info}/top_level.txt +0 -0
isar/script.py CHANGED
@@ -12,7 +12,7 @@ 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.queues import Queues
15
+ from isar.models.communication.queues.events import Events
16
16
  from isar.modules import get_injector
17
17
  from isar.robot.robot import Robot
18
18
  from isar.services.service_connections.mqtt.mqtt_client import MqttClient
@@ -94,7 +94,7 @@ def start() -> None:
94
94
  state_machine: StateMachine = injector.get(StateMachine)
95
95
  uploader: Uploader = injector.get(Uploader)
96
96
  robot: RobotInterface = injector.get(RobotInterface)
97
- queues: Queues = injector.get(Queues)
97
+ events: Events = injector.get(Events)
98
98
  robot_service: Robot = injector.get(Robot)
99
99
 
100
100
  threads: List[Thread] = []
@@ -121,12 +121,12 @@ def start() -> None:
121
121
  inspection,
122
122
  mission,
123
123
  )
124
- state_machine.queues.upload_queue.put(message)
124
+ state_machine.events.upload_queue.put(message)
125
125
 
126
126
  robot.register_inspection_callback(inspections_callback)
127
127
 
128
128
  if settings.MQTT_ENABLED:
129
- mqtt_client: MqttClient = MqttClient(mqtt_queue=queues.mqtt_queue)
129
+ mqtt_client: MqttClient = MqttClient(mqtt_queue=events.mqtt_queue)
130
130
 
131
131
  mqtt_thread: Thread = Thread(
132
132
  target=mqtt_client.run, name="ISAR MQTT Client", daemon=True
@@ -134,7 +134,7 @@ def start() -> None:
134
134
  threads.append(mqtt_thread)
135
135
 
136
136
  robot_info_publisher: RobotInfoPublisher = RobotInfoPublisher(
137
- mqtt_queue=queues.mqtt_queue
137
+ mqtt_queue=events.mqtt_queue
138
138
  )
139
139
  robot_info_thread: Thread = Thread(
140
140
  target=robot_info_publisher.run,
@@ -144,7 +144,7 @@ def start() -> None:
144
144
  threads.append(robot_info_thread)
145
145
 
146
146
  robot_heartbeat_publisher: RobotHeartbeatPublisher = RobotHeartbeatPublisher(
147
- mqtt_queue=queues.mqtt_queue
147
+ mqtt_queue=events.mqtt_queue
148
148
  )
149
149
 
150
150
  robot_heartbeat_thread: Thread = Thread(
@@ -155,7 +155,7 @@ def start() -> None:
155
155
  threads.append(robot_heartbeat_thread)
156
156
 
157
157
  publishers: List[Thread] = robot.get_telemetry_publishers(
158
- queue=queues.mqtt_queue,
158
+ queue=events.mqtt_queue,
159
159
  robot_name=settings.ROBOT_NAME,
160
160
  isar_id=settings.ISAR_ID,
161
161
  )
@@ -16,9 +16,9 @@ from isar.mission_planner.mission_planner_interface import (
16
16
  MissionPlannerInterface,
17
17
  )
18
18
  from isar.models.communication.message import StartMissionMessage
19
+ from isar.models.communication.queues.events import APIRequests, Events, SharedState
19
20
  from isar.models.communication.queues.queue_io import QueueIO
20
21
  from isar.models.communication.queues.queue_timeout_error import QueueTimeoutError
21
- from isar.models.communication.queues.queues import Queues
22
22
  from isar.services.utilities.queue_utilities import QueueUtilities
23
23
  from isar.state_machine.states_enum import States
24
24
  from robot_interface.models.mission.mission import Mission
@@ -33,11 +33,13 @@ class SchedulingUtilities:
33
33
  @inject
34
34
  def __init__(
35
35
  self,
36
- queues: Queues,
36
+ events: Events,
37
+ shared_state: SharedState,
37
38
  mission_planner: MissionPlannerInterface,
38
39
  queue_timeout: int = settings.QUEUE_TIMEOUT,
39
40
  ):
40
- self.queues: Queues = queues
41
+ self.api_events: APIRequests = events.api_requests
42
+ self.shared_state: SharedState = shared_state
41
43
  self.mission_planner: MissionPlannerInterface = mission_planner
42
44
  self.queue_timeout: int = queue_timeout
43
45
  self.logger = logging.getLogger("api")
@@ -51,7 +53,7 @@ class SchedulingUtilities:
51
53
  If the current state is not available on the queue
52
54
  """
53
55
  try:
54
- return self.queues.state.check()
56
+ return self.shared_state.state.check()
55
57
  except Empty:
56
58
  error_message: str = (
57
59
  "Internal Server Error - Current state of the state machine is unknown"
@@ -145,7 +147,7 @@ class SchedulingUtilities:
145
147
  try:
146
148
  self._send_command(
147
149
  StartMissionMessage(mission=deepcopy(mission)),
148
- self.queues.api_start_mission,
150
+ self.api_events.start_mission,
149
151
  )
150
152
  except QueueTimeoutError:
151
153
  error_message = "Internal Server Error - Failed to start mission in ISAR"
@@ -164,7 +166,7 @@ class SchedulingUtilities:
164
166
  If there is a timeout while communicating with the state machine
165
167
  """
166
168
  try:
167
- return self._send_command(True, self.queues.api_pause_mission)
169
+ return self._send_command(True, self.api_events.pause_mission)
168
170
  except QueueTimeoutError:
169
171
  error_message = "Internal Server Error - Failed to pause mission"
170
172
  self.logger.error(error_message)
@@ -183,7 +185,7 @@ class SchedulingUtilities:
183
185
  If there is a timeout while communicating with the state machine
184
186
  """
185
187
  try:
186
- return self._send_command(True, self.queues.api_resume_mission)
188
+ return self._send_command(True, self.api_events.resume_mission)
187
189
  except QueueTimeoutError:
188
190
  error_message = "Internal Server Error - Failed to resume mission"
189
191
  self.logger.error(error_message)
@@ -203,7 +205,7 @@ class SchedulingUtilities:
203
205
  """
204
206
  try:
205
207
  stop_mission_response: ControlMissionResponse = self._send_command(
206
- True, self.queues.api_stop_mission
208
+ True, self.api_events.stop_mission
207
209
  )
208
210
  except QueueTimeoutError:
209
211
  error_message = "Internal Server Error - Failed to stop mission"
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import logging
3
- import queue
4
3
  from collections import deque
5
4
  from datetime import datetime, timezone
6
5
  from typing import Deque, List, Optional
@@ -15,8 +14,8 @@ from isar.mission_planner.task_selector_interface import (
15
14
  TaskSelectorInterface,
16
15
  TaskSelectorStop,
17
16
  )
18
- from isar.models.communication.message import StartMissionMessage
19
- from isar.models.communication.queues.queues import Queues
17
+ from isar.models.communication.queues.events import Events, SharedState
18
+ from isar.models.communication.queues.queue_utils import update_shared_state
20
19
  from isar.state_machine.states.blocked_protective_stop import BlockedProtectiveStop
21
20
  from isar.state_machine.states.idle import Idle
22
21
  from isar.state_machine.states.monitor import Monitor
@@ -38,7 +37,10 @@ from isar.state_machine.transitions.start_mission import (
38
37
  set_mission_to_in_progress,
39
38
  trigger_start_mission_or_task_event,
40
39
  )
41
- from isar.state_machine.transitions.stop import stop_mission
40
+ from isar.state_machine.transitions.stop import (
41
+ stop_mission_cleanup,
42
+ trigger_stop_mission_event,
43
+ )
42
44
  from isar.state_machine.transitions.utils import def_transition
43
45
  from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
44
46
  from robot_interface.models.mission.mission import Mission
@@ -60,7 +62,8 @@ class StateMachine(object):
60
62
  @inject
61
63
  def __init__(
62
64
  self,
63
- queues: Queues,
65
+ events: Events,
66
+ shared_state: SharedState,
64
67
  robot: RobotInterface,
65
68
  mqtt_publisher: MqttClientInterface,
66
69
  task_selector: TaskSelectorInterface,
@@ -72,8 +75,8 @@ class StateMachine(object):
72
75
 
73
76
  Parameters
74
77
  ----------
75
- queues : Queues
76
- Queues used for API communication.
78
+ events : Events
79
+ Events used for API and robot service communication.
77
80
  robot : RobotInterface
78
81
  Instance of robot interface.
79
82
  mqtt_publisher : MqttClientInterface
@@ -88,7 +91,8 @@ class StateMachine(object):
88
91
  """
89
92
  self.logger = logging.getLogger("state_machine")
90
93
 
91
- self.queues: Queues = queues
94
+ self.events: Events = events
95
+ self.shared_state: SharedState = shared_state
92
96
  self.robot: RobotInterface = robot
93
97
  self.mqtt_publisher: Optional[MqttClientInterface] = mqtt_publisher
94
98
  self.task_selector: TaskSelectorInterface = task_selector
@@ -134,6 +138,7 @@ class StateMachine(object):
134
138
  self.paused_state,
135
139
  ],
136
140
  "dest": self.stop_state,
141
+ "before": def_transition(self, trigger_stop_mission_event),
137
142
  },
138
143
  {
139
144
  "trigger": "request_mission_start",
@@ -176,7 +181,7 @@ class StateMachine(object):
176
181
  "trigger": "mission_stopped",
177
182
  "source": self.stop_state,
178
183
  "dest": self.idle_state,
179
- "before": def_transition(self, stop_mission),
184
+ "before": def_transition(self, stop_mission_cleanup),
180
185
  },
181
186
  {
182
187
  "trigger": "robot_turned_offline",
@@ -246,7 +251,7 @@ class StateMachine(object):
246
251
  def update_state(self):
247
252
  """Updates the current state of the state machine."""
248
253
  self.current_state = States(self.state) # type: ignore
249
- self.send_state_status()
254
+ update_shared_state(self.shared_state.state, self.current_state)
250
255
  self._log_state_transition(self.current_state)
251
256
  self.logger.info(f"State: {self.current_state}")
252
257
  self.publish_status()
@@ -263,68 +268,10 @@ class StateMachine(object):
263
268
 
264
269
  self.task_selector.initialize(tasks=self.current_mission.tasks)
265
270
 
266
- def should_start_mission(self) -> Optional[StartMissionMessage]:
267
- try:
268
- return self.queues.api_start_mission.input.get(block=False)
269
- except queue.Empty:
270
- return None
271
-
272
- def should_stop_mission(self) -> bool:
273
- try:
274
- return self.queues.api_stop_mission.input.get(block=False)
275
- except queue.Empty:
276
- return False
277
-
278
- def should_pause_mission(self) -> bool:
279
- try:
280
- return self.queues.api_pause_mission.input.get(block=False)
281
- except queue.Empty:
282
- return False
283
-
284
- def get_task_status_event(self) -> Optional[TaskStatus]:
285
- try:
286
- return self.queues.robot_task_status.input.get(block=False)
287
- except queue.Empty:
288
- return None
289
-
290
- def request_task_status(self, task_id: str) -> None:
291
- self.queues.state_machine_task_status_request.input.put(task_id)
292
-
293
- def get_mission_started_event(self) -> bool:
294
- try:
295
- return self.queues.robot_mission_started.input.get(block=False)
296
- except queue.Empty:
297
- return False
298
-
299
- def get_mission_failed_event(self) -> Optional[ErrorMessage]:
300
- try:
301
- return self.queues.robot_mission_failed.input.get(block=False)
302
- except queue.Empty:
303
- return None
304
-
305
- def get_task_failure_event(self) -> Optional[ErrorMessage]:
306
- try:
307
- return self.queues.robot_task_status_failed.input.get(block=False)
308
- except queue.Empty:
309
- return None
310
-
311
- def should_resume_mission(self) -> bool:
312
- try:
313
- return self.queues.api_resume_mission.input.get(block=False)
314
- except queue.Empty:
315
- return False
316
-
317
- def get_robot_status(self) -> bool:
318
- try:
319
- return self.queues.robot_status.check()
320
- except queue.Empty:
321
- return False
322
-
323
- def send_state_status(self) -> None:
324
- self.queues.state.update(self.current_state)
325
-
326
271
  def send_task_status(self):
327
- self.queues.state_machine_current_task.update(self.current_task)
272
+ update_shared_state(
273
+ self.shared_state.state_machine_current_task, self.current_task
274
+ )
328
275
 
329
276
  def publish_mission_status(self) -> None:
330
277
  if not self.mqtt_publisher:
@@ -437,7 +384,7 @@ class StateMachine(object):
437
384
  )
438
385
 
439
386
  def _queue_empty_response(self) -> None:
440
- self.queues.api_stop_mission.output.put(
387
+ self.events.api_requests.stop_mission.output.put(
441
388
  ControlMissionResponse(
442
389
  mission_id="None",
443
390
  mission_status="None",
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  from transitions import State
6
6
 
7
+ from isar.models.communication.queues.queue_utils import check_shared_state
7
8
  from isar.services.utilities.threaded_request import ThreadedRequest
8
9
  from robot_interface.models.mission.status import RobotStatus
9
10
 
@@ -19,27 +20,18 @@ class BlockedProtectiveStop(State):
19
20
  self.state_machine: "StateMachine" = state_machine
20
21
  self.logger = logging.getLogger("state_machine")
21
22
  self.robot_status_thread: Optional[ThreadedRequest] = None
23
+ self.shared_state = self.state_machine.shared_state
22
24
 
23
25
  def start(self) -> None:
24
26
  self.state_machine.update_state()
25
27
  self._run()
26
28
 
27
29
  def stop(self) -> None:
28
- if self.robot_status_thread:
29
- self.robot_status_thread.wait_for_thread()
30
- self.robot_status_thread = None
30
+ return
31
31
 
32
32
  def _run(self) -> None:
33
33
  while True:
34
- if not self.robot_status_thread:
35
- self.robot_status_thread = ThreadedRequest(
36
- request_func=self.state_machine.robot.robot_status
37
- )
38
- self.robot_status_thread.start_thread(
39
- name="State Machine BlockedProtectiveStop Get Robot Status"
40
- )
41
-
42
- robot_status = self.state_machine.get_robot_status()
34
+ robot_status = check_shared_state(self.shared_state.robot_status)
43
35
 
44
36
  if robot_status == RobotStatus.Offline:
45
37
  transition = self.state_machine.robot_turned_offline # type: ignore
@@ -1,11 +1,16 @@
1
1
  import logging
2
2
  import time
3
- from typing import TYPE_CHECKING, Callable, Optional
3
+ from queue import Queue
4
+ from typing import TYPE_CHECKING, Optional
4
5
 
5
6
  from transitions import State
6
7
 
7
- from isar.config.settings import settings
8
8
  from isar.models.communication.message import StartMissionMessage
9
+ from isar.models.communication.queues.queue_utils import (
10
+ check_for_event,
11
+ check_shared_state,
12
+ )
13
+ from isar.models.communication.queues.status_queue import StatusQueue
9
14
  from robot_interface.models.mission.status import RobotStatus
10
15
 
11
16
  if TYPE_CHECKING:
@@ -18,6 +23,8 @@ class Idle(State):
18
23
  self.state_machine: "StateMachine" = state_machine
19
24
  self.logger = logging.getLogger("state_machine")
20
25
  self.last_robot_status_poll_time: float = time.time()
26
+ self.events = self.state_machine.events
27
+ self.shared_state = self.state_machine.shared_state
21
28
 
22
29
  def start(self) -> None:
23
30
  self.state_machine.update_state()
@@ -26,36 +33,49 @@ class Idle(State):
26
33
  def stop(self) -> None:
27
34
  return
28
35
 
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
+ def _check_and_handle_stop_mission_event(self, event: Queue) -> bool:
37
+ if check_for_event(event):
38
+ self.state_machine.stop() # type: ignore
39
+ return True
40
+ return False
41
+
42
+ def _check_and_handle_start_mission_event(
43
+ self, event: Queue[StartMissionMessage]
44
+ ) -> bool:
45
+ start_mission: Optional[StartMissionMessage] = check_for_event(event)
46
+ if start_mission:
47
+ self.state_machine.start_mission(mission=start_mission.mission)
48
+ self.state_machine.request_mission_start() # type: ignore
49
+ return True
50
+ return False
51
+
52
+ def _check_and_handle_robot_status_event(
53
+ self, event: StatusQueue[RobotStatus]
54
+ ) -> bool:
55
+ robot_status: RobotStatus = check_shared_state(event)
56
+ if robot_status == RobotStatus.Offline:
57
+ self.state_machine.robot_turned_offline() # type: ignore
58
+ return True
59
+ elif robot_status == RobotStatus.BlockedProtectiveStop:
60
+ self.state_machine.robot_protective_stop_engaged() # type: ignore
61
+ return True
62
+ return False
36
63
 
37
64
  def _run(self) -> None:
38
- transition: Callable
39
65
  while True:
40
- if self.state_machine.should_stop_mission():
41
- transition = self.state_machine.stop # type: ignore
66
+ if self._check_and_handle_stop_mission_event(
67
+ self.events.api_requests.stop_mission.input
68
+ ):
42
69
  break
43
70
 
44
- start_mission: Optional[StartMissionMessage] = (
45
- self.state_machine.should_start_mission()
46
- )
47
- if start_mission:
48
- self.state_machine.start_mission(mission=start_mission.mission)
49
- transition = self.state_machine.request_mission_start # type: ignore
71
+ if self._check_and_handle_start_mission_event(
72
+ self.events.api_requests.start_mission.input
73
+ ):
50
74
  break
51
75
 
52
- robot_status = self.state_machine.get_robot_status()
53
- if robot_status == RobotStatus.Offline:
54
- transition = self.state_machine.robot_turned_offline # type: ignore
55
- break
56
- elif robot_status == RobotStatus.BlockedProtectiveStop:
57
- transition = self.state_machine.robot_protective_stop_engaged # type: ignore
76
+ if self._check_and_handle_robot_status_event(
77
+ self.shared_state.robot_status
78
+ ):
58
79
  break
59
80
 
60
81
  time.sleep(self.state_machine.sleep_time)
61
- transition()