Revive yaris-obd: USB OBD adapter, real GPS/GNSS, offline map, hotspot, SQLite logging, dashcam retrieval
Squashed rewrite of the project revival work (replaces prior history on this branch to remove personal data that had been accidentally committed - real LAN IP, GPS coordinates, home WiFi SSID, and phone model). See PLAN.md for full phase-by-phase details. Summary: - Phase 2: adapt app_obd.py for a USB OBD-II adapter (was Bluetooth-only), auto-detect /dev/ttyUSB*, fix several pre-existing crash bugs. - Phase 3/4: make the web UI work fully offline (vendored Socket.IO/Leaflet/ Chart.js/FontAwesome, offline map tile downloader script, live GPS marker tracking fix). - Phase 5: systemd deployment/autostart on the Pi. - Phase 6: WiFi hotspot mode via NetworkManager (auto-switch between home WiFi and a local AP for in-car web UI access), mDNS hostname + port 80 redirect so the UI is reachable at a plain hostname with no port number. - Phase 7: fix app_db.py/app_sql.py bugs, wire up local SQLite trip logging. - Phase 8: brand-agnostic USB dashcam file retrieval (lsblk transport-based detection, works with any dashcam brand/folder layout). - Rewritten readme.md with a proper project overview. Co-authored-by: Copilot <[email protected]>
This commit is contained in:
+119
-22
@@ -1,22 +1,119 @@
|
||||
from modules.app_events import socketio
|
||||
from threading import Thread
|
||||
|
||||
class GPS:
|
||||
def __init__(self):
|
||||
self.LocationX = None
|
||||
self.LocationY= None
|
||||
|
||||
@socketio.on('gps')
|
||||
def get_gps():
|
||||
global LocationX
|
||||
global LocationY
|
||||
|
||||
LocationX = "45.5202844"
|
||||
LocationY = "-73.5493208"
|
||||
Location = [LocationX, LocationY]
|
||||
|
||||
socketio.emit('gps_location', Location)
|
||||
|
||||
print("websocket should be sent now")
|
||||
|
||||
return print("socket.io emitted")
|
||||
from modules.app_events import socketio
|
||||
from threading import Thread, Lock
|
||||
import serial
|
||||
import time
|
||||
|
||||
# SIM868 GNSS/GSM/GPRS HAT is wired to the Pi's GPIO UART (jumper set to "B: Pi-SIM868"),
|
||||
# exposed as /dev/serial0 once the Pi's onboard Bluetooth has been disabled/freed.
|
||||
SERIAL_PORT = "/dev/serial0"
|
||||
BAUDRATE = 115200
|
||||
POLL_INTERVAL = 2 # seconds between AT+CGNSINF polls
|
||||
|
||||
LocationX = None
|
||||
LocationY = None
|
||||
|
||||
_serial_lock = Lock()
|
||||
_serial = None
|
||||
_gps_thread = None
|
||||
|
||||
|
||||
class GPS:
|
||||
def __init__(self):
|
||||
self.LocationX = None
|
||||
self.LocationY = None
|
||||
|
||||
|
||||
def _get_serial():
|
||||
global _serial
|
||||
if _serial is None or not _serial.is_open:
|
||||
_serial = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1)
|
||||
time.sleep(0.3)
|
||||
_serial.reset_input_buffer()
|
||||
return _serial
|
||||
|
||||
|
||||
def _send_at(cmd, wait=0.5):
|
||||
"""Send an AT command to the SIM868 module and return the raw response."""
|
||||
ser = _get_serial()
|
||||
ser.write((cmd + "\r\n").encode())
|
||||
time.sleep(wait)
|
||||
return ser.read(1000).decode(errors="replace")
|
||||
|
||||
|
||||
def _ensure_gnss_powered():
|
||||
resp = _send_at("AT+CGNSPWR?")
|
||||
if "+CGNSPWR: 0" in resp:
|
||||
print("GNSS was off, powering it on...")
|
||||
_send_at("AT+CGNSPWR=1", wait=1)
|
||||
|
||||
|
||||
def _parse_cgnsinf(resp):
|
||||
"""Parse a +CGNSINF response line into a dict, or None if there's no fix yet.
|
||||
|
||||
Format: +CGNSINF: run,fix,utc,lat,lon,alt,speed,course,fixmode,reserved1,
|
||||
hdop,pdop,vdop,reserved2,gps_sat_view,gnss_sat_used,gnss_sat_view,cn0,hpa,vpa
|
||||
"""
|
||||
for line in resp.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("+CGNSINF:"):
|
||||
fields = line.split(":", 1)[1].strip().split(",")
|
||||
if len(fields) < 8 or fields[1] != "1":
|
||||
return None
|
||||
lat, lon = fields[3], fields[4]
|
||||
if not lat or not lon:
|
||||
return None
|
||||
return {
|
||||
"lat": lat,
|
||||
"lon": lon,
|
||||
"alt": fields[5],
|
||||
"speed": fields[6],
|
||||
"course": fields[7],
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def _poll_loop():
|
||||
global LocationX, LocationY
|
||||
try:
|
||||
with _serial_lock:
|
||||
_ensure_gnss_powered()
|
||||
except Exception as e:
|
||||
print("Failed to initialize GNSS:", str(e))
|
||||
|
||||
while True:
|
||||
try:
|
||||
with _serial_lock:
|
||||
resp = _send_at("AT+CGNSINF")
|
||||
fix = _parse_cgnsinf(resp)
|
||||
except Exception as e:
|
||||
print("GPS read error:", str(e))
|
||||
fix = None
|
||||
|
||||
if fix:
|
||||
LocationX = fix["lat"]
|
||||
LocationY = fix["lon"]
|
||||
socketio.emit("gps_location", [LocationX, LocationY])
|
||||
|
||||
time.sleep(POLL_INTERVAL)
|
||||
|
||||
|
||||
def start_gps():
|
||||
"""Start the background thread that continuously polls the SIM868 for a GNSS fix."""
|
||||
global _gps_thread
|
||||
if _gps_thread is None or not _gps_thread.is_alive():
|
||||
_gps_thread = Thread(target=_poll_loop)
|
||||
_gps_thread.daemon = True
|
||||
_gps_thread.start()
|
||||
print(f"GPS thread started, alive: {_gps_thread.is_alive()}")
|
||||
else:
|
||||
print(f"GPS thread already running, alive: {_gps_thread.is_alive()}")
|
||||
return GPS()
|
||||
|
||||
|
||||
@socketio.on("gps")
|
||||
def get_gps():
|
||||
"""Return the last known GPS location and emit it over the 'gps_location' event."""
|
||||
Location = [LocationX, LocationY]
|
||||
socketio.emit("gps_location", Location)
|
||||
print("gps_location emitted:", Location)
|
||||
return Location
|
||||
|
||||
Reference in New Issue
Block a user