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:
Mathieu Moran
2026-08-08 18:01:35 -04:00
co-authored by Copilot
parent f91a3d479d
commit 261ef3f4db
46 changed files with 2150 additions and 255 deletions
+98 -60
View File
@@ -1,7 +1,7 @@
from modules import app_gps, app_sql, app_events
import obd, time, threading
import glob, obd, os, time, threading
from datetime import datetime
from threading import Thread
from threading import Event, Thread
status = None
speed = 0
@@ -16,88 +16,126 @@ class OBD:
obd_status = ["Disconnected", "Connected", "Connection...", "wtf"]
obd_speed_lock = threading.Lock()
obd_rpm_lock = threading.Lock()
thread_speed = None
thread_rpm = None
stop_speed = Event()
stop_rpm = Event()
try:
print(obd_status[2])
connection = obd.OBD() # put linux /dev/rfi... something in () for the rpi bluetooth adpater
connection = None
def _find_obd_port():
"""Locate the USB-serial OBD-II adapter.
Honors an OBD_PORT env var override; otherwise picks the first
/dev/ttyUSB* device found (the PL2303 USB adapter shows up here).
Returns None if nothing is found, letting python-obd fall back to its
own auto-scan (which also covers /dev/rfcomm* for the old Bluetooth setup).
"""
env_port = os.environ.get("OBD_PORT")
if env_port:
return env_port
ports = sorted(glob.glob("/dev/ttyUSB*"))
return ports[0] if ports else None
def connect_obd():
"""(Re)connect to the OBD-II adapter. Safe to call repeatedly."""
global connection
port = _find_obd_port()
try:
print(obd_status[2])
connection = obd.OBD(portstr=port) if port else obd.OBD()
except Exception as e:
print("An error occurred:", str(e))
connection = None
return connection
connect_obd()
except Exception as e:
print("An error occurred:", str(e))
def get_speedtime():
global speed
date = datetime.now()
today = str(date.today())
time_now = str(date.strftime("%H:%M:%S"))
return [str(time_now), speed]
time_now = str(datetime.now().strftime("%H:%M:%S"))
return [time_now, speed]
def obd_connection():
if connection.is_connected():
def obd_connection():
global thread_speed, thread_rpm
if connection is None or not connection.is_connected():
connect_obd()
if connection is not None and connection.is_connected():
print(obd_status[1])
app_events.socketio.emit('obd_status', obd_status[1])
print("OBD connection established.")
if thread_speed is None or not thread_speed.is_alive():
stop_speed.clear()
thread_speed = Thread(target=obd_speed)
thread_speed.daemon = True
thread_speed.start()
print(f'thread_speed alive? {thread_speed.is_alive()}')
if thread_rpm is None or not thread_rpm.is_alive():
stop_rpm.clear()
thread_rpm = Thread(target=obd_rpm)
thread_rpm.daemon = True
thread_rpm.start()
print(f'thread_rpm alive? {thread_rpm.is_alive()}')
else:
print("OBD connection failed.")
print(obd_status[0])
app_events.socketio.emit('obd_status', obd_status[0])
app_events.socketio.emit('obd_speed', "-")
app_events.socketio.emit('obm_error', "-")
app_events.socketio.emit('obd_error', "-")
app_events.socketio.emit('obd_rpm', "-")
# debug speed for testing
thread_speed = Thread(target=obd_speed)
thread_speed.daemon = True
thread_speed.start()
# debug rpm for testing
thread_rpm = Thread(target=obd_rpm)
thread_rpm.daemon = True
thread_rpm.start()
def obd_speed():
obd_speed_lock.acquire()
cmd = obd.commands.SPEED # select an OBD command (sensor)
response = connection.query(cmd) # send the command and parse the response
loop = True
try:
while loop == True:
global speed
speed = speed + 1
time.sleep(10)
global speed
with obd_speed_lock:
cmd = obd.commands.SPEED # select an OBD command (sensor)
while not stop_speed.is_set():
if connection is None or not connection.is_connected():
print("OBD disconnected, stopping speed polling.")
break
response = connection.query(cmd) # send the command and parse the response
if response.is_null():
print("No data received. #"+ str(get_speedtime()))
print(app_gps.get_gps())
app_events.socketio.emit('obd_speed', get_speedtime())
print("No data received. #" + str(get_speedtime()))
print(app_gps.get_gps())
else:
print("Original value:", response.value)
print("Value in kph:", response.value.to("kph"))
app_events.socketio.emit('obd_speed', response.value.to("kph"))
finally:
obd_speed_lock.release()
speed = round(response.value.to("kph").magnitude, 1)
print("Speed:", speed, "kph")
app_events.socketio.emit('obd_speed', get_speedtime())
time.sleep(2)
def obd_rpm():
obd_speed_lock.acquire()
cmd = obd.commands.RPM # select an OBD command (sensor)
response = connection.query(cmd) # send the command and parse the response
loop = True
try:
while loop == True:
global rpm
speed = speed + 1
time.sleep(1)
global rpm
with obd_rpm_lock:
cmd = obd.commands.RPM # select an OBD command (sensor)
while not stop_rpm.is_set():
if connection is None or not connection.is_connected():
print("OBD disconnected, stopping rpm polling.")
break
response = connection.query(cmd) # send the command and parse the response
if response.is_null():
print("No data received. #"+ str(get_speedtime()))
app_events.socketio.emit('obd_speed', get_speedtime())
else:
print("RPM:", response.value)
app_events.socketio.emit('obd_speed', response.value.to("kph"))
finally:
obd_rpm_lock.release()
print("No RPM data received. #" + str(get_speedtime()))
app_events.socketio.emit('obd_rpm', "-")
else:
rpm = round(response.value.magnitude, 0)
print("RPM:", rpm)
app_events.socketio.emit('obd_rpm', rpm)
time.sleep(1)
def kill_obd(obd_type):
if obd_type == "speed":
obd_speed.loop = False
stop_speed.set()
elif obd_type == "rpm":
obd_rpm.loop = False
stop_rpm.set()