isar 1.33.0__py3-none-any.whl → 1.33.2__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 (30) hide show
  1. isar/apis/schedule/scheduling_controller.py +21 -52
  2. isar/config/settings.py +5 -0
  3. isar/models/events.py +45 -26
  4. isar/services/service_connections/mqtt/mqtt_client.py +46 -10
  5. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +3 -0
  6. isar/services/utilities/scheduling_utilities.py +3 -0
  7. isar/state_machine/state_machine.py +6 -3
  8. isar/state_machine/states/monitor.py +1 -3
  9. isar/state_machine/states/returning_home.py +9 -12
  10. isar/state_machine/states/stopping.py +33 -0
  11. isar/state_machine/transitions/functions/pause.py +1 -1
  12. isar/state_machine/transitions/functions/resume.py +1 -1
  13. isar/state_machine/transitions/functions/start_mission.py +3 -3
  14. isar/state_machine/transitions/functions/stop.py +3 -30
  15. isar/state_machine/transitions/mission.py +0 -2
  16. isar/state_machine/transitions/return_home.py +1 -1
  17. isar/state_machine/utils/common_event_handlers.py +10 -13
  18. isar/storage/blob_storage.py +46 -24
  19. isar/storage/local_storage.py +21 -11
  20. isar/storage/storage_interface.py +27 -6
  21. isar/storage/uploader.py +30 -13
  22. {isar-1.33.0.dist-info → isar-1.33.2.dist-info}/METADATA +1 -1
  23. {isar-1.33.0.dist-info → isar-1.33.2.dist-info}/RECORD +30 -30
  24. robot_interface/telemetry/mqtt_client.py +62 -6
  25. robot_interface/telemetry/payloads.py +4 -2
  26. robot_interface/utilities/json_service.py +6 -0
  27. {isar-1.33.0.dist-info → isar-1.33.2.dist-info}/WHEEL +0 -0
  28. {isar-1.33.0.dist-info → isar-1.33.2.dist-info}/entry_points.txt +0 -0
  29. {isar-1.33.0.dist-info → isar-1.33.2.dist-info}/licenses/LICENSE +0 -0
  30. {isar-1.33.0.dist-info → isar-1.33.2.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  from typing import TYPE_CHECKING, Callable, Optional
2
2
 
3
3
  from isar.apis.models.models import ControlMissionResponse, MissionStartResponse
4
- from isar.models.events import Event, EventTimeoutError
4
+ from isar.models.events import Event
5
5
  from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
6
6
  from robot_interface.models.mission.mission import Mission
7
7
  from robot_interface.models.mission.status import RobotStatus, TaskStatus
@@ -18,17 +18,13 @@ def start_mission_event_handler(
18
18
  mission: Optional[Mission] = event.consume_event()
19
19
  if mission:
20
20
  if not state_machine.battery_level_is_above_mission_start_threshold():
21
- try:
22
- response.trigger_event(
23
- MissionStartResponse(
24
- mission_id=mission.id,
25
- mission_started=False,
26
- mission_not_started_reason="Robot battery too low",
27
- ),
28
- timeout=1, # This conflict can happen if two API requests are received at the same time
21
+ response.trigger_event(
22
+ MissionStartResponse(
23
+ mission_id=mission.id,
24
+ mission_started=False,
25
+ mission_not_started_reason="Robot battery too low",
29
26
  )
30
- except EventTimeoutError:
31
- pass
27
+ )
32
28
  return None
33
29
  state_machine.start_mission(mission=mission)
34
30
  return state_machine.request_mission_start # type: ignore
@@ -39,7 +35,7 @@ def return_home_event_handler(
39
35
  state_machine: "StateMachine", event: Event[bool]
40
36
  ) -> Optional[Callable]:
41
37
  if event.consume_event():
42
- state_machine.events.api_requests.return_home.response.put(True)
38
+ state_machine.events.api_requests.return_home.response.trigger_event(True)
43
39
  return state_machine.request_return_home # type: ignore
44
40
  return None
45
41
 
@@ -63,7 +59,7 @@ def stop_mission_event_handler(
63
59
  if state_machine.current_mission.id == mission_id or mission_id == "":
64
60
  return state_machine.stop # type: ignore
65
61
  else:
66
- state_machine.events.api_requests.stop_mission.response.put(
62
+ state_machine.events.api_requests.stop_mission.response.trigger_event(
67
63
  ControlMissionResponse(
68
64
  mission_id=mission_id,
69
65
  mission_status=state_machine.current_mission.status,
@@ -80,6 +76,7 @@ def mission_started_event_handler(
80
76
  event: Event[bool],
81
77
  ) -> Optional[Callable]:
82
78
  if event.consume_event():
79
+ state_machine.logger.info("Received confirmation that mission has started")
83
80
  state_machine.mission_ongoing = True
84
81
  return None
85
82
 
@@ -1,13 +1,17 @@
1
1
  import logging
2
2
  from pathlib import Path
3
- from typing import Union
4
3
 
5
4
  from azure.core.exceptions import ResourceExistsError
6
- from azure.storage.blob import BlobServiceClient
5
+ from azure.storage.blob import BlobServiceClient, ContainerClient
7
6
 
8
7
  from isar.config.keyvault.keyvault_service import Keyvault
9
8
  from isar.config.settings import settings
10
- from isar.storage.storage_interface import StorageException, StorageInterface
9
+ from isar.storage.storage_interface import (
10
+ BlobStoragePath,
11
+ StorageException,
12
+ StorageInterface,
13
+ StoragePaths,
14
+ )
11
15
  from isar.storage.utilities import construct_metadata_file, construct_paths
12
16
  from robot_interface.models.inspection.inspection import InspectionBlob
13
17
  from robot_interface.models.mission.mission import Mission
@@ -17,12 +21,18 @@ class BlobStorage(StorageInterface):
17
21
  def __init__(self, keyvault: Keyvault) -> None:
18
22
  self.logger = logging.getLogger("uploader")
19
23
 
20
- storage_connection_string = keyvault.get_secret(
21
- "AZURE-STORAGE-CONNECTION-STRING"
22
- ).value
24
+ self.container_client_data = self._get_container_client(
25
+ keyvault, "AZURE-STORAGE-CONNECTION-STRING-DATA"
26
+ )
27
+ self.container_client_metadata = self._get_container_client(
28
+ keyvault, "AZURE-STORAGE-CONNECTION-STRING-METADATA"
29
+ )
30
+
31
+ def _get_container_client(self, keyvault: Keyvault, secret_name: str):
32
+ storage_connection_string = keyvault.get_secret(secret_name).value
23
33
 
24
34
  if storage_connection_string is None:
25
- raise RuntimeError("AZURE-STORAGE-CONNECTION-STRING from keyvault is None")
35
+ raise RuntimeError(f"{secret_name} from keyvault is None")
26
36
 
27
37
  try:
28
38
  blob_service_client = BlobServiceClient.from_connection_string(
@@ -32,48 +42,60 @@ class BlobStorage(StorageInterface):
32
42
  self.logger.error("Unable to retrieve blob service client. Error: %s", e)
33
43
  raise e
34
44
 
35
- self.container_client = blob_service_client.get_container_client(
45
+ container_client = blob_service_client.get_container_client(
36
46
  settings.BLOB_CONTAINER
37
47
  )
38
48
 
39
- if not self.container_client.exists():
49
+ if not container_client.exists():
40
50
  raise RuntimeError(
41
51
  "The configured blob container %s does not exist",
42
52
  settings.BLOB_CONTAINER,
43
53
  )
54
+ return container_client
44
55
 
45
- def store(self, inspection: InspectionBlob, mission: Mission) -> Union[str, dict]:
56
+ def store(
57
+ self, inspection: InspectionBlob, mission: Mission
58
+ ) -> StoragePaths[BlobStoragePath]:
46
59
  if inspection.data is None:
47
60
  raise StorageException("Nothing to store. The inspection data is empty")
48
61
 
49
- data_path, metadata_path = construct_paths(
62
+ data_filename, metadata_filename = construct_paths(
50
63
  inspection=inspection, mission=mission
51
64
  )
52
65
 
53
66
  metadata_bytes: bytes = construct_metadata_file(
54
- inspection=inspection, mission=mission, filename=data_path.name
67
+ inspection=inspection, mission=mission, filename=data_filename.name
55
68
  )
56
69
 
57
- self._upload_file(path=metadata_path, data=metadata_bytes)
58
- return self._upload_file(path=data_path, data=inspection.data)
70
+ data_path = self._upload_file(
71
+ filename=data_filename,
72
+ data=inspection.data,
73
+ container_client=self.container_client_data,
74
+ )
75
+ metadata_path = self._upload_file(
76
+ filename=metadata_filename,
77
+ data=metadata_bytes,
78
+ container_client=self.container_client_metadata,
79
+ )
80
+ return StoragePaths(data_path=data_path, metadata_path=metadata_path)
59
81
 
60
- def _upload_file(self, path: Path, data: bytes) -> Union[str, dict]:
61
- blob_client = self.container_client.get_blob_client(path.as_posix())
82
+ def _upload_file(
83
+ self, filename: Path, data: bytes, container_client: ContainerClient
84
+ ) -> BlobStoragePath:
85
+ blob_client = container_client.get_blob_client(filename.as_posix())
62
86
  try:
63
87
  blob_client.upload_blob(data=data)
64
88
  except ResourceExistsError as e:
65
89
  self.logger.error(
66
- f"Blob {path.as_posix()} already exists in container. Error: {e}"
90
+ "Blob %s already exists in container. Error: %s", filename.as_posix(), e
67
91
  )
68
92
  raise StorageException from e
69
93
  except Exception as e:
70
94
  self.logger.error("An unexpected error occurred while uploading blob")
71
95
  raise StorageException from e
72
96
 
73
- absolute_inspection_path = {
74
- "source": "blob",
75
- "storage_account": settings.BLOB_STORAGE_ACCOUNT,
76
- "blob_container": settings.BLOB_CONTAINER,
77
- "blob_name": blob_client.blob_name,
78
- }
79
- return absolute_inspection_path
97
+ return BlobStoragePath(
98
+ storage_account=settings.BLOB_STORAGE_ACCOUNT,
99
+ blob_container=settings.BLOB_CONTAINER,
100
+ blob_name=blob_client.blob_name,
101
+ )
@@ -2,7 +2,12 @@ import logging
2
2
  from pathlib import Path
3
3
 
4
4
  from isar.config.settings import settings
5
- from isar.storage.storage_interface import StorageException, StorageInterface
5
+ from isar.storage.storage_interface import (
6
+ LocalStoragePath,
7
+ StorageException,
8
+ StorageInterface,
9
+ StoragePaths,
10
+ )
6
11
  from isar.storage.utilities import construct_metadata_file, construct_paths
7
12
  from robot_interface.models.inspection.inspection import InspectionBlob
8
13
  from robot_interface.models.mission.mission import Mission
@@ -13,33 +18,35 @@ class LocalStorage(StorageInterface):
13
18
  self.root_folder: Path = Path(settings.LOCAL_STORAGE_PATH)
14
19
  self.logger = logging.getLogger("uploader")
15
20
 
16
- def store(self, inspection: InspectionBlob, mission: Mission) -> str:
21
+ def store(
22
+ self, inspection: InspectionBlob, mission: Mission
23
+ ) -> StoragePaths[LocalStoragePath]:
17
24
  if inspection.data is None:
18
25
  raise StorageException("Nothing to store. The inspection data is empty")
19
26
 
20
- local_path, local_metadata_path = construct_paths(
27
+ local_filename, local_metadata_filename = construct_paths(
21
28
  inspection=inspection, mission=mission
22
29
  )
23
30
 
24
- absolute_path: Path = self.root_folder.joinpath(local_path)
25
- absolute_metadata_path: Path = self.root_folder.joinpath(local_metadata_path)
31
+ data_path: Path = self.root_folder.joinpath(local_filename)
32
+ metadata_path: Path = self.root_folder.joinpath(local_metadata_filename)
26
33
 
27
- absolute_path.parent.mkdir(parents=True, exist_ok=True)
34
+ data_path.parent.mkdir(parents=True, exist_ok=True)
28
35
 
29
36
  metadata_bytes: bytes = construct_metadata_file(
30
- inspection=inspection, mission=mission, filename=local_path.name
37
+ inspection=inspection, mission=mission, filename=local_filename.name
31
38
  )
32
39
  try:
33
40
  with (
34
- open(absolute_path, "wb") as file,
35
- open(absolute_metadata_path, "wb") as metadata_file,
41
+ open(data_path, "wb") as file,
42
+ open(metadata_path, "wb") as metadata_file,
36
43
  ):
37
44
  file.write(inspection.data)
38
45
  metadata_file.write(metadata_bytes)
39
46
  except IOError as e:
40
47
  self.logger.warning(
41
48
  f"Failed open/write for one of the following files: \n"
42
- f"{absolute_path}\n{absolute_metadata_path}"
49
+ f"{data_path}\n{metadata_path}"
43
50
  )
44
51
  raise StorageException from e
45
52
  except Exception as e:
@@ -47,4 +54,7 @@ class LocalStorage(StorageInterface):
47
54
  "An unexpected error occurred while writing to local storage"
48
55
  )
49
56
  raise StorageException from e
50
- return str(absolute_path)
57
+ return StoragePaths(
58
+ data_path=LocalStoragePath(file_path=data_path),
59
+ metadata_path=LocalStoragePath(file_path=metadata_path),
60
+ )
@@ -1,25 +1,46 @@
1
1
  from abc import ABCMeta, abstractmethod
2
- from typing import Union
2
+ from pathlib import Path
3
+ from typing import Generic, TypeVar
4
+
5
+ from pydantic import BaseModel
3
6
 
4
7
  from robot_interface.models.inspection.inspection import InspectionBlob
5
8
  from robot_interface.models.mission.mission import Mission
6
9
 
7
10
 
11
+ class BlobStoragePath(BaseModel):
12
+ storage_account: str
13
+ blob_container: str
14
+ blob_name: str
15
+
16
+
17
+ class LocalStoragePath(BaseModel):
18
+ file_path: Path
19
+
20
+
21
+ TPath = TypeVar("TPath", BlobStoragePath, LocalStoragePath)
22
+
23
+
24
+ class StoragePaths(BaseModel, Generic[TPath]):
25
+ data_path: TPath
26
+ metadata_path: TPath
27
+
28
+
8
29
  class StorageInterface(metaclass=ABCMeta):
9
30
  @abstractmethod
10
- def store(self, inspection: InspectionBlob, mission: Mission) -> Union[str, dict]:
31
+ def store(self, inspection: InspectionBlob, mission: Mission) -> StoragePaths:
11
32
  """
12
33
  Parameters
13
34
  ----------
14
- mission : Mission
15
- Mission the inspection is a part of.
16
35
  inspection : InspectionBlob
17
36
  The inspection object to be stored.
37
+ mission : Mission
38
+ Mission the inspection is a part of.
18
39
 
19
40
  Returns
20
41
  ----------
21
- String
22
- Path of the saved inspection
42
+ StoragePaths
43
+ Paths to the data and metadata
23
44
 
24
45
  Raises
25
46
  ----------
isar/storage/uploader.py CHANGED
@@ -4,11 +4,17 @@ from dataclasses import dataclass
4
4
  from datetime import datetime, timedelta, timezone
5
5
  from queue import Empty, Queue
6
6
  from threading import Event
7
- from typing import List, Union
7
+ from typing import List
8
8
 
9
9
  from isar.config.settings import settings
10
10
  from isar.models.events import Events
11
- from isar.storage.storage_interface import StorageException, StorageInterface
11
+ from isar.storage.storage_interface import (
12
+ BlobStoragePath,
13
+ LocalStoragePath,
14
+ StorageException,
15
+ StorageInterface,
16
+ StoragePaths,
17
+ )
12
18
  from robot_interface.models.inspection.inspection import (
13
19
  Inspection,
14
20
  InspectionBlob,
@@ -133,10 +139,10 @@ class Uploader:
133
139
  except Empty:
134
140
  continue
135
141
 
136
- def _upload(self, item: BlobItem) -> Union[str, dict]:
137
- inspection_path: Union[str, dict] = ""
142
+ def _upload(self, item: BlobItem) -> StoragePaths:
143
+ inspection_paths: StoragePaths
138
144
  try:
139
- inspection_path = item.storage_handler.store(
145
+ inspection_paths = item.storage_handler.store(
140
146
  inspection=item.inspection, mission=item.mission
141
147
  )
142
148
  self.logger.info(
@@ -144,7 +150,7 @@ class Uploader:
144
150
  f"uploaded inspection {str(item.inspection.id)[:8]}"
145
151
  )
146
152
  self._internal_upload_queue.remove(item)
147
- except StorageException:
153
+ except StorageException as e:
148
154
  if item.get_retry_count() < self.max_retry_attempts:
149
155
  item.increment_retry(self.max_wait_time)
150
156
  self.logger.warning(
@@ -160,7 +166,8 @@ class Uploader:
160
166
  f"{str(item.inspection.id)[:8]}. Aborting upload."
161
167
  )
162
168
  self._internal_upload_queue.remove(item)
163
- return inspection_path
169
+ raise e
170
+ return inspection_paths
164
171
 
165
172
  def _process_upload_queue(self) -> None:
166
173
  def should_upload(_item):
@@ -181,10 +188,17 @@ class Uploader:
181
188
  )
182
189
  self._internal_upload_queue.remove(item)
183
190
  elif isinstance(item, BlobItem):
184
- inspection_path = self._upload(item)
185
- self._publish_inspection_result(
186
- inspection=item.inspection, inspection_path=inspection_path
187
- )
191
+ try:
192
+ inspection_paths = self._upload(item)
193
+ if isinstance(inspection_paths.data_path, LocalStoragePath):
194
+ self.logger.info("Skipping publishing when using local storage")
195
+ else:
196
+ self._publish_inspection_result(
197
+ inspection=item.inspection,
198
+ inspection_paths=inspection_paths,
199
+ )
200
+ except StorageException:
201
+ pass
188
202
  else:
189
203
  self.logger.warning(
190
204
  f"Unable to process upload item as its type {type(item).__name__} is not supported"
@@ -223,7 +237,9 @@ class Uploader:
223
237
  )
224
238
 
225
239
  def _publish_inspection_result(
226
- self, inspection: InspectionBlob, inspection_path: Union[str, dict]
240
+ self,
241
+ inspection: InspectionBlob,
242
+ inspection_paths: StoragePaths[BlobStoragePath],
227
243
  ) -> None:
228
244
  """Publishes the reference of the inspection result to the MQTT Broker
229
245
  along with the analysis type
@@ -235,7 +251,8 @@ class Uploader:
235
251
  isar_id=settings.ISAR_ID,
236
252
  robot_name=settings.ROBOT_NAME,
237
253
  inspection_id=inspection.id,
238
- inspection_path=inspection_path,
254
+ blob_storage_data_path=inspection_paths.data_path,
255
+ blob_storage_metadata_path=inspection_paths.metadata_path,
239
256
  installation_code=settings.PLANT_SHORT_NAME,
240
257
  tag_id=inspection.metadata.tag_id,
241
258
  inspection_type=type(inspection).__name__,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isar
3
- Version: 1.33.0
3
+ Version: 1.33.2
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
@@ -8,7 +8,7 @@ isar/apis/models/models.py,sha256=iRyqflpjGopm4KJjgRrOnC2swNEk3a-iwVynzw3Nm6c,19
8
8
  isar/apis/models/start_mission_definition.py,sha256=v-wt1XDd53Sw7DCdFAkxBBut-xd_uGJa7qMJnE3VI9k,6364
9
9
  isar/apis/robot_control/robot_controller.py,sha256=0EHjMqOBdfJlMrCItGPz5X-4X2kc-2XlNnUU2LOcLfc,1219
10
10
  isar/apis/schedule/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- isar/apis/schedule/scheduling_controller.py,sha256=S93DNyOWqVYAe3EnBnWuoCHLkeVFFjkR7ayBRw9J7f4,10603
11
+ isar/apis/schedule/scheduling_controller.py,sha256=xpKlxxxBQuHZ37WP1OU8SR198YIIrr2NdWWJeaZpuEc,9366
12
12
  isar/apis/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  isar/apis/security/authentication.py,sha256=Se2puhe2TUBmfio2RLma52-VSLRhqvWgu0Cd1bhdwMo,2000
14
14
  isar/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -16,7 +16,7 @@ isar/config/configuration_error.py,sha256=rO6WOhafX6xvVib8WxV-eY483Z0PpN-9PxGsq5
16
16
  isar/config/log.py,sha256=f_mLLz5RSa0kZkdpi1m0iMdwwDc4RQp12mnT6gu2exE,1303
17
17
  isar/config/logging.conf,sha256=a7ZBvZkrMDaPU3eRGAjL_eZz6hZsa6BaRJOfx8mbnnM,629
18
18
  isar/config/open_telemetry.py,sha256=Lgu0lbRQA-zz6ZDoBKKk0whQex5w18jl1wjqWzHUGdg,3634
19
- isar/config/settings.py,sha256=fUEsKeCwxWg1kMb3rlOgmIyw0V_ia9o-sqfXq2KMovA,14040
19
+ isar/config/settings.py,sha256=etvCAo1ndds9zTE1yK2CBjWPS_5cD6oM1lJ0R74nnqU,14242
20
20
  isar/config/certs/ca-cert.pem,sha256=qoNljfad_qcMxhXJIUMLd7nT-Qwf_d4dYSdoOFEOE8I,2179
21
21
  isar/config/keyvault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  isar/config/keyvault/keyvault_error.py,sha256=zvPCsZLjboxsxthYkxpRERCTFxYV8R5WmACewAUQLwk,41
@@ -42,7 +42,7 @@ isar/mission_planner/mission_planner_interface.py,sha256=UgpPIM4FbrWOD7fGY3Ul64k
42
42
  isar/mission_planner/sequential_task_selector.py,sha256=66agRPHuJnEa1vArPyty4muTasAZ50XPjjrSaTdY_Cc,643
43
43
  isar/mission_planner/task_selector_interface.py,sha256=pnLeaGPIuyXThcflZ_A7YL2b2xQjFT88hAZidkMomxU,707
44
44
  isar/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- isar/models/events.py,sha256=7xawOqXnM48JhTGIaKz7tiTjIMauMfyJZUd68jEgzdo,4194
45
+ isar/models/events.py,sha256=M-0p4krtLpCfBMbClQvBFvfFB4NWBm55AlO1ngzVM_g,5024
46
46
  isar/robot/robot.py,sha256=keqsYmfRlyIC4CWkMG0erDt_4uIC0IPvyG2ni4MemyI,5350
47
47
  isar/robot/robot_start_mission.py,sha256=RPYH9VtXDFdPqhOpt6kSbT17RHkJQPKkQ6d4784_pFE,3210
48
48
  isar/robot/robot_status.py,sha256=OXx18Qj0bvb2sKzn5-yKXWGdZ9GYyCOIgiTaGof-0Wc,2055
@@ -54,49 +54,49 @@ isar/services/auth/azure_credentials.py,sha256=9PlwGe5FrPRbW2dp0go7LMp8_l_FRvL8x
54
54
  isar/services/service_connections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  isar/services/service_connections/request_handler.py,sha256=0LxC0lu_HXeEf_xmJWjfEsh14oAUI97cpG1IWtBlcs4,4278
56
56
  isar/services/service_connections/mqtt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- isar/services/service_connections/mqtt/mqtt_client.py,sha256=se8uLvjy-cElM-WhmHEPXfEA8u05huOLnkcOCYRpcE4,3551
58
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py,sha256=_bUOG7CfqBlCRvG4vh2XGoMXucBxsJarFIeXIKOH1aw,1019
57
+ isar/services/service_connections/mqtt/mqtt_client.py,sha256=ABJLJOckSq9ElkZYLKF64mOEUESgxjW1V6ySZX3de6M,4462
58
+ isar/services/service_connections/mqtt/robot_heartbeat_publisher.py,sha256=SKPvY2QwBxqnhL9aGuZQDGQ8F_NDqPtQI5bzRBIUxkQ,1203
59
59
  isar/services/service_connections/mqtt/robot_info_publisher.py,sha256=AxokGk51hRPTxxD2r0P9braPJCMrf1InaCxrUBKkF4g,1402
60
60
  isar/services/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  isar/services/utilities/robot_utilities.py,sha256=4zCigsLXfqDC8POHchktSq81zr1_pTaRve_LQsVr6Mk,514
62
- isar/services/utilities/scheduling_utilities.py,sha256=G3SgWUIMAmAmdfbFd1oeUtRVOZEClE0wugdldEEwN_s,13499
62
+ isar/services/utilities/scheduling_utilities.py,sha256=3vFU7t7oEZTFQNJdAxfhaADScRJIxznWhDbagHyLKOQ,13605
63
63
  isar/services/utilities/threaded_request.py,sha256=py4G-_RjnIdHljmKFAcQ6ddqMmp-ZYV39Ece-dqRqjs,1874
64
64
  isar/state_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- isar/state_machine/state_machine.py,sha256=VvArtwOkvA63WMVAeW2WVHvxWse_qSok1RjustPYJgk,18028
65
+ isar/state_machine/state_machine.py,sha256=PL-dImdaFsvoWQ_z8U9IImFXfSplUOQ5V7VBgePoW64,18314
66
66
  isar/state_machine/states_enum.py,sha256=4Kysag9JPi2JSyC2me2B1sLH3Sfo7qGJonc-Q_IiUug,511
67
67
  isar/state_machine/states/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  isar/state_machine/states/await_next_mission.py,sha256=yg0aH4eukcnE1xRKiPVafRxqHt3WdvqD08EATzdEojk,1961
69
69
  isar/state_machine/states/blocked_protective_stop.py,sha256=GEOvxnHXjpCsrh3aa8Idtn1zMYv5HLQzj9qQAq8HvaU,1180
70
70
  isar/state_machine/states/home.py,sha256=TYv7WF5yX31Zw6Cn5MY2GHebUfypL5w510I92BEi_vE,1950
71
71
  isar/state_machine/states/intervention_needed.py,sha256=A0QYz1vD1tDKAealihXDuoGuMLtXrRfn_AwFoaUhT_Q,1640
72
- isar/state_machine/states/monitor.py,sha256=_Y6FFsuUEzEveH4skKAE8Cf6cie4keXr_LGA6gbUlQg,4741
72
+ isar/state_machine/states/monitor.py,sha256=GiNGyPcskrv3QDxMfsOmHUe_MRCaNA-pYuk9p6kcKp0,4623
73
73
  isar/state_machine/states/offline.py,sha256=5wdNzC1wG0cWrpuT_r_mk8UNFHA4QSyg8ejXUTAg4Io,1137
74
74
  isar/state_machine/states/paused.py,sha256=osmLZhm0-2jcZmNRauveeidCBYayDZvLy-1TNYv6CgQ,1091
75
75
  isar/state_machine/states/recharging.py,sha256=ni5WqFU0VDhdGC3ZfHcabSICm_5kwaS72wc3ecAB_os,1651
76
- isar/state_machine/states/returning_home.py,sha256=B57eFsMOMTbYH1IwbLLnhhMy-SaeGLnrXH1oY8V5Vk8,4508
76
+ isar/state_machine/states/returning_home.py,sha256=mPwsOFxMTO6On5PzeYKW8Roy6TaQIXsoWCsbtzcV7M8,4267
77
77
  isar/state_machine/states/robot_standing_still.py,sha256=EWG2_IPbxK6HFERHcmMFlWdN5PnXJXjwEfNlpsJnPkQ,1985
78
- isar/state_machine/states/stopping.py,sha256=fMXXvcXtzML-Q5A-uRD_gXePy51fnt9jhOTVFN-3qqA,2420
78
+ isar/state_machine/states/stopping.py,sha256=ROKbQcJFS4A_U0Pd8lAViG7Dz8JqfBaR3vHmpvKC2oc,3848
79
79
  isar/state_machine/states/unknown_status.py,sha256=Y-g9cgme5zriyZ6YUzFRYhOvv9ZylCSwfaY8MJ7xEJ0,1791
80
- isar/state_machine/transitions/mission.py,sha256=mUOtEyK2UX_kdK-_L4wq5a60882H2KnlxImO72YMtuo,5557
81
- isar/state_machine/transitions/return_home.py,sha256=E14fZR4RhEBdLaeeKPbRYXb2Y36zus7fXoVT_02PiEI,4649
80
+ isar/state_machine/transitions/mission.py,sha256=oJiANT1lHd6YhEA_mQscDPDyfwMxwfosq7UcP0gfTNk,5456
81
+ isar/state_machine/transitions/return_home.py,sha256=EIR14rnukV_mEYkwtz0J_D1Dp51oVna0wV71HSbqtuw,4630
82
82
  isar/state_machine/transitions/robot_status.py,sha256=sALt9BwZUnIFmVe35N1ptC-PyhfdHiTGu1R0GzpAQXk,3056
83
83
  isar/state_machine/transitions/functions/fail_mission.py,sha256=jHHXhfQVYQEzCXgTEhv5e6uEK9p6iDPFFXqS9bzs_-A,720
84
84
  isar/state_machine/transitions/functions/finish_mission.py,sha256=TRQrk7HdllmAkwsp25HRZAFAk46Y1hLx3jmkIAKrHDI,1442
85
- isar/state_machine/transitions/functions/pause.py,sha256=gWr-s76gL0-otrZxCBNmTxUicrAVsdNWDQmHSAOvs6E,1977
86
- isar/state_machine/transitions/functions/resume.py,sha256=ji4GjQvQQCr_jYRUGYHSYt-vePtO2z7VAUeKF-hyLBw,2106
85
+ isar/state_machine/transitions/functions/pause.py,sha256=Wa9XlhXVRNzxoYSsHc0w87ht4nQ5dGh50fjfA9lrAmI,1987
86
+ isar/state_machine/transitions/functions/resume.py,sha256=SAu4hWomPlrvO0lnpc6uM3rj79Bwq01acnaTEvNbO9U,2116
87
87
  isar/state_machine/transitions/functions/return_home.py,sha256=5WPO40MtuRKm9-NtyrS6m0IVEit14MXfMKjgZ2sCXRU,1666
88
88
  isar/state_machine/transitions/functions/robot_status.py,sha256=P1Cu8xVysbiKRKL4E8mSyoL2-72HfxrLvvOcdnBOlvw,889
89
- isar/state_machine/transitions/functions/start_mission.py,sha256=dUNy_EvkdR9Ud9PHwAaew9pzpdyus6n-X_F2Jwv1X7c,2834
90
- isar/state_machine/transitions/functions/stop.py,sha256=ZG33TK3VuJwT-LAQ0a3thgt714yCIu42_bo-u1-V1J0,2956
89
+ isar/state_machine/transitions/functions/start_mission.py,sha256=tIpZzYXCoeC6ZWj18UB4DiZuICpxfzFUK23wfunad7Q,2864
90
+ isar/state_machine/transitions/functions/stop.py,sha256=4idsNh7v6CRJivD36oKnVmdKlP7mSugTLCh9n12R1-U,2114
91
91
  isar/state_machine/transitions/functions/utils.py,sha256=Wa72Ocq4QT1E6qkpEJZQ3h5o33pGvx7Tlkt2JZ2Grbk,314
92
- isar/state_machine/utils/common_event_handlers.py,sha256=0rQJM-HWGjaSc_60NYcEkJFMzEWCBSYC7i8IHrz1S9M,6447
92
+ isar/state_machine/utils/common_event_handlers.py,sha256=yFZzTmTLVWzHU4RNW3q9gc6NU9eajNn50L-vt6geH38,6318
93
93
  isar/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
- isar/storage/blob_storage.py,sha256=QueyHzCJfUUyOLxBeYwK0Ot7w_CYakBEhtTtITelKAo,3026
95
- isar/storage/local_storage.py,sha256=-9Bz2WmniLKwKMeAdrgoMzkj781AOLIUsfaqQERqpUk,1985
96
- isar/storage/storage_interface.py,sha256=DRIiy0mRZL3KMZysG0R2Cque6hoqd5haZBw11WM53Vc,840
97
- isar/storage/uploader.py,sha256=uD1DzvJ2yYtNAwQGa7UD7kNOxZfKxJ1cCdi7sfOVZ10,9443
94
+ isar/storage/blob_storage.py,sha256=d44z3XpZDUbiKwN8Av2gytTJxnefMXrp5VhiGm4PWxU,3703
95
+ isar/storage/local_storage.py,sha256=Rn-iiiz9DI7PzIhevOMshPIaqzJaqBXeVJMQRhVSl2M,2191
96
+ isar/storage/storage_interface.py,sha256=x-imVeQTdL6dCaTaPTHpXwCR6N4e27WxK_Vpumg0x-Y,1230
97
+ isar/storage/uploader.py,sha256=HurLZb4MbZYRRocsJimoUrP3UHrE7wiO0IfIpKBSHrw,9988
98
98
  isar/storage/utilities.py,sha256=oLH0Rp7UtrQQdilfITnmXO1Z0ExdeDhBImYHid55vBA,3449
99
- isar-1.33.0.dist-info/licenses/LICENSE,sha256=3fc2-ebLwHWwzfQbulGNRdcNob3SBQeCfEVUDYxsuqw,14058
99
+ isar-1.33.2.dist-info/licenses/LICENSE,sha256=3fc2-ebLwHWwzfQbulGNRdcNob3SBQeCfEVUDYxsuqw,14058
100
100
  robot_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
101
  robot_interface/robot_interface.py,sha256=A6t19lcNU_6AfkYKY5DaF0sheym_SZEAawbfaj36Kjk,8997
102
102
  robot_interface/test_robot_interface.py,sha256=FV1urn7SbsMyWBIcTKjsBwAG4IsXeZ6pLHE0mA9EGGs,692
@@ -115,13 +115,13 @@ robot_interface/models/robots/battery_state.py,sha256=ktOtJ8ltdK0k_i7BoqYfhc5dbO
115
115
  robot_interface/models/robots/media.py,sha256=8A-CuuubfngzPprs6zWB9hSaqe3jzgsE8rcCzRX2Uto,227
116
116
  robot_interface/models/robots/robot_model.py,sha256=-0jNKWPcEgtF_2klb1It3u0SCoAR0hSW9nce58Zq0Co,417
117
117
  robot_interface/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
- robot_interface/telemetry/mqtt_client.py,sha256=ueXdtIFNCwciTj4spvdJj9emd-IOmUuJjpsXQSSWZPY,2987
119
- robot_interface/telemetry/payloads.py,sha256=T7EK-b0bVhADDTKMIAHmcPVtPxuR16csmZoOJQl9P4c,3103
118
+ robot_interface/telemetry/mqtt_client.py,sha256=0P1S9mWdJcByGoSOwwn2NPQr9I-OX4b1VPbrIYOU-Zo,4334
119
+ robot_interface/telemetry/payloads.py,sha256=RfLlm_te-bV_xcLtbBx27bgE8gkwPAhWBTF9JrxY7f8,3209
120
120
  robot_interface/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
- robot_interface/utilities/json_service.py,sha256=qkzVkb60Gi_pto-b5n1vNzCrQze2yqgIJqSLNLYj1Fg,1034
121
+ robot_interface/utilities/json_service.py,sha256=9N1zijW7K4d3WFR2autpaS8U9o1ibymiOX-6stTKCyk,1243
122
122
  robot_interface/utilities/uuid_string_factory.py,sha256=_NQIbBQ56w0qqO0MUDP6aPpHbxW7ATRhK8HnQiBSLkc,76
123
- isar-1.33.0.dist-info/METADATA,sha256=T0cbwIlPcWsZ0jwsQbKTLZBo3BI2FTlXq_LwoLKcbdQ,31217
124
- isar-1.33.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
- isar-1.33.0.dist-info/entry_points.txt,sha256=TFam7uNNw7J0iiDYzsH2gfG0u1eV1wh3JTw_HkhgKLk,49
126
- isar-1.33.0.dist-info/top_level.txt,sha256=UwIML2RtuQKCyJJkatcSnyp6-ldDjboB9k9JgKipO-U,21
127
- isar-1.33.0.dist-info/RECORD,,
123
+ isar-1.33.2.dist-info/METADATA,sha256=3Hzfh4ScmPbEXrX9zdzbcaC1I9cbGLpO23j_2Ihy61M,31217
124
+ isar-1.33.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
+ isar-1.33.2.dist-info/entry_points.txt,sha256=TFam7uNNw7J0iiDYzsH2gfG0u1eV1wh3JTw_HkhgKLk,49
126
+ isar-1.33.2.dist-info/top_level.txt,sha256=UwIML2RtuQKCyJJkatcSnyp6-ldDjboB9k9JgKipO-U,21
127
+ isar-1.33.2.dist-info/RECORD,,
@@ -5,6 +5,10 @@ from datetime import datetime, timezone
5
5
  from queue import Queue
6
6
  from typing import Callable, Tuple
7
7
 
8
+ from paho.mqtt.packettypes import PacketTypes
9
+ from paho.mqtt.properties import Properties
10
+
11
+ from isar.config.settings import settings
8
12
  from robot_interface.models.exceptions.robot_exceptions import (
9
13
  RobotTelemetryException,
10
14
  RobotTelemetryNoUpdateException,
@@ -14,10 +18,21 @@ from robot_interface.telemetry.payloads import CloudHealthPayload
14
18
  from robot_interface.utilities.json_service import EnhancedJSONEncoder
15
19
 
16
20
 
21
+ def props_expiry(seconds: int) -> Properties:
22
+ p = Properties(PacketTypes.PUBLISH)
23
+ p.MessageExpiryInterval = seconds
24
+ return p
25
+
26
+
17
27
  class MqttClientInterface(metaclass=ABCMeta):
18
28
  @abstractmethod
19
29
  def publish(
20
- self, topic: str, payload: str, qos: int = 0, retain: bool = False
30
+ self,
31
+ topic: str,
32
+ payload: str,
33
+ qos: int = 0,
34
+ retain: bool = False,
35
+ properties: Properties = None,
21
36
  ) -> None:
22
37
  """
23
38
  Parameters
@@ -42,9 +57,20 @@ class MqttPublisher(MqttClientInterface):
42
57
  self.mqtt_queue: Queue = mqtt_queue
43
58
 
44
59
  def publish(
45
- self, topic: str, payload: str, qos: int = 0, retain: bool = False
60
+ self,
61
+ topic: str,
62
+ payload: str,
63
+ qos: int = 0,
64
+ retain: bool = False,
65
+ properties: Properties = None,
46
66
  ) -> None:
47
- queue_message: Tuple[str, str, int, bool] = (topic, payload, qos, retain)
67
+ queue_message: Tuple[str, str, int, bool, Properties] = (
68
+ topic,
69
+ payload,
70
+ qos,
71
+ retain,
72
+ properties,
73
+ )
48
74
  self.mqtt_queue.put(queue_message)
49
75
 
50
76
 
@@ -57,6 +83,7 @@ class MqttTelemetryPublisher(MqttClientInterface):
57
83
  interval: float,
58
84
  qos: int = 0,
59
85
  retain: bool = False,
86
+ properties: Properties = None,
60
87
  ) -> None:
61
88
  self.mqtt_queue: Queue = mqtt_queue
62
89
  self.telemetry_method: Callable = telemetry_method
@@ -64,9 +91,13 @@ class MqttTelemetryPublisher(MqttClientInterface):
64
91
  self.interval: float = interval
65
92
  self.qos: int = qos
66
93
  self.retain: bool = retain
94
+ self.properties: Properties = properties
67
95
 
68
96
  def run(self, isar_id: str, robot_name: str) -> None:
69
97
  self.cloud_health_topic: str = f"isar/{isar_id}/cloud_health"
98
+ self.battery_topic: str = f"isar/{isar_id}/battery"
99
+ self.pose_topic: str = f"isar/{isar_id}/pose"
100
+ self.pressure_topic: str = f"isar/{isar_id}/pressure"
70
101
  topic: str
71
102
  payload: str
72
103
 
@@ -84,12 +115,37 @@ class MqttTelemetryPublisher(MqttClientInterface):
84
115
  )
85
116
  topic = self.cloud_health_topic
86
117
 
87
- self.publish(topic=topic, payload=payload, qos=self.qos, retain=self.retain)
118
+ publish_properties = self.properties
119
+
120
+ if topic in (
121
+ self.battery_topic,
122
+ self.pose_topic,
123
+ self.pressure_topic,
124
+ ):
125
+ publish_properties = props_expiry(settings.MQTT_TELEMETRY_EXPIRY)
88
126
 
127
+ self.publish(
128
+ topic=topic,
129
+ payload=payload,
130
+ qos=self.qos,
131
+ retain=self.retain,
132
+ properties=publish_properties,
133
+ )
89
134
  time.sleep(self.interval)
90
135
 
91
136
  def publish(
92
- self, topic: str, payload: str, qos: int = 0, retain: bool = False
137
+ self,
138
+ topic: str,
139
+ payload: str,
140
+ qos: int = 0,
141
+ retain: bool = False,
142
+ properties: Properties = None,
93
143
  ) -> None:
94
- queue_message: Tuple[str, str, int, bool] = (topic, payload, qos, retain)
144
+ queue_message: Tuple[str, str, int, bool, Properties] = (
145
+ topic,
146
+ payload,
147
+ qos,
148
+ retain,
149
+ properties,
150
+ )
95
151
  self.mqtt_queue.put(queue_message)