feat: odometer in trips/overview, trips upload toggle, fix video count
- OBD odometer now logged to tracking rows on every GPS snapshot - trips table: START_ODOMETER/END_ODOMETER columns (migration) - build_trip_summaries: picks odometer from first/last tracking row - list_trips: uses real OBD odo, falls back to cumulative distance - overview stats: odometer_km (max seen from OBD), video_count fixed - history page: Odometer column shows start→end km per trip - app_upload: TRIPS_UPLOAD_SETTING_KEY, is_trips_upload_enabled() - _upload_tracking_batch: skips when trips upload is off - settings: separate 'Upload trips & GPS' toggle from dashcam toggle Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -37,6 +37,7 @@ def _number_value(setting_key, converter):
|
||||
def get_settings():
|
||||
return {
|
||||
"dashcam_upload_enabled": app_upload.is_dashcam_upload_enabled(),
|
||||
"trips_upload_enabled": app_upload.is_trips_upload_enabled(),
|
||||
"local_video_retention_days": app_video.retention_days(),
|
||||
"video_convert_enabled": app_video.is_convert_enabled(),
|
||||
"cellular_enabled": _setting_bool(
|
||||
@@ -99,6 +100,7 @@ def update_settings(body):
|
||||
values = {}
|
||||
for field in (
|
||||
"dashcam_upload_enabled",
|
||||
"trips_upload_enabled",
|
||||
"video_convert_enabled",
|
||||
"cellular_enabled",
|
||||
"ntfy_enabled",
|
||||
@@ -184,6 +186,9 @@ def update_settings(body):
|
||||
app_upload.DASHCAM_UPLOAD_SETTING_KEY: (
|
||||
"1" if values["dashcam_upload_enabled"] else "0"
|
||||
),
|
||||
app_upload.TRIPS_UPLOAD_SETTING_KEY: (
|
||||
"1" if values["trips_upload_enabled"] else "0"
|
||||
),
|
||||
app_video.RETENTION_SETTING: str(values["local_video_retention_days"]),
|
||||
app_video.CONVERT_SETTING: "1" if values["video_convert_enabled"] else "0",
|
||||
"cellular_enabled": "1" if values["cellular_enabled"] else "0",
|
||||
|
||||
+48
-19
@@ -90,7 +90,9 @@ SQL_CREATE_TRIPS_TABLE = """CREATE TABLE IF NOT EXISTS trips (
|
||||
DURATION_SECONDS integer NOT NULL,
|
||||
POINT_COUNT integer NOT NULL,
|
||||
AVERAGE_SPEED_KMH float NOT NULL DEFAULT 0,
|
||||
MAX_SPEED_KMH float NOT NULL DEFAULT 0
|
||||
MAX_SPEED_KMH float NOT NULL DEFAULT 0,
|
||||
START_ODOMETER float,
|
||||
END_ODOMETER float
|
||||
);"""
|
||||
|
||||
SQL_CREATE_TRIP_BUILD_STATE_TABLE = """CREATE TABLE IF NOT EXISTS trip_build_state (
|
||||
@@ -180,6 +182,8 @@ def _migrate_trips_table(conn):
|
||||
additions = {
|
||||
"AVERAGE_SPEED_KMH": "float NOT NULL DEFAULT 0",
|
||||
"MAX_SPEED_KMH": "float NOT NULL DEFAULT 0",
|
||||
"START_ODOMETER": "float",
|
||||
"END_ODOMETER": "float",
|
||||
}
|
||||
for name, definition in additions.items():
|
||||
if name not in existing_columns:
|
||||
@@ -271,7 +275,7 @@ def refresh_trips(force=False):
|
||||
if intersecting_trip[0] is not None:
|
||||
rebuild_start_id = min(rebuild_start_id, intersecting_trip[0])
|
||||
rows = conn.execute(
|
||||
"""SELECT id, DATETIME, GPSX, GPSY, SPEED
|
||||
"""SELECT id, DATETIME, GPSX, GPSY, SPEED, ODOMETER
|
||||
FROM tracking WHERE id >= ? ORDER BY id ASC""",
|
||||
(rebuild_start_id,),
|
||||
).fetchall()
|
||||
@@ -289,13 +293,15 @@ def refresh_trips(force=False):
|
||||
START_DATETIME, END_DATETIME,
|
||||
START_GPSX, START_GPSY, END_GPSX, END_GPSY,
|
||||
DISTANCE_KM, DURATION_SECONDS, POINT_COUNT,
|
||||
AVERAGE_SPEED_KMH, MAX_SPEED_KMH
|
||||
AVERAGE_SPEED_KMH, MAX_SPEED_KMH,
|
||||
START_ODOMETER, END_ODOMETER
|
||||
) VALUES (
|
||||
:id, :start_tracking_id, :end_tracking_id,
|
||||
:start_datetime, :end_datetime,
|
||||
:start_gpsx, :start_gpsy, :end_gpsx, :end_gpsy,
|
||||
:distance_km, :duration_seconds, :point_count,
|
||||
:average_speed_kmh, :max_speed_kmh
|
||||
:average_speed_kmh, :max_speed_kmh,
|
||||
:start_odometer, :end_odometer
|
||||
)""",
|
||||
trips,
|
||||
)
|
||||
@@ -325,15 +331,25 @@ def get_overview_stats():
|
||||
COALESCE(SUM(DURATION_SECONDS),0), COALESCE(MAX(MAX_SPEED_KMH),0)
|
||||
FROM trips
|
||||
""").fetchone()
|
||||
odo_row = conn.execute("""
|
||||
SELECT MAX(ODOMETER) FROM tracking WHERE ODOMETER IS NOT NULL AND ODOMETER > 0
|
||||
""").fetchone()
|
||||
dtc_row = conn.execute("""
|
||||
SELECT COUNT(*) FROM dtc_codes WHERE cleared_at IS NULL
|
||||
""").fetchone() if _table_exists(conn, "dtc_codes") else (0,)
|
||||
from modules import app_video as _av
|
||||
try:
|
||||
video_count = sum(1 for _ in _av.list_local_videos().get("files", []))
|
||||
except Exception:
|
||||
video_count = 0
|
||||
return {
|
||||
"trip_count": row[0],
|
||||
"total_km": row[1],
|
||||
"total_duration_s": row[2],
|
||||
"all_time_max_speed": row[3],
|
||||
"active_dtc": dtc_row[0] if dtc_row else 0,
|
||||
"video_count": video_count,
|
||||
"odometer_km": odo_row[0] if odo_row and odo_row[0] else None,
|
||||
}
|
||||
|
||||
|
||||
@@ -353,21 +369,34 @@ def list_trips():
|
||||
"""SELECT id, START_DATETIME, END_DATETIME,
|
||||
START_GPSX, START_GPSY, END_GPSX, END_GPSY,
|
||||
DISTANCE_KM, DURATION_SECONDS, POINT_COUNT,
|
||||
AVERAGE_SPEED_KMH, MAX_SPEED_KMH
|
||||
FROM trips ORDER BY START_DATETIME DESC"""
|
||||
AVERAGE_SPEED_KMH, MAX_SPEED_KMH,
|
||||
START_ODOMETER, END_ODOMETER
|
||||
FROM trips ORDER BY START_DATETIME ASC"""
|
||||
).fetchall()
|
||||
return [{
|
||||
"id": row[0],
|
||||
"start_datetime": row[1],
|
||||
"end_datetime": row[2],
|
||||
"start": {"latitude": row[3], "longitude": row[4]},
|
||||
"end": {"latitude": row[5], "longitude": row[6]},
|
||||
"distance_km": row[7],
|
||||
"duration_seconds": row[8],
|
||||
"point_count": row[9],
|
||||
"average_speed_kmh": row[10],
|
||||
"max_speed_kmh": row[11],
|
||||
} for row in rows]
|
||||
# If real OBD odometer is available use it; otherwise fall back to cumulative distance
|
||||
result = []
|
||||
cumulative_km = 0.0
|
||||
for row in rows:
|
||||
dist = row[7] or 0
|
||||
start_odo = row[12] if row[12] is not None else round(cumulative_km, 1)
|
||||
end_odo = row[13] if row[13] is not None else round(cumulative_km + dist, 1)
|
||||
cumulative_km += dist
|
||||
result.append({
|
||||
"id": row[0],
|
||||
"start_datetime": row[1],
|
||||
"end_datetime": row[2],
|
||||
"start": {"latitude": row[3], "longitude": row[4]},
|
||||
"end": {"latitude": row[5], "longitude": row[6]},
|
||||
"distance_km": row[7],
|
||||
"duration_seconds": row[8],
|
||||
"point_count": row[9],
|
||||
"average_speed_kmh": row[10],
|
||||
"max_speed_kmh": row[11],
|
||||
"odo_start_km": start_odo,
|
||||
"odo_end_km": end_odo,
|
||||
})
|
||||
result.reverse() # newest first for display
|
||||
return result
|
||||
|
||||
|
||||
def get_trip_route(trip_id, max_points=2000):
|
||||
@@ -674,7 +703,7 @@ def _log_loop():
|
||||
)
|
||||
if should_log_tracking(
|
||||
position, speed, last_position, seconds_since_log
|
||||
) and insert_tracking(position[0], position[1], speed):
|
||||
) and insert_tracking(position[0], position[1], speed, app_obd.odometer):
|
||||
last_position = position
|
||||
last_logged_at = now
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ def normalize_tracking_rows(rows):
|
||||
latitude = _number(row[2])
|
||||
longitude = _number(row[3])
|
||||
speed = _number(row[4]) or 0
|
||||
odometer = _number(row[5]) if len(row) > 5 else None
|
||||
if timestamp is None or latitude is None or longitude is None:
|
||||
continue
|
||||
if not (-90 <= latitude <= 90 and -180 <= longitude <= 180):
|
||||
@@ -56,6 +57,7 @@ def normalize_tracking_rows(rows):
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"speed": max(0, speed),
|
||||
"odometer": odometer,
|
||||
})
|
||||
return normalized
|
||||
|
||||
@@ -202,6 +204,8 @@ def build_trip_summaries(raw_rows):
|
||||
"max_speed_kmh": round(
|
||||
max(obd_speeds or segment_speeds or [0]), 1
|
||||
),
|
||||
"start_odometer": next((r["odometer"] for r in trip_rows if r.get("odometer")), None),
|
||||
"end_odometer": next((r["odometer"] for r in reversed(trip_rows) if r.get("odometer")), None),
|
||||
})
|
||||
return trips
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ BACKOFF_MAX = 300
|
||||
DASHCAM_CHUNK_SIZE = 40 * 1024 * 1024
|
||||
|
||||
DASHCAM_UPLOAD_SETTING_KEY = "dashcam_upload_enabled"
|
||||
TRIPS_UPLOAD_SETTING_KEY = "trips_upload_enabled"
|
||||
|
||||
_upload_thread = None
|
||||
_last_error = None
|
||||
@@ -90,6 +91,11 @@ def set_dashcam_upload_enabled(enabled):
|
||||
app_sql.set_setting(DASHCAM_UPLOAD_SETTING_KEY, "1" if enabled else "0")
|
||||
|
||||
|
||||
def is_trips_upload_enabled():
|
||||
"""Defaults to enabled."""
|
||||
return app_sql.get_setting(TRIPS_UPLOAD_SETTING_KEY, "1") == "1"
|
||||
|
||||
|
||||
def get_status():
|
||||
"""Snapshot used by the /api/upload/status endpoint for the web UI."""
|
||||
return {
|
||||
@@ -138,6 +144,8 @@ def _upload_tracking_batch():
|
||||
modem's AT-command HTTP client before giving up.
|
||||
"""
|
||||
global _last_network_path
|
||||
if not is_trips_upload_enabled():
|
||||
return 0
|
||||
|
||||
rows = app_sql.get_unsent_tracking_rows(limit=TRACKING_BATCH_SIZE)
|
||||
if not rows:
|
||||
|
||||
Reference in New Issue
Block a user