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:
+136
-78
@@ -1,78 +1,136 @@
|
||||
import sqlite3
|
||||
from modules import app_obd, app_car, app_account, app_gps
|
||||
from datetime import datetime
|
||||
from sqlite3 import Error
|
||||
|
||||
currentdatetime = datetime.now()
|
||||
account = app_account.Account()
|
||||
car = app_car.Car()
|
||||
sCarModel = ''.join(letter for letter in car.model if letter.isalnum())
|
||||
sql_file = account.name + "_" + sCarModel + ".db"
|
||||
sql_path = "../db/sql/" + sql_file
|
||||
|
||||
def create_conn(db_file):
|
||||
conn = None
|
||||
try:
|
||||
conn = sqlite3.connect(db_file)
|
||||
print(sqlite3.version)
|
||||
except Error as e:
|
||||
print(e)
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def create_table(conn, create_table_sql):
|
||||
""" create a table from the create_table_sql statement
|
||||
:param conn: Connection object
|
||||
:param create_table_sql: a CREATE TABLE statement
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(create_table_sql)
|
||||
except Error as e:
|
||||
print(e)
|
||||
|
||||
def main():
|
||||
db = sql_path
|
||||
|
||||
sql_create_tracking_table = """CREATE TABLE IF NOT EXISTS tracking (
|
||||
id integer PRIMARY KEY,
|
||||
VN text NOT NULL,
|
||||
NAME text NOT NULL,
|
||||
DATETIME datetime,
|
||||
GPSX float,
|
||||
GPSY float,
|
||||
SPEED float,
|
||||
ODOMETER float
|
||||
);"""
|
||||
|
||||
sql_create_dtc_table = """CREATE TABLE IF NOT EXISTS dtc (
|
||||
id integer PRIMARY KEY,
|
||||
NAME text NOT NULL,
|
||||
STATUS text,
|
||||
DTC_ID text
|
||||
);"""
|
||||
|
||||
# create a database connection
|
||||
conn = create_conn(db)
|
||||
|
||||
# create tables
|
||||
if conn is not None:
|
||||
# create diagnostics table
|
||||
create_table(conn, sql_create_tracking_table)
|
||||
|
||||
# create diagnostics table
|
||||
create_table(conn, sql_create_dtc_table)
|
||||
|
||||
else:
|
||||
print("Error! cannot create the database connection.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_conn(sql_path)
|
||||
|
||||
|
||||
|
||||
# ## VN, NAME, DATE, TIME, GPS_X, GPS_Y, SPEED, ODOMETER
|
||||
# (["VN", "NAME", "DATE", "TIME", "GPSX", "GPSY", "SPEED", "ODOMETER"])
|
||||
# ([car.model, account.name, currentday, currenttime, GPSX, GPSY, SPEED, ODOMETER])
|
||||
import sqlite3
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from sqlite3 import Error
|
||||
from threading import Thread, Lock
|
||||
|
||||
from modules import app_car, app_account, app_gps
|
||||
|
||||
# Resolve db/sql/ relative to the repo root regardless of the process's cwd
|
||||
# (previously a relative "../db/sql/" path, which broke depending on how/from
|
||||
# where app.py was launched).
|
||||
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_SQL_DIR = os.path.join(_BASE_DIR, "db", "sql")
|
||||
|
||||
account = app_account.Account()
|
||||
car = app_car.Car()
|
||||
sCarModel = ''.join(letter for letter in car.model if letter.isalnum())
|
||||
sql_file = account.name + "_" + sCarModel + ".db"
|
||||
sql_path = os.path.join(_SQL_DIR, sql_file)
|
||||
|
||||
LOG_INTERVAL = 5 # seconds between trip-tracking snapshots
|
||||
|
||||
_conn = None
|
||||
_conn_lock = Lock()
|
||||
_log_thread = None
|
||||
|
||||
SQL_CREATE_TRACKING_TABLE = """CREATE TABLE IF NOT EXISTS tracking (
|
||||
id integer PRIMARY KEY,
|
||||
VN text NOT NULL,
|
||||
NAME text NOT NULL,
|
||||
DATETIME datetime,
|
||||
GPSX float,
|
||||
GPSY float,
|
||||
SPEED float,
|
||||
ODOMETER float
|
||||
);"""
|
||||
|
||||
SQL_CREATE_DTC_TABLE = """CREATE TABLE IF NOT EXISTS dtc (
|
||||
id integer PRIMARY KEY,
|
||||
NAME text NOT NULL,
|
||||
STATUS text,
|
||||
DTC_ID text
|
||||
);"""
|
||||
|
||||
|
||||
def create_conn(db_file):
|
||||
"""Open a SQLite connection to db_file, creating its parent folder if needed.
|
||||
|
||||
Returns the open connection (or None on failure) - previously this closed
|
||||
the connection immediately in a `finally` block and returned nothing,
|
||||
so every caller always got a dead `None` connection.
|
||||
"""
|
||||
os.makedirs(os.path.dirname(db_file), exist_ok=True)
|
||||
conn = None
|
||||
try:
|
||||
conn = sqlite3.connect(db_file, check_same_thread=False)
|
||||
print("SQLite library version:", sqlite3.sqlite_version)
|
||||
except Error as e:
|
||||
print(e)
|
||||
return conn
|
||||
|
||||
|
||||
def create_table(conn, create_table_sql):
|
||||
""" create a table from the create_table_sql statement
|
||||
:param conn: Connection object
|
||||
:param create_table_sql: a CREATE TABLE statement
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(create_table_sql)
|
||||
except Error as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def get_conn():
|
||||
"""Return the shared SQLite connection, opening + initializing tables on first use."""
|
||||
global _conn
|
||||
if _conn is None:
|
||||
_conn = create_conn(sql_path)
|
||||
if _conn is not None:
|
||||
create_table(_conn, SQL_CREATE_TRACKING_TABLE)
|
||||
create_table(_conn, SQL_CREATE_DTC_TABLE)
|
||||
else:
|
||||
print("Error! cannot create the database connection.")
|
||||
return _conn
|
||||
|
||||
|
||||
def insert_tracking(gps_x, gps_y, speed, odometer=None):
|
||||
"""Insert a single trip-tracking snapshot row (GPS position + speed)."""
|
||||
conn = get_conn()
|
||||
if conn is None:
|
||||
return
|
||||
|
||||
sql = """INSERT INTO tracking (VN, NAME, DATETIME, GPSX, GPSY, SPEED, ODOMETER)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)"""
|
||||
with _conn_lock:
|
||||
try:
|
||||
conn.execute(sql, (car.model, account.name, datetime.now(), gps_x, gps_y, speed, odometer))
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
print("Failed to insert tracking row:", e)
|
||||
|
||||
|
||||
def _log_loop():
|
||||
# Deferred import to avoid a circular import at module load time
|
||||
# (app_obd.py imports app_sql, and app_sql needs app_obd.speed here).
|
||||
from modules import app_obd
|
||||
|
||||
while True:
|
||||
gps_x = app_gps.LocationX
|
||||
gps_y = app_gps.LocationY
|
||||
speed = app_obd.speed
|
||||
|
||||
if gps_x is not None and gps_y is not None:
|
||||
insert_tracking(gps_x, gps_y, speed)
|
||||
|
||||
time.sleep(LOG_INTERVAL)
|
||||
|
||||
|
||||
def start_logging():
|
||||
"""Start the background thread that periodically snapshots GPS+speed into SQLite."""
|
||||
global _log_thread
|
||||
if _log_thread is None or not _log_thread.is_alive():
|
||||
get_conn() # make sure the db file/tables exist before the first insert
|
||||
_log_thread = Thread(target=_log_loop)
|
||||
_log_thread.daemon = True
|
||||
_log_thread.start()
|
||||
print(f"SQL tracking-logger thread started, alive: {_log_thread.is_alive()}")
|
||||
else:
|
||||
print(f"SQL tracking-logger thread already running, alive: {_log_thread.is_alive()}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
get_conn()
|
||||
|
||||
Reference in New Issue
Block a user