isar 1.17.0__py3-none-any.whl → 1.19.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of isar might be problematic. Click here for more details.

isar/config/settings.py CHANGED
@@ -56,6 +56,9 @@ class Settings(BaseSettings):
56
56
  # Number of attempts to initiate a step or mission before cancelling
57
57
  INITIATE_FAILURE_COUNTER_LIMIT: int = Field(default=10)
58
58
 
59
+ # Number of attempts to request a step status in monitor before cancelling
60
+ REQUEST_STATUS_FAILURE_COUNTER_LIMIT: int = Field(default=3)
61
+
59
62
  # Number of attempts to stop the robot before giving up
60
63
  STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=10)
61
64
 
@@ -226,14 +229,13 @@ class Settings(BaseSettings):
226
229
  DATA_CLASSIFICATION: str = Field(default="internal")
227
230
 
228
231
  # List of MQTT Topics
229
- TOPIC_ISAR_STATE: str = Field(default="state", validate_default=True)
232
+ TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
230
233
  TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
231
234
  TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
232
235
  TOPIC_ISAR_STEP: str = Field(default="step", validate_default=True)
233
236
  TOPIC_ISAR_INSPECTION_RESULT: str = Field(
234
237
  default="inspection_result", validate_default=True
235
238
  )
236
- TOPIC_ISAR_ROBOT_STATUS: str = Field(default="robot_status", validate_default=True)
237
239
  TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info", validate_default=True)
238
240
  TOPIC_ISAR_ROBOT_HEARTBEAT: str = Field(
239
241
  default="robot_heartbeat", validate_default=True
@@ -281,11 +283,10 @@ class Settings(BaseSettings):
281
283
  }
282
284
 
283
285
  @field_validator(
284
- "TOPIC_ISAR_STATE",
286
+ "TOPIC_ISAR_STATUS",
285
287
  "TOPIC_ISAR_MISSION",
286
288
  "TOPIC_ISAR_TASK",
287
289
  "TOPIC_ISAR_STEP",
288
- "TOPIC_ISAR_ROBOT_STATUS",
289
290
  "TOPIC_ISAR_ROBOT_INFO",
290
291
  "TOPIC_ISAR_ROBOT_HEARTBEAT",
291
292
  "TOPIC_ISAR_INSPECTION_RESULT",
@@ -24,6 +24,7 @@ from isar.state_machine.states import (
24
24
  Initiate,
25
25
  Monitor,
26
26
  Off,
27
+ Offline,
27
28
  Paused,
28
29
  Stop,
29
30
  )
@@ -31,7 +32,12 @@ from isar.state_machine.states_enum import States
31
32
  from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
32
33
  from robot_interface.models.initialize.initialize_params import InitializeParams
33
34
  from robot_interface.models.mission.mission import Mission
34
- from robot_interface.models.mission.status import MissionStatus, StepStatus, TaskStatus
35
+ from robot_interface.models.mission.status import (
36
+ MissionStatus,
37
+ RobotStatus,
38
+ StepStatus,
39
+ TaskStatus,
40
+ )
35
41
  from robot_interface.models.mission.step import Step
36
42
  from robot_interface.models.mission.task import Task
37
43
  from robot_interface.robot_interface import RobotInterface
@@ -88,6 +94,7 @@ class StateMachine(object):
88
94
  self.monitor_state: State = Monitor(self)
89
95
  self.initiate_state: State = Initiate(self)
90
96
  self.off_state: State = Off(self)
97
+ self.offline_state: State = Offline(self)
91
98
 
92
99
  self.states: List[State] = [
93
100
  self.off_state,
@@ -97,6 +104,7 @@ class StateMachine(object):
97
104
  self.monitor_state,
98
105
  self.stop_state,
99
106
  self.paused_state,
107
+ self.offline_state,
100
108
  ]
101
109
 
102
110
  self.machine = Machine(self, states=self.states, initial="off", queued=True)
@@ -194,6 +202,18 @@ class StateMachine(object):
194
202
  "dest": self.idle_state,
195
203
  "before": self._mission_stopped,
196
204
  },
