isar 1.20.2__py3-none-any.whl → 1.34.13__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.
- isar/apis/api.py +135 -86
- isar/apis/models/__init__.py +0 -1
- isar/apis/models/models.py +21 -11
- isar/apis/models/start_mission_definition.py +115 -170
- isar/apis/robot_control/robot_controller.py +41 -0
- isar/apis/schedule/scheduling_controller.py +123 -187
- isar/apis/security/authentication.py +5 -5
- isar/config/certs/ca-cert.pem +33 -31
- isar/config/keyvault/keyvault_service.py +4 -2
- isar/config/log.py +45 -40
- isar/config/logging.conf +16 -31
- isar/config/open_telemetry.py +102 -0
- isar/config/settings.py +74 -117
- isar/eventhandlers/eventhandler.py +123 -0
- isar/models/events.py +184 -0
- isar/models/status.py +22 -0
- isar/modules.py +117 -200
- isar/robot/robot.py +383 -0
- isar/robot/robot_battery.py +60 -0
- isar/robot/robot_monitor_mission.py +357 -0
- isar/robot/robot_pause_mission.py +74 -0
- isar/robot/robot_resume_mission.py +67 -0
- isar/robot/robot_start_mission.py +66 -0
- isar/robot/robot_status.py +61 -0
- isar/robot/robot_stop_mission.py +68 -0
- isar/robot/robot_upload_inspection.py +75 -0
- isar/script.py +58 -41
- isar/services/service_connections/mqtt/mqtt_client.py +47 -11
- isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
- isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
- isar/services/service_connections/persistent_memory.py +69 -0
- isar/services/utilities/mqtt_utilities.py +93 -0
- isar/services/utilities/robot_utilities.py +20 -0
- isar/services/utilities/scheduling_utilities.py +386 -100
- isar/state_machine/state_machine.py +242 -539
- isar/state_machine/states/__init__.py +0 -8
- isar/state_machine/states/await_next_mission.py +114 -0
- isar/state_machine/states/blocked_protective_stop.py +60 -0
- isar/state_machine/states/going_to_lockdown.py +95 -0
- isar/state_machine/states/going_to_recharging.py +92 -0
- isar/state_machine/states/home.py +115 -0
- isar/state_machine/states/intervention_needed.py +77 -0
- isar/state_machine/states/lockdown.py +38 -0
- isar/state_machine/states/maintenance.py +43 -0
- isar/state_machine/states/monitor.py +137 -247
- isar/state_machine/states/offline.py +51 -53
- isar/state_machine/states/paused.py +92 -23
- isar/state_machine/states/pausing.py +48 -0
- isar/state_machine/states/pausing_return_home.py +48 -0
- isar/state_machine/states/recharging.py +80 -0
- isar/state_machine/states/resuming.py +57 -0
- isar/state_machine/states/resuming_return_home.py +64 -0
- isar/state_machine/states/return_home_paused.py +109 -0
- isar/state_machine/states/returning_home.py +217 -0
- isar/state_machine/states/stopping.py +69 -0
- isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
- isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
- isar/state_machine/states/stopping_go_to_recharge.py +51 -0
- isar/state_machine/states/stopping_paused_mission.py +36 -0
- isar/state_machine/states/stopping_paused_return_home.py +59 -0
- isar/state_machine/states/stopping_return_home.py +59 -0
- isar/state_machine/states/unknown_status.py +74 -0
- isar/state_machine/states_enum.py +23 -5
- isar/state_machine/transitions/mission.py +225 -0
- isar/state_machine/transitions/return_home.py +108 -0
- isar/state_machine/transitions/robot_status.py +87 -0
- isar/state_machine/utils/common_event_handlers.py +138 -0
- isar/storage/blob_storage.py +70 -52
- isar/storage/local_storage.py +25 -12
- isar/storage/storage_interface.py +28 -7
- isar/storage/uploader.py +174 -55
- isar/storage/utilities.py +32 -29
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/METADATA +119 -123
- isar-1.34.13.dist-info/RECORD +120 -0
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/WHEEL +1 -1
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/entry_points.txt +1 -0
- robot_interface/models/exceptions/robot_exceptions.py +91 -41
- robot_interface/models/inspection/__init__.py +0 -13
- robot_interface/models/inspection/inspection.py +42 -33
- robot_interface/models/mission/mission.py +14 -15
- robot_interface/models/mission/status.py +20 -26
- robot_interface/models/mission/task.py +154 -121
- robot_interface/models/robots/battery_state.py +6 -0
- robot_interface/models/robots/media.py +13 -0
- robot_interface/models/robots/robot_model.py +7 -7
- robot_interface/robot_interface.py +119 -84
- robot_interface/telemetry/mqtt_client.py +74 -12
- robot_interface/telemetry/payloads.py +91 -13
- robot_interface/utilities/json_service.py +7 -1
- isar/config/configuration_error.py +0 -2
- isar/config/keyvault/keyvault_error.py +0 -2
- isar/config/predefined_mission_definition/__init__.py +0 -0
- isar/config/predefined_mission_definition/default_exr.json +0 -51
- isar/config/predefined_mission_definition/default_mission.json +0 -91
- isar/config/predefined_mission_definition/default_turtlebot.json +0 -124
- isar/config/predefined_missions/__init__.py +0 -0
- isar/config/predefined_missions/default.json +0 -92
- isar/config/predefined_missions/default_turtlebot.json +0 -110
- isar/config/predefined_poses/__init__.py +0 -0
- isar/config/predefined_poses/predefined_poses.py +0 -616
- isar/config/settings.env +0 -25
- isar/mission_planner/__init__.py +0 -0
- isar/mission_planner/local_planner.py +0 -82
- isar/mission_planner/mission_planner_interface.py +0 -26
- isar/mission_planner/sequential_task_selector.py +0 -23
- isar/mission_planner/task_selector_interface.py +0 -31
- isar/models/communication/__init__.py +0 -0
- isar/models/communication/message.py +0 -12
- isar/models/communication/queues/__init__.py +0 -4
- isar/models/communication/queues/queue_io.py +0 -12
- isar/models/communication/queues/queue_timeout_error.py +0 -2
- isar/models/communication/queues/queues.py +0 -19
- isar/models/communication/queues/status_queue.py +0 -20
- isar/models/mission_metadata/__init__.py +0 -0
- isar/services/auth/__init__.py +0 -0
- isar/services/auth/azure_credentials.py +0 -14
- isar/services/readers/__init__.py +0 -0
- isar/services/readers/base_reader.py +0 -37
- isar/services/service_connections/request_handler.py +0 -153
- isar/services/service_connections/stid/__init__.py +0 -0
- isar/services/utilities/queue_utilities.py +0 -39
- isar/services/utilities/threaded_request.py +0 -68
- isar/state_machine/states/idle.py +0 -85
- isar/state_machine/states/initialize.py +0 -71
- isar/state_machine/states/initiate.py +0 -142
- isar/state_machine/states/off.py +0 -18
- isar/state_machine/states/stop.py +0 -95
- isar/storage/slimm_storage.py +0 -191
- isar-1.20.2.dist-info/RECORD +0 -116
- robot_interface/models/initialize/__init__.py +0 -1
- robot_interface/models/initialize/initialize_params.py +0 -9
- robot_interface/models/mission/step.py +0 -234
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info/licenses}/LICENSE +0 -0
- {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from threading import Event as ThreadEvent
|
|
6
|
+
from typing import TYPE_CHECKING, Callable, Generic, List, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
from transitions import State
|
|
9
|
+
|
|
10
|
+
from isar.config.settings import settings
|
|
11
|
+
from isar.models.events import Event
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class EventHandlerMapping(Generic[T]):
|
|
18
|
+
name: str
|
|
19
|
+
event: Event[T]
|
|
20
|
+
handler: Callable[[Event[T]], Optional[Callable]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class TimeoutHandlerMapping:
|
|
25
|
+
name: str
|
|
26
|
+
timeout_in_seconds: float
|
|
27
|
+
handler: Callable[[], Optional[Callable]]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from isar.state_machine.state_machine import StateMachine
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EventHandlerBase(State):
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
state_machine: "StateMachine",
|
|
38
|
+
state_name: str,
|
|
39
|
+
event_handler_mappings: List[EventHandlerMapping],
|
|
40
|
+
timers: List[TimeoutHandlerMapping] = [],
|
|
41
|
+
on_entry: Optional[Callable[[], None]] = None,
|
|
42
|
+
on_transition: Optional[Callable[[], None]] = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
|
|
45
|
+
super().__init__(name=state_name, on_enter=self.start)
|
|
46
|
+
self.state_machine: "StateMachine" = state_machine
|
|
47
|
+
self.logger = logging.getLogger("state_machine")
|
|
48
|
+
self.events = state_machine.events
|
|
49
|
+
self.signal_state_machine_to_stop: ThreadEvent = (
|
|
50
|
+
state_machine.signal_state_machine_to_stop
|
|
51
|
+
)
|
|
52
|
+
self.event_handler_mappings = event_handler_mappings
|
|
53
|
+
self.state_name: str = state_name
|
|
54
|
+
self.timers = timers
|
|
55
|
+
self.on_entry = on_entry
|
|
56
|
+
self.on_transition = on_transition
|
|
57
|
+
|
|
58
|
+
def start(self) -> None:
|
|
59
|
+
self.state_machine.update_state()
|
|
60
|
+
if self.on_entry:
|
|
61
|
+
self.on_entry()
|
|
62
|
+
self._run()
|
|
63
|
+
|
|
64
|
+
def stop(self) -> None:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
def get_event_handler_by_name(
|
|
68
|
+
self, event_handler_name: str
|
|
69
|
+
) -> Optional[EventHandlerMapping]:
|
|
70
|
+
filtered_handlers = list(
|
|
71
|
+
filter(
|
|
72
|
+
lambda mapping: mapping.name == event_handler_name,
|
|
73
|
+
self.event_handler_mappings,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
return filtered_handlers[0] if len(filtered_handlers) > 0 else None
|
|
77
|
+
|
|
78
|
+
def get_event_timer_by_name(
|
|
79
|
+
self, event_timer_name: str
|
|
80
|
+
) -> Optional[TimeoutHandlerMapping]:
|
|
81
|
+
filtered_timers = list(
|
|
82
|
+
filter(
|
|
83
|
+
lambda mapping: mapping.name == event_timer_name,
|
|
84
|
+
self.timers,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
return filtered_timers[0] if len(filtered_timers) > 0 else None
|
|
88
|
+
|
|
89
|
+
def _run(self) -> None:
|
|
90
|
+
should_exit_state: bool = False
|
|
91
|
+
timers = deepcopy(self.timers)
|
|
92
|
+
entered_time = time.time()
|
|
93
|
+
while True:
|
|
94
|
+
if self.signal_state_machine_to_stop.is_set():
|
|
95
|
+
self.logger.info(
|
|
96
|
+
"Stopping state machine from %s state", self.state_name
|
|
97
|
+
)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
for timer in timers:
|
|
101
|
+
if time.time() - entered_time > timer.timeout_in_seconds:
|
|
102
|
+
transition_func = timer.handler()
|
|
103
|
+
timers.remove(timer)
|
|
104
|
+
if transition_func is not None:
|
|
105
|
+
transition_func()
|
|
106
|
+
should_exit_state = True
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if should_exit_state:
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
for handler_mapping in self.event_handler_mappings:
|
|
113
|
+
transition_func = handler_mapping.handler(handler_mapping.event)
|
|
114
|
+
if transition_func is not None:
|
|
115
|
+
transition_func()
|
|
116
|
+
should_exit_state = True
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
if should_exit_state:
|
|
120
|
+
break
|
|
121
|
+
time.sleep(settings.FSM_SLEEP_TIME)
|
|
122
|
+
if self.on_transition:
|
|
123
|
+
self.on_transition()
|
isar/models/events.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from queue import Empty, Queue
|
|
3
|
+
from typing import Generic, Optional, Tuple, TypeVar
|
|
4
|
+
|
|
5
|
+
from transitions import State
|
|
6
|
+
|
|
7
|
+
from isar.apis.models.models import (
|
|
8
|
+
ControlMissionResponse,
|
|
9
|
+
LockdownResponse,
|
|
10
|
+
MaintenanceResponse,
|
|
11
|
+
MissionStartResponse,
|
|
12
|
+
)
|
|
13
|
+
from isar.config.settings import settings
|
|
14
|
+
from robot_interface.models.exceptions.robot_exceptions import ErrorMessage
|
|
15
|
+
from robot_interface.models.mission.mission import Mission
|
|
16
|
+
from robot_interface.models.mission.status import MissionStatus, RobotStatus
|
|
17
|
+
from robot_interface.models.mission.task import TASKS
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
T1 = TypeVar("T1")
|
|
21
|
+
T2 = TypeVar("T2")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Event(Queue[T]):
|
|
25
|
+
def __init__(self, name: str) -> None:
|
|
26
|
+
super().__init__(maxsize=1)
|
|
27
|
+
self.name = name
|
|
28
|
+
|
|
29
|
+
def trigger_event(self, data: T, timeout: int = None) -> None:
|
|
30
|
+
try:
|
|
31
|
+
# We always want a timeout when blocking for results, so that
|
|
32
|
+
# the thread will never get stuck waiting for a result
|
|
33
|
+
self.put(data, block=timeout is not None, timeout=timeout)
|
|
34
|
+
except Exception:
|
|
35
|
+
if timeout is not None:
|
|
36
|
+
raise EventTimeoutError
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def consume_event(self, timeout: int = None) -> Optional[T]:
|
|
40
|
+
try:
|
|
41
|
+
return self.get(block=timeout is not None, timeout=timeout)
|
|
42
|
+
except Empty:
|
|
43
|
+
if timeout is not None:
|
|
44
|
+
raise EventTimeoutError
|
|
45
|
+
return None
|
|
46
|
+
except ValueError:
|
|
47
|
+
raise EventConflictError
|
|
48
|
+
|
|
49
|
+
def clear_event(self) -> None:
|
|
50
|
+
while True:
|
|
51
|
+
try:
|
|
52
|
+
self.get(block=False)
|
|
53
|
+
except Empty:
|
|
54
|
+
break
|
|
55
|
+
except ValueError:
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
def has_event(self) -> bool:
|
|
59
|
+
return (
|
|
60
|
+
self.qsize() != 0
|
|
61
|
+
) # Queue size is not reliable, but should be sufficient for this case
|
|
62
|
+
|
|
63
|
+
def check(self) -> Optional[T]:
|
|
64
|
+
if not self._qsize():
|
|
65
|
+
return None
|
|
66
|
+
with self.mutex:
|
|
67
|
+
queueList = list(self.queue)
|
|
68
|
+
return queueList.pop()
|
|
69
|
+
|
|
70
|
+
def update(self, item: T):
|
|
71
|
+
with self.mutex:
|
|
72
|
+
self.queue: deque[T] = deque()
|
|
73
|
+
self.queue.append(item)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Events:
|
|
77
|
+
def __init__(self) -> None:
|
|
78
|
+
self.api_requests: APIRequests = APIRequests()
|
|
79
|
+
self.state_machine_events: StateMachineEvents = StateMachineEvents()
|
|
80
|
+
self.robot_service_events: RobotServiceEvents = RobotServiceEvents()
|
|
81
|
+
|
|
82
|
+
self.upload_queue: Queue = Queue(maxsize=10)
|
|
83
|
+
|
|
84
|
+
if settings.MQTT_ENABLED:
|
|
85
|
+
self.mqtt_queue: Queue = Queue()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class APIEvent(Generic[T1, T2]):
|
|
89
|
+
"""
|
|
90
|
+
Creates request and response event. The events are defined such that the request is from
|
|
91
|
+
api to state machine while the response is from state machine to api.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, name: str):
|
|
95
|
+
self.request: Event[T1] = Event("api-" + name + "-request")
|
|
96
|
+
self.response: Event[T2] = Event("api-" + name + "-request")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class APIRequests:
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
self.start_mission: APIEvent[Mission, MissionStartResponse] = APIEvent(
|
|
102
|
+
"start_mission"
|
|
103
|
+
)
|
|
104
|
+
self.stop_mission: APIEvent[str, ControlMissionResponse] = APIEvent(
|
|
105
|
+
"stop_mission"
|
|
106
|
+
)
|
|
107
|
+
self.pause_mission: APIEvent[bool, ControlMissionResponse] = APIEvent(
|
|
108
|
+
"pause_mission"
|
|
109
|
+
)
|
|
110
|
+
self.resume_mission: APIEvent[bool, ControlMissionResponse] = APIEvent(
|
|
111
|
+
"resume_mission"
|
|
112
|
+
)
|
|
113
|
+
self.return_home: APIEvent[bool, bool] = APIEvent("return_home")
|
|
114
|
+
self.release_intervention_needed: APIEvent[bool, bool] = APIEvent(
|
|
115
|
+
"release_intervention_needed"
|
|
116
|
+
)
|
|
117
|
+
self.send_to_lockdown: APIEvent[bool, LockdownResponse] = APIEvent(
|
|
118
|
+
"send_to_lockdown"
|
|
119
|
+
)
|
|
120
|
+
self.release_from_lockdown: APIEvent[bool, bool] = APIEvent(
|
|
121
|
+
"release_from_lockdown"
|
|
122
|
+
)
|
|
123
|
+
self.set_maintenance_mode: APIEvent[bool, MaintenanceResponse] = APIEvent(
|
|
124
|
+
"set_maintenance_mode"
|
|
125
|
+
)
|
|
126
|
+
self.release_from_maintenance_mode: APIEvent[bool, bool] = APIEvent(
|
|
127
|
+
"release_from_maintenance_mode"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class StateMachineEvents:
|
|
132
|
+
def __init__(self) -> None:
|
|
133
|
+
self.start_mission: Event[Mission] = Event("start_mission")
|
|
134
|
+
self.stop_mission: Event[bool] = Event("stop_mission")
|
|
135
|
+
self.pause_mission: Event[bool] = Event("pause_mission")
|
|
136
|
+
self.resume_mission: Event[bool] = Event("resume_mission")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class RobotServiceEvents:
|
|
140
|
+
def __init__(self) -> None:
|
|
141
|
+
self.mission_status_updated: Event[MissionStatus] = Event(
|
|
142
|
+
"mission_status_updated"
|
|
143
|
+
)
|
|
144
|
+
self.mission_started: Event[bool] = Event("mission_started")
|
|
145
|
+
self.mission_failed: Event[ErrorMessage] = Event("mission_failed")
|
|
146
|
+
self.robot_status_changed: Event[bool] = Event("robot_status_changed")
|
|
147
|
+
self.mission_failed_to_stop: Event[ErrorMessage] = Event(
|
|
148
|
+
"mission_failed_to_stop"
|
|
149
|
+
)
|
|
150
|
+
self.mission_successfully_stopped: Event[bool] = Event(
|
|
151
|
+
"mission_successfully_stopped"
|
|
152
|
+
)
|
|
153
|
+
self.mission_failed_to_pause: Event[ErrorMessage] = Event(
|
|
154
|
+
"mission_failed_to_pause"
|
|
155
|
+
)
|
|
156
|
+
self.mission_successfully_paused: Event[bool] = Event(
|
|
157
|
+
"mission_successfully_paused"
|
|
158
|
+
)
|
|
159
|
+
self.mission_failed_to_resume: Event[ErrorMessage] = Event(
|
|
160
|
+
"mission_failed_to_resume"
|
|
161
|
+
)
|
|
162
|
+
self.mission_successfully_resumed: Event[bool] = Event(
|
|
163
|
+
"mission_successfully_resumed"
|
|
164
|
+
)
|
|
165
|
+
self.request_inspection_upload: Event[Tuple[TASKS, Mission]] = Event(
|
|
166
|
+
"request_inspection_upload"
|
|
167
|
+
)
|
|
168
|
+
self.robot_already_home: Event[bool] = Event("robot_already_home")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class SharedState:
|
|
172
|
+
def __init__(self) -> None:
|
|
173
|
+
self.state: Event[State] = Event("state")
|
|
174
|
+
self.robot_status: Event[RobotStatus] = Event("robot_status")
|
|
175
|
+
self.robot_battery_level: Event[float] = Event("robot_battery_level")
|
|
176
|
+
self.mission_id: Event[Optional[str]] = Event("mission_id")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class EventTimeoutError(Exception):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class EventConflictError(Exception):
|
|
184
|
+
pass
|
isar/models/status.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IsarStatus(Enum):
|
|
5
|
+
Available = "available"
|
|
6
|
+
ReturnHomePaused = "returnhomepaused"
|
|
7
|
+
Paused = "paused"
|
|
8
|
+
Busy = "busy"
|
|
9
|
+
Home = "home"
|
|
10
|
+
Offline = "offline"
|
|
11
|
+
BlockedProtectiveStop = "blockedprotectivestop"
|
|
12
|
+
ReturningHome = "returninghome"
|
|
13
|
+
InterventionNeeded = "interventionneeded"
|
|
14
|
+
Recharging = "recharging"
|
|
15
|
+
Lockdown = "lockdown"
|
|
16
|
+
GoingToLockdown = "goingtolockdown"
|
|
17
|
+
GoingToRecharging = "goingtorecharging"
|
|
18
|
+
Maintenance = "maintenance"
|
|
19
|
+
Pausing = "pausing"
|
|
20
|
+
PausingReturnHome = "pausingreturnhome"
|
|
21
|
+
Stopping = "stopping"
|
|
22
|
+
StoppingReturnHome = "stoppingreturnhome"
|
isar/modules.py
CHANGED
|
@@ -1,214 +1,131 @@
|
|
|
1
|
-
import
|
|
1
|
+
import os
|
|
2
2
|
from importlib import import_module
|
|
3
|
-
from logging import Logger
|
|
4
|
-
from types import ModuleType
|
|
5
|
-
from typing import Dict, List, Tuple, Union
|
|
6
3
|
|
|
7
|
-
from
|
|
4
|
+
from dependency_injector import containers, providers
|
|
8
5
|
|
|
9
6
|
from isar.apis.api import API
|
|
7
|
+
from isar.apis.robot_control.robot_controller import RobotController
|
|
10
8
|
from isar.apis.schedule.scheduling_controller import SchedulingController
|
|
11
9
|
from isar.apis.security.authentication import Authenticator
|
|
12
10
|
from isar.config.keyvault.keyvault_service import Keyvault
|
|
13
11
|
from isar.config.settings import settings
|
|
14
|
-
from isar.
|
|
15
|
-
from isar.
|
|
16
|
-
from isar.
|
|
17
|
-
from isar.mission_planner.task_selector_interface import TaskSelectorInterface
|
|
18
|
-
from isar.models.communication.queues.queues import Queues
|
|
19
|
-
from isar.services.service_connections.request_handler import RequestHandler
|
|
12
|
+
from isar.models.events import Events, SharedState
|
|
13
|
+
from isar.robot.robot import Robot
|
|
14
|
+
from isar.services.utilities.robot_utilities import RobotUtilities
|
|
20
15
|
from isar.services.utilities.scheduling_utilities import SchedulingUtilities
|
|
21
16
|
from isar.state_machine.state_machine import StateMachine
|
|
22
17
|
from isar.storage.blob_storage import BlobStorage
|
|
23
18
|
from isar.storage.local_storage import LocalStorage
|
|
24
|
-
from isar.storage.slimm_storage import SlimmStorage
|
|
25
|
-
from isar.storage.storage_interface import StorageInterface
|
|
26
19
|
from isar.storage.uploader import Uploader
|
|
27
|
-
from robot_interface.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class RobotModule(Module):
|
|
59
|
-
@provider
|
|
60
|
-
@singleton
|
|
61
|
-
def provide_robot_interface(self) -> RobotInterface:
|
|
62
|
-
robot_package_name: str = settings.ROBOT_PACKAGE
|
|
63
|
-
robot: ModuleType = import_module(robot_package_name)
|
|
64
|
-
return robot.robotinterface.Robot() # type: ignore
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class QueuesModule(Module):
|
|
68
|
-
@provider
|
|
69
|
-
@singleton
|
|
70
|
-
def provide_queues(self) -> Queues:
|
|
71
|
-
return Queues()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class RequestHandlerModule(Module):
|
|
75
|
-
@provider
|
|
76
|
-
@singleton
|
|
77
|
-
def provide_request_handler(self) -> RequestHandler:
|
|
78
|
-
return RequestHandler()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class BlobStorageModule(Module):
|
|
82
|
-
@multiprovider
|
|
83
|
-
@singleton
|
|
84
|
-
def provide_blob_storage(self, keyvault: Keyvault) -> List[StorageInterface]:
|
|
85
|
-
return [BlobStorage(keyvault)]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class LocalStorageModule(Module):
|
|
89
|
-
@multiprovider
|
|
90
|
-
@singleton
|
|
91
|
-
def provide_local_storage(self) -> List[StorageInterface]:
|
|
92
|
-
return [LocalStorage()]
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class SlimmStorageModule(Module):
|
|
96
|
-
@multiprovider
|
|
97
|
-
@singleton
|
|
98
|
-
def provide_slimm_storage(
|
|
99
|
-
self, request_handler: RequestHandler
|
|
100
|
-
) -> List[StorageInterface]:
|
|
101
|
-
return [SlimmStorage(request_handler=request_handler)]
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class LocalPlannerModule(Module):
|
|
105
|
-
@provider
|
|
106
|
-
@singleton
|
|
107
|
-
def provide_local_planner(self) -> MissionPlannerInterface:
|
|
108
|
-
return LocalPlanner()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class StateMachineModule(Module):
|
|
112
|
-
@provider
|
|
113
|
-
@singleton
|
|
114
|
-
def provide_state_machine(
|
|
115
|
-
self,
|
|
116
|
-
queues: Queues,
|
|
117
|
-
robot: RobotInterface,
|
|
118
|
-
mqtt_client: MqttClientInterface,
|
|
119
|
-
task_selector: TaskSelectorInterface,
|
|
120
|
-
) -> StateMachine:
|
|
121
|
-
return StateMachine(
|
|
122
|
-
queues=queues,
|
|
123
|
-
robot=robot,
|
|
124
|
-
mqtt_publisher=mqtt_client,
|
|
125
|
-
task_selector=task_selector,
|
|
20
|
+
from robot_interface.telemetry.mqtt_client import MqttPublisher
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ApplicationContainer(containers.DeclarativeContainer):
|
|
24
|
+
config = providers.Configuration(pydantic_settings=[settings])
|
|
25
|
+
|
|
26
|
+
# Core services
|
|
27
|
+
keyvault = providers.Singleton(
|
|
28
|
+
Keyvault,
|
|
29
|
+
keyvault_name=settings.KEYVAULT_NAME,
|
|
30
|
+
client_id=settings.AZURE_CLIENT_ID,
|
|
31
|
+
client_secret=os.environ.get("AZURE_CLIENT_SECRET"),
|
|
32
|
+
tenant_id=settings.AZURE_TENANT_ID,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Events and shared state
|
|
36
|
+
events = providers.Singleton(Events)
|
|
37
|
+
shared_state = providers.Singleton(SharedState)
|
|
38
|
+
|
|
39
|
+
# Robot-related services
|
|
40
|
+
robot_interface = providers.Singleton(
|
|
41
|
+
lambda: import_module(f"{settings.ROBOT_PACKAGE}.robotinterface").Robot()
|
|
42
|
+
)
|
|
43
|
+
robot_utilities = providers.Singleton(RobotUtilities, robot=robot_interface)
|
|
44
|
+
|
|
45
|
+
# Mqtt client
|
|
46
|
+
mqtt_client = (
|
|
47
|
+
providers.Singleton(
|
|
48
|
+
MqttPublisher,
|
|
49
|
+
mqtt_queue=providers.Callable(events.provided.mqtt_queue),
|
|
126
50
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
f"\n {category:<15} : {config_option:<20} ({module.__name__})"
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
logger: Logger = logging.getLogger("modules")
|
|
212
|
-
logger.info(f"Loaded the following module configurations:{module_overview}")
|
|
213
|
-
|
|
214
|
-
return Injector(injector_modules)
|
|
51
|
+
if settings.MQTT_ENABLED
|
|
52
|
+
else None
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# API and controllers
|
|
56
|
+
authenticator = providers.Singleton(Authenticator)
|
|
57
|
+
scheduling_utilities = providers.Singleton(
|
|
58
|
+
SchedulingUtilities,
|
|
59
|
+
events=events,
|
|
60
|
+
shared_state=shared_state,
|
|
61
|
+
)
|
|
62
|
+
scheduling_controller = providers.Singleton(
|
|
63
|
+
SchedulingController, scheduling_utilities=scheduling_utilities
|
|
64
|
+
)
|
|
65
|
+
robot_controller = providers.Singleton(
|
|
66
|
+
RobotController, robot_utilities=robot_utilities
|
|
67
|
+
)
|
|
68
|
+
api = providers.Singleton(
|
|
69
|
+
API,
|
|
70
|
+
authenticator=authenticator,
|
|
71
|
+
scheduling_controller=scheduling_controller,
|
|
72
|
+
robot_controller=robot_controller,
|
|
73
|
+
keyvault=keyvault,
|
|
74
|
+
mqtt_publisher=mqtt_client,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Storage
|
|
78
|
+
storage_handlers_temp = []
|
|
79
|
+
if settings.STORAGE_LOCAL_ENABLED:
|
|
80
|
+
local_storage = providers.Singleton(LocalStorage)
|
|
81
|
+
storage_handlers_temp.append(local_storage)
|
|
82
|
+
if settings.STORAGE_BLOB_ENABLED:
|
|
83
|
+
blob_storage = providers.Singleton(BlobStorage, keyvault=keyvault)
|
|
84
|
+
storage_handlers_temp.append(blob_storage)
|
|
85
|
+
storage_handlers = providers.List(*storage_handlers_temp)
|
|
86
|
+
|
|
87
|
+
# State machine
|
|
88
|
+
state_machine = providers.Singleton(
|
|
89
|
+
StateMachine,
|
|
90
|
+
events=events,
|
|
91
|
+
shared_state=shared_state,
|
|
92
|
+
robot=robot_interface,
|
|
93
|
+
mqtt_publisher=mqtt_client,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Robot
|
|
97
|
+
robot = providers.Singleton(
|
|
98
|
+
Robot,
|
|
99
|
+
events=events,
|
|
100
|
+
robot=robot_interface,
|
|
101
|
+
shared_state=shared_state,
|
|
102
|
+
mqtt_publisher=mqtt_client,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Uploader
|
|
106
|
+
uploader = providers.Singleton(
|
|
107
|
+
Uploader,
|
|
108
|
+
events=events,
|
|
109
|
+
storage_handlers=storage_handlers,
|
|
110
|
+
mqtt_publisher=mqtt_client,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_injector() -> ApplicationContainer:
|
|
115
|
+
container = ApplicationContainer()
|
|
116
|
+
container.init_resources()
|
|
117
|
+
container.wire(modules=[__name__])
|
|
118
|
+
container.config.from_dict(
|
|
119
|
+
{
|
|
120
|
+
"KEYVAULT_NAME": settings.KEYVAULT_NAME,
|
|
121
|
+
"MQTT_ENABLED": settings.MQTT_ENABLED,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
print("Loaded the following module configurations:")
|
|
126
|
+
for provider_name, provider in container.providers.items():
|
|
127
|
+
provider_repr = repr(provider)
|
|
128
|
+
simplified_provider = provider_repr.split(".")[-1].split(">")[0]
|
|
129
|
+
print(f" {provider_name:<20}: {simplified_provider}")
|
|
130
|
+
|
|
131
|
+
return container
|