gui: add File source type to connection tab
- ConnectionConfig: new file_path and delay fields - ConnectionRow: File page with path input, Browse button, delay spinner (0–10 s), and auto-fill of receiver-ID from filename - ReceiverManager.connect_receiver: FileBackend wired for 'file' type; UBX poll timer skipped for file sources - FileBackend.register_xcvr: fallback to first unoccupied entry when name does not match filename pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+54
-6
@@ -8,12 +8,13 @@ for _p in [_root, _gui]:
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QLabel,
|
||||
QLineEdit, QComboBox, QSpinBox, QStackedWidget, QScrollArea,
|
||||
QFrame, QMessageBox, QSizePolicy,
|
||||
QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox, QStackedWidget,
|
||||
QScrollArea, QFrame, QMessageBox, QSizePolicy, QFileDialog,
|
||||
)
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
|
||||
from data_model import ReceiverManager, ConnectionConfig
|
||||
from backend.file_backend import source_id_from_path
|
||||
|
||||
BAUD_RATES = ['4800', '9600', '19200', '38400', '57600', '115200',
|
||||
'230400', '460800', '921600']
|
||||
@@ -53,7 +54,7 @@ class ConnectionRow(QWidget):
|
||||
|
||||
# ── Connection type ──────────────────────────────────────────────────
|
||||
self._type_combo = QComboBox()
|
||||
self._type_combo.addItems(['TCP', 'Serial'])
|
||||
self._type_combo.addItems(['TCP', 'Serial', 'File'])
|
||||
self._type_combo.currentIndexChanged.connect(self._on_type_changed)
|
||||
layout.addWidget(self._type_combo)
|
||||
|
||||
@@ -95,6 +96,32 @@ class ConnectionRow(QWidget):
|
||||
ser_l.addWidget(self._baud_combo)
|
||||
self._stack.addWidget(ser_w)
|
||||
|
||||
# File page
|
||||
file_w = QWidget()
|
||||
file_l = QHBoxLayout(file_w)
|
||||
file_l.setContentsMargins(0, 0, 0, 0)
|
||||
file_l.setSpacing(4)
|
||||
file_l.addWidget(QLabel("Path:"))
|
||||
self._file_edit = QLineEdit()
|
||||
self._file_edit.setPlaceholderText("log file path…")
|
||||
self._file_edit.setMinimumWidth(200)
|
||||
self._file_edit.textChanged.connect(self._on_file_path_changed)
|
||||
file_l.addWidget(self._file_edit)
|
||||
browse_btn = QPushButton("Browse")
|
||||
browse_btn.setFixedWidth(60)
|
||||
browse_btn.clicked.connect(self._browse_file)
|
||||
file_l.addWidget(browse_btn)
|
||||
file_l.addWidget(QLabel("Delay:"))
|
||||
self._delay_spin = QDoubleSpinBox()
|
||||
self._delay_spin.setRange(0.0, 10.0)
|
||||
self._delay_spin.setSingleStep(0.1)
|
||||
self._delay_spin.setDecimals(1)
|
||||
self._delay_spin.setValue(1.0)
|
||||
self._delay_spin.setSuffix(" s")
|
||||
self._delay_spin.setFixedWidth(72)
|
||||
file_l.addWidget(self._delay_spin)
|
||||
self._stack.addWidget(file_w)
|
||||
|
||||
layout.addWidget(self._stack)
|
||||
layout.addStretch()
|
||||
|
||||
@@ -115,6 +142,17 @@ class ConnectionRow(QWidget):
|
||||
if config:
|
||||
self.apply_config(config)
|
||||
|
||||
# ── File browse ──────────────────────────────────────────────────────────
|
||||
|
||||
def _browse_file(self):
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self, "Select log file", "", "Log files (*.log);;All files (*)")
|
||||
if path:
|
||||
self._file_edit.setText(path)
|
||||
|
||||
def _on_file_path_changed(self, path: str):
|
||||
self._id_edit.setText(source_id_from_path(path) or '')
|
||||
|
||||
# ── Serial port detection ────────────────────────────────────────────────
|
||||
|
||||
def _populate_serial_ports(self):
|
||||
@@ -129,7 +167,8 @@ class ConnectionRow(QWidget):
|
||||
|
||||
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._type_combo.setCurrentIndex(
|
||||
{'tcp': 0, 'serial': 1, 'file': 2}.get(config.conn_type, 0))
|
||||
self._host_edit.setText(config.host)
|
||||
self._port_spin.setValue(config.port)
|
||||
if config.serial_port:
|
||||
@@ -137,9 +176,11 @@ class ConnectionRow(QWidget):
|
||||
self._ser_combo.setCurrentIndex(idx) if idx >= 0 \
|
||||
else self._ser_combo.setCurrentText(config.serial_port)
|
||||
self._baud_combo.setCurrentText(str(config.baud_rate))
|
||||
self._file_edit.setText(config.file_path)
|
||||
self._delay_spin.setValue(config.delay)
|
||||
|
||||
def get_config(self) -> ConnectionConfig:
|
||||
t = 'tcp' if self._type_combo.currentIndex() == 0 else 'serial'
|
||||
t = ['tcp', 'serial', 'file'][self._type_combo.currentIndex()]
|
||||
return ConnectionConfig(
|
||||
receiver_id = self._id_edit.text().strip(),
|
||||
conn_type = t,
|
||||
@@ -147,6 +188,8 @@ class ConnectionRow(QWidget):
|
||||
port = self._port_spin.value(),
|
||||
serial_port = self._ser_combo.currentText().strip(),
|
||||
baud_rate = int(self._baud_combo.currentText()),
|
||||
file_path = self._file_edit.text().strip(),
|
||||
delay = self._delay_spin.value(),
|
||||
)
|
||||
|
||||
def get_receiver_id(self) -> str:
|
||||
@@ -157,7 +200,8 @@ class ConnectionRow(QWidget):
|
||||
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]:
|
||||
self._port_spin, self._ser_combo, self._baud_combo,
|
||||
self._file_edit, self._delay_spin]:
|
||||
w.setEnabled(not connected)
|
||||
|
||||
# ── Slots ────────────────────────────────────────────────────────────────
|
||||
@@ -180,6 +224,10 @@ class ConnectionRow(QWidget):
|
||||
QMessageBox.warning(self, "GNSS Monitor",
|
||||
f"ID '{config.receiver_id}' wird bereits verwendet.")
|
||||
return
|
||||
if config.conn_type == 'file' and not config.file_path:
|
||||
QMessageBox.warning(self, "GNSS Monitor",
|
||||
"Bitte eine Log-Datei auswählen.")
|
||||
return
|
||||
|
||||
self._model.add_receiver(config)
|
||||
self._rid = config.receiver_id
|
||||
|
||||
Reference in New Issue
Block a user