feat: AR-GPS initial commit — Python + JavaScript PyQt5 (standalone desktop app) + FastAPI (charts REST router) + OpenLayers (frontend map)
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user