110 lines
4.4 KiB
Python
110 lines
4.4 KiB
Python
"""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()
|