isar 1.18.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
@@ -229,14 +229,13 @@ class Settings(BaseSettings):
229
229
  DATA_CLASSIFICATION: str = Field(default="internal")
230
230
 
231
231
  # List of MQTT Topics
232
- TOPIC_ISAR_STATE: str = Field(default="state", validate_default=True)
232
+ TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
233
233
  TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
234
234
  TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
235
235
  TOPIC_ISAR_STEP: str = Field(default="step", validate_default=True)
236
236
  TOPIC_ISAR_INSPECTION_RESULT: str = Field(
237
237
  default="inspection_result", validate_default=True
238
238
  )
239
- TOPIC_ISAR_ROBOT_STATUS: str = Field(default="robot_status", validate_default=True)
240
239
  TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info", validate_default=True)
241
240
  TOPIC_ISAR_ROBOT_HEARTBEAT: str = Field(
242
241
  default="robot_heartbeat", validate_default=True
@@ -284,11 +283,10 @@ class Settings(BaseSettings):
284
283
  }
285
284
 
286
285
  @field_validator(
287
- "TOPIC_ISAR_STATE",
286
+ "TOPIC_ISAR_STATUS",
288
287
  "TOPIC_ISAR_MISSION",
289
288
  "TOPIC_ISAR_TASK",
290
289
  "TOPIC_ISAR_STEP",
291
- "TOPIC_ISAR_ROBOT_STATUS",
292
290
  "TOPIC_ISAR_ROBOT_INFO",
293
291
  "TOPIC_ISAR_ROBOT_HEARTBEAT",
294
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,7 +1,7 @@
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
@@ -14,11 +14,11 @@ from isar.services.utilities.threaded_request import (
14
14
  )
15
15
  from robot_interface.models.exceptions.robot_exceptions import (
16
16
  ErrorMessage,
17
+ RobotCommunicationTimeoutException,
17
18
  RobotException,
18
19
  RobotMissionStatusException,
19
20
  RobotRetrieveInspectionException,
20
21
  RobotStepStatusException,
21
- RobotCommunicationTimeoutException,
22
22
  )
23
23
  from robot_interface.models.inspection.inspection import Inspection
24
24
  from robot_interface.models.mission.mission import Mission
@@ -131,8 +131,6 @@ class Monitor(State):
131
131
  f"Retrieving the status failed because: {e.error_description}"
132
132
  )
133
133
 
134
- self.request_status_failure_counter = 0
135
-
136
134
  if isinstance(status, StepStatus):
137
135
  self.state_machine.current_step.status = status
138
136
  elif isinstance(status, MissionStatus):
@@ -158,6 +156,7 @@ class Monitor(State):
158
156
  else:
159
157
  if isinstance(status, StepStatus):
160
158
  if self._step_finished(self.state_machine.current_step):
159
+ self.state_machine.update_current_step()
161
160
  self.state_machine.current_task.update_task_status()
162
161
  else: # If not all steps are done
163
162
  self.state_machine.current_task.status = TaskStatus.InProgress
@@ -166,7 +165,6 @@ class Monitor(State):
166
165
  self.state_machine.current_task
167
166
  )
168
167
  if self.state_machine.current_task.status == TaskStatus.Successful:
169
- self.state_machine.update_remaining_steps()
170
168
  try:
171
169
  self.state_machine.current_task = (
172
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.18.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=eiVXkjcKpUDOTchzWYooaZZ3xdEXjD8Uv4V7ynHuXwY,13201
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=iuGiIEWSp6_rC6CLz5DNfb71Dttlo0KFxfg7xYwiZYE,10665
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
@@ -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.18.0.dist-info/LICENSE,sha256=3fc2-ebLwHWwzfQbulGNRdcNob3SBQeCfEVUDYxsuqw,14058
111
- isar-1.18.0.dist-info/METADATA,sha256=wjDHWKgNqxMggWZ3Rboq-AFVnuuRLeY7j0lzV3py7Z0,14751
112
- isar-1.18.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
113
- isar-1.18.0.dist-info/top_level.txt,sha256=UwIML2RtuQKCyJJkatcSnyp6-ldDjboB9k9JgKipO-U,21
114
- isar-1.18.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,,
@@ -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