isar 1.33.1__py3-none-any.whl → 1.33.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of isar might be problematic. Click here for more details.
- isar/apis/schedule/scheduling_controller.py +3 -1
- isar/config/settings.py +5 -0
- isar/services/service_connections/mqtt/mqtt_client.py +46 -10
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +3 -0
- isar/services/utilities/scheduling_utilities.py +5 -9
- isar/state_machine/state_machine.py +16 -9
- isar/state_machine/states/home.py +14 -5
- isar/state_machine/states/paused.py +24 -1
- isar/state_machine/states/return_home_paused.py +66 -0
- isar/state_machine/states/returning_home.py +10 -5
- isar/state_machine/states/stopping.py +5 -22
- isar/state_machine/states/stopping_return_home.py +73 -0
- isar/state_machine/states_enum.py +2 -1
- isar/state_machine/transitions/functions/pause.py +38 -0
- isar/state_machine/transitions/functions/robot_status.py +2 -7
- isar/state_machine/transitions/mission.py +46 -19
- isar/state_machine/transitions/return_home.py +1 -7
- isar/state_machine/transitions/robot_status.py +2 -17
- isar/storage/blob_storage.py +46 -24
- isar/storage/local_storage.py +21 -11
- isar/storage/storage_interface.py +27 -6
- isar/storage/uploader.py +30 -13
- {isar-1.33.1.dist-info → isar-1.33.3.dist-info}/METADATA +2 -2
- {isar-1.33.1.dist-info → isar-1.33.3.dist-info}/RECORD +32 -31
- robot_interface/models/mission/status.py +2 -1
- robot_interface/telemetry/mqtt_client.py +62 -6
- robot_interface/telemetry/payloads.py +4 -2
- robot_interface/utilities/json_service.py +6 -0
- isar/state_machine/states/robot_standing_still.py +0 -52
- {isar-1.33.1.dist-info → isar-1.33.3.dist-info}/WHEEL +0 -0
- {isar-1.33.1.dist-info → isar-1.33.3.dist-info}/entry_points.txt +0 -0
- {isar-1.33.1.dist-info → isar-1.33.3.dist-info}/licenses/LICENSE +0 -0
- {isar-1.33.1.dist-info → isar-1.33.3.dist-info}/top_level.txt +0 -0
|
@@ -4,8 +4,15 @@ from isar.state_machine.transitions.functions.fail_mission import (
|
|
|
4
4
|
report_failed_mission_and_finalize,
|
|
5
5
|
)
|
|
6
6
|
from isar.state_machine.transitions.functions.finish_mission import finish_mission
|
|
7
|
-
from isar.state_machine.transitions.functions.pause import
|
|
7
|
+
from isar.state_machine.transitions.functions.pause import (
|
|
8
|
+
pause_mission,
|
|
9
|
+
pause_return_home_mission,
|
|
10
|
+
)
|
|
8
11
|
from isar.state_machine.transitions.functions.resume import resume_mission
|
|
12
|
+
from isar.state_machine.transitions.functions.return_home import (
|
|
13
|
+
reset_return_home_failure_counter,
|
|
14
|
+
return_home_finished,
|
|
15
|
+
)
|
|
9
16
|
from isar.state_machine.transitions.functions.start_mission import (
|
|
10
17
|
acknowledge_mission,
|
|
11
18
|
initialize_robot,
|
|
@@ -15,7 +22,6 @@ from isar.state_machine.transitions.functions.start_mission import (
|
|
|
15
22
|
)
|
|
16
23
|
from isar.state_machine.transitions.functions.stop import (
|
|
17
24
|
stop_mission_failed,
|
|
18
|
-
stop_return_home_mission_cleanup,
|
|
19
25
|
stop_return_home_mission_failed,
|
|
20
26
|
trigger_stop_mission_event,
|
|
21
27
|
)
|
|
@@ -38,6 +44,17 @@ def get_mission_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
38
44
|
"source": state_machine.monitor_state,
|
|
39
45
|
"dest": state_machine.monitor_state,
|
|
40
46
|
},
|
|
47
|
+
{
|
|
48
|
+
"trigger": "pause",
|
|
49
|
+
"source": state_machine.returning_home_state,
|
|
50
|
+
"dest": state_machine.return_home_paused_state,
|
|
51
|
+
"conditions": def_transition(state_machine, pause_return_home_mission),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"trigger": "pause",
|
|
55
|
+
"source": state_machine.returning_home_state,
|
|
56
|
+
"dest": state_machine.returning_home_state,
|
|
57
|
+
},
|
|
41
58
|
{
|
|
42
59
|
"trigger": "resume",
|
|
43
60
|
"source": state_machine.paused_state,
|
|
@@ -49,18 +66,40 @@ def get_mission_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
49
66
|
"source": state_machine.paused_state,
|
|
50
67
|
"dest": state_machine.paused_state,
|
|
51
68
|
},
|
|
69
|
+
{
|
|
70
|
+
"trigger": "resume",
|
|
71
|
+
"source": state_machine.return_home_paused_state,
|
|
72
|
+
"dest": state_machine.returning_home_state,
|
|
73
|
+
"conditions": def_transition(state_machine, resume_mission),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"trigger": "resume",
|
|
77
|
+
"source": state_machine.return_home_paused_state,
|
|
78
|
+
"dest": state_machine.return_home_paused_state,
|
|
79
|
+
},
|
|
52
80
|
{
|
|
53
81
|
"trigger": "stop",
|
|
54
82
|
"source": [
|
|
55
83
|
state_machine.await_next_mission_state,
|
|
56
|
-
state_machine.robot_standing_still_state,
|
|
57
84
|
state_machine.monitor_state,
|
|
58
|
-
state_machine.returning_home_state,
|
|
59
85
|
state_machine.paused_state,
|
|
60
86
|
],
|
|
61
87
|
"dest": state_machine.stopping_state,
|
|
62
88
|
"before": def_transition(state_machine, trigger_stop_mission_event),
|
|
63
89
|
},
|
|
90
|
+
{
|
|
91
|
+
"trigger": "stop_return_home",
|
|
92
|
+
"source": [
|
|
93
|
+
state_machine.returning_home_state,
|
|
94
|
+
state_machine.return_home_paused_state,
|
|
95
|
+
],
|
|
96
|
+
"dest": state_machine.stopping_return_home_state,
|
|
97
|
+
"before": [
|
|
98
|
+
def_transition(state_machine, trigger_stop_mission_event),
|
|
99
|
+
def_transition(state_machine, reset_return_home_failure_counter),
|
|
100
|
+
def_transition(state_machine, return_home_finished),
|
|
101
|
+
],
|
|
102
|
+
},
|
|
64
103
|
{
|
|
65
104
|
"trigger": "mission_stopped",
|
|
66
105
|
"source": state_machine.stopping_state,
|
|
@@ -74,22 +113,16 @@ def get_mission_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
74
113
|
},
|
|
75
114
|
{
|
|
76
115
|
"trigger": "return_home_mission_stopping_failed",
|
|
77
|
-
"source": state_machine.
|
|
116
|
+
"source": state_machine.stopping_return_home_state,
|
|
78
117
|
"dest": state_machine.returning_home_state,
|
|
79
118
|
"before": def_transition(state_machine, stop_return_home_mission_failed),
|
|
80
119
|
},
|
|
81
|
-
{
|
|
82
|
-
"trigger": "return_home_mission_stopped",
|
|
83
|
-
"source": state_machine.stopping_state,
|
|
84
|
-
"dest": state_machine.robot_standing_still_state,
|
|
85
|
-
"before": def_transition(state_machine, stop_return_home_mission_cleanup),
|
|
86
|
-
},
|
|
87
120
|
{
|
|
88
121
|
"trigger": "request_mission_start",
|
|
89
122
|
"source": [
|
|
90
123
|
state_machine.await_next_mission_state,
|
|
91
124
|
state_machine.home_state,
|
|
92
|
-
state_machine.
|
|
125
|
+
state_machine.stopping_return_home_state,
|
|
93
126
|
],
|
|
94
127
|
"dest": state_machine.monitor_state,
|
|
95
128
|
"prepare": def_transition(state_machine, acknowledge_mission),
|
|
@@ -108,12 +141,6 @@ def get_mission_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
108
141
|
"dest": state_machine.await_next_mission_state,
|
|
109
142
|
"before": def_transition(state_machine, report_failed_mission_and_finalize),
|
|
110
143
|
},
|
|
111
|
-
{
|
|
112
|
-
"trigger": "request_mission_start",
|
|
113
|
-
"source": state_machine.robot_standing_still_state,
|
|
114
|
-
"dest": state_machine.robot_standing_still_state,
|
|
115
|
-
"before": def_transition(state_machine, report_failed_mission_and_finalize),
|
|
116
|
-
},
|
|
117
144
|
{
|
|
118
145
|
"trigger": "request_mission_start",
|
|
119
146
|
"source": state_machine.home_state,
|
|
@@ -123,7 +150,7 @@ def get_mission_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
123
150
|
{
|
|
124
151
|
"trigger": "mission_failed_to_start",
|
|
125
152
|
"source": [state_machine.monitor_state, state_machine.returning_home_state],
|
|
126
|
-
"dest": state_machine.
|
|
153
|
+
"dest": state_machine.await_next_mission_state,
|
|
127
154
|
"before": def_transition(state_machine, report_failed_mission_and_finalize),
|
|
128
155
|
},
|
|
129
156
|
{
|
|
@@ -28,10 +28,10 @@ def get_return_home_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
28
28
|
"source": [
|
|
29
29
|
state_machine.await_next_mission_state,
|
|
30
30
|
state_machine.home_state,
|
|
31
|
-
state_machine.robot_standing_still_state,
|
|
32
31
|
state_machine.intervention_needed_state,
|
|
33
32
|
state_machine.monitor_state,
|
|
34
33
|
state_machine.stopping_state,
|
|
34
|
+
state_machine.stopping_return_home_state,
|
|
35
35
|
],
|
|
36
36
|
"dest": state_machine.returning_home_state,
|
|
37
37
|
"conditions": [
|
|
@@ -53,12 +53,6 @@ def get_return_home_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
53
53
|
"dest": state_machine.home_state,
|
|
54
54
|
"before": def_transition(state_machine, report_failed_mission_and_finalize),
|
|
55
55
|
},
|
|
56
|
-
{
|
|
57
|
-
"trigger": "request_return_home",
|
|
58
|
-
"source": state_machine.robot_standing_still_state,
|
|
59
|
-
"dest": state_machine.robot_standing_still_state,
|
|
60
|
-
"before": def_transition(state_machine, report_failed_mission_and_finalize),
|
|
61
|
-
},
|
|
62
56
|
{
|
|
63
57
|
"trigger": "returned_home",
|
|
64
58
|
"source": state_machine.returning_home_state,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, List
|
|
2
2
|
|
|
3
3
|
from isar.state_machine.transitions.functions.robot_status import (
|
|
4
|
-
|
|
4
|
+
is_available_or_home,
|
|
5
5
|
is_blocked_protective_stop,
|
|
6
|
-
is_home,
|
|
7
6
|
is_offline,
|
|
8
7
|
)
|
|
9
8
|
from isar.state_machine.transitions.functions.utils import def_transition
|
|
@@ -22,24 +21,12 @@ def get_robot_status_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
22
21
|
state_machine.offline_state,
|
|
23
22
|
state_machine.unknown_status_state,
|
|
24
23
|
],
|
|
25
|
-
"dest": state_machine.robot_standing_still_state,
|
|
26
|
-
"conditions": def_transition(state_machine, is_available),
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"trigger": "robot_status_changed",
|
|
30
|
-
"source": [
|
|
31
|
-
state_machine.robot_standing_still_state,
|
|
32
|
-
state_machine.blocked_protective_stopping_state,
|
|
33
|
-
state_machine.offline_state,
|
|
34
|
-
state_machine.unknown_status_state,
|
|
35
|
-
],
|
|
36
24
|
"dest": state_machine.home_state,
|
|
37
|
-
"conditions": def_transition(state_machine,
|
|
25
|
+
"conditions": def_transition(state_machine, is_available_or_home),
|
|
38
26
|
},
|
|
39
27
|
{
|
|
40
28
|
"trigger": "robot_status_changed",
|
|
41
29
|
"source": [
|
|
42
|
-
state_machine.robot_standing_still_state,
|
|
43
30
|
state_machine.home_state,
|
|
44
31
|
state_machine.offline_state,
|
|
45
32
|
state_machine.unknown_status_state,
|
|
@@ -50,7 +37,6 @@ def get_robot_status_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
50
37
|
{
|
|
51
38
|
"trigger": "robot_status_changed",
|
|
52
39
|
"source": [
|
|
53
|
-
state_machine.robot_standing_still_state,
|
|
54
40
|
state_machine.home_state,
|
|
55
41
|
state_machine.blocked_protective_stopping_state,
|
|
56
42
|
state_machine.unknown_status_state,
|
|
@@ -61,7 +47,6 @@ def get_robot_status_transitions(state_machine: "StateMachine") -> List[dict]:
|
|
|
61
47
|
{
|
|
62
48
|
"trigger": "robot_status_changed",
|
|
63
49
|
"source": [
|
|
64
|
-
state_machine.robot_standing_still_state,
|
|
65
50
|
state_machine.home_state,
|
|
66
51
|
state_machine.blocked_protective_stopping_state,
|
|
67
52
|
state_machine.offline_state,
|
isar/storage/blob_storage.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
21
|
-
"AZURE-STORAGE-CONNECTION-STRING"
|
|
22
|
-
)
|
|
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("
|
|
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
|
-
|
|
45
|
+
container_client = blob_service_client.get_container_client(
|
|
36
46
|
settings.BLOB_CONTAINER
|
|
37
47
|
)
|
|
38
48
|
|
|
39
|
-
if not
|
|
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(
|
|
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
|
-
|
|
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=
|
|
67
|
+
inspection=inspection, mission=mission, filename=data_filename.name
|
|
55
68
|
)
|
|
56
69
|
|
|
57
|
-
self._upload_file(
|
|
58
|
-
|
|
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(
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
)
|
isar/storage/local_storage.py
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
27
|
+
local_filename, local_metadata_filename = construct_paths(
|
|
21
28
|
inspection=inspection, mission=mission
|
|
22
29
|
)
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
data_path: Path = self.root_folder.joinpath(local_filename)
|
|
32
|
+
metadata_path: Path = self.root_folder.joinpath(local_metadata_filename)
|
|
26
33
|
|
|
27
|
-
|
|
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=
|
|
37
|
+
inspection=inspection, mission=mission, filename=local_filename.name
|
|
31
38
|
)
|
|
32
39
|
try:
|
|
33
40
|
with (
|
|
34
|
-
open(
|
|
35
|
-
open(
|
|
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"{
|
|
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
|
|
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
|
|
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) ->
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
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
|
|
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) ->
|
|
137
|
-
|
|
142
|
+
def _upload(self, item: BlobItem) -> StoragePaths:
|
|
143
|
+
inspection_paths: StoragePaths
|
|
138
144
|
try:
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
3
|
+
Version: 1.33.3
|
|
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
|
|
@@ -369,7 +369,7 @@ States.Paused,
|
|
|
369
369
|
indicates that the state machine is already running. For running a mission the state machine need to be in the states
|
|
370
370
|
|
|
371
371
|
```
|
|
372
|
-
States.Home, States.
|
|
372
|
+
States.Home, States.AwaitNextMission or States.ReturningHome
|
|
373
373
|
```
|
|
374
374
|
|
|
375
375
|
### FastAPI
|