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.
Files changed (41) hide show
  1. iotsploit_core/__init__.py +7 -0
  2. iotsploit_core/context.py +27 -0
  3. iotsploit_core/core/__init__.py +0 -0
  4. iotsploit_core/core/base_plugin.py +290 -0
  5. iotsploit_core/core/device_config.py +105 -0
  6. iotsploit_core/core/device_manager.py +942 -0
  7. iotsploit_core/core/device_registry.py +118 -0
  8. iotsploit_core/core/device_scanner.py +74 -0
  9. iotsploit_core/core/device_spec.py +105 -0
  10. iotsploit_core/core/device_store.py +86 -0
  11. iotsploit_core/core/execution_backend.py +464 -0
  12. iotsploit_core/core/execution_queue.py +456 -0
  13. iotsploit_core/core/exploit_manager.py +728 -0
  14. iotsploit_core/core/exploit_spec.py +73 -0
  15. iotsploit_core/core/stream_manager.py +157 -0
  16. iotsploit_core/core/tool_config.py +430 -0
  17. iotsploit_core/core/tool_manager.py +1166 -0
  18. iotsploit_core/core/tool_service.py +936 -0
  19. iotsploit_core/domain/__init__.py +3 -0
  20. iotsploit_core/domain/device.py +70 -0
  21. iotsploit_core/domain/execution_plan.py +29 -0
  22. iotsploit_core/domain/plugin.py +17 -0
  23. iotsploit_core/domain/stream.py +63 -0
  24. iotsploit_core/domain/target.py +243 -0
  25. iotsploit_core/platforms/__init__.py +19 -0
  26. iotsploit_core/platforms/consts.py +25 -0
  27. iotsploit_core/ports/__init__.py +6 -0
  28. iotsploit_core/ports/driver_state_repo.py +15 -0
  29. iotsploit_core/ports/plugin_repo.py +26 -0
  30. iotsploit_core/ports/stream_backend.py +28 -0
  31. iotsploit_core/ports/task_runner.py +22 -0
  32. iotsploit_core/ports/wifi_backend.py +148 -0
  33. iotsploit_core/py.typed +2 -0
  34. iotsploit_core/utils/__init__.py +53 -0
  35. iotsploit_core/utils/exceptions.py +152 -0
  36. iotsploit_core/utils/helpers.py +65 -0
  37. iotsploit_core/utils/iots_logger.py +164 -0
  38. iotsploit_core/utils/result.py +41 -0
  39. iotsploit_core-0.0.6.dist-info/METADATA +117 -0
  40. iotsploit_core-0.0.6.dist-info/RECORD +41 -0
  41. 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 {}