isar 1.15.0__py3-none-any.whl → 1.34.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.
Files changed (129) hide show
  1. isar/__init__.py +2 -5
  2. isar/apis/api.py +159 -66
  3. isar/apis/models/__init__.py +0 -1
  4. isar/apis/models/models.py +22 -12
  5. isar/apis/models/start_mission_definition.py +128 -123
  6. isar/apis/robot_control/robot_controller.py +41 -0
  7. isar/apis/schedule/scheduling_controller.py +135 -121
  8. isar/apis/security/authentication.py +5 -5
  9. isar/config/certs/ca-cert.pem +32 -32
  10. isar/config/keyvault/keyvault_service.py +1 -2
  11. isar/config/log.py +47 -39
  12. isar/config/logging.conf +16 -31
  13. isar/config/open_telemetry.py +102 -0
  14. isar/config/predefined_mission_definition/default_exr.json +49 -0
  15. isar/config/predefined_mission_definition/default_mission.json +1 -5
  16. isar/config/predefined_mission_definition/default_turtlebot.json +4 -11
  17. isar/config/predefined_missions/default.json +67 -87
  18. isar/config/predefined_missions/default_extra_capabilities.json +107 -0
  19. isar/config/settings.py +119 -142
  20. isar/eventhandlers/eventhandler.py +123 -0
  21. isar/mission_planner/local_planner.py +6 -20
  22. isar/mission_planner/mission_planner_interface.py +1 -1
  23. isar/models/events.py +184 -0
  24. isar/models/status.py +18 -0
  25. isar/modules.py +118 -205
  26. isar/robot/robot.py +377 -0
  27. isar/robot/robot_battery.py +60 -0
  28. isar/robot/robot_monitor_mission.py +357 -0
  29. isar/robot/robot_pause_mission.py +74 -0
  30. isar/robot/robot_resume_mission.py +67 -0
  31. isar/robot/robot_start_mission.py +66 -0
  32. isar/robot/robot_status.py +61 -0
  33. isar/robot/robot_stop_mission.py +68 -0
  34. isar/robot/robot_upload_inspection.py +75 -0
  35. isar/script.py +171 -0
  36. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  37. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +32 -0
  38. isar/services/service_connections/mqtt/robot_info_publisher.py +4 -3
  39. isar/services/service_connections/persistent_memory.py +69 -0
  40. isar/services/utilities/mqtt_utilities.py +93 -0
  41. isar/services/utilities/robot_utilities.py +20 -0
  42. isar/services/utilities/scheduling_utilities.py +393 -65
  43. isar/state_machine/state_machine.py +227 -486
  44. isar/state_machine/states/__init__.py +0 -7
  45. isar/state_machine/states/await_next_mission.py +114 -0
  46. isar/state_machine/states/blocked_protective_stop.py +60 -0
  47. isar/state_machine/states/going_to_lockdown.py +95 -0
  48. isar/state_machine/states/going_to_recharging.py +92 -0
  49. isar/state_machine/states/home.py +115 -0
  50. isar/state_machine/states/intervention_needed.py +77 -0
  51. isar/state_machine/states/lockdown.py +38 -0
  52. isar/state_machine/states/maintenance.py +36 -0
  53. isar/state_machine/states/monitor.py +137 -166
  54. isar/state_machine/states/offline.py +60 -0
  55. isar/state_machine/states/paused.py +92 -23
  56. isar/state_machine/states/pausing.py +48 -0
  57. isar/state_machine/states/pausing_return_home.py +48 -0
  58. isar/state_machine/states/recharging.py +80 -0
  59. isar/state_machine/states/resuming.py +57 -0
  60. isar/state_machine/states/resuming_return_home.py +64 -0
  61. isar/state_machine/states/return_home_paused.py +109 -0
  62. isar/state_machine/states/returning_home.py +217 -0
  63. isar/state_machine/states/stopping.py +61 -0
  64. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  65. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  66. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  67. isar/state_machine/states/stopping_return_home.py +77 -0
  68. isar/state_machine/states/unknown_status.py +72 -0
  69. isar/state_machine/states_enum.py +22 -5
  70. isar/state_machine/transitions/mission.py +192 -0
  71. isar/state_machine/transitions/return_home.py +106 -0
  72. isar/state_machine/transitions/robot_status.py +80 -0
  73. isar/state_machine/utils/common_event_handlers.py +73 -0
  74. isar/storage/blob_storage.py +71 -45
  75. isar/storage/local_storage.py +28 -14
  76. isar/storage/storage_interface.py +28 -6
  77. isar/storage/uploader.py +184 -55
  78. isar/storage/utilities.py +35 -27
  79. isar-1.34.9.dist-info/METADATA +496 -0
  80. isar-1.34.9.dist-info/RECORD +135 -0
  81. {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/WHEEL +1 -1
  82. isar-1.34.9.dist-info/entry_points.txt +3 -0
  83. robot_interface/models/exceptions/__init__.py +0 -7
  84. robot_interface/models/exceptions/robot_exceptions.py +274 -4
  85. robot_interface/models/initialize/__init__.py +0 -1
  86. robot_interface/models/inspection/__init__.py +0 -13
  87. robot_interface/models/inspection/inspection.py +43 -34
  88. robot_interface/models/mission/mission.py +18 -14
  89. robot_interface/models/mission/status.py +20 -25
  90. robot_interface/models/mission/task.py +156 -92
  91. robot_interface/models/robots/battery_state.py +6 -0
  92. robot_interface/models/robots/media.py +13 -0
  93. robot_interface/models/robots/robot_model.py +7 -7
  94. robot_interface/robot_interface.py +135 -66
  95. robot_interface/telemetry/mqtt_client.py +84 -12
  96. robot_interface/telemetry/payloads.py +111 -12
  97. robot_interface/utilities/json_service.py +7 -1
  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 -26
  102. isar/mission_planner/sequential_task_selector.py +0 -23
  103. isar/mission_planner/task_selector_interface.py +0 -31
  104. isar/models/communication/__init__.py +0 -0
  105. isar/models/communication/message.py +0 -12
  106. isar/models/communication/queues/__init__.py +0 -4
  107. isar/models/communication/queues/queue_io.py +0 -12
  108. isar/models/communication/queues/queue_timeout_error.py +0 -2
  109. isar/models/communication/queues/queues.py +0 -19
  110. isar/models/communication/queues/status_queue.py +0 -20
  111. isar/models/mission_metadata/__init__.py +0 -0
  112. isar/services/readers/__init__.py +0 -0
  113. isar/services/readers/base_reader.py +0 -37
  114. isar/services/service_connections/mqtt/robot_status_publisher.py +0 -93
  115. isar/services/service_connections/stid/__init__.py +0 -0
  116. isar/services/service_connections/stid/stid_service.py +0 -45
  117. isar/services/utilities/queue_utilities.py +0 -39
  118. isar/state_machine/states/idle.py +0 -40
  119. isar/state_machine/states/initialize.py +0 -60
  120. isar/state_machine/states/initiate.py +0 -129
  121. isar/state_machine/states/off.py +0 -18
  122. isar/state_machine/states/stop.py +0 -78
  123. isar/storage/slimm_storage.py +0 -181
  124. isar-1.15.0.dist-info/METADATA +0 -417
  125. isar-1.15.0.dist-info/RECORD +0 -113
  126. robot_interface/models/initialize/initialize_params.py +0 -9
  127. robot_interface/models/mission/step.py +0 -211
  128. {isar-1.15.0.dist-info → isar-1.34.9.dist-info/licenses}/LICENSE +0 -0
  129. {isar-1.15.0.dist-info → isar-1.34.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,73 @@
1
+ from typing import TYPE_CHECKING, Callable, Optional
2
+
3
+ from isar.apis.models.models import ControlMissionResponse, MissionStartResponse
4
+ from isar.models.events import Event
5
+ from robot_interface.models.mission.mission import Mission
6
+
7
+ if TYPE_CHECKING:
8
+ from isar.state_machine.state_machine import StateMachine
9
+
10
+
11
+ def start_mission_event_handler(
12
+ state_machine: "StateMachine",
13
+ event: Event[Mission],
14
+ response: Event[MissionStartResponse],
15
+ ) -> Optional[Callable]:
16
+ mission: Optional[Mission] = event.consume_event()
17
+ if not mission:
18
+ return None
19
+
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
+ )
27
+ )
28
+ return None
29
+ state_machine.start_mission(mission=mission)
30
+ response.trigger_event(MissionStartResponse(mission_started=True))
31
+ return state_machine.start_mission_monitoring # type: ignore
32
+
33
+
34
+ def return_home_event_handler(
35
+ state_machine: "StateMachine", event: Event[bool]
36
+ ) -> Optional[Callable]:
37
+ if not event.consume_event():
38
+ return None
39
+
40
+ state_machine.events.api_requests.return_home.response.trigger_event(True)
41
+ state_machine.start_return_home_mission()
42
+ return state_machine.start_return_home_monitoring # type: ignore
43
+
44
+
45
+ def stop_mission_event_handler(
46
+ state_machine: "StateMachine", event: Event[str]
47
+ ) -> Optional[Callable]:
48
+ mission_id: str = event.consume_event()
49
+ if mission_id is None:
50
+ return None
51
+
52
+ if state_machine.shared_state.mission_id.check() == mission_id or mission_id == "":
53
+ state_machine.events.api_requests.stop_mission.response.trigger_event(
54
+ ControlMissionResponse(success=True)
55
+ )
56
+ state_machine.events.state_machine_events.stop_mission.trigger_event(True)
57
+ return state_machine.stop # type: ignore
58
+ else:
59
+ state_machine.events.api_requests.stop_mission.response.trigger_event(
60
+ ControlMissionResponse(success=False, failure_reason="Mission not found")
61
+ )
62
+ return None
63
+
64
+
65
+ def mission_started_event_handler(
66
+ state_machine: "StateMachine",
67
+ event: Event[bool],
68
+ ) -> Optional[Callable]:
69
+ if not event.consume_event():
70
+ return None
71
+
72
+ state_machine.logger.info("Received confirmation that mission has started")
73
+ return None
@@ -2,74 +2,100 @@ import logging
2
2
  from pathlib import Path
