summaryrefslogtreecommitdiff
path: root/ui/devices_window.py
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-09-19 17:38:07 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-09-19 17:38:07 -0600
commitf3327d56d48d63b21ffc03a55fa5e7db753d1707 (patch)
treee19d4c7846dd4a434f8a18f82290597731b4a12c /ui/devices_window.py
parent00671a90bb51bfe485d98a928a456d55adbe1faf (diff)
Redesign Signal/Channel naming and fix pin-remap breaking references
Arduino analog Signal ids used to be the pin name itself, so fixing a wrong pin silently changed the id and orphaned any plot trace, control, or filter pipeline pointing at it. Analog Signal ids are now positional (ai0, ai1, ...) and decoupled from the underlying pin, matching how digital I/O already worked. More broadly, formalizes a Device -> Signal -> Channel -> Plot naming model (SignalConfig for physical device channels, Channel for derived/ computed ones) and adds core/reference_registry.py to propagate any Signal or Channel id rename through plot traces, controls, and filter pipelines instead of leaving them dangling. Signal ids are now editable in the Signals tab with the same propagation. .labui profile schema bumped to v2.0 with a transparent migration for old files, including remapping old pin-name Signal ids to the new positional ones. Removed six confirmed-dead files (two legacy device classes, four superseded top-level ui/ duplicates).
Diffstat (limited to 'ui/devices_window.py')
-rw-r--r--ui/devices_window.py224
1 files changed, 0 insertions, 224 deletions
diff --git a/ui/devices_window.py b/ui/devices_window.py
deleted file mode 100644
index 0548330..0000000
--- a/ui/devices_window.py
+++ /dev/null
@@ -1,224 +0,0 @@
-"""
-ui/devices_window.py
-
-Floating "Devices" settings window.
-Opens from the toolbar ⚙ DEVICES button; can stay open alongside the main window.
-Contains: device status cards, Add Device, and per-device Configure.
-"""
-
-from PyQt6.QtWidgets import (
- QWidget, QVBoxLayout, QHBoxLayout, QLabel,
- QPushButton, QScrollArea, QFrame, QSizePolicy,
- QDialog,
-)
-from PyQt6.QtCore import Qt, pyqtSignal, QTimer
-from PyQt6.QtGui import QCloseEvent
-
-from devices.base_device import DeviceStatus
-from devices.device_registry import DeviceRegistry
-from core.acquisition import AcquisitionEngine
-
-
-_STATUS_STYLE = {
- DeviceStatus.CONNECTED: "color:#22c55e;",
- DeviceStatus.SIMULATED: "color:#3b82f6;",
- DeviceStatus.DISCONNECTED: "color:#475569;",
- DeviceStatus.CONNECTING: "color:#f59e0b;",
- DeviceStatus.ERROR: "color:#ef4444;",
-}
-_STATUS_LABEL = {
- DeviceStatus.CONNECTED: "CONNECTED",
- DeviceStatus.SIMULATED: "SIMULATED",
- DeviceStatus.DISCONNECTED: "OFFLINE",
- DeviceStatus.CONNECTING: "CONNECTING",
- DeviceStatus.ERROR: "ERROR",
-}
-
-
-class DeviceCard(QFrame):
- config_requested = pyqtSignal(str)
-
- def __init__(self, device):
- super().__init__()
- self.device = device
- self.setObjectName("deviceCard")
- self._build()
-
- def _build(self):
- layout = QVBoxLayout(self)
- layout.setContentsMargins(10, 10, 10, 10)
- layout.setSpacing(5)
-
- # Header: icon + name + status dot
- hdr = QHBoxLayout()
- icon_lbl = QLabel(self.device.info.icon)
- icon_lbl.setObjectName("deviceIcon")
- hdr.addWidget(icon_lbl)
-
- name_lbl = QLabel(self.device.info.name)
- name_lbl.setObjectName("deviceName")
- hdr.addWidget(name_lbl, 1)
-
- self._dot = QLabel("●")
- self._dot.setStyleSheet(
- _STATUS_STYLE.get(self.device.status, "color:#475569;") + " font-size:10px;"
- )
- hdr.addWidget(self._dot)
- layout.addLayout(hdr)
-
- # ID
- id_lbl = QLabel(self.device.info.device_id)
- id_lbl.setObjectName("deviceSub")
- layout.addWidget(id_lbl)
-
- # Status text
- self._status_lbl = QLabel(_STATUS_LABEL.get(self.device.status, "UNKNOWN"))
- self._apply_status_style()
- layout.addWidget(self._status_lbl)
-
- # Channel count · type
- n = sum(1 for c in self.device.info.channels if c.enabled)
- ch_lbl = QLabel(f"{n} ch · {self.device.info.device_type}")
- ch_lbl.setObjectName("deviceChannelCount")
- layout.addWidget(ch_lbl)
-
- # Configure button
- cfg = QPushButton("Configure")
- cfg.setObjectName("configButton")
- cfg.clicked.connect(lambda: self.config_requested.emit(self.device.info.device_id))
- layout.addWidget(cfg)
-
- def refresh(self):
- dot_style = _STATUS_STYLE.get(self.device.status, "color:#475569;") + " font-size:10px;"
- self._dot.setStyleSheet(dot_style)
- self._status_lbl.setText(_STATUS_LABEL.get(self.device.status, "UNKNOWN"))
- self._apply_status_style()
-
- def _apply_status_style(self):
- s = _STATUS_STYLE.get(self.device.status, "color:#475569;")
- self._status_lbl.setStyleSheet(
- s + " font-family:'IBM Plex Mono',monospace;"
- " font-size:10px; font-weight:700; letter-spacing:1px;"
- )
-
-
-class DevicesWindow(QWidget):
- """
- Standalone floating window for device management.
-
- Signals
- -------
- config_requested(device_id) — user clicked Configure on a card
- device_added() — a new device was added via the dialog
- closed() — window was closed (so toolbar btn can untoggle)
- """
-
- config_requested = pyqtSignal(str)
- device_added = pyqtSignal()
- closed = pyqtSignal()
-
- def __init__(self, registry: DeviceRegistry, engine: AcquisitionEngine, parent=None):
- super().__init__(
- parent,
- # Acts like a real standalone window but stays on top of parent
- Qt.WindowType.Window |
- Qt.WindowType.Tool,
- )
- self.registry = registry
- self.engine = engine
- self._cards: dict = {}
-
- self.setWindowTitle("Devices")
- self.setMinimumSize(300, 480)
- self.resize(320, 600)
- self._build()
- self.refresh()
-
- self._timer = QTimer(self)
- self._timer.setInterval(2000)
- self._timer.timeout.connect(self._refresh_status)
- self._timer.start()
-
- # ── Build ─────────────────────────────────────────────────────────────
-
- def _build(self):
- root = QVBoxLayout(self)
- root.setContentsMargins(0, 0, 0, 0)
- root.setSpacing(0)
-
- # ── Title bar row ──────────────────────────────────────────────
- title_bar = QWidget()
- title_bar.setObjectName("devWindowTitleBar")
- title_bar.setFixedHeight(40)
- tb_lay = QHBoxLayout(title_bar)
- tb_lay.setContentsMargins(12, 0, 8, 0)
-
- title_lbl = QLabel("⚙ DEVICES")
- title_lbl.setObjectName("devWindowTitle")
- tb_lay.addWidget(title_lbl, 1)
-
- self._add_btn = QPushButton("+ Add")
- self._add_btn.setObjectName("addDeviceButton")
- self._add_btn.clicked.connect(self._on_add)
- tb_lay.addWidget(self._add_btn)
-
- root.addWidget(title_bar)
-
- # ── Divider ────────────────────────────────────────────────────
- div = QFrame()
- div.setFrameShape(QFrame.Shape.HLine)
- div.setObjectName("devWindowDivider")
- root.addWidget(div)
-
- # ── Scrollable device cards ────────────────────────────────────
- scroll = QScrollArea()
- scroll.setObjectName("deviceScroll")
- scroll.setWidgetResizable(True)
- scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
-
- self._container = QWidget()
- self._inner = QVBoxLayout(self._container)
- self._inner.setContentsMargins(10, 10, 10, 10)
- self._inner.setSpacing(10)
- self._inner.addStretch()
-
- scroll.setWidget(self._container)
- root.addWidget(scroll)
-
- # ── Public API ────────────────────────────────────────────────────────
-
- def refresh(self):
- """Rebuild all device cards from current registry state."""
- for card in self._cards.values():
- self._inner.removeWidget(card)
- card.deleteLater()
- self._cards.clear()
-
- for dev in self.registry.all_instances():
- card = DeviceCard(dev)
- card.config_requested.connect(self.config_requested.emit)
- self._inner.insertWidget(self._inner.count() - 1, card)
- self._cards[dev.info.device_id] = card
-
- # ── Slots ────────────────────────────────────────────────────────────
-
- def _on_add(self):
- from ui.add_device_dialog import AddDeviceDialog
- from ui.config_dialog import DeviceConfigDialog
- dlg = AddDeviceDialog(self.registry, self)
- if dlg.exec():
- dev = dlg.created_device
- if dev:
- dev.connect()
- self.registry.add_instance(dev)
- self.engine.add_device(dev)
- self.refresh()
- self.device_added.emit()
-
- def _refresh_status(self):
- for card in self._cards.values():
- card.refresh()
-
- def closeEvent(self, event: QCloseEvent):
- self.closed.emit()
- event.accept()