feat: AR-GPS initial commit — Python + JavaScript PyQt5 (standalone desktop app) + FastAPI (charts REST router) + OpenLayers (frontend map)

This commit is contained in:
2026-07-03 12:15:59 -04:00
commit 346bc1ffcb
19 changed files with 7149 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
"""GPS Navigator — PyQt5 standalone application.
Reads NMEA from serial port, displays in embedded WebView.
Suitable for Raspberry Pi 4 or any desktop (Windows/Linux/macOS).
"""
import sys
import os
from pathlib import Path
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings, QWebEngineScript
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtCore import QUrl, QFile, QIODevice
from dotenv import load_dotenv
load_dotenv()
BASE_DIR = Path(__file__).parent
DB_PATH = BASE_DIR / "data" / "gps.db"
from PyQt5.QtWebEngineWidgets import QWebEnginePage
from backend.database import init_db
from bridge import GPSBridge
class DebugPage(QWebEnginePage):
"""Re-envía mensajes de consola JS a stdout de Python."""
def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
tag = ["DBG", "INF", "WRN", "ERR"][level] if level < 4 else "???"
src = sourceID.split("/")[-1] if sourceID else "?"
print(f"[JS:{tag}] {src}:{lineNumber} {message}")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("AR GPS Navigator")
_logo = BASE_DIR / "frontend" / "assets" / "images" / "ar_logo_full.png"
if _logo.exists():
self.setWindowIcon(QIcon(str(_logo)))
self.resize(1280, 800)
# ── Web view ──────────────────────────────────────────────────────────
self.view = QWebEngineView(self)
self.setCentralWidget(self.view)
page = DebugPage(self.view)
self.view.setPage(page)
# Settings — allow file:// to load local resources + touch
s = page.settings()
s.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
s.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, True)
s.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
# TouchEventsEnabled no existe en todas las versiones de PyQt5 — omitir
# ── Bridge ────────────────────────────────────────────────────────────
self.bridge = GPSBridge(DB_PATH, parent=self)
# ── QWebChannel ───────────────────────────────────────────────────────
self.channel = QWebChannel(page)
self.channel.registerObject("py", self.bridge)
page.setWebChannel(self.channel)
# ── Inject qwebchannel.js from Qt internal resources ─────────────────
qwcf = QFile(":/qtwebchannel/qwebchannel.js")
if qwcf.open(QIODevice.ReadOnly):
qwc_js = bytes(qwcf.readAll()).decode("utf-8")
qwcf.close()
script = QWebEngineScript()
script.setName("qwebchannel.js")
script.setSourceCode(qwc_js)
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
script.setWorldId(QWebEngineScript.MainWorld)
page.scripts().insert(script)
# ── Load frontend ─────────────────────────────────────────────────────
index_html = BASE_DIR / "frontend" / "index.html"
self.view.setUrl(QUrl.fromLocalFile(str(index_html)))
def closeEvent(self, event):
self.bridge.shutdown()
super().closeEvent(event)
def main():
# Needed on some Linux/RPi setups
os.environ.setdefault("QTWEBENGINE_CHROMIUM_FLAGS", "--no-sandbox")
(BASE_DIR / "charts").mkdir(exist_ok=True)
app = QApplication(sys.argv)
# Touch screen support
from PyQt5.QtCore import Qt
app.setAttribute(Qt.AA_SynthesizeTouchForUnhandledMouseEvents, False)
app.setApplicationName("GPS Navigator")
init_db(DB_PATH)
win = MainWindow()
win.show()
# GPS autodetect is triggered from JS (bootApp) once QWebChannel
# is ready — avoids the race condition where the "connected" signal
# would be emitted before the JS handler is registered.
sys.exit(app.exec_())
if __name__ == "__main__":
main()