205
+ {
206
+ "trigger": "robot_turned_offline",
207
+ "source": [self.idle_state],
208
+ "dest": self.offline_state,
209
+ "before": self._offline,
210
+ },
211
+ {
212
+ "trigger": "robot_turned_online",
213
+ "source": self.offline_state,
214
+ "dest": self.idle_state,
215
+ "before": self._online,
216
+ },
197
217
  ]
198
218
  )
199
219
 
@@ -239,6 +259,12 @@ class StateMachine(object):
239
259
  def _off(self) -> None:
240
260
  return
241
261
 
262
+ def _offline(self) -> None:
263
+ return
264
+
265
+ def _online(self) -> None:
266
+ return
267
+
242
268
  def _resume(self) -> None:
243
269
  self.logger.info(f"Resuming mission: {self.current_mission.id}")
244
270
  self.current_mission.status = MissionStatus.InProgress
@@ -417,7 +443,7 @@ class StateMachine(object):
417
443
  self.send_state_status()
418
444
  self._log_state_transition(self.current_state)
419
445
  self.logger.info(f"State: {self.current_state}")
420
- self.publish_state()
446
+ self.publish_status()
421
447
 
422
448
  def reset_state_machine(self) -> None:
423
449
  self.logger.info("Resetting state machine")
@@ -559,25 +585,33 @@ class StateMachine(object):
559
585
  retain=False,
560
586
  )
561
587
 
562
- def publish_state(self) -> None:
588
+ def publish_status(self) -> None:
563
589
  if not self.mqtt_publisher:
564
590
  return
565
591
  payload: str = json.dumps(
566
592
  {
567
593
  "isar_id": settings.ISAR_ID,
568
594
  "robot_name": settings.ROBOT_NAME,
569
- "state": self.current_state,
595
+ "status": self._current_status(),
570
596
  "timestamp": datetime.utcnow(),
571
597
  },
572
598
  cls=EnhancedJSONEncoder,
573
599
  )
574
600
 
575
601
  self.mqtt_publisher.publish(
576
- topic=settings.TOPIC_ISAR_STATE,
602
+ topic=settings.TOPIC_ISAR_STATUS,
577
603
  payload=payload,
578
604
  retain=False,
579
605
  )
580
606
 
607
+ def _current_status(self) -> RobotStatus:
608
+ if self.current_state == States.Idle:
609
+ return RobotStatus.Available
610
+ elif self.current_state == States.Offline:
611
+ return RobotStatus.Offline
612
+ else:
613
+ return RobotStatus.Busy
614
+
581
615
  def _log_state_transition(self, next_state):
582
616
  """Logs all state transitions that are not self-transitions."""
583
617
  self.transitions_list.append(next_state)
@@ -3,5 +3,6 @@ from .initialize import Initialize
3
3
  from .initiate import Initiate
4
4
  from .monitor import Monitor
5
5
  from .off import Off
6
+ from .offline import Offline
6
7
  from .paused import Paused
7
8
  from .stop import Stop
@@ -1,10 +1,17 @@
1
1
  import logging
2
2
  import time
3
- from typing import Optional, TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  from transitions import State
6
6
 
7
+ from isar.config.settings import settings
7
8
  from isar.models.communication.message import StartMissionMessage
9
+ from isar.services.utilities.threaded_request import (
10
+ ThreadedRequest,
11
+ ThreadedRequestNotFinishedError,
12
+ )
13
+ from robot_interface.models.exceptions.robot_exceptions import RobotException
14
+ from robot_interface.models.mission.status import RobotStatus
8
15
 
9
16
  if TYPE_CHECKING:
10
17
  from isar.state_machine.state_machine import StateMachine
@@ -15,13 +22,17 @@ class Idle(State):
15
22
  super().__init__(name="idle", on_enter=self.start, on_exit=self.stop)
16
23
  self.state_machine: "StateMachine" = state_machine
17
24
  self.logger = logging.getLogger("state_machine")