3
3
 
4
4
  from azure.core.exceptions import ResourceExistsError
5
- from azure.storage.blob import BlobClient, BlobServiceClient, ContainerClient
6
- from injector import inject
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 robot_interface.models.mission.mission import Mission
11
- from isar.storage.storage_interface import StorageException, StorageInterface
9
+ from isar.storage.storage_interface import (
10
+ BlobStoragePath,
11
+ StorageException,
12
+ StorageInterface,
13
+ StoragePaths,
14
+ )
12
15
  from isar.storage.utilities import construct_metadata_file, construct_paths
13
- from robot_interface.models.inspection.inspection import Inspection
16
+ from robot_interface.models.inspection.inspection import InspectionBlob
17
+ from robot_interface.models.mission.mission import Mission
14
18
 
15
19
 
16
20
  class BlobStorage(StorageInterface):
17
- @inject
18
- def __init__(
19
- self, keyvault: Keyvault, container_name: str = settings.BLOB_CONTAINER
20
- ):
21
- self.keyvault = keyvault
22
- self.storage_connection_string = self.keyvault.get_secret(
23
- "AZURE-STORAGE-CONNECTION-STRING"
24
- ).value
25
- self.container_name = container_name
26
-
27
- self.blob_service_client = self._get_blob_service_client()
28
- self.container_client = self._get_container_client(
29
- blob_service_client=self.blob_service_client
21
+ def __init__(self, keyvault: Keyvault) -> None:
22
+ self.logger = logging.getLogger("uploader")
23
+
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"
30
29
  )
