isar 1.10.14__py3-none-any.whl → 1.12.0__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/api.py +54 -4
- isar/apis/models/start_mission_definition.py +44 -35
- isar/apis/security/authentication.py +4 -5
- isar/config/keyvault/keyvault_service.py +38 -21
- isar/config/log.py +42 -13
- isar/config/predefined_mission_definition/__init__.py +0 -0
- isar/config/predefined_mission_definition/default_mission.json +98 -0
- isar/config/predefined_mission_definition/default_turtlebot.json +136 -0
- isar/config/predefined_missions/default.json +84 -84
- isar/config/settings.env +5 -0
- isar/config/settings.py +51 -10
- isar/mission_planner/echo_planner.py +1 -1
- isar/models/communication/queues/queues.py +1 -1
- isar/models/mission/status.py +5 -5
- isar/models/mission_metadata/mission_metadata.py +2 -0
- isar/modules.py +3 -2
- isar/services/service_connections/mqtt/mqtt_client.py +0 -18
- isar/services/service_connections/mqtt/robot_info_publisher.py +33 -0
- isar/services/service_connections/mqtt/robot_status_publisher.py +66 -0
- isar/services/service_connections/request_handler.py +1 -1
- isar/services/utilities/scheduling_utilities.py +5 -5
- isar/services/utilities/threaded_request.py +3 -3
- isar/state_machine/state_machine.py +13 -5
- isar/state_machine/states/idle.py +6 -6
- isar/state_machine/states/initialize.py +9 -8
- isar/state_machine/states/initiate_step.py +16 -16
- isar/state_machine/states/monitor.py +17 -11
- isar/state_machine/states/paused.py +6 -6
- isar/state_machine/states/stop_step.py +10 -10
- isar/state_machine/states_enum.py +0 -1
- isar/storage/local_storage.py +2 -2
- isar/storage/slimm_storage.py +107 -41
- isar/storage/uploader.py +4 -5
- isar/storage/utilities.py +1 -23
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/LICENSE +0 -0
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/METADATA +4 -1
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/RECORD +47 -40
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/WHEEL +0 -0
- {isar-1.10.14.dist-info → isar-1.12.0.dist-info}/top_level.txt +0 -0
- robot_interface/models/inspection/inspection.py +3 -22
- robot_interface/models/mission/status.py +6 -1
- robot_interface/models/mission/step.py +5 -32
- robot_interface/models/robots/__init__.py +0 -0
- robot_interface/models/robots/robot_model.py +13 -0
- robot_interface/robot_interface.py +17 -0
- robot_interface/telemetry/payloads.py +34 -0
- robot_interface/utilities/json_service.py +3 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
@@ -11,19 +11,19 @@ if TYPE_CHECKING:
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Idle(State):
|
|
14
|
-
def __init__(self, state_machine: "StateMachine"):
|
|
14
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
15
15
|
super().__init__(name="idle", on_enter=self.start, on_exit=self.stop)
|
|
16
16
|
self.state_machine: "StateMachine" = state_machine
|
|
17
17
|
self.logger = logging.getLogger("state_machine")
|
|
18
18
|
|
|
19
|
-
def start(self):
|
|
19
|
+
def start(self) -> None:
|
|
20
20
|
self.state_machine.update_state()
|
|
21
21
|
self._run()
|
|
22
22
|
|
|
23
|
-
def stop(self):
|
|
23
|
+
def stop(self) -> None:
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
|
-
def _run(self):
|
|
26
|
+
def _run(self) -> None:
|
|
27
27
|
while True:
|
|
28
28
|
start_mission: Optional[
|
|
29
29
|
StartMissionMessage
|
|
@@ -33,7 +33,7 @@ class Idle(State):
|
|
|
33
33
|
mission=start_mission.mission,
|
|
34
34
|
initial_pose=start_mission.initial_pose,
|
|
35
35
|
)
|
|
36
|
-
transition = self.state_machine.mission_started
|
|
36
|
+
transition = self.state_machine.mission_started # type: ignore
|
|
37
37
|
break
|
|
38
38
|
time.sleep(self.state_machine.sleep_time)
|
|
39
39
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Callable, Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from injector import inject
|
|
6
6
|
from transitions import State
|
|
@@ -17,23 +17,23 @@ if TYPE_CHECKING:
|
|
|
17
17
|
|
|
18
18
|
class Initialize(State):
|
|
19
19
|
@inject
|
|
20
|
-
def __init__(self, state_machine: "StateMachine"):
|
|
20
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
21
21
|
super().__init__(name="initialize", on_enter=self.start, on_exit=self.stop)
|
|
22
22
|
self.state_machine: "StateMachine" = state_machine
|
|
23
23
|
|
|
24
24
|
self.logger = logging.getLogger("state_machine")
|
|
25
25
|
self.initialize_thread: Optional[ThreadedRequest] = None
|
|
26
26
|
|
|
27
|
-
def start(self):
|
|
27
|
+
def start(self) -> None:
|
|
28
28
|
self.state_machine.update_state()
|
|
29
29
|
self._run()
|
|
30
30
|
|
|
31
|
-
def stop(self):
|
|
31
|
+
def stop(self) -> None:
|
|
32
32
|
if self.initialize_thread:
|
|
33
33
|
self.initialize_thread.wait_for_thread()
|
|
34
34
|
self.initialize_thread = None
|
|
35
35
|
|
|
36
|
-
def _run(self):
|
|
36
|
+
def _run(self) -> None:
|
|
37
37
|
transition: Callable
|
|
38
38
|
while True:
|
|
39
39
|
if not self.initialize_thread:
|
|
@@ -41,7 +41,8 @@ class Initialize(State):
|
|
|
41
41
|
self.state_machine.robot.initialize
|
|
42
42
|
)
|
|
43
43
|
self.initialize_thread.start_thread(
|
|
44
|
-
self.state_machine.get_initialize_params()
|
|
44
|
+
self.state_machine.get_initialize_params(),
|
|
45
|
+
name="State Machine Initialize Robot",
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
try:
|
|
@@ -51,9 +52,9 @@ class Initialize(State):
|
|
|
51
52
|
continue
|
|
52
53
|
except RobotException as e:
|
|
53
54
|
self.logger.error(f"Initialization of robot failed. Error: {e}")
|
|
54
|
-
transition = self.state_machine.initialization_failed
|
|
55
|
+
transition = self.state_machine.initialization_failed # type: ignore
|
|
55
56
|
break
|
|
56
57
|
|
|
57
|
-
transition = self.state_machine.initialization_successful
|
|
58
|
+
transition = self.state_machine.initialization_successful # type: ignore
|
|
58
59
|
break
|
|
59
60
|
transition()
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Callable, Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
7
7
|
from isar.config.settings import settings
|
|
8
|
-
from isar.models.mission.status import MissionStatus
|
|
9
8
|
from isar.services.utilities.threaded_request import (
|
|
10
9
|
ThreadedRequest,
|
|
11
10
|
ThreadedRequestNotFinishedError,
|
|
12
11
|
)
|
|
13
|
-
from isar.state_machine.states_enum import States
|
|
14
12
|
from robot_interface.models.exceptions import (
|
|
15
13
|
RobotException,
|
|
16
14
|
RobotInfeasibleStepException,
|
|
17
15
|
)
|
|
18
|
-
from robot_interface.models.mission.status import StepStatus
|
|
19
16
|
|
|
20
17
|
if TYPE_CHECKING:
|
|
21
18
|
from isar.state_machine.state_machine import StateMachine
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
class InitiateStep(State):
|
|
25
|
-
def __init__(self, state_machine: "StateMachine"):
|
|
22
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
26
23
|
super().__init__(name="initiate_step", on_enter=self.start, on_exit=self.stop)
|
|
27
24
|
self.state_machine: "StateMachine" = state_machine
|
|
28
25
|
self.initiate_step_failure_counter: int = 0
|
|
@@ -31,45 +28,48 @@ class InitiateStep(State):
|
|
|
31
28
|
)
|
|
32
29
|
self.logger = logging.getLogger("state_machine")
|
|
33
30
|
|
|
34
|
-
self.initiate_step_thread = None
|
|
31
|
+
self.initiate_step_thread: Optional[ThreadedRequest] = None
|
|
35
32
|
|
|
36
|
-
def start(self):
|
|
33
|
+
def start(self) -> None:
|
|
37
34
|
self.state_machine.update_state()
|
|
38
35
|
self._run()
|
|
39
36
|
|
|
40
|
-
def stop(self):
|
|
37
|
+
def stop(self) -> None:
|
|
41
38
|
self.initiate_step_failure_counter = 0
|
|
42
39
|
if self.initiate_step_thread:
|
|
43
40
|
self.initiate_step_thread.wait_for_thread()
|
|
44
41
|
self.initiate_step_thread = None
|
|
45
42
|
|
|
46
|
-
def _run(self):
|
|
43
|
+
def _run(self) -> None:
|
|
47
44
|
transition: Callable
|
|
48
45
|
while True:
|
|
49
46
|
if self.state_machine.should_stop_mission():
|
|
50
|
-
transition = self.state_machine.stop
|
|
47
|
+
transition = self.state_machine.stop # type: ignore
|
|
51
48
|
break
|
|
52
49
|
|
|
53
50
|
if self.state_machine.should_pause_mission():
|
|
54
|
-
transition = self.state_machine.pause
|
|
51
|
+
transition = self.state_machine.pause # type: ignore
|
|
55
52
|
break
|
|
56
53
|
|
|
57
54
|
if not self.state_machine.current_task:
|
|
58
55
|
self.logger.info(
|
|
59
56
|
f"Completed mission: {self.state_machine.current_mission.id}"
|
|
60
57
|
)
|
|
61
|
-
transition = self.state_machine.mission_finished
|
|
58
|
+
transition = self.state_machine.mission_finished # type: ignore
|
|
62
59
|
break
|
|
63
60
|
|
|
64
61
|
if not self.initiate_step_thread:
|
|
65
62
|
self.initiate_step_thread = ThreadedRequest(
|
|
66
63
|
self.state_machine.robot.initiate_step
|
|
67
64
|
)
|
|
68
|
-
self.initiate_step_thread.start_thread(
|
|
65
|
+
self.initiate_step_thread.start_thread(
|
|
66
|
+
self.state_machine.current_step,
|
|
67
|
+
name="State Machine Initiate Step",
|
|
68
|
+
)
|
|
69
69
|
|
|
70
70
|
try:
|
|
71
71
|
self.initiate_step_thread.get_output()
|
|
72
|
-
transition = self.state_machine.step_initiated
|
|
72
|
+
transition = self.state_machine.step_initiated # type: ignore
|
|
73
73
|
break
|
|
74
74
|
except ThreadedRequestNotFinishedError:
|
|
75
75
|
time.sleep(self.state_machine.sleep_time)
|
|
@@ -80,7 +80,7 @@ class InitiateStep(State):
|
|
|
80
80
|
f"{type(self.state_machine.current_step).__name__}"
|
|
81
81
|
f"Invalid step: {str(self.state_machine.current_step.id)[:8]}"
|
|
82
82
|
)
|
|
83
|
-
transition = self.state_machine.step_infeasible
|
|
83
|
+
transition = self.state_machine.step_infeasible # type: ignore
|
|
84
84
|
break
|
|
85
85
|
except RobotException as e:
|
|
86
86
|
self.initiate_step_thread = None
|
|
@@ -100,7 +100,7 @@ class InitiateStep(State):
|
|
|
100
100
|
f"{self.initiate_step_failure_counter_limit} attempts. "
|
|
101
101
|
f"Cancelling mission."
|
|
102
102
|
)
|
|
103
|
-
transition = self.state_machine.initiate_step_failed
|
|
103
|
+
transition = self.state_machine.initiate_step_failed # type: ignore
|
|
104
104
|
break
|
|
105
105
|
|
|
106
106
|
time.sleep(self.state_machine.sleep_time)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
3
|
from copy import deepcopy
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Callable, Optional, Sequence, TYPE_CHECKING, Tuple
|
|
5
5
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from transitions import State
|
|
@@ -21,38 +21,40 @@ if TYPE_CHECKING:
|
|
|
21
21
|
|
|
22
22
|
class Monitor(State):
|
|
23
23
|
@inject
|
|
24
|
-
def __init__(self, state_machine: "StateMachine"):
|
|
24
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
25
25
|
super().__init__(name="monitor", on_enter=self.start, on_exit=self.stop)
|
|
26
26
|
self.state_machine: "StateMachine" = state_machine
|
|
27
27
|
|
|
28
28
|
self.logger = logging.getLogger("state_machine")
|
|
29
29
|
self.step_status_thread: Optional[ThreadedRequest] = None
|
|
30
30
|
|
|
31
|
-
def start(self):
|
|
31
|
+
def start(self) -> None:
|
|
32
32
|
self.state_machine.update_state()
|
|
33
33
|
self._run()
|
|
34
34
|
|
|
35
|
-
def stop(self):
|
|
35
|
+
def stop(self) -> None:
|
|
36
36
|
if self.step_status_thread:
|
|
37
37
|
self.step_status_thread.wait_for_thread()
|
|
38
38
|
self.step_status_thread = None
|
|
39
39
|
|
|
40
|
-
def _run(self):
|
|
40
|
+
def _run(self) -> None:
|
|
41
41
|
transition: Callable
|
|
42
42
|
while True:
|
|
43
43
|
if self.state_machine.should_stop_mission():
|
|
44
|
-
transition = self.state_machine.stop
|
|
44
|
+
transition = self.state_machine.stop # type: ignore
|
|
45
45
|
break
|
|
46
46
|
|
|
47
47
|
if self.state_machine.should_pause_mission():
|
|
48
|
-
transition = self.state_machine.pause
|
|
48
|
+
transition = self.state_machine.pause # type: ignore
|
|
49
49
|
break
|
|
50
50
|
|
|
51
51
|
if not self.step_status_thread:
|
|
52
52
|
self.step_status_thread = ThreadedRequest(
|
|
53
53
|
self.state_machine.robot.step_status
|
|
54
54
|
)
|
|
55
|
-
self.step_status_thread.start_thread(
|
|
55
|
+
self.step_status_thread.start_thread(
|
|
56
|
+
name="State Machine Monitor Current Step"
|
|
57
|
+
)
|
|
56
58
|
|
|
57
59
|
try:
|
|
58
60
|
step_status: StepStatus = self.step_status_thread.get_output()
|
|
@@ -65,8 +67,12 @@ class Monitor(State):
|
|
|
65
67
|
self.state_machine.current_step.status = step_status
|
|
66
68
|
|
|
67
69
|
if self._step_finished(step=self.state_machine.current_step):
|
|
68
|
-
self._process_finished_step
|
|
69
|
-
|
|
70
|
+
get_inspections_thread = ThreadedRequest(self._process_finished_step)
|
|
71
|
+
get_inspections_thread.start_thread(
|
|
72
|
+
self.state_machine.current_step,
|
|
73
|
+
name="State Machine Get Inspections",
|
|
74
|
+
)
|
|
75
|
+
transition = self.state_machine.step_finished # type: ignore
|
|
70
76
|
break
|
|
71
77
|
|
|
72
78
|
self.step_status_thread = None
|
|
@@ -74,7 +80,7 @@ class Monitor(State):
|
|
|
74
80
|
|
|
75
81
|
transition()
|
|
76
82
|
|
|
77
|
-
def _queue_inspections_for_upload(self, current_step: InspectionStep):
|
|
83
|
+
def _queue_inspections_for_upload(self, current_step: InspectionStep) -> None:
|
|
78
84
|
try:
|
|
79
85
|
inspections: Sequence[
|
|
80
86
|
Inspection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Callable, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
@@ -9,24 +9,24 @@ if TYPE_CHECKING:
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Paused(State):
|
|
12
|
-
def __init__(self, state_machine: "StateMachine"):
|
|
12
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
13
13
|
super().__init__(name="paused", on_enter=self.start)
|
|
14
14
|
self.state_machine: "StateMachine" = state_machine
|
|
15
15
|
self.logger = logging.getLogger("state_machine")
|
|
16
16
|
|
|
17
|
-
def start(self):
|
|
17
|
+
def start(self) -> None:
|
|
18
18
|
self.state_machine.update_state()
|
|
19
19
|
self._run()
|
|
20
20
|
|
|
21
|
-
def _run(self):
|
|
21
|
+
def _run(self) -> None:
|
|
22
22
|
transition: Callable
|
|
23
23
|
while True:
|
|
24
24
|
if self.state_machine.should_stop_mission():
|
|
25
|
-
transition = self.state_machine.mission_stopped
|
|
25
|
+
transition = self.state_machine.mission_stopped # type: ignore
|
|
26
26
|
break
|
|
27
27
|
|
|
28
28
|
if self.state_machine.should_resume_mission():
|
|
29
|
-
transition = self.state_machine.resume
|
|
29
|
+
transition = self.state_machine.resume # type: ignore
|
|
30
30
|
break
|
|
31
31
|
|
|
32
32
|
time.sleep(self.state_machine.sleep_time)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from typing import Callable, TYPE_CHECKING
|
|
3
|
+
from typing import Callable, Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from transitions import State
|
|
6
6
|
|
|
@@ -15,29 +15,29 @@ if TYPE_CHECKING:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class StopStep(State):
|
|
18
|
-
def __init__(self, state_machine: "StateMachine"):
|
|
18
|
+
def __init__(self, state_machine: "StateMachine") -> None:
|
|
19
19
|
super().__init__(name="stop_step", on_enter=self.start, on_exit=self.stop)
|
|
20
20
|
self.state_machine: "StateMachine" = state_machine
|
|
21
21
|
self.logger = logging.getLogger("state_machine")
|
|
22
|
-
self.stop_thread = None
|
|
22
|
+
self.stop_thread: Optional[ThreadedRequest] = None
|
|
23
23
|
self._count_number_retries: int = 0
|
|
24
24
|
|
|
25
|
-
def start(self):
|
|
25
|
+
def start(self) -> None:
|
|
26
26
|
self.state_machine.update_state()
|
|
27
27
|
self._run()
|
|
28
28
|
|
|
29
|
-
def stop(self):
|
|
29
|
+
def stop(self) -> None:
|
|
30
30
|
if self.stop_thread:
|
|
31
31
|
self.stop_thread.wait_for_thread()
|
|
32
32
|
self.stop_thread = None
|
|
33
33
|
self._count_number_retries = 0
|
|
34
34
|
|
|
35
|
-
def _run(self):
|
|
35
|
+
def _run(self) -> None:
|
|
36
36
|
transition: Callable
|
|
37
37
|
while True:
|
|
38
38
|
if not self.stop_thread:
|
|
39
39
|
self.stop_thread = ThreadedRequest(self.state_machine.robot.stop)
|
|
40
|
-
self.stop_thread.start_thread()
|
|
40
|
+
self.stop_thread.start_thread(name="State Machine Stop Robot")
|
|
41
41
|
|
|
42
42
|
if self.state_machine.should_stop_mission():
|
|
43
43
|
self.state_machine.stopped = True
|
|
@@ -52,16 +52,16 @@ class StopStep(State):
|
|
|
52
52
|
if self.handle_stop_fail(
|
|
53
53
|
retry_limit=self.state_machine.stop_robot_attempts_limit
|
|
54
54
|
):
|
|
55
|
-
transition = self.state_machine.mission_stopped
|
|
55
|
+
transition = self.state_machine.mission_stopped # type: ignore
|
|
56
56
|
break
|
|
57
57
|
|
|
58
58
|
self.logger.warning("Failed to stop robot. Retrying.")
|
|
59
59
|
self.stop_thread = None
|
|
60
60
|
continue
|
|
61
61
|
if self.state_machine.stopped:
|
|
62
|
-
transition = self.state_machine.mission_stopped
|
|
62
|
+
transition = self.state_machine.mission_stopped # type: ignore
|
|
63
63
|
else:
|
|
64
|
-
transition = self.state_machine.mission_paused
|
|
64
|
+
transition = self.state_machine.mission_paused # type: ignore
|
|
65
65
|
break
|
|
66
66
|
|
|
67
67
|
transition()
|
isar/storage/local_storage.py
CHANGED
|
@@ -9,11 +9,11 @@ from robot_interface.models.inspection.inspection import Inspection
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class LocalStorage(StorageInterface):
|
|
12
|
-
def __init__(self):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
13
|
self.root_folder: Path = Path(settings.LOCAL_STORAGE_PATH)
|
|
14
14
|
self.logger = logging.getLogger("uploader")
|
|
15
15
|
|
|
16
|
-
def store(self, inspection: Inspection, metadata: MissionMetadata):
|
|
16
|
+
def store(self, inspection: Inspection, metadata: MissionMetadata) -> None:
|
|
17
17
|
local_path, local_metadata_path = construct_local_paths(
|
|
18
18
|
inspection=inspection, metadata=metadata
|
|
19
19
|
)
|
isar/storage/slimm_storage.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
|
|
3
4
|
from azure.identity import DefaultAzureCredential
|
|
@@ -10,8 +11,8 @@ from isar.models.mission_metadata.mission_metadata import MissionMetadata
|
|
|
10
11
|
from isar.services.auth.azure_credentials import AzureCredentials
|
|
11
12
|
from isar.services.service_connections.request_handler import RequestHandler
|
|
12
13
|
from isar.storage.storage_interface import StorageException, StorageInterface
|
|
13
|
-
from isar.storage.utilities import get_filename
|
|
14
|
-
from robot_interface.models.inspection.inspection import Inspection
|
|
14
|
+
from isar.storage.utilities import get_filename
|
|
15
|
+
from robot_interface.models.inspection.inspection import Inspection, ThermalVideo, Video
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class SlimmStorage(StorageInterface):
|
|
@@ -31,40 +32,60 @@ class SlimmStorage(StorageInterface):
|
|
|
31
32
|
self.url: str = settings.SLIMM_API_URL
|
|
32
33
|
|
|
33
34
|
def store(self, inspection: Inspection, metadata: MissionMetadata):
|
|
34
|
-
token: str = self.credentials.get_token(self.request_scope).token
|
|
35
|
-
|
|
36
|
-
request_url: str = f"{self.url}/UploadSingleFile"
|
|
37
|
-
|
|
38
|
-
inspection_type: str = get_inspection_type(inspection=inspection)
|
|
39
35
|
filename: str = get_filename(
|
|
40
36
|
mission_id=metadata.mission_id,
|
|
41
|
-
inspection_type=
|
|
37
|
+
inspection_type=type(inspection).__name__,
|
|
42
38
|
inspection_id=inspection.id,
|
|
43
39
|
)
|
|
44
|
-
|
|
40
|
+
filename = f"{filename}.{inspection.metadata.file_type}"
|
|
41
|
+
if type(inspection) in [Video, ThermalVideo]:
|
|
42
|
+
self._store_video(filename, inspection, metadata)
|
|
43
|
+
else:
|
|
44
|
+
self._store_image(filename, inspection, metadata)
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
def _store_image(
|
|
47
|
+
self, filename: str, inspection: Inspection, metadata: MissionMetadata
|
|
48
|
+
):
|
|
49
|
+
multiform_body: MultipartEncoder = self._construct_multiform_request_image(
|
|
50
|
+
filename=filename, inspection=inspection, metadata=metadata
|
|
51
|
+
)
|
|
52
|
+
request_url: str = f"{self.url}/UploadSingleImage"
|
|
53
|
+
self._ingest(
|
|
54
|
+
inspection=inspection,
|
|
55
|
+
multiform_body=multiform_body,
|
|
56
|
+
request_url=request_url,
|
|
48
57
|
)
|
|
58
|
+
return
|
|
49
59
|
|
|
60
|
+
def _store_video(
|
|
61
|
+
self, filename: str, inspection: Inspection, metadata: MissionMetadata
|
|
62
|
+
):
|
|
63
|
+
multiform_body: MultipartEncoder = self._construct_multiform_request_video(
|
|
64
|
+
filename=filename, inspection=inspection, metadata=metadata
|
|
65
|
+
)
|
|
66
|
+
request_url = f"{self.url}/UploadSingleVideo"
|
|
50
67
|
self._ingest(
|
|
51
68
|
inspection=inspection,
|
|
52
69
|
multiform_body=multiform_body,
|
|
53
70
|
request_url=request_url,
|
|
54
|
-
token=token,
|
|
55
71
|
)
|
|
72
|
+
return
|
|
56
73
|
|
|
57
|
-
def _ingest(
|
|
74
|
+
def _ingest(
|
|
75
|
+
self, inspection: Inspection, multiform_body: MultipartEncoder, request_url: str
|
|
76
|
+
):
|
|
77
|
+
token: str = self.credentials.get_token(self.request_scope).token
|
|
58
78
|
try:
|
|
59
|
-
self.request_handler.post(
|
|
79
|
+
response = self.request_handler.post(
|
|
60
80
|
url=request_url,
|
|
61
|
-
params={"DataType": "still"},
|
|
62
81
|
data=multiform_body,
|
|
63
82
|
headers={
|
|
64
83
|
"Authorization": f"Bearer {token}",
|
|
65
84
|
"Content-Type": multiform_body.content_type,
|
|
66
85
|
},
|
|
67
86
|
)
|
|
87
|
+
guid = json.loads(response.text)["guid"]
|
|
88
|
+
self.logger.info(f"SLIMM upload GUID: {guid}")
|
|
68
89
|
except (RequestException, HTTPError) as e:
|
|
69
90
|
self.logger.warning(
|
|
70
91
|
f"Failed to upload inspection: {inspection.id} to SLIMM due to a "
|
|
@@ -73,48 +94,93 @@ class SlimmStorage(StorageInterface):
|
|
|
73
94
|
raise StorageException from e
|
|
74
95
|
|
|
75
96
|
@staticmethod
|
|
76
|
-
def
|
|
97
|
+
def _construct_multiform_request_image(
|
|
98
|
+
filename: str, inspection: Inspection, metadata: MissionMetadata
|
|
99
|
+
):
|
|
77
100
|
array_of_orientation = (
|
|
78
101
|
inspection.metadata.time_indexed_pose.pose.orientation.to_quat_array().tolist()
|
|
79
102
|
)
|
|
80
103
|
multiform_body: MultipartEncoder = MultipartEncoder(
|
|
81
104
|
fields={
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
105
|
+
"PlantFacilitySAPCode": metadata.plant_code,
|
|
106
|
+
"InstCode": metadata.plant_short_name,
|
|
107
|
+
"InternalClassification": metadata.data_classification,
|
|
108
|
+
"IsoCountryCode": "NO",
|
|
109
|
+
"Geodetic.CoordinateReferenceSystemCode": metadata.coordinate_reference_system, # noqa: E501
|
|
110
|
+
"Geodetic.VerticalCoordinateReferenceSystemCode": metadata.vertical_reference_system, # noqa: E501
|
|
111
|
+
"Geodetic.OrientationReferenceSystem": metadata.media_orientation_reference_system, # noqa: E501
|
|
112
|
+
"SensorCarrier.SensorCarrierId": metadata.robot_id,
|
|
113
|
+
"SensorCarrier.ModelName": metadata.robot_model,
|
|
114
|
+
"Mission.MissionId": metadata.mission_id,
|
|
115
|
+
"Mission.Client": "Equinor",
|
|
116
|
+
"ImageMetadata.Timestamp": inspection.metadata.start_time.isoformat(), # noqa: E501
|
|
117
|
+
"ImageMetadata.X": str(
|
|
94
118
|
inspection.metadata.time_indexed_pose.pose.position.x
|
|
95
119
|
),
|
|
96
|
-
"
|
|
120
|
+
"ImageMetadata.Y": str(
|
|
97
121
|
inspection.metadata.time_indexed_pose.pose.position.y
|
|
98
122
|
),
|
|
99
|
-
"
|
|
123
|
+
"ImageMetadata.Y": str(
|
|
100
124
|
inspection.metadata.time_indexed_pose.pose.position.z
|
|
101
125
|
),
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
),
|
|
105
|
-
"
|
|
106
|
-
|
|
126
|
+
"ImageMetadata.CameraOrientation1": str(array_of_orientation[0]),
|
|
127
|
+
"ImageMetadata.CameraOrientation2": str(array_of_orientation[1]),
|
|
128
|
+
"ImageMetadata.CameraOrientation3": str(array_of_orientation[2]),
|
|
129
|
+
"ImageMetadata.CameraOrientation4": str(array_of_orientation[3]),
|
|
130
|
+
"ImageMetadata.AnalysisMethods": str(inspection.metadata.analysis),
|
|
131
|
+
"ImageMetadata.Description": str(inspection.metadata.additional),
|
|
132
|
+
"ImageMetadata.FunctionalLocation": inspection.metadata.tag_id # noqa: E501
|
|
133
|
+
if inspection.metadata.tag_id
|
|
134
|
+
else "NA",
|
|
135
|
+
"Filename": filename,
|
|
136
|
+
"AttachedFile": (filename, inspection.data),
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
return multiform_body
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _construct_multiform_request_video(
|
|
143
|
+
filename: str,
|
|
144
|
+
inspection: Inspection,
|
|
145
|
+
metadata: MissionMetadata,
|
|
146
|
+
):
|
|
147
|
+
array_of_orientation = (
|
|
148
|
+
inspection.metadata.time_indexed_pose.pose.orientation.to_quat_array().tolist()
|
|
149
|
+
)
|
|
150
|
+
multiform_body: MultipartEncoder = MultipartEncoder(
|
|
151
|
+
fields={
|
|
152
|
+
"PlantFacilitySAPCode": metadata.plant_code,
|
|
153
|
+
"InstCode": metadata.plant_short_name,
|
|
154
|
+
"InternalClassification": metadata.data_classification,
|
|
155
|
+
"IsoCountryCode": "NO",
|
|
156
|
+
"Geodetic.CoordinateReferenceSystemCode": metadata.coordinate_reference_system, # noqa: E501
|
|
157
|
+
"Geodetic.VerticalCoordinateReferenceSystemCode": metadata.vertical_reference_system, # noqa: E501
|
|
158
|
+
"Geodetic.OrientationReferenceSystem": metadata.media_orientation_reference_system, # noqa: E501
|
|
159
|
+
"SensorCarrier.SensorCarrierId": metadata.robot_id,
|
|
160
|
+
"SensorCarrier.ModelName": metadata.robot_model,
|
|
161
|
+
"Mission.MissionId": metadata.mission_id,
|
|
162
|
+
"Mission.Client": "Equinor",
|
|
163
|
+
"VideoMetadata.Timestamp": inspection.metadata.start_time.isoformat(), # noqa: E501
|
|
164
|
+
"VideoMetadata.Duration": str(inspection.metadata.duration), # type: ignore
|
|
165
|
+
"VideoMetadata.X": str(
|
|
166
|
+
inspection.metadata.time_indexed_pose.pose.position.x
|
|
107
167
|
),
|
|
108
|
-
"
|
|
109
|
-
|
|
168
|
+
"VideoMetadata.Y": str(
|
|
169
|
+
inspection.metadata.time_indexed_pose.pose.position.y
|
|
110
170
|
),
|
|
111
|
-
"
|
|
112
|
-
|
|
171
|
+
"VideoMetadata.Y": str(
|
|
172
|
+
inspection.metadata.time_indexed_pose.pose.position.z
|
|
113
173
|
),
|
|
114
|
-
"
|
|
174
|
+
"VideoMetadata.CameraOrientation1": str(array_of_orientation[0]),
|
|
175
|
+
"VideoMetadata.CameraOrientation2": str(array_of_orientation[1]),
|
|
176
|
+
"VideoMetadata.CameraOrientation3": str(array_of_orientation[2]),
|
|
177
|
+
"VideoMetadata.CameraOrientation4": str(array_of_orientation[3]),
|
|
178
|
+
"VideoMetadata.AnalysisMethods": str(inspection.metadata.analysis),
|
|
179
|
+
"VideoMetadata.Description": str(inspection.metadata.additional),
|
|
180
|
+
"VideoMetadata.FunctionalLocation": inspection.metadata.tag_id # noqa: E501
|
|
115
181
|
if inspection.metadata.tag_id
|
|
116
182
|
else "NA",
|
|
117
|
-
"
|
|
183
|
+
"Filename": filename,
|
|
118
184
|
"AttachedFile": (filename, inspection.data),
|
|
119
185
|
}
|
|
120
186
|
)
|
isar/storage/uploader.py
CHANGED
|
@@ -4,11 +4,10 @@ from datetime import datetime, timedelta
|
|
|
4
4
|
from queue import Empty, Queue
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
|
-
from robot_interface.models.inspection.inspection import Inspection
|
|
8
|
-
|
|
9
7
|
from isar.config.settings import settings
|
|
10
8
|
from isar.models.mission_metadata.mission_metadata import MissionMetadata
|
|
11
9
|
from isar.storage.storage_interface import StorageException, StorageInterface
|
|
10
|
+
from robot_interface.models.inspection.inspection import Inspection
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@dataclass
|
|
@@ -19,7 +18,7 @@ class UploaderQueueItem:
|
|
|
19
18
|
_retry_count: int
|
|
20
19
|
_next_retry_time: datetime = datetime.utcnow()
|
|
21
20
|
|
|
22
|
-
def increment_retry(self, max_wait_time: int):
|
|
21
|
+
def increment_retry(self, max_wait_time: int) -> None:
|
|
23
22
|
self._retry_count += 1
|
|
24
23
|
seconds_until_retry: int = min(2**self._retry_count, max_wait_time)
|
|
25
24
|
self._next_retry_time = datetime.utcnow() + timedelta(
|
|
@@ -85,7 +84,7 @@ class Uploader:
|
|
|
85
84
|
except Empty:
|
|
86
85
|
continue
|
|
87
86
|
|
|
88
|
-
def _upload(self, upload_item: UploaderQueueItem):
|
|
87
|
+
def _upload(self, upload_item: UploaderQueueItem) -> None:
|
|
89
88
|
try:
|
|
90
89
|
upload_item.storage_handler.store(
|
|
91
90
|
inspection=upload_item.inspection, metadata=upload_item.mission_metadata
|
|
@@ -112,7 +111,7 @@ class Uploader:
|
|
|
112
111
|
f"{str(upload_item.inspection.id)[:8]}. Aborting upload."
|
|
113
112
|
)
|
|
114
113
|
|
|
115
|
-
def _process_upload_queue(self):
|
|
114
|
+
def _process_upload_queue(self) -> None:
|
|
116
115
|
ready_items: List[UploaderQueueItem] = [
|
|
117
116
|
x for x in self._internal_upload_queue if x.is_ready_for_upload()
|
|
118
117
|
]
|