25
+ self.robot_status_thread: Optional[ThreadedRequest] = None
26
+ self.last_robot_status_poll_time: float = time.time()
18
27
 
19
28
  def start(self) -> None:
20
29
  self.state_machine.update_state()
21
30
  self._run()
22
31
 
23
32
  def stop(self) -> None:
24
- pass
33
+ if self.robot_status_thread:
34
+ self.robot_status_thread.wait_for_thread()
35
+ self.robot_status_thread = None
25
36
 
26
37
  def _run(self) -> None:
27
38
  while True:
@@ -37,4 +48,38 @@ class Idle(State):
37
48
  break
38
49
  time.sleep(self.state_machine.sleep_time)
39
50
 
51
+ time_from_last_robot_status_poll = (
52
+ time.time() - self.last_robot_status_poll_time
53
+ )
54
+ if (
55
+ time_from_last_robot_status_poll
56
+ < settings.ROBOT_API_STATUS_POLL_INTERVAL
57
+ ):
58
+ continue
59
+
60
+ if not self.robot_status_thread:
61
+ self.robot_status_thread = ThreadedRequest(
62
+ request_func=self.state_machine.robot.robot_status
63
+ )
64
+ self.robot_status_thread.start_thread(
65
+ name="State Machine Offline Get Robot Status"
66
+ )
67
+
68
+ try:
69
+ robot_status: RobotStatus = self.robot_status_thread.get_output()
70
+ except ThreadedRequestNotFinishedError:
71
+ time.sleep(self.state_machine.sleep_time)
72
+ continue
73
+
74
+ except (RobotException,) as e:
75
+ self.logger.error(
76
+ f"Failed to get robot status because: {e.error_description}"
77
+ )
78
+
79
+ self.last_robot_status_poll_time = time.time()
80
+
81
+ if robot_status == RobotStatus.Offline:
82
+ transition = self.state_machine.robot_turned_offline # type: ignore
83
+ break
84
+
40
85
  transition()
@@ -1,18 +1,20 @@
1
1
  import logging
2
2
  import time
3
3
  from copy import deepcopy
4
- from typing import Callable, Optional, Sequence, TYPE_CHECKING, Tuple, Union
4
+ from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union
5
5
 
6
6
  from injector import inject
7
7
  from transitions import State
8
8
 
9
9
  from isar.mission_planner.task_selector_interface import TaskSelectorStop
10
+ from isar.config.settings import settings
10
11
  from isar.services.utilities.threaded_request import (
11
12
  ThreadedRequest,
12
13
  ThreadedRequestNotFinishedError,
13
14
  )
14
15
  from robot_interface.models.exceptions.robot_exceptions import (
15
16
  ErrorMessage,
17
+ RobotCommunicationTimeoutException,
16
18
  RobotException,
17
19
  RobotMissionStatusException,
18
20
  RobotRetrieveInspectionException,
@@ -32,6 +34,10 @@ class Monitor(State):
32
34
  def __init__(self, state_machine: "StateMachine") -> None:
33
35
  super().__init__(name="monitor", on_enter=self.start, on_exit=self.stop)
34
36
  self.state_machine: "StateMachine" = state_machine
37
+ self.request_status_failure_counter: int = 0
38
+ self.request_status_failure_counter_limit: int = (
39
+ settings.REQUEST_STATUS_FAILURE_COUNTER_LIMIT
40
+ )
35
41
 
36
42
  self.logger = logging.getLogger("state_machine")
37
43
  self.step_status_thread: Optional[ThreadedRequest] = None
@@ -61,7 +67,6 @@ class Monitor(State):
61
67
  status_function=self.state_machine.robot.step_status,
62
68
  thread_name="State Machine Monitor Get Step Status",
63
69
  )
64
-
65
70
  try:
