""" ui/components/device_management_panel.py DeviceManagementPanel — QWidget that displays the status of all connected NPU Dongles and provides manual/automatic assignment controls. """ from __future__ import annotations from typing import List, Optional from PyQt5.QtCore import QTimer, pyqtSignal from PyQt5.QtWidgets import ( QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget, ) from core.device.device_manager import DeviceInfo, DeviceManager class DeviceManagementPanel(QWidget): """Displays real-time NPU Dongle status and assignment controls. Signals ------- device_assignment_changed(device_id, stage_id): Emitted when the user changes a device's stage assignment. """ device_assignment_changed = pyqtSignal(str, str) def __init__( self, device_manager: DeviceManager, parent: Optional[QWidget] = None, ) -> None: super().__init__(parent) self._device_manager = device_manager self._devices: List[DeviceInfo] = [] self._auto_refresh_interval_ms: int = 0 self._timer: Optional[QTimer] = None self._setup_ui() self.refresh() # ------------------------------------------------------------------ # UI construction # ------------------------------------------------------------------ def _setup_ui(self) -> None: layout = QVBoxLayout() # Toolbar row: Auto Balance button toolbar = QHBoxLayout() self.auto_balance_button = QPushButton("Auto Balance") self.auto_balance_button.clicked.connect(self._on_auto_balance) toolbar.addWidget(self.auto_balance_button) toolbar.addStretch() # Device cards area self._cards_layout = QVBoxLayout() self._no_device_label = QLabel("No devices connected") layout.addLayout(toolbar) layout.addWidget(self._no_device_label) layout.addLayout(self._cards_layout) self.setLayout(layout) # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ def refresh(self) -> None: """Re-scan devices and update the displayed cards.""" self._devices = self._device_manager.scan_devices() self._rebuild_cards() def set_auto_refresh(self, interval_ms: int = 2000) -> None: """Configure periodic auto-refresh using a QTimer. Parameters ---------- interval_ms: Refresh interval in milliseconds. Defaults to 2 000 ms. """ if interval_ms <= 0: if self._timer is not None: self._timer.stop() return self._auto_refresh_interval_ms = interval_ms if self._timer is None: self._timer = QTimer(self) self._timer.timeout.connect(self.refresh) self._timer.start(interval_ms) # ------------------------------------------------------------------ # Private helpers # ------------------------------------------------------------------ def _rebuild_cards(self) -> None: """Recreate device card widgets from the current device list.""" if not self._devices: self._no_device_label.setVisible(True) return self._no_device_label.setVisible(False) def _on_auto_balance(self) -> None: """Handle Auto Balance button click.""" if not self._devices: return stage_ids = [ d.assigned_stage for d in self._devices if d.assigned_stage ] if not stage_ids: return recommendations = self._device_manager.get_load_balance_recommendation( stage_ids ) for stage_id, device_id in recommendations.items(): if device_id: self.device_assignment_changed.emit(device_id, stage_id)