Dieses Python-Programm liest GPS-Daten über ein internes Modem aus und zeigt sie in einer grafischen Oberfläche mit Karte an.
python3 gps_dashboard.py
#!/usr/bin/env python3
import sys
import serial
import time
from PyQt5 import QtWidgets, QtCore, QtGui, QtWebEngineWidgets
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
# --- WEB SERVER ---
class WebServerThread(QtCore.QThread):
def __init__(self, port=8000):
super().__init__()
self.port = port
self.httpd = None
def run(self):
class ReusableTCPServer(TCPServer):
allow_reuse_address = True
handler = SimpleHTTPRequestHandler
try:
self.httpd = ReusableTCPServer(("127.0.0.1", self.port), handler)
self.httpd.serve_forever()
except: pass
def stop(self):
if self.httpd:
self.httpd.shutdown()
# --- GPS POLLER ---
class GPSPoller(QtCore.QThread):
data = QtCore.pyqtSignal(dict)
debug = QtCore.pyqtSignal(str)
def __init__(self, port="/dev/ttyACM0"):
super().__init__()
self.port = port
self.running = True
self.state = {
"lat": None, "lon": None, "speed": 0, "course": 0,
"hdop": None, "fix": False, "alt": None, "utc": None, "sats": []
}
self.tmp_sats = []
def run(self):
try:
# Wichtig: Höherer Timeout für Initialisierung
self.ser = serial.Serial(self.port, 115200, timeout=1.0)
self.debug.emit(f" Port offen: {self.port}")
except Exception as e:
self.debug.emit(f" Fehler: {e}")
return
# Wake up
for _ in range(2):
self.ser.write(b"AT\r")
time.sleep(0.1)
# Initialisierungsbefehle
cmds = [b"AT+CFUN=1\r", b"AT*E2GPSCTL=1,1,1\r", b"AT*E2GPSNMEA=1\r"]
for c in cmds:
self.ser.write(c)
time.sleep(0.5)
r = self.ser.read_all().decode(errors="ignore").replace("\r", " ").replace("\n", " ")
self.debug.emit(f"CMD {c.decode().strip()} -> {r}")
self.ser.timeout = 0.1 # Zurück auf schnelles Lesen
buffer = ""
while self.running:
try:
if self.ser.in_waiting:
raw = self.ser.read(self.ser.in_waiting).decode(errors="ignore")
buffer += raw
# Wir suchen nach vollständigen NMEA Sätzen
while "$" in buffer and "\n" in buffer:
# Alles vor dem ersten $ wegwerfen
start_idx = buffer.find("$")
buffer = buffer[start_idx:]
# Ende der Zeile suchen
end_idx = buffer.find("\n")
if end_idx == -1: break # Unvollständig
line = buffer[:end_idx].strip()
buffer = buffer[end_idx+1:]
if line:
# self.debug.emit(f"RAW: {line}") # Zum Testen einkommentieren
parsed = self.parse_nmea(line)
if parsed:
self.data.emit(parsed)
except Exception as e:
self.debug.emit(f"Loop Fehler: {e}")
time.sleep(0.05)
self.ser.write(b"AT*E2GPSCTL=0,1,1\r")
self.ser.close()
def parse_nmea(self, line):
# Wir splitten und entfernen mögliche Checksummen-Reste
if "*" in line:
line = line.split("*")[0]
p = line.split(",")
try:
if "GGA" in p[0] and len(p) > 9:
# GGA: $GPGGA,time,lat,N,lon,E,fix,sats,hdop,alt...
self.state["lat"] = self.conv(p[2], p[3])
self.state["lon"] = self.conv(p[4], p[5])
self.state["fix"] = int(p[6]) > 0
if p[8]: self.state["hdop"] = float(p[8])
if p[9]: self.state["alt"] = float(p[9])
elif "RMC" in p[0] and len(p) > 9:
if p[7]: self.state["speed"] = float(p[7]) * 1.852
if p[8]: self.state["course"] = float(p[8])
if p[1] and len(p[1]) >= 6:
# Wir extrahieren nur HH, MM, SS
hh = p[1][0:2]
mm = p[1][2:4]
ss = p[1][4:6]
# Das Datum ignorieren wir komplett
self.state["utc"] = f"{hh}:{mm}:{ss}"
elif "GSV" in p[0] and len(p) > 3:
msg_num = int(p[2])
if msg_num == 1: self.tmp_sats = []
for i in range(4, len(p) - 3, 4):
if i+3 < len(p) and p[i]:
self.tmp_sats.append({"prn": p[i], "elev": p[i+1], "az": p[i+2], "snr": p[i+3]})
if msg_num == int(p[1]):
self.state["sats"] = self.tmp_sats
except: pass
return self.state.copy()
def conv(self, val, d):
if not val or len(val) < 4: return None
try:
dot = val.find(".")
deg = float(val[:dot-2])
minutes = float(val[dot-2:])
res = deg + (minutes / 60)
if d in ["S", "W"]: res *= -1
return res
except: return None
# --- UI (Vereinfacht für Stabilität) ---
class Cockpit(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.web_port = 8000
self.server = WebServerThread(self.web_port)
self.server.start()
self.init_ui()
def init_ui(self):
self.setWindowTitle("ThinkPad GPS Cockpit PRO")
layout = QtWidgets.QGridLayout()
self.lbl_pos = QtWidgets.QLabel("Suche Satelliten...")
self.lbl_utc = QtWidgets.QLabel("UTC: --:--:--")
self.lbl_fix = QtWidgets.QLabel("NO FIX")
self.lbl_fix.setStyleSheet("color: red; font-weight: bold")
layout.addWidget(self.lbl_utc, 0, 0)
layout.addWidget(self.lbl_fix, 0, 1)
layout.addWidget(self.lbl_pos, 1, 0, 1, 2)
self.map = QtWebEngineWidgets.QWebEngineView()
self.write_map()
self.map.load(QtCore.QUrl(f"http://127.0.0.1:{self.web_port}/map.html"))
layout.addWidget(self.map, 2, 0, 1, 4)
self.log = QtWidgets.QTextEdit()
self.log.setMaximumHeight(100)
layout.addWidget(self.log, 3, 0, 1, 4)
self.btn = QtWidgets.QPushButton("Start GPS")
self.btn.clicked.connect(self.toggle)
layout.addWidget(self.btn, 4, 0, 1, 4)
self.setLayout(layout)
self.active = False
def write_map(self):
with open("map.html", "w") as f:
f.write("""""")
def toggle(self):
if not self.active:
self.poller = GPSPoller("/dev/ttyACM0") # HIER PORT ANPASSEN FALLS NÖTIG
self.poller.data.connect(self.upd)
self.poller.debug.connect(lambda m: self.log.append(m))
self.poller.start()
self.btn.setText("Stop GPS")
self.active = True
else:
self.poller.running = False
self.btn.setText("Start GPS")
self.active = False
def upd(self, d):
if d["lat"]:
self.lbl_pos.setText(f"LAT: {d['lat']:.6f} | LON: {d['lon']:.6f} | ALT: {d['alt']}m")
self.map.page().runJavaScript(f"update({d['lat']},{d['lon']})")
# Nur die Zeit anzeigen, falls vorhanden
if d["utc"]:
self.lbl_utc.setText(f"UTC Zeit: {d['utc']}")
else:
self.lbl_utc.setText("UTC Zeit: --:--:--")
#if d["utc"]: self.lbl_utc.setText(f"UTC: {d['utc']}")
self.lbl_fix.setText("FIX OK" if d["fix"] else "NO FIX")
self.lbl_fix.setStyleSheet("color: green" if d["fix"] else "color: red")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
c = Cockpit()
c.resize(900, 600)
c.show()
sys.exit(app.exec_())
Dieses Projekt wurde mit Unterstützung von KI (ChatGPT) sowie eigenen Anpassungen entwickelt.