196 lines
7.0 KiB
Python
196 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
GNSS Sky Plot Viewer
|
||
Unterstützt UBX über serielle Schnittstelle UND TCP/IP (Ser2net).
|
||
TCP/IP-Verbindungen werden in einer persistenten LRU-Liste (max. 5) gespeichert.
|
||
"""
|
||
|
||
import sys
|
||
from PyQt5.QtWidgets import (
|
||
QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||
QHBoxLayout, QLabel, QStatusBar, QSplitter, QTabWidget)
|
||
from PyQt5.QtCore import Qt
|
||
|
||
from connection_panel import ConnectionPanel
|
||
from gnss_thread import GnssReader
|
||
from sat_table import SatelliteTable
|
||
from sky_plot_widget import SkyPlotWidget
|
||
|
||
from plot_tab import PlotTab
|
||
# ---------------------------------------------------------------------------
|
||
# Hauptfenster
|
||
# ---------------------------------------------------------------------------
|
||
class MainWindow(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.setWindowTitle("GNSS Sky Plot – UBX | Seriell + TCP/IP")
|
||
self.resize(980, 680)
|
||
self.setStyleSheet("background-color: #12192b; color: #cccccc;")
|
||
self._reader = None
|
||
self._build_ui()
|
||
|
||
def _build_ui(self):
|
||
central = QWidget()
|
||
self.setCentralWidget(central)
|
||
root = QVBoxLayout(central)
|
||
root.setContentsMargins(8, 8, 8, 8)
|
||
|
||
# Haupt-Tabs
|
||
self._main_tabs = QTabWidget()
|
||
self._main_tabs.setStyleSheet("""
|
||
QTabBar::tab {
|
||
background: #1a3a5c;
|
||
color: #cccccc;
|
||
padding: 6px 20px;
|
||
border-radius: 3px;
|
||
}
|
||
QTabBar::tab:selected {
|
||
background: #2a5a8c;
|
||
color: #ffffff;
|
||
}
|
||
""")
|
||
|
||
# ── Tab 1: Verbindung ─────────────────────────────────────────────────
|
||
conn_tab = QWidget()
|
||
conn_layout = QVBoxLayout(conn_tab)
|
||
conn_layout.setContentsMargins(8, 8, 8, 8)
|
||
|
||
self._conn_panel = ConnectionPanel()
|
||
self._conn_panel.connect_requested.connect(self._connect)
|
||
self._conn_panel.disconnect_requested.connect(self._disconnect)
|
||
conn_layout.addWidget(self._conn_panel)
|
||
conn_layout.addStretch()
|
||
|
||
self._main_tabs.addTab(conn_tab, "🔌 Verbindung")
|
||
|
||
# ── Tab 2: Sky View ───────────────────────────────────────────────────
|
||
view_tab = QWidget()
|
||
view_layout = QVBoxLayout(view_tab)
|
||
view_layout.setContentsMargins(8, 8, 8, 8)
|
||
|
||
# ── Tab 3: Plot ───────────────────────────────────────────────────
|
||
self._plot_tab = PlotTab()
|
||
self._main_tabs.addTab(self._plot_tab, "📈 Plot")
|
||
|
||
# Satellitenanzahl
|
||
self._sat_label = QLabel("Satelliten: –")
|
||
self._sat_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||
self._sat_label.setStyleSheet("font-size: 13px; color: #7ab7d4;")
|
||
view_layout.addWidget(self._sat_label)
|
||
|
||
# Splitter: Sky Plot | Tabelle
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
self._sky_plot = SkyPlotWidget()
|
||
self._table = SatelliteTable()
|
||
splitter.addWidget(self._sky_plot)
|
||
splitter.addWidget(self._table)
|
||
splitter.setSizes([540, 380])
|
||
view_layout.addWidget(splitter)
|
||
|
||
self._main_tabs.addTab(view_tab, "🛰 Sky View")
|
||
|
||
root.addWidget(self._main_tabs)
|
||
|
||
# Status Bar
|
||
self._status = QStatusBar()
|
||
self.setStatusBar(self._status)
|
||
self._status.showMessage("Nicht verbunden.")
|
||
|
||
def __build_ui(self):
|
||
central = QWidget()
|
||
self.setCentralWidget(central)
|
||
root = QVBoxLayout(central)
|
||
root.setContentsMargins(8, 8, 8, 8)
|
||
|
||
# Obere Leiste
|
||
top = QHBoxLayout()
|
||
|
||
self._conn_panel = ConnectionPanel()
|
||
self._conn_panel.connect_requested.connect(self._connect)
|
||
self._conn_panel.disconnect_requested.connect(self._disconnect)
|
||
top.addWidget(self._conn_panel)
|
||
|
||
self._sat_label = QLabel("Satelliten: –")
|
||
self._sat_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||
self._sat_label.setStyleSheet("font-size: 13px; color: #7ab7d4;")
|
||
top.addStretch()
|
||
top.addWidget(self._sat_label)
|
||
|
||
root.addLayout(top)
|
||
|
||
# Splitter: Sky Plot | Tabelle
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
self._sky_plot = SkyPlotWidget()
|
||
self._table = SatelliteTable()
|
||
splitter.addWidget(self._sky_plot)
|
||
splitter.addWidget(self._table)
|
||
splitter.setSizes([540, 380])
|
||
root.addWidget(splitter)
|
||
|
||
self._status = QStatusBar()
|
||
self.setStatusBar(self._status)
|
||
self._status.showMessage("Nicht verbunden.")
|
||
|
||
def _connect(self, factory, label: str):
|
||
self._reader = GnssReader(factory)
|
||
self._reader.satellites_updated.connect(self._on_satellites)
|
||
self._reader.error_occurred.connect(self._on_error)
|
||
self._reader.connected.connect(
|
||
lambda msg: (
|
||
self._status.showMessage(f"{msg}: {label}"),
|
||
self._main_tabs.setCurrentIndex(1), # → Sky View
|
||
)
|
||
)
|
||
self._reader.start()
|
||
self._conn_panel.set_connected(True)
|
||
self._status.showMessage(f"Verbinde mit {label} …")
|
||
|
||
def __connect(self, factory, label: str):
|
||
self._reader = GnssReader(factory)
|
||
self._reader.satellites_updated.connect(self._on_satellites)
|
||
self._reader.error_occurred.connect(self._on_error)
|
||
self._reader.connected.connect(
|
||
lambda msg: self._status.showMessage(f"{msg}: {label}")
|
||
)
|
||
self._reader.start()
|
||
self._conn_panel.set_connected(True)
|
||
self._status.showMessage(f"Verbinde mit {label} …")
|
||
|
||
def _disconnect(self):
|
||
if self._reader:
|
||
self._reader.stop()
|
||
self._reader = None
|
||
self._conn_panel.set_connected(False)
|
||
self._status.showMessage("Verbindung getrennt.")
|
||
|
||
def _on_satellites(self, msg: dict):
|
||
if 'sat' in msg:
|
||
sats = msg['sat']
|
||
visible = [s for s in sats if s["elev"] >= 0]
|
||
used = sum(1 for s in sats if s["used"])
|
||
self._sat_label.setText(f"Satelliten: {len(visible)} sichtbar, {used} genutzt")
|
||
self._sky_plot.update_satellites(visible)
|
||
self._table.update_satellites(visible)
|
||
self._plot_tab.update_data(visible) # ← neu
|
||
if 'dop' in msg:
|
||
dop = msg['dop']
|
||
self._plot_tab.update_data([], pdop=dop['p_dop']) # ← neu
|
||
|
||
|
||
def _on_error(self, msg: str):
|
||
self._status.showMessage(f"Fehler: {msg}")
|
||
self._disconnect()
|
||
|
||
def closeEvent(self, event):
|
||
self._disconnect()
|
||
super().closeEvent(event)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
if __name__ == "__main__":
|
||
app = QApplication(sys.argv)
|
||
app.setStyle("Fusion")
|
||
win = MainWindow()
|
||
win.show()
|
||
sys.exit(app.exec_())
|