31
30
 
32
- self.logger = logging.getLogger("uploader")
31
+ def _get_container_client(self, keyvault: Keyvault, secret_name: str):
32
+ storage_connection_string = keyvault.get_secret(secret_name).value
33
+
34
+ if storage_connection_string is None:
35
+ raise RuntimeError(f"{secret_name} from keyvault is None")
36
+
37
+ try:
38
+ blob_service_client = BlobServiceClient.from_connection_string(
39
+ storage_connection_string
40
+ )
41
+ except Exception as e:
42
+ self.logger.error("Unable to retrieve blob service client. Error: %s", e)
43
+ raise e
44
+
45
+ container_client = blob_service_client.get_container_client(
46
+ settings.BLOB_CONTAINER
47
+ )
48
+
49
+ if not container_client.exists():
50
+ raise RuntimeError(
51
+ "The configured blob container %s does not exist",
52
+ settings.BLOB_CONTAINER,
53
+ )
54
+ return container_client
55
+
56
+ def store(
57
+ self, inspection: InspectionBlob, mission: Mission
58
+ ) -> StoragePaths[BlobStoragePath]:
59
+ if inspection.data is None:
60
+ raise StorageException("Nothing to store. The inspection data is empty")
33
61
 
34
- def store(self, inspection: Inspection, mission: Mission) -> str:
35
- data_path, metadata_path = construct_paths(
62
+ data_filename, metadata_filename = construct_paths(
36
63
  inspection=inspection, mission=mission
37
64
  )
38
65
 
39
66
  metadata_bytes: bytes = construct_metadata_file(
40
- inspection=inspection, mission=mission, filename=data_path.name
67
+ inspection=inspection, mission=mission, filename=data_filename.name
41
68
  )
42
69
 
43
- self._upload_file(path=metadata_path, data=metadata_bytes)
44
- 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)
45
81
 
46
- def _upload_file(self, path: Path, data: bytes) -> str:
47
- blob_client = self._get_blob_client(path)
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())
48
86
  try:
49
- blob_properties = blob_client.upload_blob(data=data)
87
+ blob_client.upload_blob(data=data)
50
88
  except ResourceExistsError as e:
51
89
  self.logger.error(
52
- f"Blob {path.as_posix()} already exists in container. Error: {e}"
90
+ "Blob %s already exists in container. Error: %s", filename.as_posix(), e
53
91
  )
54
92
  raise StorageException from e
55
93
  except Exception as e:
56
94
  self.logger.error("An unexpected error occurred while uploading blob")
