isar 1.20.2__py3-none-any.whl → 1.34.13__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.
Files changed (134) hide show
  1. isar/apis/api.py +135 -86
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +115 -170
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +123 -187
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +4 -2
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/settings.py +74 -117
  14. isar/eventhandlers/eventhandler.py +123 -0
  15. isar/models/events.py +184 -0
  16. isar/models/status.py +22 -0
  17. isar/modules.py +117 -200
  18. isar/robot/robot.py +383 -0
  19. isar/robot/robot_battery.py +60 -0
  20. isar/robot/robot_monitor_mission.py +357 -0
  21. isar/robot/robot_pause_mission.py +74 -0
  22. isar/robot/robot_resume_mission.py +67 -0
  23. isar/robot/robot_start_mission.py +66 -0
  24. isar/robot/robot_status.py +61 -0
  25. isar/robot/robot_stop_mission.py +68 -0
  26. isar/robot/robot_upload_inspection.py +75 -0
  27. isar/script.py +58 -41
  28. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  29. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  30. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  31. isar/services/service_connections/persistent_memory.py +69 -0
  32. isar/services/utilities/mqtt_utilities.py +93 -0
  33. isar/services/utilities/robot_utilities.py +20 -0
  34. isar/services/utilities/scheduling_utilities.py +386 -100
  35. isar/state_machine/state_machine.py +242 -539
  36. isar/state_machine/states/__init__.py +0 -8
  37. isar/state_machine/states/await_next_mission.py +114 -0
  38. isar/state_machine/states/blocked_protective_stop.py +60 -0
  39. isar/state_machine/states/going_to_lockdown.py +95 -0
  40. isar/state_machine/states/going_to_recharging.py +92 -0
  41. isar/state_machine/states/home.py +115 -0
  42. isar/state_machine/states/intervention_needed.py +77 -0
  43. isar/state_machine/states/lockdown.py +38 -0
  44. isar/state_machine/states/maintenance.py +43 -0
  45. isar/state_machine/states/monitor.py +137 -247
  46. isar/state_machine/states/offline.py +51 -53
  47. isar/state_machine/states/paused.py +92 -23
  48. isar/state_machine/states/pausing.py +48 -0
  49. isar/state_machine/states/pausing_return_home.py +48 -0
  50. isar/state_machine/states/recharging.py +80 -0
  51. isar/state_machine/states/resuming.py +57 -0
  52. isar/state_machine/states/resuming_return_home.py +64 -0
  53. isar/state_machine/states/return_home_paused.py +109 -0
  54. isar/state_machine/states/returning_home.py +217 -0
  55. isar/state_machine/states/stopping.py +69 -0
  56. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  57. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  58. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  59. isar/state_machine/states/stopping_paused_mission.py +36 -0
  60. isar/state_machine/states/stopping_paused_return_home.py +59 -0
  61. isar/state_machine/states/stopping_return_home.py +59 -0
  62. isar/state_machine/states/unknown_status.py +74 -0
  63. isar/state_machine/states_enum.py +23 -5
  64. isar/state_machine/transitions/mission.py +225 -0
  65. isar/state_machine/transitions/return_home.py +108 -0
  66. isar/state_machine/transitions/robot_status.py +87 -0
  67. isar/state_machine/utils/common_event_handlers.py +138 -0
  68. isar/storage/blob_storage.py +70 -52
  69. isar/storage/local_storage.py +25 -12
  70. isar/storage/storage_interface.py +28 -7
  71. isar/storage/uploader.py +174 -55
  72. isar/storage/utilities.py +32 -29
  73. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/METADATA +119 -123
  74. isar-1.34.13.dist-info/RECORD +120 -0
  75. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/WHEEL +1 -1
  76. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/entry_points.txt +1 -0
  77. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  78. robot_interface/models/inspection/__init__.py +0 -13
  79. robot_interface/models/inspection/inspection.py +42 -33
  80. robot_interface/models/mission/mission.py +14 -15
  81. robot_interface/models/mission/status.py +20 -26
  82. robot_interface/models/mission/task.py +154 -121
  83. robot_interface/models/robots/battery_state.py +6 -0
  84. robot_interface/models/robots/media.py +13 -0
  85. robot_interface/models/robots/robot_model.py +7 -7
  86. robot_interface/robot_interface.py +119 -84
  87. robot_interface/telemetry/mqtt_client.py +74 -12
  88. robot_interface/telemetry/payloads.py +91 -13
  89. robot_interface/utilities/json_service.py +7 -1
  90. isar/config/configuration_error.py +0 -2
  91. isar/config/keyvault/keyvault_error.py +0 -2
  92. isar/config/predefined_mission_definition/__init__.py +0 -0
  93. isar/config/predefined_mission_definition/default_exr.json +0 -51
  94. isar/config/predefined_mission_definition/default_mission.json +0 -91
  95. isar/config/predefined_mission_definition/default_turtlebot.json +0 -124
  96. isar/config/predefined_missions/__init__.py +0 -0
  97. isar/config/predefined_missions/default.json +0 -92
  98. isar/config/predefined_missions/default_turtlebot.json +0 -110
  99. isar/config/predefined_poses/__init__.py +0 -0
  100. isar/config/predefined_poses/predefined_poses.py +0 -616
  101. isar/config/settings.env +0 -25
  102. isar/mission_planner/__init__.py +0 -0
  103. isar/mission_planner/local_planner.py +0 -82
  104. isar/mission_planner/mission_planner_interface.py +0 -26
  105. isar/mission_planner/sequential_task_selector.py +0 -23
  106. isar/mission_planner/task_selector_interface.py +0 -31
  107. isar/models/communication/__init__.py +0 -0
  108. isar/models/communication/message.py +0 -12
  109. isar/models/communication/queues/__init__.py +0 -4
  110. isar/models/communication/queues/queue_io.py +0 -12
  111. isar/models/communication/queues/queue_timeout_error.py +0 -2
  112. isar/models/communication/queues/queues.py +0 -19
  113. isar/models/communication/queues/status_queue.py +0 -20
  114. isar/models/mission_metadata/__init__.py +0 -0
  115. isar/services/auth/__init__.py +0 -0
  116. isar/services/auth/azure_credentials.py +0 -14
  117. isar/services/readers/__init__.py +0 -0
  118. isar/services/readers/base_reader.py +0 -37
  119. isar/services/service_connections/request_handler.py +0 -153
  120. isar/services/service_connections/stid/__init__.py +0 -0
  121. isar/services/utilities/queue_utilities.py +0 -39
  122. isar/services/utilities/threaded_request.py +0 -68
  123. isar/state_machine/states/idle.py +0 -85
  124. isar/state_machine/states/initialize.py +0 -71
  125. isar/state_machine/states/initiate.py +0 -142
  126. isar/state_machine/states/off.py +0 -18
  127. isar/state_machine/states/stop.py +0 -95
  128. isar/storage/slimm_storage.py +0 -191
  129. isar-1.20.2.dist-info/RECORD +0 -116
  130. robot_interface/models/initialize/__init__.py +0 -1
  131. robot_interface/models/initialize/initialize_params.py +0 -9
  132. robot_interface/models/mission/step.py +0 -234
  133. {isar-1.20.2.dist-info → isar-1.34.13.dist-info/licenses}/LICENSE +0 -0
  134. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/top_level.txt +0 -0
