iotsploit-core 0.0.6__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.
- iotsploit_core/__init__.py +7 -0
- iotsploit_core/context.py +27 -0
- iotsploit_core/core/__init__.py +0 -0
- iotsploit_core/core/base_plugin.py +290 -0
- iotsploit_core/core/device_config.py +105 -0
- iotsploit_core/core/device_manager.py +942 -0
- iotsploit_core/core/device_registry.py +118 -0
- iotsploit_core/core/device_scanner.py +74 -0
- iotsploit_core/core/device_spec.py +105 -0
- iotsploit_core/core/device_store.py +86 -0
- iotsploit_core/core/execution_backend.py +464 -0
- iotsploit_core/core/execution_queue.py +456 -0
- iotsploit_core/core/exploit_manager.py +728 -0
- iotsploit_core/core/exploit_spec.py +73 -0
- iotsploit_core/core/stream_manager.py +157 -0
- iotsploit_core/core/tool_config.py +430 -0
- iotsploit_core/core/tool_manager.py +1166 -0
- iotsploit_core/core/tool_service.py +936 -0
- iotsploit_core/domain/__init__.py +3 -0
- iotsploit_core/domain/device.py +70 -0
- iotsploit_core/domain/execution_plan.py +29 -0
- iotsploit_core/domain/plugin.py +17 -0
- iotsploit_core/domain/stream.py +63 -0
- iotsploit_core/domain/target.py +243 -0
- iotsploit_core/platforms/__init__.py +19 -0
- iotsploit_core/platforms/consts.py +25 -0
- iotsploit_core/ports/__init__.py +6 -0
- iotsploit_core/ports/driver_state_repo.py +15 -0
- iotsploit_core/ports/plugin_repo.py +26 -0
- iotsploit_core/ports/stream_backend.py +28 -0
- iotsploit_core/ports/task_runner.py +22 -0
- iotsploit_core/ports/wifi_backend.py +148 -0
- iotsploit_core/py.typed +2 -0
- iotsploit_core/utils/__init__.py +53 -0
- iotsploit_core/utils/exceptions.py +152 -0
- iotsploit_core/utils/helpers.py +65 -0
- iotsploit_core/utils/iots_logger.py +164 -0
- iotsploit_core/utils/result.py +41 -0
- iotsploit_core-0.0.6.dist-info/METADATA +117 -0
- iotsploit_core-0.0.6.dist-info/RECORD +41 -0
- iotsploit_core-0.0.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from importlib import metadata as importlib_metadata
|
|
5
|
+
import importlib.util
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import threading
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from iotsploit_core.core.base_plugin import BaseDeviceDriver
|
|
14
|
+
from iotsploit_core.core.device_spec import DevicePluginSpec, DeviceState
|
|
15
|
+
from iotsploit_core.domain.device import Device
|
|
16
|
+
from iotsploit_core.ports.driver_state_repo import DriverStateRepository
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
class DeviceDriverManager:
|
|
21
|
+
_instance = None
|
|
22
|
+
_lock = threading.Lock()
|
|
23
|
+
|
|
24
|
+
def __new__(cls, *args, **kwargs):
|
|
25
|
+
if cls._instance is None:
|
|
26
|
+
with cls._lock:
|
|
27
|
+
if cls._instance is None:
|
|
28
|
+
cls._instance = super(DeviceDriverManager, cls).__new__(cls)
|
|
29
|
+
cls._instance._initialized = False
|
|
30
|
+
return cls._instance
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
*,
|
|
35
|
+
driver_state_repo: DriverStateRepository,
|
|
36
|
+
plugins_dir: str | Path | None = None,
|
|
37
|
+
usb_config_file: str | Path | None = None,
|
|
38
|
+
):
|
|
39
|
+
if not self._initialized:
|
|
40
|
+
logger.info("Initializing DeviceDriverManager")
|
|
41
|
+
self._driver_state_repo = driver_state_repo
|
|
42
|
+
|
|
43
|
+
self.plugins_dir = Path(plugins_dir) if plugins_dir is not None else self._default_plugins_dir()
|
|
44
|
+
self.usb_config_file = str(
|
|
45
|
+
Path(usb_config_file) if usb_config_file is not None else self._default_usb_config_file()
|
|
46
|
+
)
|
|
47
|
+
self.plugins = {}
|
|
48
|
+
self.drivers = {} # Store driver instances
|
|
49
|
+
self.device_states = {} # Store device states, format: 'driver_name::device_id': DeviceState
|
|
50
|
+
self._connection_locks = {} # Device operation locks, format: 'driver_name::device_id': Lock
|
|
51
|
+
self.driver_states = {} # Store driver enablement states
|
|
52
|
+
|
|
53
|
+
# 添加USB设备配置
|
|
54
|
+
self.usb_device_configs = self._load_usb_config()
|
|
55
|
+
|
|
56
|
+
# Define valid state transitions
|
|
57
|
+
self._state_transitions = {
|
|
58
|
+
DeviceState.UNKNOWN: [DeviceState.DISCOVERED],
|
|
59
|
+
DeviceState.DISCOVERED: [DeviceState.INITIALIZED],
|
|
60
|
+
DeviceState.INITIALIZED: [DeviceState.CONNECTED, DeviceState.DISCONNECTED],
|
|
61
|
+
DeviceState.CONNECTED: [DeviceState.ACTIVE, DeviceState.DISCONNECTED],
|
|
62
|
+
DeviceState.ACTIVE: [DeviceState.CONNECTED, DeviceState.ERROR],
|
|
63
|
+
DeviceState.ERROR: [DeviceState.DISCONNECTED],
|
|
64
|
+
DeviceState.DISCONNECTED: [DeviceState.INITIALIZED]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
self.load_plugins()
|
|
68
|
+
self._load_driver_states()
|
|
69
|
+
self._initialized = True
|
|
70
|
+
logger.info("DeviceDriverManager initialized")
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def _default_plugins_dir() -> Path | None:
|
|
74
|
+
env = os.getenv("IOTSPLOIT_DEVICE_PLUGINS_DIR") or os.getenv("SAT_DEVICE_PLUGINS_DIR")
|
|
75
|
+
if env:
|
|
76
|
+
return Path(env)
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _default_usb_config_file() -> Path:
|
|
81
|
+
env = os.getenv("IOTSPLOIT_USB_CONFIG_FILE") or os.getenv("SAT_USB_CONFIG_FILE")
|
|
82
|
+
if env:
|
|
83
|
+
return Path(env)
|
|
84
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
85
|
+
return repo_root / "conf" / "usb_devices.json"
|
|
86
|
+
|
|
87
|
+
def _load_usb_config(self) -> Dict:
|
|
88
|
+
"""加载USB设备配置文件"""
|
|
89
|
+
try:
|
|
90
|
+
if os.path.exists(self.usb_config_file):
|
|
91
|
+
with open(self.usb_config_file, 'r', encoding='utf-8') as f:
|
|
92
|
+
return json.load(f)
|
|
93
|
+
else:
|
|
94
|
+
logger.warning(f"USB配置文件不存在: {self.usb_config_file}")
|
|
95
|
+
return {}
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"加载USB配置文件失败: {e}")
|
|
98
|
+
return {}
|
|
99
|
+
|
|
100
|
+
def get_usb_device_info(self, device_type: str) -> List[Dict]:
|
|
101
|
+
"""根据设备类型获取USB设备完整信息
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
device_type: 设备类型,如 "logic_analyzer"
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List[Dict]: 设备完整信息列表,包含端口、VID/PID、配置等
|
|
108
|
+
"""
|
|
109
|
+
import serial.tools.list_ports
|
|
110
|
+
|
|
111
|
+
config = self.usb_device_configs.get(device_type)
|
|
112
|
+
if not config:
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
devices = []
|
|
116
|
+
for vid_pid in config.get("vid_pid_list", []):
|
|
117
|
+
vid = int(vid_pid["vid"], 16)
|
|
118
|
+
pid = int(vid_pid["pid"], 16)
|
|
119
|
+
|
|
120
|
+
# 查找匹配的串口设备
|
|
121
|
+
for port in serial.tools.list_ports.comports():
|
|
122
|
+
if port.vid == vid and port.pid == pid:
|
|
123
|
+
device_info = {
|
|
124
|
+
"device_type": device_type,
|
|
125
|
+
"port": port.device,
|
|
126
|
+
"vid": vid,
|
|
127
|
+
"pid": pid,
|
|
128
|
+
"description": port.description,
|
|
129
|
+
"manufacturer": port.manufacturer,
|
|
130
|
+
"product": port.product,
|
|
131
|
+
"serial_number": port.serial_number,
|
|
132
|
+
"config": config
|
|
133
|
+
}
|
|
134
|
+
devices.append(device_info)
|
|
135
|
+
|
|
136
|
+
return devices
|
|
137
|
+
|
|
138
|
+
def get_usb_vid_pid(self, device_type: str) -> List[Dict]:
|
|
139
|
+
"""根据设备类型获取VID/PID列表
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
device_type: 设备类型,如 "logic_analyzer"
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List[Dict]: VID/PID列表,格式 [{"vid": "0x1d50", "pid": "0x5128"}]
|
|
146
|
+
"""
|
|
147
|
+
config = self.usb_device_configs.get(device_type)
|
|
148
|
+
if not config:
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
return config.get("vid_pid_list", [])
|
|
152
|
+
|
|
153
|
+
def _load_driver_states(self):
|
|
154
|
+
"""Load driver states from database"""
|
|
155
|
+
try:
|
|
156
|
+
self.driver_states = self._driver_state_repo.list_enabled()
|
|
157
|
+
for name, enabled in self.driver_states.items():
|
|
158
|
+
logger.info(f"Loaded driver state: {name} -> {'enabled' if enabled else 'disabled'}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Error loading driver states: {e}")
|
|
161
|
+
|
|
162
|
+
# For any driver not in the database, set to enabled by default
|
|
163
|
+
for driver_name in self.drivers.keys():
|
|
164
|
+
if driver_name not in self.driver_states:
|
|
165
|
+
self.driver_states[driver_name] = True
|
|
166
|
+
self._save_driver_state(driver_name, True)
|
|
167
|
+
|
|
168
|
+
def _save_driver_state(self, driver_name, enabled, description=None):
|
|
169
|
+
"""Save driver state to database"""
|
|
170
|
+
try:
|
|
171
|
+
self._driver_state_repo.set_enabled(driver_name, bool(enabled), description=description)
|
|
172
|
+
logger.info(f"Saved driver state: {driver_name} -> {'enabled' if enabled else 'disabled'}")
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Error saving driver state: {e}")
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def _iter_entry_points(group: str):
|
|
178
|
+
entry_points = importlib_metadata.entry_points()
|
|
179
|
+
if hasattr(entry_points, "select"):
|
|
180
|
+
return list(entry_points.select(group=group))
|
|
181
|
+
return list(entry_points.get(group, []))
|
|
182
|
+
|
|
183
|
+
def _load_entry_point_drivers(self) -> None:
|
|
184
|
+
"""Load packaged drivers declared via Python entry points."""
|
|
185
|
+
group = "iotsploit.device_drivers"
|
|
186
|
+
for entry_point in self._iter_entry_points(group):
|
|
187
|
+
driver_name = entry_point.name
|
|
188
|
+
if driver_name in self.drivers:
|
|
189
|
+
logger.debug("Skipping duplicate device driver entry point: %s", driver_name)
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
driver_class = entry_point.load()
|
|
194
|
+
if (
|
|
195
|
+
not isinstance(driver_class, type)
|
|
196
|
+
or not issubclass(driver_class, BaseDeviceDriver)
|
|
197
|
+
or driver_class is BaseDeviceDriver
|
|
198
|
+
):
|
|
199
|
+
logger.warning(
|
|
200
|
+
"Ignoring invalid device driver entry point %s -> %s",
|
|
201
|
+
driver_name,
|
|
202
|
+
entry_point.value,
|
|
203
|
+
)
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
self.plugins[driver_name] = driver_class
|
|
207
|
+
self.drivers[driver_name] = driver_class()
|
|
208
|
+
logger.info("Loaded device driver entry point: %s (%s)", driver_name, driver_class.__name__)
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.error("Failed to load device driver entry point %s: %s", driver_name, str(e))
|
|
211
|
+
|
|
212
|
+
def load_plugins(self):
|
|
213
|
+
"""Load device drivers from entry points first, then legacy filesystem fallbacks."""
|
|
214
|
+
self._load_entry_point_drivers()
|
|
215
|
+
|
|
216
|
+
if self.plugins_dir is None:
|
|
217
|
+
logger.debug("No legacy device plugins directory configured; skipping filesystem scan")
|
|
218
|
+
return
|
|
219
|
+
plugin_dir = str(self.plugins_dir)
|
|
220
|
+
logger.info(f"Loading device plugins from {plugin_dir}")
|
|
221
|
+
for root, _, files in os.walk(plugin_dir):
|
|
222
|
+
for filename in files:
|
|
223
|
+
if filename.endswith(".py") and not filename.startswith("__"):
|
|
224
|
+
self.load_plugin(os.path.join(root, filename))
|
|
225
|
+
|
|
226
|
+
def load_plugin(self, filepath: str):
|
|
227
|
+
"""Load a single plugin"""
|
|
228
|
+
try:
|
|
229
|
+
module_name = os.path.splitext(os.path.basename(filepath))[0]
|
|
230
|
+
if module_name in self.drivers:
|
|
231
|
+
logger.debug("Skipping legacy device plugin because %s is already registered", module_name)
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
spec = importlib.util.spec_from_file_location(module_name, filepath)
|
|
235
|
+
module = importlib.util.module_from_spec(spec)
|
|
236
|
+
spec.loader.exec_module(module)
|
|
237
|
+
|
|
238
|
+
# Modify plugin registration logic, remove pluggy related code
|
|
239
|
+
for attr_name in dir(module):
|
|
240
|
+
attr = getattr(module, attr_name)
|
|
241
|
+
if (isinstance(attr, type) and
|
|
242
|
+
issubclass(attr, BaseDeviceDriver) and
|
|
243
|
+
attr != BaseDeviceDriver):
|
|
244
|
+
driver_instance = attr()
|
|
245
|
+
self.plugins[module_name] = module
|
|
246
|
+
self.drivers[module_name] = driver_instance
|
|
247
|
+
logger.info(f"Loaded device plugin: {module_name} ({attr_name})")
|
|
248
|
+
break
|
|
249
|
+
except Exception as e:
|
|
250
|
+
logger.error(f"Failed to load plugin {filepath}: {str(e)}")
|
|
251
|
+
|
|
252
|
+
def execute_command(self, driver_name: str, command: str, device_id: str = "", **kwargs) -> Dict:
|
|
253
|
+
"""Execute device command
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
driver_name: Driver name (e.g., 'drv_socketcan')
|
|
257
|
+
command: Command to execute
|
|
258
|
+
device_id: Optional device ID for multi-device scenarios
|
|
259
|
+
**kwargs: Command parameters
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Dict: Dictionary containing operation results
|
|
263
|
+
"""
|
|
264
|
+
# Check if driver is enabled
|
|
265
|
+
if not self.is_driver_enabled(driver_name):
|
|
266
|
+
logger.warning(f"Attempted to execute command on disabled driver: {driver_name}")
|
|
267
|
+
return {
|
|
268
|
+
"status": "error",
|
|
269
|
+
"message": f"Driver {driver_name} is disabled"
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return self._manage_device_lifecycle(
|
|
273
|
+
driver_name=driver_name,
|
|
274
|
+
action='command',
|
|
275
|
+
device_id=device_id,
|
|
276
|
+
command=command,
|
|
277
|
+
args=kwargs
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def scan_devices(self, driver_name: str) -> Dict:
|
|
281
|
+
"""Scan devices"""
|
|
282
|
+
# Check if driver is enabled
|
|
283
|
+
if not self.is_driver_enabled(driver_name):
|
|
284
|
+
return {
|
|
285
|
+
"status": "error",
|
|
286
|
+
"message": f"Driver {driver_name} is disabled"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return self._manage_device_lifecycle(
|
|
290
|
+
driver_name=driver_name,
|
|
291
|
+
action='scan'
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def initialize_device(self, driver_name: str, device: Device) -> Dict:
|
|
295
|
+
"""Initialize device"""
|
|
296
|
+
# Check if driver is enabled
|
|
297
|
+
if not self.is_driver_enabled(driver_name):
|
|
298
|
+
return {
|
|
299
|
+
"status": "error",
|
|
300
|
+
"message": f"Driver {driver_name} is disabled"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return self._manage_device_lifecycle(
|
|
304
|
+
driver_name=driver_name,
|
|
305
|
+
action='initialize',
|
|
306
|
+
device=device
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def connect_device(self, driver_name: str, device: Device) -> Dict:
|
|
310
|
+
"""Connect device"""
|
|
311
|
+
# Check if driver is enabled
|
|
312
|
+
if not self.is_driver_enabled(driver_name):
|
|
313
|
+
return {
|
|
314
|
+
"status": "error",
|
|
315
|
+
"message": f"Driver {driver_name} is disabled"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return self._manage_device_lifecycle(
|
|
319
|
+
driver_name=driver_name,
|
|
320
|
+
action='connect',
|
|
321
|
+
device=device
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def reset_device(self, driver_name: str, device: Device) -> Dict:
|
|
325
|
+
"""Reset device"""
|
|
326
|
+
# Check if driver is enabled
|
|
327
|
+
if not self.is_driver_enabled(driver_name):
|
|
328
|
+
return {
|
|
329
|
+
"status": "error",
|
|
330
|
+
"message": f"Driver {driver_name} is disabled"
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return self._manage_device_lifecycle(
|
|
334
|
+
driver_name=driver_name,
|
|
335
|
+
action='reset',
|
|
336
|
+
device=device
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def close_device(self, driver_name: str, device: Device) -> Dict:
|
|
340
|
+
"""Close device"""
|
|
341
|
+
return self._manage_device_lifecycle(
|
|
342
|
+
driver_name=driver_name,
|
|
343
|
+
action='close',
|
|
344
|
+
device=device
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def get_device_state(self, driver_name: str, device_id: str = "") -> DeviceState:
|
|
348
|
+
"""Get current device state"""
|
|
349
|
+
device_key = self._get_device_key(driver_name, device_id=device_id)
|
|
350
|
+
return self.device_states.get(device_key, DeviceState.UNKNOWN)
|
|
351
|
+
|
|
352
|
+
def get_supported_commands(self, driver_name: str) -> Dict[str, str]:
|
|
353
|
+
"""Get commands supported by the device"""
|
|
354
|
+
driver = self.get_driver_instance(driver_name)
|
|
355
|
+
if driver:
|
|
356
|
+
return driver.get_supported_commands()
|
|
357
|
+
return {}
|
|
358
|
+
|
|
359
|
+
def get_plugin_commands(self, plugin_name: str) -> Dict[str, str]:
|
|
360
|
+
"""Get commands supported by the plugin
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
plugin_name: Plugin name
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Dict[str, str]: Dictionary of command names and descriptions
|
|
367
|
+
"""
|
|
368
|
+
driver = self.get_driver_instance(plugin_name)
|
|
369
|
+
if driver:
|
|
370
|
+
return driver.get_supported_commands()
|
|
371
|
+
return {}
|
|
372
|
+
|
|
373
|
+
def _manage_device_lifecycle(self, driver_name: str, action: str, **kwargs) -> Dict:
|
|
374
|
+
"""Internal method for device lifecycle management"""
|
|
375
|
+
try:
|
|
376
|
+
driver = self.get_driver_instance(driver_name)
|
|
377
|
+
if not driver:
|
|
378
|
+
return {
|
|
379
|
+
"status": "error",
|
|
380
|
+
"message": f"Driver {driver_name} not found"
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
device = kwargs.get('device')
|
|
384
|
+
device_id = kwargs.get('device_id', '')
|
|
385
|
+
device_key = self._get_device_key(driver_name, device, device_id)
|
|
386
|
+
|
|
387
|
+
with self._get_device_lock(device_key):
|
|
388
|
+
current_state = self.device_states.get(device_key, DeviceState.UNKNOWN)
|
|
389
|
+
|
|
390
|
+
# Modify state transition logic
|
|
391
|
+
if action != 'scan':
|
|
392
|
+
if action == 'initialize':
|
|
393
|
+
# For initialization operations, only execute in uninitialized state
|
|
394
|
+
if current_state not in [DeviceState.UNKNOWN, DeviceState.DISCOVERED]:
|
|
395
|
+
return {
|
|
396
|
+
"status": "error",
|
|
397
|
+
"message": f"Cannot perform initialize in current state {current_state}. Expected states: [unknown, discovered]"
|
|
398
|
+
}
|
|
399
|
+
elif action == 'connect':
|
|
400
|
+
# For connection operations, ensure the device is initialized
|
|
401
|
+
if current_state == DeviceState.UNKNOWN:
|
|
402
|
+
# Auto scan
|
|
403
|
+
scan_result = self._handle_scan(driver, driver_name, **kwargs)
|
|
404
|
+
if scan_result["status"] != "success":
|
|
405
|
+
return scan_result
|
|
406
|
+
current_state = self.device_states.get(device_key, DeviceState.UNKNOWN)
|
|
407
|
+
|
|
408
|
+
if current_state == DeviceState.DISCOVERED:
|
|
409
|
+
# Only initialize in discovered state
|
|
410
|
+
init_result = self._handle_initialize(driver, driver_name, **kwargs)
|
|
411
|
+
if init_result["status"] != "success":
|
|
412
|
+
return init_result
|
|
413
|
+
current_state = self.device_states.get(device_key, DeviceState.UNKNOWN)
|
|
414
|
+
|
|
415
|
+
if current_state != DeviceState.INITIALIZED:
|
|
416
|
+
return {
|
|
417
|
+
"status": "error",
|
|
418
|
+
"message": f"Cannot connect device in state {current_state}. Expected state: initialized"
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return self._execute_action(driver, action, current_state, device_key, driver_name, **kwargs)
|
|
422
|
+
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.error(f"Lifecycle management failed: {str(e)}", exc_info=True)
|
|
425
|
+
self._update_device_state(device_key, DeviceState.ERROR)
|
|
426
|
+
return {
|
|
427
|
+
"status": "error",
|
|
428
|
+
"message": str(e)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
def _get_device_lock(self, device_key: str) -> threading.Lock:
|
|
432
|
+
"""Get device operation lock"""
|
|
433
|
+
if device_key not in self._connection_locks:
|
|
434
|
+
self._connection_locks[device_key] = threading.Lock()
|
|
435
|
+
return self._connection_locks[device_key]
|
|
436
|
+
|
|
437
|
+
def _update_device_state(self, device_key: str, new_state: DeviceState):
|
|
438
|
+
"""Update device state"""
|
|
439
|
+
current_state = self.device_states.get(device_key, DeviceState.UNKNOWN)
|
|
440
|
+
|
|
441
|
+
if current_state == new_state:
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
if new_state in self._state_transitions.get(current_state, []):
|
|
445
|
+
self.device_states[device_key] = new_state
|
|
446
|
+
logger.info(f"Device {device_key} state changed: {current_state} -> {new_state}")
|
|
447
|
+
else:
|
|
448
|
+
# Special handling: don't downgrade if device is in a higher state
|
|
449
|
+
state_hierarchy = {
|
|
450
|
+
DeviceState.UNKNOWN: 0,
|
|
451
|
+
DeviceState.DISCOVERED: 1,
|
|
452
|
+
DeviceState.INITIALIZED: 2,
|
|
453
|
+
DeviceState.CONNECTED: 3,
|
|
454
|
+
DeviceState.ACTIVE: 4,
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
# Only update when new state's level is higher than current state
|
|
458
|
+
if state_hierarchy.get(new_state, 0) > state_hierarchy.get(current_state, 0):
|
|
459
|
+
self.device_states[device_key] = new_state
|
|
460
|
+
logger.info(f"Device {device_key} state upgraded: {current_state} -> {new_state}")
|
|
461
|
+
else:
|
|
462
|
+
logger.info(f"Ignoring state transition: {current_state} -> {new_state}")
|
|
463
|
+
|
|
464
|
+
def _execute_action(self,
|
|
465
|
+
driver: BaseDeviceDriver,
|
|
466
|
+
action: str,
|
|
467
|
+
current_state: DeviceState,
|
|
468
|
+
device_key: str,
|
|
469
|
+
driver_name: str,
|
|
470
|
+
**kwargs) -> Dict:
|
|
471
|
+
"""Execute specific action"""
|
|
472
|
+
try:
|
|
473
|
+
if action == 'scan':
|
|
474
|
+
return self._handle_scan(driver, driver_name, **kwargs)
|
|
475
|
+
elif action == 'initialize':
|
|
476
|
+
return self._handle_initialize(driver, driver_name, **kwargs)
|
|
477
|
+
elif action == 'connect':
|
|
478
|
+
return self._handle_connect(driver, driver_name, **kwargs)
|
|
479
|
+
elif action == 'command':
|
|
480
|
+
return self._handle_command(driver, driver_name, device_key, **kwargs)
|
|
481
|
+
elif action == 'reset':
|
|
482
|
+
return self._handle_reset(driver, driver_name, **kwargs)
|
|
483
|
+
elif action == 'close':
|
|
484
|
+
return self._handle_close(driver, driver_name, **kwargs)
|
|
485
|
+
else:
|
|
486
|
+
return {"status": "error", "message": f"Unknown action: {action}"}
|
|
487
|
+
except Exception as e:
|
|
488
|
+
logger.error(f"Action execution failed: {str(e)}")
|
|
489
|
+
self._update_device_state(device_key, DeviceState.ERROR)
|
|
490
|
+
return {"status": "error", "message": str(e)}
|
|
491
|
+
|
|
492
|
+
def _get_device_key(self, driver_name: str, device: Device = None, device_id: str = "") -> str:
|
|
493
|
+
"""Method to generate unified device key
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
driver_name: Driver name (e.g., 'drv_socketcan')
|
|
497
|
+
device: Device instance (optional)
|
|
498
|
+
device_id: Device ID (optional, used when device doesn't exist)
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
str: Formatted device key (e.g., 'drv_socketcan::vcan0')
|
|
502
|
+
"""
|
|
503
|
+
if device and hasattr(device, 'device_id'):
|
|
504
|
+
key = f"{driver_name}::{device.device_id}"
|
|
505
|
+
else:
|
|
506
|
+
key = f"{driver_name}::{device_id}"
|
|
507
|
+
return key
|
|
508
|
+
|
|
509
|
+
def _parse_device_key(self, device_key: str) -> tuple[str, str]:
|
|
510
|
+
"""Parse device key
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
device_key: Device key (e.g., 'drv_socketcan::vcan0')
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
tuple[str, str]: (driver_name, device_id)
|
|
517
|
+
"""
|
|
518
|
+
try:
|
|
519
|
+
driver_name, device_id = device_key.split("::", 1)
|
|
520
|
+
return driver_name, device_id
|
|
521
|
+
except ValueError:
|
|
522
|
+
logger.error(f"Invalid device key format: {device_key}")
|
|
523
|
+
return "", ""
|
|
524
|
+
|
|
525
|
+
def _handle_scan(self, driver: BaseDeviceDriver, driver_name: str, **kwargs) -> Dict:
|
|
526
|
+
"""Handle scan operation"""
|
|
527
|
+
try:
|
|
528
|
+
devices = driver.scan()
|
|
529
|
+
for device in devices:
|
|
530
|
+
device_key = self._get_device_key(driver_name, device)
|
|
531
|
+
self._update_device_state(device_key, DeviceState.DISCOVERED)
|
|
532
|
+
return {
|
|
533
|
+
"status": "success",
|
|
534
|
+
"devices": devices
|
|
535
|
+
}
|
|
536
|
+
except Exception as e:
|
|
537
|
+
return {"status": "error", "message": str(e)}
|
|
538
|
+
|
|
539
|
+
def _handle_initialize(self, driver: BaseDeviceDriver, driver_name: str, **kwargs) -> Dict:
|
|
540
|
+
"""Handle initialization operation"""
|
|
541
|
+
try:
|
|
542
|
+
device = kwargs.get('device')
|
|
543
|
+
if not device:
|
|
544
|
+
return {"status": "error", "message": "Device not specified"}
|
|
545
|
+
|
|
546
|
+
success = driver.initialize(device)
|
|
547
|
+
device_key = self._get_device_key(driver_name, device)
|
|
548
|
+
if success:
|
|
549
|
+
self._update_device_state(device_key, DeviceState.INITIALIZED)
|
|
550
|
+
return {
|
|
551
|
+
"status": "success",
|
|
552
|
+
"message": "Device initialized"
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
"status": "error",
|
|
556
|
+
"message": "Initialization failed"
|
|
557
|
+
}
|
|
558
|
+
except Exception as e:
|
|
559
|
+
return {"status": "error", "message": str(e)}
|
|
560
|
+
|
|
561
|
+
def _handle_connect(self, driver: BaseDeviceDriver, driver_name: str, **kwargs) -> Dict:
|
|
562
|
+
"""Handle connection operation"""
|
|
563
|
+
try:
|
|
564
|
+
device = kwargs.get('device')
|
|
565
|
+
if not device:
|
|
566
|
+
return {"status": "error", "message": "Device not specified"}
|
|
567
|
+
|
|
568
|
+
success = driver.connect(device)
|
|
569
|
+
device_key = self._get_device_key(driver_name, device)
|
|
570
|
+
if success:
|
|
571
|
+
self._update_device_state(device_key, DeviceState.CONNECTED)
|
|
572
|
+
return {
|
|
573
|
+
"status": "success",
|
|
574
|
+
"message": "Device connected"
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
"status": "error",
|
|
578
|
+
"message": "Connection failed"
|
|
579
|
+
}
|
|
580
|
+
except Exception as e:
|
|
581
|
+
return {"status": "error", "message": str(e)}
|
|
582
|
+
|
|
583
|
+
def _handle_command(self, driver: BaseDeviceDriver, driver_name: str, device_key: str, **kwargs) -> Dict:
|
|
584
|
+
"""Handle command execution"""
|
|
585
|
+
try:
|
|
586
|
+
command = kwargs.get('command')
|
|
587
|
+
args = kwargs.get('args', {})
|
|
588
|
+
device_id = kwargs.get('device_id')
|
|
589
|
+
|
|
590
|
+
if not command:
|
|
591
|
+
return {"status": "error", "message": "Command not specified"}
|
|
592
|
+
|
|
593
|
+
# Get device instance
|
|
594
|
+
device = driver.get_device(device_id)
|
|
595
|
+
if not device:
|
|
596
|
+
return {"status": "error", "message": f"Device {device_id} not found"}
|
|
597
|
+
|
|
598
|
+
# Move to ACTIVE
|
|
599
|
+
self._update_device_state(device_key, DeviceState.ACTIVE)
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
# Execute command using device instance
|
|
603
|
+
result = driver.command(device, command, args)
|
|
604
|
+
|
|
605
|
+
# Transition back to CONNECTED
|
|
606
|
+
self._update_device_state(device_key, DeviceState.CONNECTED)
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
"status": "success",
|
|
610
|
+
"result": result
|
|
611
|
+
}
|
|
612
|
+
except Exception as cmd_error:
|
|
613
|
+
# On command execution failure, still return to CONNECTED state, not ERROR
|
|
614
|
+
self._update_device_state(device_key, DeviceState.CONNECTED)
|
|
615
|
+
raise cmd_error
|
|
616
|
+
|
|
617
|
+
except Exception as e:
|
|
618
|
+
# Only set ERROR state when there's an issue with the device itself
|
|
619
|
+
if isinstance(e, (IOError, ConnectionError)):
|
|
620
|
+
self._update_device_state(device_key, DeviceState.ERROR)
|
|
621
|
+
return {"status": "error", "message": str(e)}
|
|
622
|
+
|
|
623
|
+
def _handle_reset(self, driver: BaseDeviceDriver, driver_name: str, **kwargs) -> Dict:
|
|
624
|
+
"""Handle reset operation"""
|
|
625
|
+
try:
|
|
626
|
+
device = kwargs.get('device')
|
|
627
|
+
if not device:
|
|
628
|
+
return {"status": "error", "message": "Device not specified"}
|
|
629
|
+
|
|
630
|
+
success = driver.reset(device)
|
|
631
|
+
return {
|
|
632
|
+
"status": "success" if success else "error",
|
|
633
|
+
"message": "Device reset" if success else "Reset failed"
|
|
634
|
+
}
|
|
635
|
+
except Exception as e:
|
|
636
|
+
return {"status": "error", "message": str(e)}
|
|
637
|
+
|
|
638
|
+
def _handle_close(self, driver: BaseDeviceDriver, driver_name: str, **kwargs) -> Dict:
|
|
639
|
+
"""Handle close operation"""
|
|
640
|
+
try:
|
|
641
|
+
device = kwargs.get('device')
|
|
642
|
+
if not device:
|
|
643
|
+
return {"status": "error", "message": "Device not specified"}
|
|
644
|
+
|
|
645
|
+
success = driver.close(device)
|
|
646
|
+
return {
|
|
647
|
+
"status": "success" if success else "error",
|
|
648
|
+
"message": "Device closed" if success else "Close failed"
|
|
649
|
+
}
|
|
650
|
+
except Exception as e:
|
|
651
|
+
return {"status": "error", "message": str(e)}
|
|
652
|
+
|
|
653
|
+
def get_driver_instance(self, plugin_name: str) -> Optional[BaseDeviceDriver]:
|
|
654
|
+
"""Get driver instance"""
|
|
655
|
+
return self.drivers.get(plugin_name)
|
|
656
|
+
|
|
657
|
+
def list_drivers(self) -> List[str]:
|
|
658
|
+
"""Get list of all loaded drivers
|
|
659
|
+
|
|
660
|
+
Returns:
|
|
661
|
+
List[str]: List of driver names
|
|
662
|
+
"""
|
|
663
|
+
return list(self.drivers.keys())
|
|
664
|
+
|
|
665
|
+
def cleanup_all_devices(self) -> Dict:
|
|
666
|
+
"""Clean up all device connections and reset states"""
|
|
667
|
+
logger.info("Starting device cleanup")
|
|
668
|
+
|
|
669
|
+
results = {}
|
|
670
|
+
for device_key in list(self.device_states.keys()):
|
|
671
|
+
logger.info(f"Closing device: {device_key}")
|
|
672
|
+
try:
|
|
673
|
+
driver_name, device_id = self._parse_device_key(device_key)
|
|
674
|
+
if not driver_name:
|
|
675
|
+
continue
|
|
676
|
+
|
|
677
|
+
driver = self.get_driver_instance(driver_name)
|
|
678
|
+
if not driver:
|
|
679
|
+
logger.warning(f"Driver {driver_name} not found")
|
|
680
|
+
continue
|
|
681
|
+
|
|
682
|
+
device = driver.get_device(device_id)
|
|
683
|
+
if device:
|
|
684
|
+
result = self._manage_device_lifecycle(
|
|
685
|
+
driver_name=driver_name,
|
|
686
|
+
action='close',
|
|
687
|
+
device=device
|
|
688
|
+
)
|
|
689
|
+
results[driver_name] = result
|
|
690
|
+
else:
|
|
691
|
+
logger.warning(f"Device not found for ID: {device_id}")
|
|
692
|
+
|
|
693
|
+
except Exception as e:
|
|
694
|
+
logger.error(f"Error closing device {device_key}: {str(e)}")
|
|
695
|
+
results[driver_name if 'driver_name' in locals() else device_key] = {
|
|
696
|
+
"status": "error",
|
|
697
|
+
"message": str(e)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
# Reset all internal state
|
|
701
|
+
self.device_states.clear()
|
|
702
|
+
self._connection_locks.clear()
|
|
703
|
+
|
|
704
|
+
# Note: Drivers don't have a global reset method - only individual devices can be reset
|
|
705
|
+
# The device reset is handled above in the device closing loop
|
|
706
|
+
|
|
707
|
+
return results
|
|
708
|
+
|
|
709
|
+
def initialize_all_devices(self) -> Dict:
|
|
710
|
+
"""Initialize and connect all available devices"""
|
|
711
|
+
logger.info("Starting device initialization")
|
|
712
|
+
|
|
713
|
+
# Reset internal state
|
|
714
|
+
self.device_states.clear()
|
|
715
|
+
self._connection_locks.clear()
|
|
716
|
+
|
|
717
|
+
# Get all available drivers
|
|
718
|
+
available_drivers = list(self.drivers.keys())
|
|
719
|
+
if not available_drivers:
|
|
720
|
+
return {
|
|
721
|
+
"status": "warning",
|
|
722
|
+
"message": "No device drivers available!"
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
results = {}
|
|
726
|
+
for driver_name in available_drivers:
|
|
727
|
+
try:
|
|
728
|
+
# Check if driver is enabled before proceeding
|
|
729
|
+
if not self.is_driver_enabled(driver_name):
|
|
730
|
+
logger.info(f"Skipping disabled driver: {driver_name}")
|
|
731
|
+
results[driver_name] = {
|
|
732
|
+
"status": "skipped",
|
|
733
|
+
"message": "Driver is disabled"
|
|
734
|
+
}
|
|
735
|
+
continue
|
|
736
|
+
|
|
737
|
+
logger.info(f"Initializing {driver_name}...")
|
|
738
|
+
|
|
739
|
+
# Scan devices using _manage_device_lifecycle
|
|
740
|
+
scan_result = self._manage_device_lifecycle(
|
|
741
|
+
driver_name=driver_name,
|
|
742
|
+
action='scan'
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
if scan_result['status'] != 'success':
|
|
746
|
+
results[driver_name] = {
|
|
747
|
+
"status": "error",
|
|
748
|
+
"message": f"Failed to scan: {scan_result.get('message', 'Unknown error')}"
|
|
749
|
+
}
|
|
750
|
+
continue
|
|
751
|
+
|
|
752
|
+
devices = scan_result.get('devices', [])
|
|
753
|
+
if not devices:
|
|
754
|
+
results[driver_name] = {
|
|
755
|
+
"status": "warning",
|
|
756
|
+
"message": "No devices found"
|
|
757
|
+
}
|
|
758
|
+
continue
|
|
759
|
+
|
|
760
|
+
# Process each device
|
|
761
|
+
device_results = []
|
|
762
|
+
for device in devices:
|
|
763
|
+
try:
|
|
764
|
+
# Initialize device using _manage_device_lifecycle
|
|
765
|
+
init_result = self._manage_device_lifecycle(
|
|
766
|
+
driver_name=driver_name,
|
|
767
|
+
action='initialize',
|
|
768
|
+
device=device
|
|
769
|
+
)
|
|
770
|
+
if init_result['status'] != 'success':
|
|
771
|
+
device_results.append({
|
|
772
|
+
"device": device.name,
|
|
773
|
+
"status": "error",
|
|
774
|
+
"message": f"Init failed: {init_result['message']}"
|
|
775
|
+
})
|
|
776
|
+
continue
|
|
777
|
+
|
|
778
|
+
# Connect device using _manage_device_lifecycle
|
|
779
|
+
connect_result = self._manage_device_lifecycle(
|
|
780
|
+
driver_name=driver_name,
|
|
781
|
+
action='connect',
|
|
782
|
+
device=device
|
|
783
|
+
)
|
|
784
|
+
if connect_result['status'] != 'success':
|
|
785
|
+
device_results.append({
|
|
786
|
+
"device": device.name,
|
|
787
|
+
"status": "error",
|
|
788
|
+
"message": f"Connect failed: {connect_result['message']}"
|
|
789
|
+
})
|
|
790
|
+
continue
|
|
791
|
+
|
|
792
|
+
device_results.append({
|
|
793
|
+
"device": device.name,
|
|
794
|
+
"status": "success",
|
|
795
|
+
"message": "Successfully connected"
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
except Exception as e:
|
|
799
|
+
device_results.append({
|
|
800
|
+
"device": getattr(device, 'name', 'Unknown'),
|
|
801
|
+
"status": "error",
|
|
802
|
+
"message": str(e)
|
|
803
|
+
})
|
|
804
|
+
|
|
805
|
+
results[driver_name] = {
|
|
806
|
+
"status": "success",
|
|
807
|
+
"devices": device_results
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
except Exception as e:
|
|
811
|
+
results[driver_name] = {
|
|
812
|
+
"status": "error",
|
|
813
|
+
"message": str(e)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return results
|
|
817
|
+
|
|
818
|
+
# Driver enable/disable methods
|
|
819
|
+
def enable_driver(self, driver_name: str, description=None) -> Dict:
|
|
820
|
+
"""Enable a device driver
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
driver_name: Name of driver to enable
|
|
824
|
+
description: Optional description of why the driver was enabled
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
Dict: Operation result
|
|
828
|
+
"""
|
|
829
|
+
if driver_name not in self.drivers:
|
|
830
|
+
return {
|
|
831
|
+
"status": "error",
|
|
832
|
+
"message": f"Driver {driver_name} not found"
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
self.driver_states[driver_name] = True
|
|
836
|
+
self._save_driver_state(driver_name, True, description)
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
"status": "success",
|
|
840
|
+
"message": f"Driver {driver_name} enabled",
|
|
841
|
+
"driver_name": driver_name,
|
|
842
|
+
"enabled": True
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
def disable_driver(self, driver_name: str, description=None) -> Dict:
|
|
846
|
+
"""Disable a device driver
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
driver_name: Name of driver to disable
|
|
850
|
+
description: Optional description of why the driver was disabled
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
Dict: Operation result
|
|
854
|
+
"""
|
|
855
|
+
if driver_name not in self.drivers:
|
|
856
|
+
return {
|
|
857
|
+
"status": "error",
|
|
858
|
+
"message": f"Driver {driver_name} not found"
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
# Close all connected devices for this driver
|
|
862
|
+
closed_devices = self._close_all_driver_devices(driver_name)
|
|
863
|
+
|
|
864
|
+
self.driver_states[driver_name] = False
|
|
865
|
+
self._save_driver_state(driver_name, False, description)
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
"status": "success",
|
|
869
|
+
"message": f"Driver {driver_name} disabled",
|
|
870
|
+
"driver_name": driver_name,
|
|
871
|
+
"enabled": False,
|
|
872
|
+
"closed_devices": closed_devices
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
def _close_all_driver_devices(self, driver_name: str) -> List[str]:
|
|
876
|
+
"""Close all devices for a specific driver
|
|
877
|
+
|
|
878
|
+
Args:
|
|
879
|
+
driver_name: Driver name
|
|
880
|
+
|
|
881
|
+
Returns:
|
|
882
|
+
List[str]: List of closed device IDs
|
|
883
|
+
"""
|
|
884
|
+
closed_devices = []
|
|
885
|
+
|
|
886
|
+
# Find all connected devices for this driver
|
|
887
|
+
for device_key in list(self.device_states.keys()):
|
|
888
|
+
key_driver_name, device_id = self._parse_device_key(device_key)
|
|
889
|
+
|
|
890
|
+
if key_driver_name == driver_name:
|
|
891
|
+
try:
|
|
892
|
+
driver = self.get_driver_instance(driver_name)
|
|
893
|
+
if driver:
|
|
894
|
+
device = driver.get_device(device_id)
|
|
895
|
+
if device:
|
|
896
|
+
self._manage_device_lifecycle(
|
|
897
|
+
driver_name=driver_name,
|
|
898
|
+
action='close',
|
|
899
|
+
device=device
|
|
900
|
+
)
|
|
901
|
+
closed_devices.append(device_id)
|
|
902
|
+
logger.info(f"Closed device {device_id} for disabled driver {driver_name}")
|
|
903
|
+
except Exception as e:
|
|
904
|
+
logger.error(f"Error closing device {device_id} for driver {driver_name}: {e}")
|
|
905
|
+
|
|
906
|
+
return closed_devices
|
|
907
|
+
|
|
908
|
+
def is_driver_enabled(self, driver_name: str) -> bool:
|
|
909
|
+
"""Check if a driver is enabled
|
|
910
|
+
|
|
911
|
+
Args:
|
|
912
|
+
driver_name: Driver name to check
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
bool: True if driver is enabled, False otherwise
|
|
916
|
+
"""
|
|
917
|
+
if driver_name not in self.drivers:
|
|
918
|
+
return False
|
|
919
|
+
|
|
920
|
+
return self.driver_states.get(driver_name, True)
|
|
921
|
+
|
|
922
|
+
def get_driver_states(self) -> Dict[str, Dict]:
|
|
923
|
+
"""Get states of all drivers
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
Dict[str, Dict]: Dictionary of driver states
|
|
927
|
+
"""
|
|
928
|
+
try:
|
|
929
|
+
result: Dict[str, Dict] = {}
|
|
930
|
+
persisted = self._driver_state_repo.list_enabled()
|
|
931
|
+
for driver_name in self.drivers.keys():
|
|
932
|
+
enabled = persisted.get(driver_name, True)
|
|
933
|
+
result[driver_name] = {
|
|
934
|
+
"driver_name": driver_name,
|
|
935
|
+
"enabled": bool(enabled),
|
|
936
|
+
"description": None,
|
|
937
|
+
"last_updated": None,
|
|
938
|
+
}
|
|
939
|
+
return result
|
|
940
|
+
except Exception as e:
|
|
941
|
+
logger.error(f"Error getting driver states: {e}")
|
|
942
|
+
return {}
|