added gui

This commit is contained in:
2026-05-24 11:52:03 +02:00
parent 74b949f864
commit 561d4ad77f
10 changed files with 1592 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
import sys, os
_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_gui = os.path.dirname(os.path.abspath(__file__))
for _p in [_root, _gui]:
if _p not in sys.path:
sys.path.insert(0, _p)
from PyQt5.QtWidgets import (
QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QLabel,
QLineEdit, QComboBox, QSpinBox, QStackedWidget, QScrollArea,
QFrame, QMessageBox, QSizePolicy,
)
from PyQt5.QtCore import pyqtSignal, Qt
from data_model import ReceiverManager, ConnectionConfig
BAUD_RATES = ['4800', '9600', '19200', '38400', '57600', '115200',
'230400', '460800', '921600']
class _StatusDot(QLabel):
def __init__(self):
super().__init__("")
self.setFixedWidth(20)
self.setAlignment(Qt.AlignCenter)
self.set_connected(False)
def set_connected(self, connected: bool):
color = '#4CAF50' if connected else '#9E9E9E'
self.setStyleSheet(f"color: {color}; font-size: 16px;")
class ConnectionRow(QWidget):
remove_requested = pyqtSignal(object)
def __init__(self, model: ReceiverManager, config: ConnectionConfig = None):
super().__init__()
self._model = model
self._rid: str = None
self._connected = False
layout = QHBoxLayout(self)
layout.setContentsMargins(4, 2, 4, 2)
layout.setSpacing(6)
# ── History dropdown ─────────────────────────────────────────────────
self._hist_combo = QComboBox()
self._hist_combo.setPlaceholderText("History ▾")
self._hist_combo.setMinimumWidth(150)
self._hist_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self._hist_combo.currentIndexChanged.connect(self._load_history)
layout.addWidget(self._hist_combo)
sep = QFrame()
sep.setFrameShape(QFrame.VLine)
sep.setFrameShadow(QFrame.Sunken)
layout.addWidget(sep)
# ── Receiver ID ──────────────────────────────────────────────────────
layout.addWidget(QLabel("ID:"))
self._id_edit = QLineEdit()
self._id_edit.setPlaceholderText("receiver-id")
self._id_edit.setFixedWidth(110)
layout.addWidget(self._id_edit)
# ── Connection type ──────────────────────────────────────────────────
self._type_combo = QComboBox()
self._type_combo.addItems(['TCP', 'Serial'])
self._type_combo.currentIndexChanged.connect(self._on_type_changed)
layout.addWidget(self._type_combo)
# ── Stacked: TCP page / Serial page ─────────────────────────────────
self._stack = QStackedWidget()
# TCP page
tcp_w = QWidget()
tcp_l = QHBoxLayout(tcp_w)
tcp_l.setContentsMargins(0, 0, 0, 0)
tcp_l.setSpacing(4)
tcp_l.addWidget(QLabel("Host:"))
self._host_edit = QLineEdit("192.168.1.1")
self._host_edit.setFixedWidth(130)
tcp_l.addWidget(self._host_edit)
tcp_l.addWidget(QLabel("Port:"))
self._port_spin = QSpinBox()
self._port_spin.setRange(1, 65535)
self._port_spin.setValue(8721)
self._port_spin.setFixedWidth(75)
tcp_l.addWidget(self._port_spin)
self._stack.addWidget(tcp_w)
# Serial page
ser_w = QWidget()
ser_l = QHBoxLayout(ser_w)
ser_l.setContentsMargins(0, 0, 0, 0)
ser_l.setSpacing(4)
ser_l.addWidget(QLabel("Port:"))
self._ser_combo = QComboBox()
self._ser_combo.setEditable(True)
self._ser_combo.setMinimumWidth(130)
self._populate_serial_ports()
ser_l.addWidget(self._ser_combo)
ser_l.addWidget(QLabel("Baud:"))
self._baud_combo = QComboBox()
self._baud_combo.addItems(BAUD_RATES)
self._baud_combo.setCurrentText('115200')
ser_l.addWidget(self._baud_combo)
self._stack.addWidget(ser_w)
layout.addWidget(self._stack)
layout.addStretch()
# ── Status / buttons ─────────────────────────────────────────────────
self._dot = _StatusDot()
layout.addWidget(self._dot)
self._conn_btn = QPushButton("Connect")
self._conn_btn.setFixedWidth(95)
self._conn_btn.clicked.connect(self._toggle)
layout.addWidget(self._conn_btn)
rm_btn = QPushButton("Remove")
rm_btn.setFixedWidth(70)
rm_btn.clicked.connect(lambda: self.remove_requested.emit(self))
layout.addWidget(rm_btn)
if config:
self.apply_config(config)
self.refresh_history()
# ── Serial port detection ────────────────────────────────────────────────
def _populate_serial_ports(self):
try:
import serial.tools.list_ports
ports = [p.device for p in serial.tools.list_ports.comports()]
except Exception:
ports = []
self._ser_combo.addItems(ports or ['/dev/ttyUSB0', 'COM1'])
# ── Config helpers ───────────────────────────────────────────────────────
def apply_config(self, config: ConnectionConfig):
self._id_edit.setText(config.receiver_id)
self._type_combo.setCurrentIndex(0 if config.conn_type == 'tcp' else 1)
self._host_edit.setText(config.host)
self._port_spin.setValue(config.port)
if config.serial_port:
idx = self._ser_combo.findText(config.serial_port)
self._ser_combo.setCurrentIndex(idx) if idx >= 0 \
else self._ser_combo.setCurrentText(config.serial_port)
self._baud_combo.setCurrentText(str(config.baud_rate))
def get_config(self) -> ConnectionConfig:
t = 'tcp' if self._type_combo.currentIndex() == 0 else 'serial'
return ConnectionConfig(
receiver_id = self._id_edit.text().strip(),
conn_type = t,
host = self._host_edit.text().strip(),
port = self._port_spin.value(),
serial_port = self._ser_combo.currentText().strip(),
baud_rate = int(self._baud_combo.currentText()),
)
def get_receiver_id(self) -> str:
return self._rid
def refresh_history(self):
self._hist_combo.blockSignals(True)
self._hist_combo.clear()
self._hist_combo.addItem("History ▾")
for cfg in self._model.get_lru():
self._hist_combo.addItem(cfg.display_str(), userData=cfg)
self._hist_combo.blockSignals(False)
def set_connected(self, connected: bool):
self._connected = connected
self._dot.set_connected(connected)
self._conn_btn.setText("Disconnect" if connected else "Connect")
for w in [self._id_edit, self._type_combo, self._host_edit,
self._port_spin, self._ser_combo, self._baud_combo]:
w.setEnabled(not connected)
# ── Slots ────────────────────────────────────────────────────────────────
def _on_type_changed(self, idx: int):
self._stack.setCurrentIndex(idx)
def _load_history(self, idx: int):
if idx <= 0:
return
cfg = self._hist_combo.itemData(idx)
if cfg:
self.apply_config(cfg)
self._hist_combo.blockSignals(True)
self._hist_combo.setCurrentIndex(0)
self._hist_combo.blockSignals(False)
def _toggle(self):
if self._connected:
if self._rid:
self._model.disconnect_receiver(self._rid)
return
config = self.get_config()
if not config.receiver_id:
QMessageBox.warning(self, "GNSS Monitor",
"Receiver-ID darf nicht leer sein.")
return
if config.receiver_id in self._model.get_receiver_ids():
QMessageBox.warning(self, "GNSS Monitor",
f"ID '{config.receiver_id}' wird bereits verwendet.")
return
self._model.add_receiver(config)
self._rid = config.receiver_id
try:
self._model.connect_receiver(config.receiver_id)
except Exception as exc:
QMessageBox.critical(self, "Verbindungsfehler", str(exc))
self._model.remove_receiver(config.receiver_id)
self._rid = None
# ── Connection tab ────────────────────────────────────────────────────────────
class ConnectionTab(QWidget):
def __init__(self, model: ReceiverManager):
super().__init__()
self._model = model
self._rows: list = []
outer = QVBoxLayout(self)
outer.setContentsMargins(8, 8, 8, 8)
outer.setSpacing(6)
# Scroll area
self._scroll = QScrollArea()
self._scroll.setWidgetResizable(True)
self._scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self._inner = QWidget()
self._vbox = QVBoxLayout(self._inner)
self._vbox.setContentsMargins(0, 0, 0, 0)
self._vbox.setSpacing(2)
self._vbox.addStretch()
self._scroll.setWidget(self._inner)
outer.addWidget(self._scroll)
add_btn = QPushButton(" Add Connection")
add_btn.setMaximumWidth(180)
add_btn.clicked.connect(lambda: self._add_row())
outer.addWidget(add_btn, alignment=Qt.AlignLeft)
model.connection_changed.connect(self._on_connection_changed)
def _add_row(self, config: ConnectionConfig = None):
row = ConnectionRow(self._model, config)
row.remove_requested.connect(self._remove_row)
# Insert before the trailing stretch
self._vbox.insertWidget(self._vbox.count() - 1, row)
self._rows.append(row)
def _remove_row(self, row: ConnectionRow):
rid = row.get_receiver_id()
if rid:
self._model.remove_receiver(rid)
self._vbox.removeWidget(row)
row.deleteLater()
if row in self._rows:
self._rows.remove(row)
def _on_connection_changed(self, rid: str, connected: bool):
for row in self._rows:
if row.get_receiver_id() == rid:
row.set_connected(connected)
if connected:
for r in self._rows:
r.refresh_history()
break