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.
Files changed (134) hide show
  1. isar/apis/api.py +135 -86
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +115 -170
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +123 -187
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +4 -2
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/settings.py +74 -117
  14. isar/eventhandlers/eventhandler.py +123 -0
  15. isar/models/events.py +184 -0
  16. isar/models/status.py +22 -0
  17. isar/modules.py +117 -200
  18. isar/robot/robot.py +383 -0
  19. isar/robot/robot_battery.py +60 -0
  20. isar/robot/robot_monitor_mission.py +357 -0
  21. isar/robot/robot_pause_mission.py +74 -0
  22. isar/robot/robot_resume_mission.py +67 -0
  23. isar/robot/robot_start_mission.py +66 -0
  24. isar/robot/robot_status.py +61 -0
  25. isar/robot/robot_stop_mission.py +68 -0
  26. isar/robot/robot_upload_inspection.py +75 -0
  27. isar/script.py +58 -41
  28. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  29. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  30. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  31. isar/services/service_connections/persistent_memory.py +69 -0
  32. isar/services/utilities/mqtt_utilities.py +93 -0
  33. isar/services/utilities/robot_utilities.py +20 -0
  34. isar/services/utilities/scheduling_utilities.py +386 -100
  35. isar/state_machine/state_machine.py +242 -539
  36. isar/state_machine/states/__init__.py +0 -8
  37. isar/state_machine/states/await_next_mission.py +114 -0
  38. isar/state_machine/states/blocked_protective_stop.py +60 -0
  39. isar/state_machine/states/going_to_lockdown.py +95 -0
  40. isar/state_machine/states/going_to_recharging.py +92 -0
  41. isar/state_machine/states/home.py +115 -0
  42. isar/state_machine/states/intervention_needed.py +77 -0
  43. isar/state_machine/states/lockdown.py +38 -0
  44. isar/state_machine/states/maintenance.py +43 -0
  45. isar/state_machine/states/monitor.py +137 -247
  46. isar/state_machine/states/offline.py +51 -53
  47. isar/state_machine/states/paused.py +92 -23
  48. isar/state_machine/states/pausing.py +48 -0
  49. isar/state_machine/states/pausing_return_home.py +48 -0
  50. isar/state_machine/states/recharging.py +80 -0
  51. isar/state_machine/states/resuming.py +57 -0
  52. isar/state_machine/states/resuming_return_home.py +64 -0
  53. isar/state_machine/states/return_home_paused.py +109 -0
  54. isar/state_machine/states/returning_home.py +217 -0
  55. isar/state_machine/states/stopping.py +69 -0
  56. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  57. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  58. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  59. isar/state_machine/states/stopping_paused_mission.py +36 -0
  60. isar/state_machine/states/stopping_paused_return_home.py +59 -0
  61. isar/state_machine/states/stopping_return_home.py +59 -0
  62. isar/state_machine/states/unknown_status.py +74 -0
  63. isar/state_machine/states_enum.py +23 -5
  64. isar/state_machine/transitions/mission.py +225 -0
  65. isar/state_machine/transitions/return_home.py +108 -0
  66. isar/state_machine/transitions/robot_status.py +87 -0
  67. isar/state_machine/utils/common_event_handlers.py +138 -0
  68. isar/storage/blob_storage.py +70 -52
  69. isar/storage/local_storage.py +25 -12
  70. isar/storage/storage_interface.py +28 -7
  71. isar/storage/uploader.py +174 -55
  72. isar/storage/utilities.py +32 -29
  73. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/METADATA +119 -123
  74. isar-1.34.13.dist-info/RECORD +120 -0
  75. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/WHEEL +1 -1
  76. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/entry_points.txt +1 -0
  77. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  78. robot_interface/models/inspection/__init__.py +0 -13
  79. robot_interface/models/inspection/inspection.py +42 -33
  80. robot_interface/models/mission/mission.py +14 -15
  81. robot_interface/models/mission/status.py +20 -26
  82. robot_interface/models/mission/task.py +154 -121
  83. robot_interface/models/robots/battery_state.py +6 -0
  84. robot_interface/models/robots/media.py +13 -0
  85. robot_interface/models/robots/robot_model.py +7 -7
  86. robot_interface/robot_interface.py +119 -84
  87. robot_interface/telemetry/mqtt_client.py +74 -12
  88. robot_interface/telemetry/payloads.py +91 -13
  89. robot_interface/utilities/json_service.py +7 -1
  90. isar/config/configuration_error.py +0 -2
  91. isar/config/keyvault/keyvault_error.py +0 -2
  92. isar/config/predefined_mission_definition/__init__.py +0 -0
  93. isar/config/predefined_mission_definition/default_exr.json +0 -51
  94. isar/config/predefined_mission_definition/default_mission.json +0 -91
  95. isar/config/predefined_mission_definition/default_turtlebot.json +0 -124
  96. isar/config/predefined_missions/__init__.py +0 -0
  97. isar/config/predefined_missions/default.json +0 -92
  98. isar/config/predefined_missions/default_turtlebot.json +0 -110
  99. isar/config/predefined_poses/__init__.py +0 -0
  100. isar/config/predefined_poses/predefined_poses.py +0 -616
  101. isar/config/settings.env +0 -25
  102. isar/mission_planner/__init__.py +0 -0
  103. isar/mission_planner/local_planner.py +0 -82
  104. isar/mission_planner/mission_planner_interface.py +0 -26
  105. isar/mission_planner/sequential_task_selector.py +0 -23
  106. isar/mission_planner/task_selector_interface.py +0 -31
  107. isar/models/communication/__init__.py +0 -0
  108. isar/models/communication/message.py +0 -12
  109. isar/models/communication/queues/__init__.py +0 -4
  110. isar/models/communication/queues/queue_io.py +0 -12
  111. isar/models/communication/queues/queue_timeout_error.py +0 -2
  112. isar/models/communication/queues/queues.py +0 -19
  113. isar/models/communication/queues/status_queue.py +0 -20
  114. isar/models/mission_metadata/__init__.py +0 -0
  115. isar/services/auth/__init__.py +0 -0
  116. isar/services/auth/azure_credentials.py +0 -14
  117. isar/services/readers/__init__.py +0 -0
  118. isar/services/readers/base_reader.py +0 -37
  119. isar/services/service_connections/request_handler.py +0 -153
  120. isar/services/service_connections/stid/__init__.py +0 -0
  121. isar/services/utilities/queue_utilities.py +0 -39
  122. isar/services/utilities/threaded_request.py +0 -68
  123. isar/state_machine/states/idle.py +0 -85
  124. isar/state_machine/states/initialize.py +0 -71
  125. isar/state_machine/states/initiate.py +0 -142
  126. isar/state_machine/states/off.py +0 -18
  127. isar/state_machine/states/stop.py +0 -95
  128. isar/storage/slimm_storage.py +0 -191
  129. isar-1.20.2.dist-info/RECORD +0 -116
  130. robot_interface/models/initialize/__init__.py +0 -1
  131. robot_interface/models/initialize/initialize_params.py +0 -9
  132. robot_interface/models/mission/step.py +0 -234
  133. {isar-1.20.2.dist-info → isar-1.34.13.dist-info/licenses}/LICENSE +0 -0
  134. {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 logging
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 injector import Injector, Module, multiprovider, provider, singleton
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.mission_planner.local_planner import LocalPlanner
15
- from isar.mission_planner.mission_planner_interface import MissionPlannerInterface
16
- from isar.mission_planner.sequential_task_selector import SequentialTaskSelector
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.robot_interface import RobotInterface
28
- from robot_interface.telemetry.mqtt_client import MqttClientInterface, MqttPublisher
29
-
30
-
31
- class APIModule(Module):
32
- @provider
33
- @singleton
34
- def provide_api(
35
- self,
36
- authenticator: Authenticator,
37
- scheduling_controller: SchedulingController,
38
- keyvault: Keyvault,
39
- ) -> API:
40
- return API(authenticator, scheduling_controller, keyvault)
41
-
42
- @provider
43
- @singleton
44
- def provide_scheduling_controller(
45
- self,
46
- scheduling_utilities: SchedulingUtilities,
47
- ) -> SchedulingController:
48
- return SchedulingController(scheduling_utilities)
49
-
50
-
51
- class AuthenticationModule(Module):
52
- @provider
53
- @singleton
54
- def provide_authenticator(self) -> Authenticator:
55
- return Authenticator()
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
- class UploaderModule(Module):
130
- @provider
131
- @singleton
132
- def provide_uploader(
133
- self,
134
- queues: Queues,
135
- storage_handlers: List[StorageInterface],
136
- mqtt_client: MqttClientInterface,
137
- ) -> Uploader:
138
- return Uploader(
139
- queues=queues,
140
- storage_handlers=storage_handlers,
141
- mqtt_publisher=mqtt_client,
142
- )
143
-
144
-
145
- class UtilitiesModule(Module):
146
- @provider
147
- @singleton
148
- def provide_scheduling_utilities(
149
- self, queues: Queues, mission_planner: MissionPlannerInterface
150
- ) -> SchedulingUtilities:
151
- return SchedulingUtilities(queues, mission_planner)
152
-
153
-
154
- class ServiceModule(Module):
155
- @provider
156
- @singleton
157
- def provide_keyvault(self) -> Keyvault:
158
- return Keyvault(keyvault_name=settings.KEYVAULT_NAME)
159
-
160
-
161
- class MqttModule(Module):
162
- @provider
163
- @singleton
164
- def provide_mqtt_client(self, queues: Queues) -> MqttClientInterface:
165
- if settings.MQTT_ENABLED:
166
- return MqttPublisher(mqtt_queue=queues.mqtt_queue)
167
- return None
168
-
169
-
170
- class SequentialTaskSelectorModule(Module):
171
- @provider
172
- @singleton
173
- def provide_task_selector(self) -> TaskSelectorInterface:
174
- return SequentialTaskSelector()
175
-
176
-
177
- modules: Dict[str, Tuple[Module, Union[str, bool]]] = {
178
- "api": (APIModule, "required"),
179
- "authentication": (AuthenticationModule, "required"),
180
- "queues": (QueuesModule, "required"),
181
- "request_handler": (RequestHandlerModule, "required"),
182
- "robot_package": (RobotModule, settings.ROBOT_PACKAGE),
183
- "isar_id": (RobotModule, settings.ISAR_ID),
184
- "robot_name": (RobotModule, settings.ROBOT_NAME),
185
- "mission_planner": (LocalPlannerModule, settings.MISSION_PLANNER),
186
- "task_selector": (
187
- {"sequential": SequentialTaskSelectorModule}[settings.TASK_SELECTOR],
188
- settings.TASK_SELECTOR,
189
- ),
190
- "service": (ServiceModule, "required"),
191
- "state_machine": (StateMachineModule, "required"),
192
- "storage_local": (LocalStorageModule, settings.STORAGE_LOCAL_ENABLED),
193
- "storage_blob": (BlobStorageModule, settings.STORAGE_BLOB_ENABLED),
194
- "storage_slimm": (SlimmStorageModule, settings.STORAGE_SLIMM_ENABLED),
195
- "mqtt": (MqttModule, "required"),
196
- "utilities": (UtilitiesModule, "required"),
197
- }
198
-
199
-
200
- def get_injector() -> Injector:
201
- injector_modules: List[Module] = []
202
- module_overview: str = ""
203
-
204
- for category, (module, config_option) in modules.items():
205
- if config_option:
206
- injector_modules.append(module)
207
- module_overview += (
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