isar 1.33.7__py3-none-any.whl → 1.33.9__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 (31) hide show
  1. isar/config/settings.py +1 -1
  2. isar/models/events.py +4 -0
  3. isar/models/status.py +16 -0
  4. isar/services/utilities/scheduling_utilities.py +87 -1
  5. isar/state_machine/state_machine.py +20 -16
  6. isar/state_machine/states/await_next_mission.py +7 -6
  7. isar/state_machine/states/going_to_lockdown.py +13 -12
  8. isar/state_machine/states/home.py +7 -6
  9. isar/state_machine/states/intervention_needed.py +7 -6
  10. isar/state_machine/states/lockdown.py +8 -7
  11. isar/state_machine/states/monitor.py +26 -22
  12. isar/state_machine/states/paused.py +19 -17
  13. isar/state_machine/states/pausing.py +17 -16
  14. isar/state_machine/states/pausing_return_home.py +17 -16
  15. isar/state_machine/states/recharging.py +17 -11
  16. isar/state_machine/states/return_home_paused.py +27 -23
  17. isar/state_machine/states/returning_home.py +27 -23
  18. isar/state_machine/states/stopping.py +11 -9
  19. isar/state_machine/states/stopping_go_to_lockdown.py +18 -18
  20. isar/state_machine/states/stopping_return_home.py +28 -26
  21. isar/state_machine/states/unknown_status.py +7 -4
  22. isar/state_machine/utils/common_event_handlers.py +58 -49
  23. isar/storage/uploader.py +3 -0
  24. {isar-1.33.7.dist-info → isar-1.33.9.dist-info}/METADATA +2 -1
  25. {isar-1.33.7.dist-info → isar-1.33.9.dist-info}/RECORD +31 -30
  26. robot_interface/models/mission/status.py +0 -6
  27. robot_interface/telemetry/payloads.py +4 -3
  28. {isar-1.33.7.dist-info → isar-1.33.9.dist-info}/WHEEL +0 -0
  29. {isar-1.33.7.dist-info → isar-1.33.9.dist-info}/entry_points.txt +0 -0
  30. {isar-1.33.7.dist-info → isar-1.33.9.dist-info}/licenses/LICENSE +0 -0
  31. {isar-1.33.7.dist-info → isar-1.33.9.dist-info}/top_level.txt +0 -0
@@ -31,29 +31,30 @@ class PausingReturnHome(EventHandlerBase):
31
31
  state_machine.publish_mission_status()
32
32
  state_machine.send_task_status()
33
33
 
34
- if error_message is not None:
35
- return state_machine.return_home_mission_pausing_failed # type: ignore
36
- return None
34
+ if error_message is None:
35
+ return None
36
+
37
+ return state_machine.return_home_mission_pausing_failed # type: ignore
37
38
 
38
39
  def _successful_pause_event_handler(event: Event[bool]) -> Optional[Callable]:
39
- if event.consume_event():
40
+ if not event.consume_event():
41
+ return None
40
42
 
41
- state_machine.current_mission.status = MissionStatus.Paused
42
- state_machine.current_task.status = TaskStatus.Paused
43
+ state_machine.current_mission.status = MissionStatus.Paused
44
+ state_machine.current_task.status = TaskStatus.Paused
43
45
 
44
- paused_mission_response: ControlMissionResponse = (
45
- state_machine._make_control_mission_response()
46
- )
46
+ paused_mission_response: ControlMissionResponse = (
47
+ state_machine._make_control_mission_response()
48
+ )
47
49
 
48
- state_machine.events.api_requests.pause_mission.response.trigger_event(
49
- paused_mission_response
50
- )
50
+ state_machine.events.api_requests.pause_mission.response.trigger_event(
51
+ paused_mission_response
52
+ )
51
53
 
52
- state_machine.publish_mission_status()
53
- state_machine.send_task_status()
54
+ state_machine.publish_mission_status()
55
+ state_machine.send_task_status()
54
56
 
55
- return state_machine.return_home_mission_paused # type: ignore
56
- return None
57
+ return state_machine.return_home_mission_paused # type: ignore
57
58
 