66
71
  status: Union[StepStatus, MissionStatus] = (
67
72
  self.step_status_thread.get_output()
@@ -70,6 +75,34 @@ class Monitor(State):
70
75
  time.sleep(self.state_machine.sleep_time)
71
76
  continue
72
77
 
78
+ except RobotCommunicationTimeoutException as e:
79
+ self.state_machine.current_mission.error_message = ErrorMessage(
80
+ error_reason=e.error_reason, error_description=e.error_description
81
+ )
82
+ self.step_status_thread = None
83
+ self.request_status_failure_counter += 1
84
+ self.logger.warning(
85
+ f"Monitoring step {self.state_machine.current_step.id} failed #: "
86
+ f"{self.request_status_failure_counter} failed because: {e.error_description}"
87
+ )
88
+
89
+ if (
90
+ self.request_status_failure_counter
91
+ >= self.request_status_failure_counter_limit
92
+ ):
93
+ self.state_machine.current_step.error_message = ErrorMessage(
94
+ error_reason=e.error_reason,
95
+ error_description=e.error_description,
96
+ )
97
+ self.logger.error(
98
+ f"Step will be cancelled after failing to get step status "
99
+ f"{self.request_status_failure_counter} times because: "
100
+ f"{e.error_description}"
101
+ )
102
+ status = StepStatus.Failed
103
+ else:
104
+ continue
105
+
73
106
  except RobotStepStatusException as e:
74
107
  self.state_machine.current_step.error_message = ErrorMessage(
75
108
  error_reason=e.error_reason, error_description=e.error_description
@@ -123,6 +156,7 @@ class Monitor(State):
123
156
  else:
124
157
  if isinstance(status, StepStatus):
125
158
  if self._step_finished(self.state_machine.current_step):
159
+ self.state_machine.update_current_step()
126
160
  self.state_machine.current_task.update_task_status()
127
161
  else: # If not all steps are done
128
162
  self.state_machine.current_task.status = TaskStatus.InProgress
@@ -131,7 +165,6 @@ class Monitor(State):
131
165
  self.state_machine.current_task
132
166
  )
133
167
  if self.state_machine.current_task.status == TaskStatus.Successful:
134
- self.state_machine.update_remaining_steps()
135
168
  try:
136
169
  self.state_machine.current_task = (
137
170
  self.state_machine.task_selector.next_task()
@@ -0,0 +1,62 @@
1
+ import logging
2
+ import time
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ from transitions import State
6
+
7
+ from isar.config.settings import settings
8
+ from isar.services.utilities.threaded_request import (
9
+ ThreadedRequest,
10
+ ThreadedRequestNotFinishedError,
11
+ )
12
+ from robot_interface.models.exceptions.robot_exceptions import RobotException
13
+ from robot_interface.models.mission.status import RobotStatus
14
+
15
+ if TYPE_CHECKING:
16
+ from isar.state_machine.state_machine import StateMachine
17
+
18
+
19
+ class Offline(State):
20
+ def __init__(self, state_machine: "StateMachine") -> None:
21
+ super().__init__(name="offline", on_enter=self.start, on_exit=self.stop)
22
+ self.state_machine: "StateMachine" = state_machine
23
+ self.logger = logging.getLogger("state_machine")
24
+ self.robot_status_thread: Optional[ThreadedRequest] = None
25
+
26
+ def start(self) -> None:
27
+ self.state_machine.update_state()
28
+ self._run()
29
+
30
+ def stop(self) -> None:
31
+ if self.robot_status_thread:
32
+ self.robot_status_thread.wait_for_thread()
33
+ self.robot_status_thread = None
34
+
35
+ def _run(self) -> None:
36
+ while True:
37
+ if not self.robot_status_thread:
38
+ self.robot_status_thread = ThreadedRequest(
39
+ request_func=self.state_machine.robot.robot_status
40
+ )
41
+ self.robot_status_thread.start_thread(
42
+ name="State Machine Offline Get Robot Status"
43
+ )
44
+
45
+ try:
46
+ robot_status: RobotStatus = self.robot_status_thread.get_output()
47
+ except ThreadedRequestNotFinishedError:
48
+ time.sleep(self.state_machine.sleep_time)
49
+ continue
50
+
51
+ except (RobotException,) as e:
52
+ self.logger.error(
53
+ f"Failed to get robot status because: {e.error_description}"
54
+ )
55
+
56
+ if robot_status != RobotStatus.Offline:
57
+ transition = self.state_machine.robot_turned_online # type: ignore
58
+ break
59
+
60
+ time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
61
+
62
+ transition()
@@ -9,6 +9,7 @@ class States(str, Enum):
9
9
  Monitor = "monitor"
10
10
  Paused = "paused"
11
11
  Stop = "stop"
12
+ Offline = "offline"
12
13
 
13
14
  def __repr__(self):
14
15
  return self.value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: isar
3
- Version: 1.17.0
3
+ Version: 1.19.0
4
4
  Summary: Integration and Supervisory control of Autonomous Robots
5
5
  Home-page: https://github.com/equinor/isar
6
6
  Author: Equinor ASA
@@ -415,6 +415,17 @@ ISAR_MQTT_PASSWORD
415
415
 
416
416
  If not specified the password will default to an empty string.
417
417
 
418
+ ## Running several ISAR instances locally
419
+
420
+ To run several ISAR instances in parallel locally:
421
+ 1. Generate a guid: https://www.guidgenerator.com/
422
+ 2. Open a new terminal in the isar folder
423
+ 3. Run the following command before running main.py:
424
+
425
+ ```
426
+ export ISAR_API_PORT=port_name_higher_than_1024 ISAR_ISAR_ID=guid ISAR_ROBOT_NAME=random_robot_name
427
+ ```
428
+
418
429
  # Contributions
419
430
 
420
431
  Equinor welcomes all kinds of contributions, including code, bug reports, issues, feature requests, and documentation
@@ -14,7 +14,7 @@ isar/config/configuration_error.py,sha256=rO6WOhafX6xvVib8WxV-eY483Z0PpN-9PxGsq5
14
14
  isar/config/log.py,sha256=39G_Cue0qvw0Uv-1NYXvQ83yivPIKcAv-bGNNREFw5o,2251
15
15
  isar/config/logging.conf,sha256=mYO1xf27gAopEMHhGzY7-mwyfN16rwRLkPNMvy3zn2g,1127
16
16
  isar/config/settings.env,sha256=-kivj0osAAKlInnY81ugySTlcImhVABbnj9kUoBDLu8,535
17
- isar/config/settings.py,sha256=ZECul4dywwZb-hJeWi-4XfjrUjthqMs42bNfsyI7feA,13056
17
+ isar/config/settings.py,sha256=Wfau6k5ZUoC0BWgWXdzj4cSU7lny8SWrwqoy3t1U4Wg,13081
18
18
  isar/config/certs/ca-cert.pem,sha256=gSBTyY0tKSFnssyvrvbFvHpQwii0kEkBryklVmevdtc,2029
19
19
  isar/config/keyvault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  isar/config/keyvault/keyvault_error.py,sha256=zvPCsZLjboxsxthYkxpRERCTFxYV8R5WmACewAUQLwk,41
@@ -60,21 +60,21 @@ isar/services/service_connections/mqtt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
60
60
  isar/services/service_connections/mqtt/mqtt_client.py,sha256=Aib0lqaddxW9aVXXYD7wGL9jIpr2USCCH91SQgFdIG4,3548
61
61
  isar/services/service_connections/mqtt/robot_heartbeat_publisher.py,sha256=TxvpLA_qfyXwwN58h2X-eomTnhbFPhqfjamPxBz084E,1000
62
62
  isar/services/service_connections/mqtt/robot_info_publisher.py,sha256=d5HQ6Hfp5E21kaj1IJsFMWXecbzvH8iYByZhOucR004,1383
63
- isar/services/service_connections/mqtt/robot_status_publisher.py,sha256=KPxRrdtu-yLQolQ7DejC1v1NCeELUzrK_ZoiEpsrqD4,4425
64
63
  isar/services/service_connections/stid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
64
  isar/services/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
65
  isar/services/utilities/queue_utilities.py,sha256=Pw3hehSwkXJNeDv-bDVDfs58VOwtt3i5hpiJ2ZpphuQ,1225
67
66
  isar/services/utilities/scheduling_utilities.py,sha256=LFimEmacML3J9q-FNLfKPhcAr-R3f2rkYkbsoro0Gyo,8434
68
67
  isar/services/utilities/threaded_request.py,sha256=py4G-_RjnIdHljmKFAcQ6ddqMmp-ZYV39Ece-dqRqjs,1874
69
68
  isar/state_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
- isar/state_machine/state_machine.py,sha256=yud8FODucCFLVkK1E4NfKZ5YCrFb8IRfk752Nn_WTIY,22606
71
- isar/state_machine/states_enum.py,sha256=ks1A8gO7DOpLbSe6Vzq4-BfTCU78IsQZyoEJwQ9Wg4M,254
72
- isar/state_machine/states/__init__.py,sha256=gUikQDu-O-lmunwzwcN9gN6VaEmlCFlqqgdNfFAfXBc,189
73
- isar/state_machine/states/idle.py,sha256=tfciqI7NPDVF8WMe5qbSO3c-xDzUTQYLmJinb9e0JS8,1231
69
+ isar/state_machine/state_machine.py,sha256=wmjPXoavlxrLvgMr41LGZekE5D_Cnsoz5IRAreag6_8,23576
70
+ isar/state_machine/states_enum.py,sha256=BlrUcBWkM5K6D_UZXRwTaUgGpAagWmVZH6HhDBGzVU4,278
71
+ isar/state_machine/states/__init__.py,sha256=kErbKPDTwNfCLijvdyN6_AuOqDwR23nu9F0Qovsnir4,218
72
+ isar/state_machine/states/idle.py,sha256=_nrM17s4artaHezanl28_WcNyJod1_hkCyzAqZlPQiE,3034
74
73
  isar/state_machine/states/initialize.py,sha256=Vx7OxWZI_xEkRwHWFh9ymxK_7mDH0Wayt09dGGqBJWk,2359
75
74
  isar/state_machine/states/initiate.py,sha256=b0Fq3tN6TZMg5j4192OsYdGbphTXWCkftBsFX57mhrw,5652
76
- isar/state_machine/states/monitor.py,sha256=ykVQe54Lm_V-X7kJ16GMkycG7T9Kz3g2hsPtL35NSSk,9004
75
+ isar/state_machine/states/monitor.py,sha256=wJCBJo0xc42pzI8mUtOIsPsAK5V82oY3h9uiy-EkNr4,10609
77
76
  isar/state_machine/states/off.py,sha256=jjqN_oJMpBtWuY7hP-c9f0w3p2CYCfe-NpmYHHPnmyI,544
77
+ isar/state_machine/states/offline.py,sha256=wEMMIwM4JWfmDjI7pe9yKce_Mfz9aXqs6WEkxn8cx5I,2125
78
78
  isar/state_machine/states/paused.py,sha256=xVZM9WMt90FTiP5forSlA3eJ5Vt9LzZYCFDQDPIocKA,1004
79
79
  isar/state_machine/states/stop.py,sha256=Sxdo4FGhxc2Pj91jidYtVIjpSNklbEf1sJYJ6x51HgE,3348
80
80
  isar/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -89,7 +89,7 @@ robot_interface/robot_interface.py,sha256=i2qw1V9mLO-ljtS7LiRliQ7XNmmBJIwD4NG8mD
89
89
  robot_interface/test_robot_interface.py,sha256=FV1urn7SbsMyWBIcTKjsBwAG4IsXeZ6pLHE0mA9EGGs,692
90
90
  robot_interface/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  robot_interface/models/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
- robot_interface/models/exceptions/robot_exceptions.py,sha256=olW9I6VrAsNPZ4c_4Yr9Uor_0vES84wjqWesqHqSFag,8535
92
+ robot_interface/models/exceptions/robot_exceptions.py,sha256=l1h62gq7lfLzWoHh2ytdEWgn_4lS4M-GPxHkjgAXcP4,9036
93
93
  robot_interface/models/initialize/__init__.py,sha256=rz5neEDr59GDbzzI_FF0DId-C-I-50l113P-h-C_QBY,48
94
94
  robot_interface/models/initialize/initialize_params.py,sha256=2eG5Aq5bDKU6tVkaUMAoc46GERBgyaKkqv6yLupdRLc,164
95
95
  robot_interface/models/inspection/__init__.py,sha256=14wfuj4XZazrigKD7fL98khFKz-eckIpEgPcYRj40Kg,227
@@ -107,8 +107,8 @@ robot_interface/telemetry/payloads.py,sha256=eMK7mjZPsLY6yvu7AK-OcdvkeUpChzDrySD
107
107
  robot_interface/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
108
  robot_interface/utilities/json_service.py,sha256=nU2Q_3P9Fq9hs6F_wtUjWtHfl_g1Siy-yDhXXSKwHwg,1018
109
109
  robot_interface/utilities/uuid_string_factory.py,sha256=_NQIbBQ56w0qqO0MUDP6aPpHbxW7ATRhK8HnQiBSLkc,76
110
- isar-1.17.0.dist-info/LICENSE,sha256=3fc2-ebLwHWwzfQbulGNRdcNob3SBQeCfEVUDYxsuqw,14058
111
- isar-1.17.0.dist-info/METADATA,sha256=hXe-kwMnV9ZxNxA2o4KmiZXiQhAqVG6hh5xhlp6hTdo,14751
112
- isar-1.17.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
113
- isar-1.17.0.dist-info/top_level.txt,sha256=UwIML2RtuQKCyJJkatcSnyp6-ldDjboB9k9JgKipO-U,21
114
- isar-1.17.0.dist-info/RECORD,,
110
+ isar-1.19.0.dist-info/LICENSE,sha256=3fc2-ebLwHWwzfQbulGNRdcNob3SBQeCfEVUDYxsuqw,14058
111
+ isar-1.19.0.dist-info/METADATA,sha256=E_9yJSv0SF_T3xHun3k_dPsM4DqQ9ayg_E65UpXLC68,15101
112
+ isar-1.19.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
113
+ isar-1.19.0.dist-info/top_level.txt,sha256=UwIML2RtuQKCyJJkatcSnyp6-ldDjboB9k9JgKipO-U,21
114
+ isar-1.19.0.dist-info/RECORD,,
@@ -5,6 +5,7 @@ from typing import Optional
5
5
 
6
6
  class ErrorReason(str, Enum):
7
7
  RobotCommunicationException: str = "robot_communication_exception"
8
+ RobotCommunicationTimeoutException: str = "robot_communication_timeout_exception"
8
9
  RobotInfeasibleStepException: str = "robot_infeasible_step_exception"
9
10
  RobotInfeasibleMissionException: str = "robot_infeasible_mission_exception"
10
11
  RobotMissionStatusException: str = "robot_mission_status_exception"
@@ -49,6 +50,18 @@ class RobotCommunicationException(RobotException):
49
50
  pass
50
51
 
51
52
 
53
+ # An exception which should be thrown by the robot package if the communication has timed
54
+ # out and ISAR should retry the request.
55
+ class RobotCommunicationTimeoutException(RobotException):
56
+ def __init__(self, error_description: str) -> None:
57
+ super().__init__(
58
+ error_reason=ErrorReason.RobotCommunicationTimeoutException,
59
+ error_description=error_description,
60
+ )
61
+
62
+ pass
63
+
64
+
52
65
  # An exception which should be thrown by the robot package if it is unable to start the
53
66
  # current step.
54
67
  class RobotInfeasibleStepException(RobotException):
@@ -1,119 +0,0 @@
1
- import json
2
- import logging
3
- import time
4
- from datetime import datetime
5
- from logging import Logger
6
- from queue import Queue
7
- from threading import Thread
8
- from typing import Optional
9
-
10
- from isar.config.settings import settings
11
- from isar.state_machine.state_machine import StateMachine
12
- from isar.state_machine.states_enum import States
13
- from robot_interface.models.exceptions.robot_exceptions import (
14
- RobotAPIException,
15
- RobotCommunicationException,
16
- RobotException,
17
- )
18
- from robot_interface.models.mission.status import RobotStatus
19
- from robot_interface.robot_interface import RobotInterface
20
- from robot_interface.telemetry.mqtt_client import MqttPublisher
21
- from robot_interface.telemetry.payloads import RobotStatusPayload
22
- from robot_interface.utilities.json_service import EnhancedJSONEncoder
23
-
24
-
25
- class RobotStatusPublisher:
26
- def __init__(
27
- self, mqtt_queue: Queue, robot: RobotInterface, state_machine: StateMachine
28
- ):
29
- self.mqtt_publisher: MqttPublisher = MqttPublisher(mqtt_queue=mqtt_queue)
30
- self.robot: RobotInterface = robot
31
- self.state_machine: StateMachine = state_machine
32
-
33
- def _get_combined_robot_status(
34
- self, robot_status: RobotStatus, current_state: States
35
- ) -> RobotStatus:
36
- if robot_status == RobotStatus.Offline:
37
- return RobotStatus.Offline
38
- elif current_state == States.Idle and robot_status == RobotStatus.Available:
39
- return RobotStatus.Available
40
- elif robot_status == RobotStatus.Blocked:
41
- return RobotStatus.Blocked
42
- elif current_state != States.Idle or robot_status == RobotStatus.Busy:
43
- return RobotStatus.Busy
44
- return None
45
-
46
- def run(self) -> None:
47
- robot_status_monitor: RobotStatusMonitor = RobotStatusMonitor(robot=self.robot)
48
- robot_status_thread: Thread = Thread(
49
- target=robot_status_monitor.run,
50
- name="Robot Status Monitor",
51
- daemon=True,
52
- )
53
- robot_status_thread.start()
54
-
55
- previous_robot_status: Optional[RobotStatus] = None
56
-
57
- while True:
58
- time.sleep(settings.ROBOT_STATUS_PUBLISH_INTERVAL)
59
-
60
- combined_status: RobotStatus = self._get_combined_robot_status(
61
- robot_status=robot_status_monitor.robot_status,
62
- current_state=self.state_machine.current_state,
63
- )
64
-
65
- if previous_robot_status:
66
- if previous_robot_status == combined_status:
67
- continue
68
-
69
- payload: RobotStatusPayload = RobotStatusPayload(
70
- isar_id=settings.ISAR_ID,
71
- robot_name=settings.ROBOT_NAME,
72
- robot_status=combined_status,
73
- previous_robot_status=previous_robot_status,
74
- current_isar_state=self.state_machine.current_state,
75
- current_mission_id=(
76
- self.state_machine.current_mission.id
77
- if self.state_machine.current_mission
78
- else None
79
- ),
80
- current_task_id=(
81
- self.state_machine.current_task.id
82
- if self.state_machine.current_task
83
- else None
84
- ),
85
- current_step_id=(
86
- self.state_machine.current_step.id
87
- if self.state_machine.current_step
88
- else None
89
- ),
90
- timestamp=datetime.utcnow(),
91
- )
92
-
93
- self.mqtt_publisher.publish(
94
- topic=settings.TOPIC_ISAR_ROBOT_STATUS,
95
- payload=json.dumps(payload, cls=EnhancedJSONEncoder),
96
- )
97
-
98
- previous_robot_status = combined_status
99
-
100
-
101
- class RobotStatusMonitor:
102
- def __init__(self, robot: RobotInterface):
103
- self.robot: RobotInterface = robot
104
- self.robot_status: RobotStatus = RobotStatus.Offline
105
- self.logger: Logger = logging.getLogger("robot_status_monitor")
106
-
107
- def run(self) -> None:
108
- while True:
109
- try:
110
- self.robot_status = self.robot.robot_status()
111
- except (
112
- RobotCommunicationException,
113
- RobotAPIException,
114
- RobotException,
115
- ) as e:
116
- self.logger.error(
117
- f"Failed to get robot status because: {e.error_description}"
118
- )
119
- time.sleep(settings.ROBOT_API_STATUS_POLL_INTERVAL)
File without changes
File without changes