57
95
  raise StorageException from e
58
- return blob_properties["etag"]
59
96
 
60
- def _get_blob_service_client(self) -> BlobServiceClient:
61
- try:
62
- return BlobServiceClient.from_connection_string(
63
- self.storage_connection_string
64
- )
65
- except Exception as e:
66
- self.logger.error(f"Unable to retrieve blob service client. Error: {e}")
67
- raise e
68
-
69
- def _get_container_client(
70
- self, blob_service_client: BlobServiceClient
71
- ) -> ContainerClient:
72
- return blob_service_client.get_container_client(self.container_name)
73
-
74
- def _get_blob_client(self, path_to_blob: Path) -> BlobClient:
75
- return self.container_client.get_blob_client(path_to_blob.as_posix())
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,10 +2,15 @@ import logging
2
2
  from pathlib import Path
3
3
 
4
4
  from isar.config.settings import settings
5
- from robot_interface.models.mission.mission import Mission
6
- from isar.storage.storage_interface import StorageException, StorageInterface
5
+ from isar.storage.storage_interface import (
6
+ LocalStoragePath,
7
+ StorageException,
8
+ StorageInterface,
9
+ StoragePaths,
10
+ )
7
11
  from isar.storage.utilities import construct_metadata_file, construct_paths
8
- from robot_interface.models.inspection.inspection import Inspection
12
+ from robot_interface.models.inspection.inspection import InspectionBlob
13
+ from robot_interface.models.mission.mission import Mission
9
14
 
10
15
 
11
16
  class LocalStorage(StorageInterface):
@@ -13,29 +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: Inspection, mission: Mission) -> str:
17
- local_path, local_metadata_path = construct_paths(
21
+ def store(
22
+ self, inspection: InspectionBlob, mission: Mission
23
+ ) -> StoragePaths[LocalStoragePath]:
24
+ if inspection.data is None:
25
+ raise StorageException("Nothing to store. The inspection data is empty")
26
+
27
+ local_filename, local_metadata_filename = construct_paths(
18
28
  inspection=inspection, mission=mission
19
29
  )
20
30
 
21
- absolute_path: Path = self.root_folder.joinpath(local_path)
22
- 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)
23
33
 
24
- absolute_path.parent.mkdir(parents=True, exist_ok=True)
34
+ data_path.parent.mkdir(parents=True, exist_ok=True)
25
35
 
26
36
  metadata_bytes: bytes = construct_metadata_file(
27
- inspection=inspection, mission=mission, filename=local_path.name
37
+ inspection=inspection, mission=mission, filename=local_filename.name
28
38
  )
29
39
  try:
30
- with open(absolute_path, "wb") as file, open(
31
- absolute_metadata_path, "wb"
32
- ) as metadata_file:
40
+ with (
41
+ open(data_path, "wb") as file,
42
+ open(metadata_path, "wb") as metadata_file,
43
+ ):
33
44
  file.write(inspection.data)
34
45
  metadata_file.write(metadata_bytes)
35
46
  except IOError as e:
36
47
  self.logger.warning(
37
48
  f"Failed open/write for one of the following files: \n"
38
- f"{absolute_path}\n{absolute_metadata_path}"
49
+ f"{data_path}\n{metadata_path}"
39
50
  )
40
51
  raise StorageException from e
41
52
  except Exception as e:
@@ -43,4 +54,7 @@ class LocalStorage(StorageInterface):
43
54
  "An unexpected error occurred while writing to local storage"
44
55
  )
45
56
  raise StorageException from e
46
- 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,24 +1,46 @@
1
1
  from abc import ABCMeta, abstractmethod
2
+ from pathlib import Path
3
+ from typing import Generic, TypeVar
2
4
 
5
+ from pydantic import BaseModel
6
+
7
+ from robot_interface.models.inspection.inspection import InspectionBlob
3
8
  from robot_interface.models.mission.mission import Mission
4
- from robot_interface.models.inspection.inspection import Inspection
9
+
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
5
27
 
6
28
 
7
29
  class StorageInterface(metaclass=ABCMeta):
8
30
  @abstractmethod
9
- def store(self, inspection: Inspection, mission: Mission) -> str:
31
+ def store(self, inspection: InspectionBlob, mission: Mission) -> StoragePaths:
10
32
  """
11
33
  Parameters
12
34
  ----------
35
+ inspection : InspectionBlob
36
+ The inspection object to be stored.
13
37
  mission : Mission
14
38
  Mission the inspection is a part of.
15
- inspection : Inspection
16
- The inspection object to be stored.
17
39
 
18
40
  Returns
19
41
  ----------
20
- String
21
- Path of the saved inspection
42
+ StoragePaths
43
+ Paths to the data and metadata
22
44
 
23
45
  Raises
24
46
  ----------