summaryrefslogtreecommitdiff
path: root/daq_system/ui/readout_panel.py
diff options
context:
space:
mode:
Diffstat (limited to 'daq_system/ui/readout_panel.py')
-rw-r--r--daq_system/ui/readout_panel.py128
1 files changed, 128 insertions, 0 deletions
diff --git a/daq_system/ui/readout_panel.py b/daq_system/ui/readout_panel.py
new file mode 100644
index 0000000..6086c30
--- /dev/null
+++ b/daq_system/ui/readout_panel.py
@@ -0,0 +1,128 @@
+"""
+ui/readout_panel.py
+
+Right-side numeric readout panel. Shows current value for every channel
+with a colored bar indicator and alarm highlighting.
+"""
+
+from PyQt6.QtWidgets import (
+ QWidget, QVBoxLayout, QHBoxLayout, QLabel,
+ QScrollArea, QFrame, QProgressBar
+)
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtGui import QColor
+
+from devices.device_registry import DeviceRegistry
+
+
+class ChannelReadout(QFrame):
+ def __init__(self, device_name: str, channel_config, parent=None):
+ super().__init__(parent)
+ self.ch = channel_config
+ self.device_name = device_name
+ self.setObjectName("channelReadout")
+ self._alarm_active = False
+ self._build()
+
+ def _build(self):
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(8, 6, 8, 6)
+ layout.setSpacing(2)
+
+ top = QHBoxLayout()
+ self.name_lbl = QLabel(f"{self.ch.name}")
+ self.name_lbl.setObjectName("readoutName")
+ top.addWidget(self.name_lbl)
+ top.addStretch()
+ self.value_lbl = QLabel("---")
+ self.value_lbl.setObjectName("readoutValue")
+ self.value_lbl.setStyleSheet(f"color: {self.ch.color};")
+ top.addWidget(self.value_lbl)
+ unit = QLabel(self.ch.unit)
+ unit.setObjectName("readoutUnit")
+ top.addWidget(unit)
+ layout.addLayout(top)
+
+ # Mini bar
+ self.bar = QProgressBar()
+ self.bar.setRange(0, 1000)
+ self.bar.setValue(0)
+ self.bar.setTextVisible(False)
+ self.bar.setMaximumHeight(4)
+ self.bar.setObjectName("readoutBar")
+ self.bar.setStyleSheet(
+ f"QProgressBar::chunk {{ background: {self.ch.color}; border-radius: 2px; }}"
+ )
+ layout.addWidget(self.bar)
+
+ def update_value(self, value: float):
+ self.value_lbl.setText(f"{value:.3f}")
+ rng = self.ch.max_value - self.ch.min_value
+ if rng > 0:
+ norm = int(((value - self.ch.min_value) / rng) * 1000)
+ self.bar.setValue(max(0, min(1000, norm)))
+
+ def set_alarm(self, active: bool):
+ if active != self._alarm_active:
+ self._alarm_active = active
+ self.setProperty("alarm", active)
+ self.style().unpolish(self)
+ self.style().polish(self)
+
+
+class ReadoutPanel(QWidget):
+ def __init__(self, registry: DeviceRegistry):
+ super().__init__()
+ self.registry = registry
+ self._readouts: dict = {} # (device_id, channel_id) -> ChannelReadout
+ self._build_ui()
+ self.refresh()
+
+ def _build_ui(self):
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(0)
+
+ hdr = QLabel(" CHANNELS")
+ hdr.setObjectName("panelHeader")
+ layout.addWidget(hdr)
+
+ scroll = QScrollArea()
+ scroll.setWidgetResizable(True)
+ scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
+
+ self._container = QWidget()
+ self._inner = QVBoxLayout(self._container)
+ self._inner.setContentsMargins(6, 6, 6, 6)
+ self._inner.setSpacing(4)
+ self._inner.addStretch()
+
+ scroll.setWidget(self._container)
+ layout.addWidget(scroll)
+
+ def refresh(self):
+ for w in self._readouts.values():
+ self._inner.removeWidget(w)
+ w.deleteLater()
+ self._readouts.clear()
+
+ for dev in self.registry.all_instances():
+ for ch in dev.info.channels:
+ if not ch.enabled:
+ continue
+ ro = ChannelReadout(dev.info.name, ch)
+ self._inner.insertWidget(self._inner.count() - 1, ro)
+ self._readouts[(dev.info.device_id, ch.channel_id)] = ro
+
+ @pyqtSlot(str, str, float, float)
+ def on_new_data(self, device_id: str, channel_id: str, timestamp: float, value: float):
+ ro = self._readouts.get((device_id, channel_id))
+ if ro:
+ ro.update_value(value)
+ ch = ro.ch
+ alarm = False
+ if ch.alarm_low is not None and value < ch.alarm_low:
+ alarm = True
+ if ch.alarm_high is not None and value > ch.alarm_high:
+ alarm = True
+ ro.set_alarm(alarm)