"""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()