isar/robot/robot.py ADDED
@@ -0,0 +1,383 @@
1
+ import logging
2
+ from queue import Queue
3
+ from threading import Event as ThreadEvent
4
+ from threading import Thread
5
+ from typing import Callable, List, Optional, Tuple
6
+
7
+ from isar.config.settings import settings
8
+ from isar.models.events import (
9
+ Event,
10
+ Events,
11
+ RobotServiceEvents,
12
+ SharedState,
13
+ StateMachineEvents,
14
+ )
15
+ from isar.robot.robot_battery import RobotBatteryThread
16
+ from isar.robot.robot_monitor_mission import RobotMonitorMissionThread
17
+ from isar.robot.robot_pause_mission import RobotPauseMissionThread
18
+ from isar.robot.robot_resume_mission import RobotResumeMissionThread
19
+ from isar.robot.robot_start_mission import RobotStartMissionThread
20
+ from isar.robot.robot_status import RobotStatusThread
21
+ from isar.robot.robot_stop_mission import RobotStopMissionThread
22
+ from isar.robot.robot_upload_inspection import RobotUploadInspectionThread
23
+ from isar.services.utilities.mqtt_utilities import publish_mission_status
24
+ from robot_interface.models.exceptions.robot_exceptions import ErrorMessage, ErrorReason
25
+ from robot_interface.models.mission.mission import Mission
26
+ from robot_interface.models.mission.status import MissionStatus
27
+ from robot_interface.models.mission.task import TASKS
28
+ from robot_interface.robot_interface import RobotInterface
29
+ from robot_interface.telemetry.mqtt_client import MqttClientInterface
30
+
31
+
32
+ class Robot(object):
33
+ def __init__(
34
+ self,
35
+ events: Events,
36
+ robot: RobotInterface,
37
+ shared_state: SharedState,
38
+ mqtt_publisher: MqttClientInterface,
39
+ ) -> None:
40
+ self.logger = logging.getLogger("robot")
41
+ self.state_machine_events: StateMachineEvents = events.state_machine_events
42
+ self.robot_service_events: RobotServiceEvents = events.robot_service_events
43
+ self.mqtt_publisher: MqttClientInterface = mqtt_publisher
44
+ self.upload_queue: Queue = events.upload_queue
45
+ self.shared_state: SharedState = shared_state
46
+ self.robot: RobotInterface = robot
47
+ self.start_mission_thread: Optional[RobotStartMissionThread] = None
48
+ self.robot_battery_thread: Optional[RobotBatteryThread] = None
49
+ self.robot_status_thread: Optional[RobotStatusThread] = None
50
+ self.monitor_mission_thread: Optional[RobotMonitorMissionThread] = None
51
+ self.stop_mission_thread: Optional[RobotStopMissionThread] = None
52
+ self.pause_mission_thread: Optional[RobotPauseMissionThread] = None
53
+ self.resume_mission_thread: Optional[RobotResumeMissionThread] = None
54
+ self.upload_inspection_threads: List[RobotUploadInspectionThread] = []
55
+ self.signal_thread_quitting: ThreadEvent = ThreadEvent()
56
+ self.signal_mission_stopped: ThreadEvent = ThreadEvent()
57
+ self.inspection_callback_thread: Optional[Thread] = None
58
+
59
+ def stop(self) -> None:
60
+ self.signal_thread_quitting.set()
61
+ if self.robot_status_thread is not None and self.robot_status_thread.is_alive():
62
+ self.robot_status_thread.join()
63
+ if (
64
+ self.robot_battery_thread is not None
65
+ and self.robot_battery_thread.is_alive()
66
+ ):
67
+ self.robot_battery_thread.join()
68
+ if (
69
+ self.monitor_mission_thread is not None
70
+ and self.monitor_mission_thread.is_alive()
71
+ ):
72
+ self.monitor_mission_thread.join()
73
+ if (
74
+ self.start_mission_thread is not None
75
+ and self.start_mission_thread.is_alive()
76
+ ):
77
+ self.start_mission_thread.join()
78
+ if self.stop_mission_thread is not None and self.stop_mission_thread.is_alive():
79
+ self.stop_mission_thread.join()
80
+ for thread in self.upload_inspection_threads:
81
+ if thread.is_alive():
82
+ thread.join()
83
+ self.upload_inspection_threads = []
84
+ self.robot_status_thread = None
85
+ self.robot_battery_thread = None
86
+ self.start_mission_thread = None
87
+ self.monitor_mission_thread = None
88
+
89
+ def _start_mission_done_handler(self) -> None:
90
+ if (
91
+ self.start_mission_thread is not None
92
+ and not self.start_mission_thread.is_alive()
93
+ ):
94
+ self.start_mission_thread.join()
95
+ mission = self.start_mission_thread.mission
96
+ error_message = self.start_mission_thread.error_message
97
+ self.start_mission_thread = None
98
+
99
+ if (
100
+ error_message
101
+ and error_message.error_reason == ErrorReason.RobotAlreadyHomeException
102
+ ):
103
+ self.robot_service_events.robot_already_home.trigger_event(True)
104
+ return
105
+ elif error_message:
106
+ mission.status = MissionStatus.Failed
107
+ error_message.error_description = (
108
+ f"Failed to initiate due to: {error_message.error_description}"
109
+ )
110
+ mission.error_message = error_message
111
+ publish_mission_status(self.mqtt_publisher, mission)
112
+ self.robot_service_events.mission_failed.trigger_event(error_message)
113
+ return
114
+ else:
115
+ self.robot_service_events.mission_started.trigger_event(True)
116
+
117
+ if (
118
+ self.monitor_mission_thread is not None
119
+ and self.monitor_mission_thread.is_alive()
120
+ ):
121
+ self.logger.warning(
122
+ "Attempted to start mission while monitoring an old mission."
123
+ )
124
+ self.monitor_mission_thread.join()
125
+
126
+ self.signal_mission_stopped.clear()
127
+ self.monitor_mission_thread = RobotMonitorMissionThread(
128
+ self.robot_service_events,
129
+ self.shared_state,
130
+ self.robot,
131
+ self.mqtt_publisher,
132
+ self.signal_thread_quitting,
133
+ self.signal_mission_stopped,
134
+ mission,
135
+ )
136
+ self.monitor_mission_thread.start()
137
+
138
+ def _stop_mission_done_handler(self) -> None:
139
+ if (
140
+ self.stop_mission_thread is not None
141
+ and not self.stop_mission_thread.is_alive()
142
+ ):
143
+ self.stop_mission_thread.join()
144
+ error_message = self.stop_mission_thread.error_message
145
+
146
+ if error_message:
147
+ self.robot_service_events.mission_failed_to_stop.trigger_event(
148
+ error_message
149
+ )
150
+ self.stop_mission_thread = None
151
+ else:
152
+ if self.monitor_mission_thread is not None:
153
+ if self.monitor_mission_thread.is_alive():
154
+ self.signal_mission_stopped.set()
155
+ return
156
+ self.monitor_mission_thread.join()
157
+ self.monitor_mission_thread = None
158
+
159
+ self.stop_mission_thread = None
160
+ # The mission status will already be reported on MQTT, the state machine does not need the event
161
+ self.robot_service_events.mission_status_updated.clear_event()
162
+ self.robot_service_events.mission_successfully_stopped.trigger_event(
163
+ True
164
+ )
165
+
166
+ def _pause_mission_done_handler(self) -> None:
167
+ if (
168
+ self.pause_mission_thread is not None
169
+ and not self.pause_mission_thread.is_alive()
170
+ ):
171
+ self.pause_mission_thread.join()
172
+ error_message = self.pause_mission_thread.error_message
173
+ self.pause_mission_thread = None
174
+
175
+ if error_message:
176
+ self.robot_service_events.mission_failed_to_pause.trigger_event(
177
+ error_message
178
+ )
179
+ else:
180
+ self.robot_service_events.mission_successfully_paused.trigger_event(
181
+ True
182
+ )
183
+
184
+ def _start_mission_event_handler(self, event: Event[Mission]) -> None:
185
+ start_mission = event.consume_event()
186
+ if start_mission is not None:
187
+ if (
188
+ self.start_mission_thread is not None
189
+ and self.start_mission_thread.is_alive()
190
+ ):
191
+ self.logger.warning(
192
+ "Attempted to start mission while another mission was starting."
193
+ )
194
+ self.start_mission_thread.join()
195
+
196
+ self.start_mission_thread = RobotStartMissionThread(
197
+ self.robot,
198
+ self.signal_thread_quitting,
199
+ start_mission,
200
+ )
201
+ self.start_mission_thread.start()
202
+
203
+ def _stop_mission_request_handler(self, event: Event[bool]) -> None:
204
+ if event.has_event():
205
+ if (
206
+ self.stop_mission_thread is not None
207
+ and self.stop_mission_thread.is_alive()
208
+ ):
209
+ self.logger.warning(
210
+ "Received stop mission event while trying to stop a mission. Aborting stop attempt."
211
+ )
212
+ return
213
+ if (
214
+ self.start_mission_thread is not None
215
+ and self.start_mission_thread.is_alive()
216
+ ):
217
+ return
218
+ event.consume_event()
219
+ self.stop_mission_thread = RobotStopMissionThread(
220
+ self.robot, self.signal_thread_quitting
221
+ )
222
+ self.stop_mission_thread.start()
223
+
224
+ def _pause_mission_request_handler(self, event: Event[bool]) -> None:
225
+ if event.has_event():
226
+ if (
227
+ self.pause_mission_thread is not None
228
+ and self.pause_mission_thread.is_alive()
229
+ ):
230
+ self.logger.warning(
231
+ "Received pause mission event while trying to pause a mission. Aborting pause attempt."
232
+ )
233
+ return
234
+ if (
235
+ self.start_mission_thread is not None
236
+ and self.start_mission_thread.is_alive()
237
+ ):
238
+ return
239
+ event.consume_event()
240
+ self.pause_mission_thread = RobotPauseMissionThread(
241
+ self.robot, self.signal_thread_quitting
242
+ )
243
+ self.pause_mission_thread.start()
244
+
245
+ def _resume_mission_request_handler(self, event: Event[bool]) -> None:
246
+ if event.consume_event():
247
+ if (
248
+ self.resume_mission_thread is not None
249
+ and self.resume_mission_thread.is_alive()
250
+ ):
251
+ self.logger.warning(
252
+ "Received resume mission event while trying to resume a mission. Aborting resume attempt."
253
+ )
254
+ return
255
+ if (
256
+ self.start_mission_thread is not None
257
+ and self.start_mission_thread.is_alive()
258
+ ):
259
+ error_description = "Received resume mission event while trying to start a mission. Aborting resume attempt."
260
+ error_message = ErrorMessage(
261
+ error_reason=ErrorReason.RobotStillStartingMissionException,
262
+ error_description=error_description,
263
+ )
264
+ self.robot_service_events.mission_failed_to_resume.trigger_event(
265
+ error_message
266
+ )
267
+ return
268
+ self.resume_mission_thread = RobotResumeMissionThread(
269
+ self.robot, self.signal_thread_quitting
270
+ )
271
+ self.resume_mission_thread.start()
272
+
273
+ def _resume_mission_done_handler(self) -> None:
274
+ if (
275
+ self.resume_mission_thread is not None
276
+ and not self.resume_mission_thread.is_alive()
277
+ ):
278
+ self.resume_mission_thread.join()
279
+ error_message = self.resume_mission_thread.error_message
280
+ self.resume_mission_thread = None
281
+
282
+ if error_message:
283
+ self.robot_service_events.mission_failed_to_resume.trigger_event(
284
+ error_message
285
+ )
286
+ else:
287
+ self.robot_service_events.mission_successfully_resumed.trigger_event(
288
+ True
289
+ )
290
+
291
+ def _upload_inspection_event_handler(
292
+ self, event: Event[Tuple[TASKS, Mission]]
293
+ ) -> None:
294
+ upload_request = event.consume_event()
295
+ if upload_request:
296
+
297
+ upload_inspection_thread = RobotUploadInspectionThread(
298
+ self.upload_queue, self.robot, upload_request[0], upload_request[1]
299
+ )
300
+ self.upload_inspection_threads.append(upload_inspection_thread)
301
+ upload_inspection_thread.start()
302
+
303
+ def _upload_inspection_done_handler(self):
304
+ if len(self.upload_inspection_threads) > 0:
305
+
306
+ def _join_threads(thread: RobotUploadInspectionThread) -> bool:
307
+ if not thread.is_alive():
308
+ thread.join()
309
+ return True
310
+ return False
311
+
312
+ self.upload_inspection_threads[:] = [
313
+ thread
314
+ for thread in self.upload_inspection_threads
315
+ if _join_threads(thread)
316
+ ]
317
+
318
+ def register_and_monitor_inspection_callback(
319
+ self,
320
+ callback_function: Callable,
321
+ ) -> None:
322
+ self.inspection_callback_function = callback_function
323
+
324
+ self.inspection_callback_thread = self.robot.register_inspection_callback(
325
+ callback_function
326
+ )
327
+ if self.inspection_callback_thread is not None:
328
+ self.inspection_callback_thread.start()
329
+ self.logger.info("Inspection callback thread started and will be monitored")
330
+
331
+ def _monitor_inspection_callback_thread(self) -> None:
332
+ if (
333
+ self.inspection_callback_thread is not None
334
+ and not self.inspection_callback_thread.is_alive()
335
+ ):
336
+ self.logger.warning("Inspection callback thread died - restarting")
337
+ self.inspection_callback_thread.join()
338
+ self.inspection_callback_thread.start()
339
+
340
+ def run(self) -> None:
341
+ self.robot_status_thread = RobotStatusThread(
342
+ robot=self.robot,
343
+ signal_thread_quitting=self.signal_thread_quitting,
344
+ shared_state=self.shared_state,
345
+ state_machine_events=self.state_machine_events,
346
+ robot_service_events=self.robot_service_events,
347
+ )
348
+ self.robot_status_thread.start()
349
+
350
+ self.robot_battery_thread = RobotBatteryThread(
351
+ self.robot, self.signal_thread_quitting, self.shared_state
352
+ )
353
+ self.robot_battery_thread.start()
354
+
355
+ while not self.signal_thread_quitting.wait(0):
356
+ self._start_mission_event_handler(self.state_machine_events.start_mission)
357
+
358
+ self._pause_mission_request_handler(self.state_machine_events.pause_mission)
359
+
360
+ self._resume_mission_request_handler(
361
+ self.state_machine_events.resume_mission
362
+ )
363
+
364
+ self._stop_mission_request_handler(self.state_machine_events.stop_mission)
365
+
366
+ self._upload_inspection_event_handler(
367
+ self.robot_service_events.request_inspection_upload
368
+ )
369
+
370
+ self._start_mission_done_handler()
371
+
372
+ self._stop_mission_done_handler()
373
+
374
+ self._pause_mission_done_handler()
375
+
376
+ self._upload_inspection_done_handler()
377
+
378
+ self._resume_mission_done_handler()
379
+
380
+ if settings.UPLOAD_INSPECTIONS_ASYNC:
381
+ self._monitor_inspection_callback_thread()
382
+
383
+ self.logger.info("Exiting robot service main thread")
@@ -0,0 +1,60 @@
1
+ import logging
2
+ import time
3
+ from threading import Event, Thread
4
+
5
+ from isar.config.settings import settings
6
+ from isar.models.events import SharedState
7
+ from robot_interface.models.exceptions.robot_exceptions import RobotException
8
+ from robot_interface.robot_interface import RobotInterface
9
+
10
+
11
+ class RobotBatteryThread(Thread):
12
+ def __init__(
13
+ self,
14
+ robot: RobotInterface,
15
+ signal_thread_quitting: Event,
16
+ shared_state: SharedState,
17
+ ):
18
+ self.logger = logging.getLogger("robot")
19
+ self.shared_state: SharedState = shared_state
20
+ self.robot: RobotInterface = robot
21
+ self.signal_thread_quitting: Event = signal_thread_quitting
22
+ self.last_robot_battery_poll_time: float = time.time()
23
+ self.force_battery_poll_next_iteration: bool = True
24
+ Thread.__init__(self, name="Robot battery thread")
25
+
26
+ def stop(self) -> None:
27
+ return
28
+
29
+ def _is_ready_to_poll_for_battery(self) -> bool:
30
+ if self.force_battery_poll_next_iteration:
31
+ self.force_battery_poll_next_iteration = False
32
+ return True
33
+
34
+ time_since_last_robot_battery_poll = (
35
+ time.time() - self.last_robot_battery_poll_time
36
+ )
37
+ return (
38
+ time_since_last_robot_battery_poll
39
+ > settings.ROBOT_API_BATTERY_POLL_INTERVAL
40
+ )
41
+
42
+ def run(self):
43
+ if self.signal_thread_quitting.is_set():
44
+ return
45
+
46
+ thread_check_interval = settings.THREAD_CHECK_INTERVAL
47
+
48
+ while not self.signal_thread_quitting.wait(thread_check_interval):
49
+ if not self._is_ready_to_poll_for_battery():
50
+ continue
51
+ try:
52
+ self.last_robot_battery_poll_time = time.time()
53
+
54
+ robot_battery_level = self.robot.get_battery_level()
55
+
56
+ self.shared_state.robot_battery_level.update(robot_battery_level)
57
+ except RobotException as e:
58
+ self.logger.error(f"Failed to retrieve robot battery level: {e}")
59
+ continue
60
+ self.logger.info("Exiting robot battery thread")