58
59
  event_handlers: List[EventHandlerMapping] = [
59
60
  EventHandlerMapping(
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, List
1
+ from typing import TYPE_CHECKING, List, Optional
2
2
 
3
3
  from isar.apis.models.models import LockdownResponse
4
4
  from isar.config.settings import settings
@@ -18,23 +18,29 @@ class Recharging(EventHandlerBase):
18
18
 
19
19
  def robot_battery_level_updated_handler(event: Event[float]):
20
20
  battery_level: float = event.check()
21
- if battery_level >= settings.ROBOT_BATTERY_RECHARGE_THRESHOLD:
22
- return state_machine.robot_recharged # type: ignore
23
- return None
21
+ if battery_level < settings.ROBOT_BATTERY_RECHARGE_THRESHOLD:
22
+ return None
23
+
24
+ return state_machine.robot_recharged # type: ignore
24
25
 
25
26
  def robot_offline_handler(event: Event[RobotStatus]):
26
- robot_status: RobotStatus = event.check()
27
+ robot_status: Optional[RobotStatus] = event.check()
28
+
29
+ if robot_status is None:
30
+ return None
31
+
27
32
  if robot_status == RobotStatus.Offline:
28
33
  return state_machine.robot_went_offline # type: ignore
29
34
 
30
35
  def _send_to_lockdown_event_handler(event: Event[bool]):
31
36
  should_lockdown: bool = event.consume_event()
32
- if should_lockdown:
33
- events.api_requests.send_to_lockdown.response.trigger_event(
34
- LockdownResponse(lockdown_started=True)
35
- )
36
- return state_machine.reached_lockdown # type: ignore
37
- return None
37
+ if not should_lockdown:
38
+ return None
39
+
40
+ events.api_requests.send_to_lockdown.response.trigger_event(
41
+ LockdownResponse(lockdown_started=True)
42
+ )
43
+ return state_machine.reached_lockdown # type: ignore
38
44
 
39
45
  event_handlers: List[EventHandlerMapping] = [
40
46
  EventHandlerMapping(
@@ -20,38 +20,42 @@ class ReturnHomePaused(EventHandlerBase):
20
20
  event: Event[float],
21
21
  ) -> Optional[Callable]:
22
22
  battery_level: float = event.check()
23
- if battery_level < settings.ROBOT_MISSION_BATTERY_START_THRESHOLD:
24
- return state_machine.resume # type: ignore
25
- return None
23
+
24
+ if battery_level >= settings.ROBOT_MISSION_BATTERY_START_THRESHOLD:
25
+ return None
26
+
27
+ return state_machine.resume # type: ignore
26
28
 
27
29
  def _start_mission_event_handler(
28
30
  event: Event[Mission],
29
31
  ) -> Optional[Callable]:
30
- if event.has_event():
31
- if not state_machine.battery_level_is_above_mission_start_threshold():
32
- state_machine.events.api_requests.start_mission.request.consume_event()
33
- response = MissionStartResponse(
34
- mission_id=None,
35
- mission_started=False,
36
- mission_not_started_reason="Robot battery too low",
37
- )
38
- state_machine.events.api_requests.start_mission.response.trigger_event(
39
- response
40
- )
41
- return None
42
- return state_machine.stop_return_home # type: ignore
43
- return None
32
+ if not event.has_event():
33
+ return None
34
+
35
+ if not state_machine.battery_level_is_above_mission_start_threshold():
36
+ state_machine.events.api_requests.start_mission.request.consume_event()
37
+ response = MissionStartResponse(
38
+ mission_id=None,
39
+ mission_started=False,
40
+ mission_not_started_reason="Robot battery too low",
41
+ )
42
+ state_machine.events.api_requests.start_mission.response.trigger_event(
43
+ response
44
+ )
45
+ return None
46
+ return state_machine.stop_return_home # type: ignore
44
47
 
45
48
  def _send_to_lockdown_event_handler(
46
49
  event: Event[bool],
47
50
  ) -> Optional[Callable]:
48
51
  should_lockdown: bool = event.consume_event()
49
- if should_lockdown:
50
- events.api_requests.send_to_lockdown.response.trigger_event(
51
- LockdownResponse(lockdown_started=True)
52
- )
53
- return state_machine.resume_lockdown # type: ignore
54
- return None
52
+ if not should_lockdown:
53
+ return None
54
+
55
+ events.api_requests.send_to_lockdown.response.trigger_event(
56
+ LockdownResponse(lockdown_started=True)
57
+ )
58
+ return state_machine.resume_lockdown # type: ignore
55
59
 
56
60
  event_handlers: List[EventHandlerMapping] = [
57
61
  EventHandlerMapping(
@@ -24,9 +24,10 @@ class ReturningHome(EventHandlerBase):
24
24
  events = state_machine.events
25
25
 
26
26
  def _pause_mission_event_handler(event: Event[bool]) -> Optional[Callable]:
27
- if event.consume_event():
28
- return state_machine.pause_return_home # type: ignore
29
- return None
27
+ if not event.consume_event():
28
+ return None
29
+
30
+ return state_machine.pause_return_home # type: ignore
30
31
 
31
32
  def _handle_task_completed(status: TaskStatus):
32
33
  if status != TaskStatus.Successful:
@@ -45,31 +46,34 @@ class ReturningHome(EventHandlerBase):
45
46
  def _start_mission_event_handler(
46
47
  event: Event[Mission],
47
48
  ) -> Optional[Callable]:
48
- if event.has_event():
49
- if not state_machine.battery_level_is_above_mission_start_threshold():
50
- state_machine.events.api_requests.start_mission.request.consume_event()
51
- response = MissionStartResponse(
52
- mission_id=None,
53
- mission_started=False,
54
- mission_not_started_reason="Robot battery too low",
55
- )
56
- state_machine.events.api_requests.start_mission.response.trigger_event(
57
- response
58
- )
59
- return None
60
- return state_machine.stop_return_home # type: ignore
61
- return None
49
+ if not event.has_event():
50
+ return None
51
+
52
+ if not state_machine.battery_level_is_above_mission_start_threshold():
53
+ state_machine.events.api_requests.start_mission.request.consume_event()
54
+ response = MissionStartResponse(
55
+ mission_id=None,
56
+ mission_started=False,
57
+ mission_not_started_reason="Robot battery too low",
58
+ )
59
+ state_machine.events.api_requests.start_mission.response.trigger_event(
60
+ response
61
+ )
62
+ return None
63
+
64
+ return state_machine.stop_return_home # type: ignore
62
65
 
63
66
  def _send_to_lockdown_event_handler(
64
67
  event: Event[bool],
65
68
  ) -> Optional[Callable]:
66
69
  should_lockdown: bool = event.consume_event()
67
- if should_lockdown:
68
- events.api_requests.send_to_lockdown.response.trigger_event(
69
- LockdownResponse(lockdown_started=True)
70
- )
71
- return state_machine.go_to_lockdown # type: ignore
72
- return None
70
+ if not should_lockdown:
71
+ return None
72
+
73
+ events.api_requests.send_to_lockdown.response.trigger_event(
74
+ LockdownResponse(lockdown_started=True)
75
+ )
76
+ return state_machine.go_to_lockdown # type: ignore
73
77
 
74
78
  event_handlers: List[EventHandlerMapping] = [
75
79
  EventHandlerMapping(
@@ -45,17 +45,19 @@ class Stopping(EventHandlerBase):
45
45
  event: Event[ErrorMessage],
46
46
  ) -> Optional[Callable]:
47
47
  error_message: Optional[ErrorMessage] = event.consume_event()
48
- if error_message is not None:
49
- return state_machine.mission_stopping_failed # type: ignore
50
- return None
48
+ if error_message is None:
49
+ return None
50
+
51
+ return state_machine.mission_stopping_failed # type: ignore
51
52
 
52
53
  def _successful_stop_event_handler(event: Event[bool]) -> Optional[Callable]:
53
- if event.consume_event():
54
- _stop_mission_cleanup()
55
- if not state_machine.battery_level_is_above_mission_start_threshold():
56
- return state_machine.request_return_home # type: ignore
57
- return state_machine.mission_stopped # type: ignore
58
- return None
54
+ if not event.consume_event():
55
+ return None
56
+
57
+ _stop_mission_cleanup()
58
+ if not state_machine.battery_level_is_above_mission_start_threshold():
59
+ return state_machine.request_return_home # type: ignore
60
+ return state_machine.mission_stopped # type: ignore
59
61
 
60
62
  event_handlers: List[EventHandlerMapping] = [
61
63
  EventHandlerMapping(
@@ -38,27 +38,27 @@ class StoppingGoToLockdown(EventHandlerBase):
38
38
  event: Event[ErrorMessage],
39
39
  ) -> Optional[Callable]:
40
40
  error_message: Optional[ErrorMessage] = event.consume_event()
41
- if error_message is not None:
42
- events.api_requests.send_to_lockdown.response.trigger_event(
43
- LockdownResponse(
44
- lockdown_started=False,
45
- failure_reason="Failed to stop ongoing mission",
46
- )
41
+ if error_message is None:
42
+ return None
43
+
44
+ events.api_requests.send_to_lockdown.response.trigger_event(
45
+ LockdownResponse(
46
+ lockdown_started=False,
47
+ failure_reason="Failed to stop ongoing mission",
47
48
  )
48
- return state_machine.mission_stopping_failed # type: ignore
49
- return None
49
+ )
50
+ return state_machine.mission_stopping_failed # type: ignore
50
51
 
51
52
  def _successful_stop_event_handler(event: Event[bool]) -> Optional[Callable]:
52
- if event.consume_event():
53
- state_machine.publish_mission_aborted(
54
- "Robot being sent to lockdown", True
55
- )
56
- _stop_mission_cleanup()
57
- events.api_requests.send_to_lockdown.response.trigger_event(
58
- LockdownResponse(lockdown_started=True)
59
- )
60
- return state_machine.request_lockdown_mission # type: ignore
61
- return None
53
+ if not event.consume_event():
54
+ return None
55
+
56
+ state_machine.publish_mission_aborted("Robot being sent to lockdown", True)
57
+ _stop_mission_cleanup()
58
+ events.api_requests.send_to_lockdown.response.trigger_event(
59
+ LockdownResponse(lockdown_started=True)
60
+ )
61
+ return state_machine.request_lockdown_mission # type: ignore
62
62
 
63
63
  event_handlers: List[EventHandlerMapping] = [
64
64
  EventHandlerMapping(
@@ -21,38 +21,40 @@ class StoppingReturnHome(EventHandlerBase):
21
21
  event: Event[ErrorMessage],
22
22
  ) -> Optional[Callable]:
23
23
  error_message: Optional[ErrorMessage] = event.consume_event()
24
- if error_message is not None:
25
- logger.warning(error_message.error_description)
26
- mission: Mission = (
27
- state_machine.events.api_requests.start_mission.request.consume_event()
28
- )
29
- state_machine.events.api_requests.start_mission.response.trigger_event(
30
- MissionStartResponse(
31
- mission_id=mission.id,
32
- mission_started=False,
33
- mission_not_started_reason="Failed to cancel return home mission",
34
- )
24
+ if error_message is None:
25
+ return None
26
+
27
+ logger.warning(error_message.error_description)
28
+ mission: Mission = (
29
+ state_machine.events.api_requests.start_mission.request.consume_event()
30
+ )
31
+ state_machine.events.api_requests.start_mission.response.trigger_event(
32
+ MissionStartResponse(
33
+ mission_id=mission.id,
34
+ mission_started=False,
35
+ mission_not_started_reason="Failed to cancel return home mission",
35
36
  )
36
- return state_machine.return_home_mission_stopping_failed # type: ignore
37
- return None
37
+ )
38
+ return state_machine.return_home_mission_stopping_failed # type: ignore
38
39
 
39
40
  def _successful_stop_event_handler(event: Event[bool]) -> Optional[Callable]:
40
- if event.consume_event():
41
- mission: Mission = (
42
- state_machine.events.api_requests.start_mission.request.consume_event()
43
- )
41
+ if not event.consume_event():
42
+ return None
44
43
 
45
- state_machine.reset_state_machine()
44
+ mission: Mission = (
45
+ state_machine.events.api_requests.start_mission.request.consume_event()
46
+ )
46
47
 
47
- if mission:
48
- state_machine.start_mission(mission=mission)
49
- return state_machine.request_mission_start # type: ignore
48
+ state_machine.reset_state_machine()
50
49
 
51
- state_machine.logger.error(
52
- "Stopped return home without a new mission to start"
53
- )
54
- return state_machine.request_return_home # type: ignore
55
- return None
50
+ if mission:
51
+ state_machine.start_mission(mission=mission)
52
+ return state_machine.request_mission_start # type: ignore
53
+
54
+ state_machine.logger.error(
55
+ "Stopped return home without a new mission to start"
56
+ )
57
+ return state_machine.request_return_home # type: ignore
56
58
 
57
59
  event_handlers: List[EventHandlerMapping] = [
58
60
  EventHandlerMapping(
@@ -18,15 +18,18 @@ class UnknownStatus(EventHandlerBase):
18
18
  def _robot_status_event_handler(
19
19
  event: Event[RobotStatus],
20
20
  ) -> Optional[Callable]:
21
- robot_status: RobotStatus = event.check()
22
- if (
21
+ robot_status: Optional[RobotStatus] = event.check()
22
+ if robot_status is None:
23
+ return None
24
+ if not (
23
25
  robot_status == RobotStatus.Home
24
26
  or robot_status == RobotStatus.Offline
25
27
  or robot_status == RobotStatus.BlockedProtectiveStop
26
28
  or robot_status == RobotStatus.Available
27
29
  ):
28
- return state_machine.robot_status_changed # type: ignore
29
- return None
30
+ return None
31
+
32
+ return state_machine.robot_status_changed # type: ignore
30
33
 
31
34
  event_handlers: List[EventHandlerMapping] = [
32
35
  EventHandlerMapping(
@@ -16,28 +16,30 @@ def start_mission_event_handler(
16
16
  response: Event[MissionStartResponse],
17
17
  ) -> Optional[Callable]:
18
18
  mission: Optional[Mission] = event.consume_event()
19
- if mission:
20
- if not state_machine.battery_level_is_above_mission_start_threshold():
21
- response.trigger_event(
22
- MissionStartResponse(
23
- mission_id=mission.id,
24
- mission_started=False,
25
- mission_not_started_reason="Robot battery too low",
26
- )
19
+ if not mission:
20
+ return None
21
+
22
+ if not state_machine.battery_level_is_above_mission_start_threshold():
23
+ response.trigger_event(
24
+ MissionStartResponse(
25
+ mission_id=mission.id,
26
+ mission_started=False,
27
+ mission_not_started_reason="Robot battery too low",
27
28
  )
28
- return None
29
- state_machine.start_mission(mission=mission)
30
- return state_machine.request_mission_start # type: ignore
31
- return None
29
+ )
30
+ return None
31
+ state_machine.start_mission(mission=mission)
32
+ return state_machine.request_mission_start # type: ignore
32
33
 
33
34
 
34
35
  def return_home_event_handler(
35
36
  state_machine: "StateMachine", event: Event[bool]
36
37
  ) -> Optional[Callable]:
37
- if event.consume_event():
38
- state_machine.events.api_requests.return_home.response.trigger_event(True)
39
- return state_machine.request_return_home # type: ignore
40
- return None
38
+ if not event.consume_event():
39
+ return None
40
+
41
+ state_machine.events.api_requests.return_home.response.trigger_event(True)
42
+ return state_machine.request_return_home # type: ignore
41
43
 
42
44
 
43
45
  def robot_status_event_handler(
@@ -48,6 +50,7 @@ def robot_status_event_handler(
48
50
  ) -> Optional[Callable]:
49
51
  if not status_changed_event.consume_event():
50
52
  return None
53
+
51
54
  robot_status: Optional[RobotStatus] = status_event.check()
52
55
  if robot_status != expected_status:
53
56
  return state_machine.robot_status_changed # type: ignore
@@ -58,29 +61,33 @@ def stop_mission_event_handler(
58
61
  state_machine: "StateMachine", event: Event[str]
59
62
  ) -> Optional[Callable]:
60
63
  mission_id: str = event.consume_event()
61
- if mission_id is not None:
62
- if state_machine.current_mission.id == mission_id or mission_id == "":
63
- return state_machine.stop # type: ignore
64
- else:
65
- state_machine.events.api_requests.stop_mission.response.trigger_event(
66
- ControlMissionResponse(
67
- mission_id=mission_id,
68
- mission_status=state_machine.current_mission.status,
69
- mission_not_found=True,
70
- task_id=state_machine.current_task.id,
71
- task_status=state_machine.current_task.status,
72
- )
64
+ if mission_id is None:
65
+ return None
66
+
67
+ if state_machine.current_mission.id == mission_id or mission_id == "":
68
+ return state_machine.stop # type: ignore
69
+ else:
70
+ state_machine.events.api_requests.stop_mission.response.trigger_event(
71
+ ControlMissionResponse(
72
+ mission_id=mission_id,
73
+ mission_status=state_machine.current_mission.status,
74
+ mission_not_found=True,
75
+ task_id=state_machine.current_task.id,
76
+ task_status=state_machine.current_task.status,
73
77
  )
74
- return None
78
+ )
79
+ return None
75
80
 
76
81
 
77
82
  def mission_started_event_handler(
78
83
  state_machine: "StateMachine",
79
84
  event: Event[bool],
80
85
  ) -> Optional[Callable]:
81
- if event.consume_event():
82
- state_machine.logger.info("Received confirmation that mission has started")
83
- state_machine.mission_ongoing = True
86
+ if not event.consume_event():
87
+ return None
88
+
89
+ state_machine.logger.info("Received confirmation that mission has started")
90
+ state_machine.mission_ongoing = True
84
91
  return None
85
92
 
86
93
 
@@ -89,18 +96,19 @@ def mission_failed_event_handler(
89
96
  event: Event[Optional[ErrorMessage]],
90
97
  ) -> Optional[Callable]:
91
98
  mission_failed: Optional[ErrorMessage] = event.consume_event()
92
- if mission_failed is not None:
93
- state_machine.logger.warning(
94
- f"Failed to initiate mission "
95
- f"{str(state_machine.current_mission.id)[:8]} because: "
96
- f"{mission_failed.error_description}"
97
- )
98
- state_machine.current_mission.error_message = ErrorMessage(
99
- error_reason=mission_failed.error_reason,
100
- error_description=mission_failed.error_description,
101
- )
102
- return state_machine.mission_failed_to_start # type: ignore
103
- return None
99
+ if mission_failed is None:
100
+ return None
101
+
102
+ state_machine.logger.warning(
103
+ f"Failed to initiate mission "
104
+ f"{str(state_machine.current_mission.id)[:8]} because: "
105
+ f"{mission_failed.error_description}"
106
+ )
107
+ state_machine.current_mission.error_message = ErrorMessage(
108
+ error_reason=mission_failed.error_reason,
109
+ error_description=mission_failed.error_description,
110
+ )
111
+ return state_machine.mission_failed_to_start # type: ignore
104
112
 
105
113
 
106
114
  def task_status_failed_event_handler(
@@ -173,9 +181,10 @@ def _handle_new_task_status(
173
181
 
174
182
  state_machine.current_task.status = status
175
183
 
176
- if state_machine.current_task.is_finished():
177
- state_machine.report_task_status(state_machine.current_task)
178
- state_machine.publish_task_status(task=state_machine.current_task)
184
+ if not state_machine.current_task.is_finished():
185
+ return None
179
186
 
180
- return handle_task_completed(status)
181
- return None
187
+ state_machine.report_task_status(state_machine.current_task)
188
+ state_machine.publish_task_status(task=state_machine.current_task)
189
+
190
+ return handle_task_completed(status)
isar/storage/uploader.py CHANGED
@@ -146,6 +146,9 @@ class Uploader:
146
146
  )
147
147
  except Empty:
148
148
  continue
149
+ except Exception as e:
150
+ self.logger.error(f"Unexpected error in uploader thread: {e}")
151
+ continue
149
152
 
150
153
  def _upload(self, item: BlobItem) -> StoragePaths:
151
154
  inspection_paths: StoragePaths
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isar
3
- Version: 1.33.7
3
+ Version: 1.33.9
4
4
  Summary: Integration and Supervisory control of Autonomous Robots
5
5
  Author-email: Equinor ASA <fg_robots_dev@equinor.com>
6
6
  License: Eclipse Public License version 2.0
@@ -480,6 +480,7 @@ Enabling API authentication also requires the same environment variables. The re
480
480
  AZURE_CLIENT_ID
481
481
  AZURE_TENANT_ID
482
482
  AZURE_CLIENT_SECRET
483
+ ISAR_BLOB_STORAGE_ACCOUNT
483
484
  ```
484
485
 
485
486
  ## MQTT communication