isar 1.19.0__py3-none-any.whl → 1.20.1__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/__init__.py +2 -5
- isar/apis/models/start_mission_definition.py +22 -1
- isar/config/log.py +4 -3
- isar/config/settings.py +15 -11
- isar/script.py +154 -0
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +2 -2
- isar/services/service_connections/mqtt/robot_info_publisher.py +2 -2
- isar/state_machine/state_machine.py +13 -9
- isar/state_machine/states/initialize.py +1 -1
- isar/state_machine/states/initiate.py +1 -1
- isar/state_machine/states/monitor.py +5 -1
- isar/state_machine/states/paused.py +1 -1
- isar/state_machine/states/stop.py +1 -1
- isar/storage/local_storage.py +5 -4
- isar/storage/uploader.py +8 -7
- isar/storage/utilities.py +3 -3
- isar-1.20.1.dist-info/METADATA +533 -0
- {isar-1.19.0.dist-info → isar-1.20.1.dist-info}/RECORD +25 -23
- isar-1.20.1.dist-info/entry_points.txt +2 -0
- robot_interface/models/mission/mission.py +3 -0
- robot_interface/models/mission/task.py +11 -2
- robot_interface/telemetry/mqtt_client.py +5 -7
- isar-1.19.0.dist-info/METADATA +0 -433
- {isar-1.19.0.dist-info → isar-1.20.1.dist-info}/LICENSE +0 -0
- {isar-1.19.0.dist-info → isar-1.20.1.dist-info}/WHEEL +0 -0
- {isar-1.19.0.dist-info → isar-1.20.1.dist-info}/top_level.txt +0 -0
isar/__init__.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
from
|
|
1
|
+
from importlib.metadata import version
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
__version__ = get_distribution(__name__).version
|
|
5
|
-
except DistributionNotFound:
|
|
6
|
-
pass # package is not installed
|
|
3
|
+
__version__ = version(__package__ or __name__)
|
|
@@ -2,7 +2,7 @@ import time
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import Any, Dict, List, Optional, Union
|
|
4
4
|
|
|
5
|
-
from alitra import Position
|
|
5
|
+
from alitra import Position, Pose, Orientation, Frame
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
7
7
|
|
|
8
8
|
from isar.apis.models.models import InputPose, InputPosition
|
|
@@ -61,6 +61,7 @@ class StartMissionDefinition(BaseModel):
|
|
|
61
61
|
tasks: List[StartMissionTaskDefinition]
|
|
62
62
|
id: Optional[str] = None
|
|
63
63
|
name: Optional[str] = None
|
|
64
|
+
start_pose: Optional[InputPose] = None
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def to_isar_mission(mission_definition: StartMissionDefinition) -> Mission:
|
|
@@ -92,6 +93,26 @@ def to_isar_mission(mission_definition: StartMissionDefinition) -> Mission:
|
|
|
92
93
|
if mission_definition.id:
|
|
93
94
|
isar_mission.id = mission_definition.id
|
|
94
95
|
|
|
96
|
+
if mission_definition.start_pose:
|
|
97
|
+
input_pose: InputPose = mission_definition.start_pose
|
|
98
|
+
input_frame: Frame = Frame(name=input_pose.frame_name)
|
|
99
|
+
input_position: Position = Position(
|
|
100
|
+
input_pose.position.x,
|
|
101
|
+
input_pose.position.y,
|
|
102
|
+
input_pose.position.z,
|
|
103
|
+
input_frame,
|
|
104
|
+
)
|
|
105
|
+
input_orientation: Orientation = Orientation(
|
|
106
|
+
input_pose.orientation.x,
|
|
107
|
+
input_pose.orientation.y,
|
|
108
|
+
input_pose.orientation.z,
|
|
109
|
+
input_pose.orientation.w,
|
|
110
|
+
input_frame,
|
|
111
|
+
)
|
|
112
|
+
isar_mission.start_pose = Pose(
|
|
113
|
+
position=input_position, orientation=input_orientation, frame=input_frame
|
|
114
|
+
)
|
|
115
|
+
|
|
95
116
|
return isar_mission
|
|
96
117
|
|
|
97
118
|
|
isar/config/log.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import importlib.resources as pkg_resources
|
|
2
1
|
import logging
|
|
3
2
|
import logging.config
|
|
3
|
+
from importlib.resources import as_file, files
|
|
4
4
|
|
|
5
5
|
import yaml
|
|
6
6
|
from opencensus.ext.azure.log_exporter import AzureLogHandler
|
|
@@ -14,8 +14,9 @@ from isar.config.settings import settings
|
|
|
14
14
|
|
|
15
15
|
def setup_loggers(keyvault: Keyvault) -> None:
|
|
16
16
|
log_levels: dict = settings.LOG_LEVELS
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
source = files("isar").joinpath("config").joinpath("logging.conf")
|
|
18
|
+
with as_file(source) as f:
|
|
19
|
+
log_config = yaml.safe_load(open(f))
|
|
19
20
|
|
|
20
21
|
logging.config.dictConfig(log_config)
|
|
21
22
|
|
isar/config/settings.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import importlib.resources as pkg_resources
|
|
2
1
|
import os
|
|
2
|
+
from importlib.resources import as_file, files
|
|
3
3
|
from typing import Any, List, Optional
|
|
4
4
|
|
|
5
5
|
from dotenv import load_dotenv
|
|
@@ -14,11 +14,12 @@ from robot_interface.telemetry.payloads import VideoStream
|
|
|
14
14
|
class Settings(BaseSettings):
|
|
15
15
|
def __init__(self) -> None:
|
|
16
16
|
try:
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
source = files("isar").joinpath("config").joinpath("settings.env")
|
|
18
|
+
with as_file(source) as eml:
|
|
19
|
+
env_file = eml
|
|
19
20
|
except ModuleNotFoundError:
|
|
20
|
-
|
|
21
|
-
super().__init__(_env_file=
|
|
21
|
+
env_file = None
|
|
22
|
+
super().__init__(_env_file=env_file)
|
|
22
23
|
|
|
23
24
|
# Determines which robot package ISAR will attempt to import
|
|
24
25
|
# Name must match with an installed python package in the local environment
|
|
@@ -310,13 +311,16 @@ settings = Settings()
|
|
|
310
311
|
class RobotSettings(BaseSettings):
|
|
311
312
|
def __init__(self) -> None:
|
|
312
313
|
try:
|
|
313
|
-
|
|
314
|
-
f"{settings.ROBOT_PACKAGE}
|
|
315
|
-
|
|
316
|
-
|
|
314
|
+
source = (
|
|
315
|
+
files(f"{settings.ROBOT_PACKAGE}")
|
|
316
|
+
.joinpath("config")
|
|
317
|
+
.joinpath("settings.env")
|
|
318
|
+
)
|
|
319
|
+
with as_file(source) as eml:
|
|
320
|
+
env_file = eml
|
|
317
321
|
except ModuleNotFoundError:
|
|
318
|
-
|
|
319
|
-
super().__init__(_env_file=
|
|
322
|
+
env_file = None
|
|
323
|
+
super().__init__(_env_file=env_file)
|
|
320
324
|
|
|
321
325
|
# ISAR steps the robot is capable of performing
|
|
322
326
|
# This should be set in the robot package settings.env file
|
isar/script.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from logging import Logger
|
|
4
|
+
from threading import Thread
|
|
5
|
+
from typing import Any, List
|
|
6
|
+
|
|
7
|
+
from injector import Injector
|
|
8
|
+
|
|
9
|
+
import isar
|
|
10
|
+
from isar.apis.api import API
|
|
11
|
+
from isar.config.keyvault.keyvault_service import Keyvault
|
|
12
|
+
from isar.config.log import setup_loggers
|
|
13
|
+
from isar.config.settings import settings
|
|
14
|
+
from isar.models.communication.queues.queues import Queues
|
|
15
|
+
from isar.modules import get_injector
|
|
16
|
+
from isar.services.service_connections.mqtt.mqtt_client import MqttClient
|
|
17
|
+
from isar.services.service_connections.mqtt.robot_heartbeat_publisher import (
|
|
18
|
+
RobotHeartbeatPublisher,
|
|
19
|
+
)
|
|
20
|
+
from isar.services.service_connections.mqtt.robot_info_publisher import (
|
|
21
|
+
RobotInfoPublisher,
|
|
22
|
+
)
|
|
23
|
+
from isar.state_machine.state_machine import StateMachine, main
|
|
24
|
+
from isar.storage.uploader import Uploader
|
|
25
|
+
from robot_interface.robot_interface import RobotInterface
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def print_setting(
|
|
29
|
+
setting: str = "", value: Any = "", fillchar: str = " ", width: int = 48
|
|
30
|
+
):
|
|
31
|
+
separator = ": " if value != "" else ""
|
|
32
|
+
text = setting.ljust(22, fillchar) + separator + str(value)
|
|
33
|
+
print("*", text.ljust(width - 4, fillchar), "*")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def print_startup_info():
|
|
37
|
+
print(
|
|
38
|
+
"""
|
|
39
|
+
__ ________ ___ ________
|
|
40
|
+
/ / / ______/ / | / ____ /
|
|
41
|
+
/ / / /_____ / /| | / /___/ /
|
|
42
|
+
/ / /_____ / / __ | / __ __/
|
|
43
|
+
/ / ______/ / / / | | / / | |
|
|
44
|
+
/_/ /_______/ /_/ |_| /_/ |_|
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
WIDTH = 48
|
|
50
|
+
|
|
51
|
+
def print_setting(setting: str = "", value: Any = "", fillchar: str = " "):
|
|
52
|
+
separator = ": " if value != "" else ""
|
|
53
|
+
text = setting.ljust(22, fillchar) + separator + str(value)
|
|
54
|
+
print("*", text.ljust(WIDTH - 4, fillchar), "*")
|
|
55
|
+
|
|
56
|
+
print("Integration and Supervisory control".center(WIDTH, " "))
|
|
57
|
+
print("of Autonomous Robots".center(WIDTH, " "))
|
|
58
|
+
print()
|
|
59
|
+
print(f"Version: {isar.__version__}\n".center(WIDTH, " "))
|
|
60
|
+
|
|
61
|
+
print_setting(fillchar="*")
|
|
62
|
+
print_setting("ISAR settings")
|
|
63
|
+
print_setting(fillchar="-")
|
|
64
|
+
print_setting("Robot package", settings.ROBOT_PACKAGE)
|
|
65
|
+
print_setting("Robot name", settings.ROBOT_NAME)
|
|
66
|
+
print_setting("Run mission stepwise", settings.RUN_MISSION_STEPWISE)
|
|
67
|
+
print_setting("Running on port", settings.API_PORT)
|
|
68
|
+
print_setting("Mission planner", settings.MISSION_PLANNER)
|
|
69
|
+
print_setting("Using local storage", settings.STORAGE_LOCAL_ENABLED)
|
|
70
|
+
print_setting("Using blob storage", settings.STORAGE_BLOB_ENABLED)
|
|
71
|
+
print_setting("Using SLIMM storage", settings.STORAGE_SLIMM_ENABLED)
|
|
72
|
+
print_setting("Plant code", settings.PLANT_CODE)
|
|
73
|
+
print_setting("Plant name", settings.PLANT_NAME)
|
|
74
|
+
print_setting("Plant shortname", settings.PLANT_SHORT_NAME)
|
|
75
|
+
print_setting(fillchar="*")
|
|
76
|
+
print()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def start():
|
|
80
|
+
injector: Injector = get_injector()
|
|
81
|
+
|
|
82
|
+
keyvault_client = injector.get(Keyvault)
|
|
83
|
+
setup_loggers(keyvault=keyvault_client)
|
|
84
|
+
logger: Logger = logging.getLogger("main")
|
|
85
|
+
|
|
86
|
+
print_startup_info()
|
|
87
|
+
|
|
88
|
+
state_machine: StateMachine = injector.get(StateMachine)
|
|
89
|
+
uploader: Uploader = injector.get(Uploader)
|
|
90
|
+
robot: RobotInterface = injector.get(RobotInterface)
|
|
91
|
+
queues: Queues = injector.get(Queues)
|
|
92
|
+
|
|
93
|
+
threads: List[Thread] = []
|
|
94
|
+
|
|
95
|
+
state_machine_thread: Thread = Thread(
|
|
96
|
+
target=main, name="ISAR State Machine", args=[state_machine], daemon=True
|
|
97
|
+
)
|
|
98
|
+
threads.append(state_machine_thread)
|
|
99
|
+
|
|
100
|
+
uploader_thread: Thread = Thread(
|
|
101
|
+
target=uploader.run, name="ISAR Uploader", daemon=True
|
|
102
|
+
)
|
|
103
|
+
threads.append(uploader_thread)
|
|
104
|
+
if settings.MQTT_ENABLED:
|
|
105
|
+
mqtt_client: MqttClient = MqttClient(mqtt_queue=queues.mqtt_queue)
|
|
106
|
+
|
|
107
|
+
mqtt_thread: Thread = Thread(
|
|
108
|
+
target=mqtt_client.run, name="ISAR MQTT Client", daemon=True
|
|
109
|
+
)
|
|
110
|
+
threads.append(mqtt_thread)
|
|
111
|
+
|
|
112
|
+
robot_info_publisher: RobotInfoPublisher = RobotInfoPublisher(
|
|
113
|
+
mqtt_queue=queues.mqtt_queue
|
|
114
|
+
)
|
|
115
|
+
robot_info_thread: Thread = Thread(
|
|
116
|
+
target=robot_info_publisher.run,
|
|
117
|
+
name="ISAR Robot Info Publisher",
|
|
118
|
+
daemon=True,
|
|
119
|
+
)
|
|
120
|
+
threads.append(robot_info_thread)
|
|
121
|
+
|
|
122
|
+
robot_heartbeat_publisher: RobotHeartbeatPublisher = RobotHeartbeatPublisher(
|
|
123
|
+
mqtt_queue=queues.mqtt_queue
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
robot_heartbeat_thread: Thread = Thread(
|
|
127
|
+
target=robot_heartbeat_publisher.run,
|
|
128
|
+
name="ISAR Robot Heartbeat Publisher",
|
|
129
|
+
daemon=True,
|
|
130
|
+
)
|
|
131
|
+
threads.append(robot_heartbeat_thread)
|
|
132
|
+
|
|
133
|
+
publishers: List[Thread] = robot.get_telemetry_publishers(
|
|
134
|
+
queue=queues.mqtt_queue,
|
|
135
|
+
robot_name=settings.ROBOT_NAME,
|
|
136
|
+
isar_id=settings.ISAR_ID,
|
|
137
|
+
)
|
|
138
|
+
if publishers:
|
|
139
|
+
threads.extend(publishers)
|
|
140
|
+
|
|
141
|
+
api: API = injector.get(API)
|
|
142
|
+
api_thread: Thread = Thread(target=api.run_app, name="ISAR API", daemon=True)
|
|
143
|
+
threads.append(api_thread)
|
|
144
|
+
|
|
145
|
+
for thread in threads:
|
|
146
|
+
thread.start()
|
|
147
|
+
logger.info(f"Started thread: {thread.name}")
|
|
148
|
+
|
|
149
|
+
while True:
|
|
150
|
+
for thread in threads:
|
|
151
|
+
if not thread.is_alive():
|
|
152
|
+
logger.critical("Thread '%s' failed - ISAR shutting down", thread.name)
|
|
153
|
+
exit(1)
|
|
154
|
+
time.sleep(state_machine.sleep_time)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import time
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
4
|
from queue import Queue
|
|
5
5
|
|
|
6
6
|
from isar.config.settings import settings
|
|
@@ -18,7 +18,7 @@ class RobotHeartbeatPublisher:
|
|
|
18
18
|
payload: RobotHeartbeatPayload = RobotHeartbeatPayload(
|
|
19
19
|
isar_id=settings.ISAR_ID,
|
|
20
20
|
robot_name=settings.ROBOT_NAME,
|
|
21
|
-
timestamp=datetime.
|
|
21
|
+
timestamp=datetime.now(UTC),
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
self.mqtt_publisher.publish(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import time
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
4
|
from queue import Queue
|
|
5
5
|
|
|
6
6
|
from isar.config.settings import robot_settings, settings
|
|
@@ -25,7 +25,7 @@ class RobotInfoPublisher:
|
|
|
25
25
|
host=settings.API_HOST_VIEWED_EXTERNALLY,
|
|
26
26
|
port=settings.API_PORT,
|
|
27
27
|
capabilities=robot_settings.CAPABILITIES,
|
|
28
|
-
timestamp=datetime.
|
|
28
|
+
timestamp=datetime.now(UTC),
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
self.mqtt_publisher.publish(
|
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import queue
|
|
4
4
|
from collections import deque
|
|
5
|
-
from datetime import datetime
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
6
|
from typing import Deque, List, Optional
|
|
7
7
|
|
|
8
8
|
from alitra import Pose
|
|
@@ -508,7 +508,7 @@ class StateMachine(object):
|
|
|
508
508
|
"error_description": (
|
|
509
509
|
error_message.error_description if error_message else None
|
|
510
510
|
),
|
|
511
|
-
"timestamp": datetime.
|
|
511
|
+
"timestamp": datetime.now(UTC),
|
|
512
512
|
},
|
|
513
513
|
cls=EnhancedJSONEncoder,
|
|
514
514
|
)
|
|
@@ -516,7 +516,8 @@ class StateMachine(object):
|
|
|
516
516
|
self.mqtt_publisher.publish(
|
|
517
517
|
topic=settings.TOPIC_ISAR_MISSION,
|
|
518
518
|
payload=payload,
|
|
519
|
-
|
|
519
|
+
qos=1,
|
|
520
|
+
retain=True,
|
|
520
521
|
)
|
|
521
522
|
|
|
522
523
|
def publish_task_status(self, task: Task) -> None:
|
|
@@ -540,7 +541,7 @@ class StateMachine(object):
|
|
|
540
541
|
"error_description": (
|
|
541
542
|
error_message.error_description if error_message else None
|
|
542
543
|
),
|
|
543
|
-
"timestamp": datetime.
|
|
544
|
+
"timestamp": datetime.now(UTC),
|
|
544
545
|
},
|
|
545
546
|
cls=EnhancedJSONEncoder,
|
|
546
547
|
)
|
|
@@ -548,7 +549,8 @@ class StateMachine(object):
|
|
|
548
549
|
self.mqtt_publisher.publish(
|
|
549
550
|
topic=settings.TOPIC_ISAR_TASK,
|
|
550
551
|
payload=payload,
|
|
551
|
-
|
|
552
|
+
qos=1,
|
|
553
|
+
retain=True,
|
|
552
554
|
)
|
|
553
555
|
|
|
554
556
|
def publish_step_status(self, step: Step) -> None:
|
|
@@ -574,7 +576,7 @@ class StateMachine(object):
|
|
|
574
576
|
"error_description": (
|
|
575
577
|
error_message.error_description if error_message else None
|
|
576
578
|
),
|
|
577
|
-
"timestamp": datetime.
|
|
579
|
+
"timestamp": datetime.now(UTC),
|
|
578
580
|
},
|
|
579
581
|
cls=EnhancedJSONEncoder,
|
|
580
582
|
)
|
|
@@ -582,7 +584,8 @@ class StateMachine(object):
|
|
|
582
584
|
self.mqtt_publisher.publish(
|
|
583
585
|
topic=settings.TOPIC_ISAR_STEP,
|
|
584
586
|
payload=payload,
|
|
585
|
-
|
|
587
|
+
qos=1,
|
|
588
|
+
retain=True,
|
|
586
589
|
)
|
|
587
590
|
|
|
588
591
|
def publish_status(self) -> None:
|
|
@@ -593,7 +596,7 @@ class StateMachine(object):
|
|
|
593
596
|
"isar_id": settings.ISAR_ID,
|
|
594
597
|
"robot_name": settings.ROBOT_NAME,
|
|
595
598
|
"status": self._current_status(),
|
|
596
|
-
"timestamp": datetime.
|
|
599
|
+
"timestamp": datetime.now(UTC),
|
|
597
600
|
},
|
|
598
601
|
cls=EnhancedJSONEncoder,
|
|
599
602
|
)
|
|
@@ -601,7 +604,8 @@ class StateMachine(object):
|
|
|
601
604
|
self.mqtt_publisher.publish(
|
|
602
605
|
topic=settings.TOPIC_ISAR_STATUS,
|
|
603
606
|
payload=payload,
|
|
604
|
-
|
|
607
|
+
qos=1,
|
|
608
|
+
retain=True,
|
|
605
609
|
)
|
|
606
610
|
|
|
607
611
|
def _current_status(self) -> RobotStatus:
|
|
@@ -6,8 +6,8 @@ from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from transitions import State
|
|
8
8
|
|
|
9
|
-
from isar.mission_planner.task_selector_interface import TaskSelectorStop
|
|
10
9
|
from isar.config.settings import settings
|
|
10
|
+
from isar.mission_planner.task_selector_interface import TaskSelectorStop
|
|
11
11
|
from isar.services.utilities.threaded_request import (
|
|
12
12
|
ThreadedRequest,
|
|
13
13
|
ThreadedRequestNotFinishedError,
|
|
@@ -156,6 +156,10 @@ class Monitor(State):
|
|
|
156
156
|
else:
|
|
157
157
|
if isinstance(status, StepStatus):
|
|
158
158
|
if self._step_finished(self.state_machine.current_step):
|
|
159
|
+
self.state_machine.update_current_task()
|
|
160
|
+
if self.state_machine.current_task == None:
|
|
161
|
+
transition = self.state_machine.full_mission_finished # type: ignore
|
|
162
|
+
break
|
|
159
163
|
self.state_machine.update_current_step()
|
|
160
164
|
self.state_machine.current_task.update_task_status()
|
|
161
165
|
else: # If not all steps are done
|
isar/storage/local_storage.py
CHANGED
|
@@ -2,10 +2,10 @@ 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
5
|
from isar.storage.storage_interface import StorageException, StorageInterface
|
|
7
6
|
from isar.storage.utilities import construct_metadata_file, construct_paths
|
|
8
7
|
from robot_interface.models.inspection.inspection import Inspection
|
|
8
|
+
from robot_interface.models.mission.mission import Mission
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class LocalStorage(StorageInterface):
|
|
@@ -27,9 +27,10 @@ class LocalStorage(StorageInterface):
|
|
|
27
27
|
inspection=inspection, mission=mission, filename=local_path.name
|
|
28
28
|
)
|
|
29
29
|
try:
|
|
30
|
-
with
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
with (
|
|
31
|
+
open(absolute_path, "wb") as file,
|
|
32
|
+
open(absolute_metadata_path, "wb") as metadata_file,
|
|
33
|
+
):
|
|
33
34
|
file.write(inspection.data)
|
|
34
35
|
metadata_file.write(metadata_bytes)
|
|
35
36
|
except IOError as e:
|
isar/storage/uploader.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from datetime import datetime, timedelta
|
|
4
|
+
from datetime import UTC, datetime, timedelta
|
|
5
5
|
from queue import Empty, Queue
|
|
6
6
|
from typing import List, Union
|
|
7
7
|
|
|
@@ -22,12 +22,12 @@ class UploaderQueueItem:
|
|
|
22
22
|
mission: Mission
|
|
23
23
|
storage_handler: StorageInterface
|
|
24
24
|
_retry_count: int
|
|
25
|
-
_next_retry_time: datetime = datetime.
|
|
25
|
+
_next_retry_time: datetime = datetime.now(UTC)
|
|
26
26
|
|
|
27
27
|
def increment_retry(self, max_wait_time: int) -> None:
|
|
28
28
|
self._retry_count += 1
|
|
29
29
|
seconds_until_retry: int = min(2**self._retry_count, max_wait_time)
|
|
30
|
-
self._next_retry_time = datetime.
|
|
30
|
+
self._next_retry_time = datetime.now(UTC) + timedelta(
|
|
31
31
|
seconds=seconds_until_retry
|
|
32
32
|
)
|
|
33
33
|
|
|
@@ -35,10 +35,10 @@ class UploaderQueueItem:
|
|
|
35
35
|
return self._retry_count
|
|
36
36
|
|
|
37
37
|
def is_ready_for_upload(self) -> bool:
|
|
38
|
-
return datetime.
|
|
38
|
+
return datetime.now(UTC) >= self._next_retry_time
|
|
39
39
|
|
|
40
40
|
def seconds_until_retry(self) -> int:
|
|
41
|
-
return max(0, int((self._next_retry_time - datetime.
|
|
41
|
+
return max(0, int((self._next_retry_time - datetime.now(UTC)).total_seconds()))
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class Uploader:
|
|
@@ -154,12 +154,13 @@ class Uploader:
|
|
|
154
154
|
"inspection_id": inspection.id,
|
|
155
155
|
"inspection_path": inspection_path,
|
|
156
156
|
"analysis_type": inspection.metadata.analysis_type,
|
|
157
|
-
"timestamp": datetime.
|
|
157
|
+
"timestamp": datetime.now(UTC),
|
|
158
158
|
},
|
|
159
159
|
cls=EnhancedJSONEncoder,
|
|
160
160
|
)
|
|
161
161
|
self.mqtt_publisher.publish(
|
|
162
162
|
topic=settings.TOPIC_ISAR_INSPECTION_RESULT,
|
|
163
163
|
payload=payload,
|
|
164
|
-
|
|
164
|
+
qos=1,
|
|
165
|
+
retain=True,
|
|
165
166
|
)
|
isar/storage/utilities.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import time
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Tuple
|
|
6
6
|
|
|
@@ -37,7 +37,7 @@ def construct_metadata_file(
|
|
|
37
37
|
"mission_id": mission.id,
|
|
38
38
|
"mission_name": mission.name,
|
|
39
39
|
"plant_name": settings.PLANT_NAME,
|
|
40
|
-
"mission_date": datetime.
|
|
40
|
+
"mission_date": datetime.now(UTC).date(),
|
|
41
41
|
"isar_id": settings.ISAR_ID,
|
|
42
42
|
"robot_name": settings.ROBOT_NAME,
|
|
43
43
|
"analysis_type": (
|
|
@@ -80,4 +80,4 @@ def get_filename(
|
|
|
80
80
|
|
|
81
81
|
def get_foldername(mission: Mission) -> str:
|
|
82
82
|
mission_name: str = mission.name.replace(" ", "-")
|
|
83
|
-
return f"{datetime.
|
|
83
|
+
return f"{datetime.now(UTC).date()}__{settings.PLANT_SHORT_NAME}__{mission_name}__{mission.id}"
|