Commit Graph
60 Commits
Author SHA1 Message Date
Mathieu Moran a0be96dfaa fix: BUG-22 smooth GPS-derived speeds in trip max (trailing window) 2026-08-14 23:00:53 -04:00
Mathieu Moran d9314969d8 fix: BUG-22 include GPS-derived speeds in trip max (Pi + server) 2026-08-14 22:56:37 -04:00
Mathieu Moran 2fee2de357 fix: BUG-21 GPS-speed fallback in server route replay 2026-08-14 22:34:29 -04:00
Mathieu Moran 01c7fdf0c4 fix: BUG-20 clamp trips sequence after recompute + renumber migration 2026-08-14 21:45:26 -04:00
Mathieu Moran fa8ae0001a ui: use redacted dashboard screenshots + restore GitHub footer link
- pics/ui.png regenerated from redacted desktop + mobile screenshots
- restore GitHub footer link in Pi + server base.html (accidentally removed)
2026-08-12 23:44:40 -04:00
Mathieu Moran 495e0a99f9 docs: refresh readme status + dashboard screenshots
- Project status -> Phase 27 done, Phase 28 (Android app) in progress
- Added Android companion app to feature list
- Regenerated pics/ui.png (mobile 390px + desktop 1280px dashboards)
2026-08-12 22:58:59 -04:00
Mathieu Moran 2012370cef ui: stack odometer under speed in replay timeline + GitHub footer link (Pi + server) 2026-08-12 17:24:34 -04:00
Mathieu Moran 0d5aea5947 feat: simulated odometer in trip replay + trip list (continuous across trips)
- Pi route_points: emit per-point odometer (raw OBD value or baseline+prior+within-trip distance)
- Pi get_trip_route: SELECT ODOMETER, compute prior-trips distance + baseline
- Pi list_trips: fallback now starts from baseline (was 0)
- Server get_trip_route: per-point odometer (raw or simulated) with haversine accumulation
- Server list_trips: odo_start/end from baseline + cumulative (was hardcoded None)
- history.js: Odometer line in replay scrubber below speed
- Both history templates: trip-replay-odometer element
2026-08-12 16:59:00 -04:00
Mathieu Moran db971ab0e3 style: red underline on mobile topbar logo 2026-08-12 05:37:26 -04:00
Mathieu Moran 4a919b55a1 style: dark mobile UI — bottom nav + topbar match sidebar
- Mobile bottom nav: white → dark (#1c1c1e) bg, white icons/text
- Mobile topbar: white → dark bg on small screens, white text/controls
- Mobile logo color: dark → white for dark bg readability
- Desktop topbar remains white (contrasts with dark sidebar)
2026-08-12 05:34:24 -04:00
Mathieu Moran 2989f60f9c style: minimalist Material icon buttons (download, upload, delete)
- DashcamScreen: Download + Delete as IconButton with Icons.Filled
- DownloadsScreen: CloudUpload + Delete as IconButton
- No text labels, just clean Material icons
2026-08-12 04:27:51 -04:00
Mathieu Moran 6b47dafdb8 style: yaris-obd lowercase logo 2026-08-12 04:23:42 -04:00
Mathieu Moran d613252155 style: dark sidebar + Racing Sans One YARIS wordmark logo
- Sidebar bg: navy (#1a1a2e) → dark gray (#1c1c1e)
- Logo: replaced fa-car icon + text with styled YARIS wordmark
  using Racing Sans One (free Google Font, OFL licensed)
- Red accent underline (#e63946) on logo
- Applied to both server (server/templates/base.html) and
  Pi (templates/base.html) + mobile topbar
2026-08-12 04:15:51 -04:00
Mathieu Moran 0ddfe39901 refactor: ditch Nominatim, use reverse_geocoder exclusively for geocoding
Nominatim was slow (rate-limited, 1 req/sec, ~4s timeouts) and unreliable
from Docker/Cloudflare (blocked or throttled). It was the root cause of
location labels failing to appear during trip scrubbing.

- server/app.py: _geocode_cached now uses reverse_geocoder exclusively
  (offline, instant, city/province level). Removed Nominatim HTTP calls,
  ThreadPoolExecutor, concurrent.futures import.
- app.py (Pi): same — reverse_geocoder imported at module level,
  Nominatim + urllib.request removed.
- Batch endpoint simplified to sequential loop with lru_cache + dedup.
  No network dependency at all — geocoding is instant and always works.

reverse_geocoder provides city/province labels (e.g. 'Montreal, Quebec')
which is the right granularity for trip scrubbing.
2026-08-12 03:27:00 -04:00
Mathieu Moran e60b7d7d36 perf: geocode pipeline — batch POST + parallel server, 200→1 roundtrip
JS preload(): replaced N individual GET /api/geocode (concurrency 2) with
a single POST /api/geocode/batch — one HTTP roundtrip instead of up to
N/2 sequential ones. schedLoc debounce reduced 600→200ms.

Server geocode_batch(): ThreadPoolExecutor(max_workers=4) parallelises
Nominatim/reverse_geocoder calls instead of sequential loop. Deduplicates
coords before submitting futures. lru_cache absorbs duplicates within batch.

Pi geocode_batch_route(): deduplicate coords, removed stale 'if label:'
guard (label always non-empty after BUG-13 fix).
2026-08-12 03:15:30 -04:00
Mathieu Moran 830981934f fix: BUG-13 — server geocode returns raw coords instead of location names
Root cause: server _geocode_cached() returned None → /api/geocode
returned HTTP 204 with no body. JS frontend treated 204 as success,
r.json() on empty body threw → showed raw-coordinate fallback.

Server fixes:
- _geocode_cached() now always returns a non-empty string (coordinate
  fallback when both Nominatim and reverse_geocoder fail)
- /api/geocode always returns HTTP 200 with {label: '...'} (never 204)
- /api/geocode/batch always includes every coord key in result dict
- Nominatim call: jsonv2 + accept-language=en (aligned with Pi)
- reverse_geocoder: mode=1 (aligned with Pi)

Pi alignment:
- _geocode_cached() now checks for empty reverse_geocoder label before
  returning, falling through to coordinate string (same guard as server)
2026-08-12 02:53:44 -04:00
Mathieu Moran e30b85d218 feat: portal-wide device selector — session-based, all pages now use selected device
- _portal_device_id() reads from session, falls back to DEVICE_NAME env var
- GET/POST /api/v1/portal/select-device for selector data + set
- Dropdown in server base.html topbar, JS populates from API, page reloads on change
- All portal pages (overview, trips, dashcam, DTC, device settings) now use selected device
2026-08-12 01:40:51 -04:00
Mathieu Moran 3916f9879e feat: FEAT-05 — DTC history sync to remote server
- Server: dtc_events table (migrate_phase27_dtc.sql), db.py helpers
  (upsert_dtc_snapshot, list_dtc_events, delete_dtc_event)
- Server API: POST /api/v1/dtc/batch (device auth), GET/DELETE /api/v1/portal/dtc
- Server portal: /dtc page with code, description, MIL, status, timestamps, delete
- Server nav: DTC history in sidebar + mobile bottom nav
- Pi: _upload_dtc_snapshot() in app_upload.py — uploads when snapshot changes,
  skips when OBD disconnected or unchanged
2026-08-12 01:23:23 -04:00
Mathieu Moran 40f92d8703 feat: Phase 28.1 — gradle wrapper + debug APK builds successfully
- gradlew + gradle-wrapper.jar (Gradle 8.11.1)
- Fixed manifest icon to @android:mipmap/sym_def_app_icon (no custom asset needed yet)
- Added adaptive icon XML + colors for future custom icon
- Removed deprecated buildfeatures.buildconfig from gradle.properties
- 17MB debug APK builds: ./gradlew assembleDebug
- local.properties auto-ignored (contains SDK path)
2026-08-12 00:48:05 -04:00
Mathieu Moran 0b2162a595 style: odometer field uses plaintext display — no stepper arrows, grey background 2026-08-11 23:44:05 -04:00
Mathieu Moran d35cea367f fix: odometer sync is Pi→Server only — portal displays read-only value from DB 2026-08-11 22:14:46 -04:00
Mathieu Moran 44cd8bd192 feat: odometer_baseline field on server portal device settings page 2026-08-11 19:52:51 -04:00
Mathieu Moran 32de77131c feat: odometer baseline syncs to server — push on save, pull on sync, +/api/device-config/push endpoint 2026-08-11 19:31:52 -04:00
Mathieu Moran 3202045654 fix: restore separate checkbox+ID cells, add matching headers (8 columns server) 2026-08-11 15:25:29 -04:00
Mathieu Moran e8c5bfec9a fix: put checkbox inside date cell — both Pi and server now share same 6-column layout 2026-08-11 15:22:05 -04:00
Mathieu Moran 22f4ae2e69 fix: merge ID into date cell, add empty checkbox header — columns now align 2026-08-11 15:17:51 -04:00
Mathieu Moran 5713417f4f Server: restore checkbox + batch-delete. Conditional ID (server only) and checkbox via data-show-id/data-batch-delete-url. 2026-08-11 15:11:12 -04:00
Mathieu Moran 2ee4862edd Remove checkbox column and batch-delete from trip history 2026-08-11 15:05:40 -04:00
Mathieu Moran 23ac6c218b feat: add trip ID column (#18517) to history table on Pi and server 2026-08-11 14:58:25 -04:00
Mathieu Moran f6a50b6bc1 docs: BUG-06 closed — migration shifted 3593 rows +4h EDT, recomputed 43 trips, times now correct 2026-08-11 14:53:18 -04:00
Mathieu Moran 5018c6c531 fix(phase26): batch geocode endpoint for faster server trip scrubber 2026-08-11 04:44:37 -04:00
Mathieu Moran bdc7a9afb7 fix(phase26): server trip delete persists across recompute + batch delete with checkboxes 2026-08-11 04:18:58 -04:00
Mathieu Moran 96fb8971eb fix(phase26): server trip detection mirrors Pi motion-aware logic (BUG-04) 2026-08-11 02:27:17 -04:00
Mathieu Moran c5c478bef6 fix(phase26): add Pi trip deletion (BUG-08) 2026-08-11 02:07:59 -04:00
Mathieu Moran e2f2314318 fix(phase26): persist DTC/MIL state + server speed_source (BUG-03, BUG-05)
BUG-03: DTC/MIL codes were never persisted; lost on OBD disconnect or reboot.
- Add new dtc table schema (code, description, MIL, first/last seen, IS_SET)
  and a migration that rebuilds the original unused NAME/STATUS/DTC_ID table.
- Persist MIL + codes to SQLite on each successful OBD poll (_persist_dtc_state).
- /api/dtc falls back to the last-persisted snapshot when OBD is offline/stale.
- Fix get_overview_stats to count the real dtc table (was querying non-existent
  dtc_codes table).
- Add cleared/since tracking via IS_SET + FIRST_SEEN_AT/LAST_SEEN_AT.

BUG-05: server trip replay hardcoded speed_source:'gps'; mirror Pi's
'obd if speed>0 else gps' logic in server get_trip_route.

Adds tests/test_dtc_persistence.py (7 tests).
2026-08-11 00:38:05 -04:00
Mathieu MoranandCopilot d216aac97e Refresh docs and reorder roadmap
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:48:59 -04:00
Mathieu MoranandCopilot 675bffb4a4 feat: add server-side device deletion
- add portal API and UI action to delete non-default devices
- protect the configured portal default device from deletion
- document that trip/history pages still scope to the single DEVICE_NAME

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:09:15 -04:00
Mathieu MoranandCopilot ba682fbf3a feat: add multi-device provisioning and local remote-config UI
- add multi-device portal device management, key rotation, and sync request APIs
- add Pi settings UI for remote server URL, device name, and API key
- add local test-connection and sync-now actions
- make upload and config sync reuse dynamic shared credentials
- add Phase 25.5c migration and docs updates

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:02:09 -04:00
Mathieu MoranandCopilot a114939914 fix: authenticate device config sync like upload client
- send X-Device-Name and X-Api-Key to /api/device-config
- require YARIS_DEVICE_NAME alongside shared upload creds
- fix server create_device output to match actual Pi env names
- document shared upload/config sync credential requirements

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:42:40 -04:00
Mathieu MoranandCopilot 585da18225 feat: add remote device settings portal and Pi hot reload
- add /settings/device portal page and portal device-config GET endpoint
- add portal navigation and client-side device settings editor UI
- hot-reload Account/Car display config after local saves and server syncs
- document portal workflow and Phase 25.5b completion
- add runtime reload tests for account, car, and sync callback

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:34:22 -04:00
Mathieu MoranandCopilot cd004fbdde fix: wire Phase 25.5a deploy docs and sync connectivity
- use app_network status for device sync connectivity checks
- mount migrate_phase25_5a.sql in server docker compose
- document manual server upgrade migration steps
- record verified Pi and unraid SSH access details in PLAN

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:22:02 -04:00
Mathieu MoranandCopilot 2395cf6dc4 feat: add Phase 25.5a server-side device config API endpoints
- Add database migration (migrate_phase25_5a.sql):
  * Add owner_name, car_model, device_name, avatar_path columns to devices table
  * Add config_version and config_updated_at for cache invalidation

- Add server/modules/device_config.py:
  * get_device_config(device_id): retrieve config for Pi sync
  * update_device_config(device_id, data): update config with validation

- Add server API endpoints:
  * GET /api/device-config (authenticated with device_key, Pi fetches config)
  * POST /api/device-config (authenticated as user, admin updates config)

- Enhance server/modules/db.py:
  * Add get_device_by_id() function to retrieve all device config fields

Phase 25.5a enables server-side device identity management and connection-driven sync.
Pi will fetch from this endpoint when internet available, keeping config in sync.

Co-authored-by: Copilot <[email protected]>
2026-08-10 16:46:52 -04:00
Mathieu MoranandCopilot 0cd798ee19 fix: use total trip distance as odometer fallback when PID 0xA6 unavailable
If no real odometer data exists (all NULL — expected on 2013 Yaris which
likely doesn't support PID 0xA6), fall back to SUM(distance_km) from the
computed trips table so the overview shows something meaningful.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:59:53 -04:00
Mathieu MoranandCopilot 8f8c03a26a fix: server odometer stat shows 0 when no odometer data exists
COALESCE(MAX(odometer), 0) returned 0 when all tracking rows have NULL
odometer (expected before the car is connected — PID 0xA6 likely unsupported
on 2013 Yaris). Now returns NULL which the overview JS renders as '—'.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:58:22 -04:00
Mathieu MoranandCopilot f90a5e5826 fix: switch server portal map tiles to CARTO (OSM was rate-limiting/blocking)
OSM tile.openstreetmap.org was returning grey tiles on the portal (likely
rate-limiting the Cloudflare tunnel egress IP). Switch to CARTO Voyager
tiles which are free, CDN-backed, and don't have the same restrictions.

Also add data-tile-subdomains attribute support in history.js so tile
provider subdomains can be configured per-deployment.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:51:18 -04:00
Mathieu MoranandCopilot c287ab67a3 fix: map zoom limits configurable per-deployment via data attributes
Pi offline tiles only cover zoom 8-15; OSM live tiles support 0-19.
history.js now reads data-min-zoom/data-max-zoom from the #history-map div
instead of hardcoding 3/19. Pi template caps to 8-15 (prevents blank tiles
at extreme zooms), server template allows full 3-19 OSM range.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:42:04 -04:00
Mathieu MoranandCopilot 1b39ac0cc8 fix: pre-car-test bug fixes from full code audit
- app_alerts.py: null guard on list_local_videos() result (.get('files', []))
- app_cellular.py: remove erroneous .encode(errors='replace') on str response body
- app_settings.py: initialize apn='' when isinstance check fails (NameError guard)
- app_upload.py: move json import to top of file instead of inside except handler
- server/app.py: wrap int(request.args.get('limit')) in try/except ValueError
- server/app.py: wrap os.path.commonpath() in try/except ValueError (cross-root paths)
- tests/test_app_settings.py: add missing trips_upload_enabled/video_convert_enabled
  to valid_payload() (fields added in Phase 23.5 but test not updated)

All 63 tests pass.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:15:27 -04:00
Mathieu MoranandCopilot 21e44a8586 Fix server sidebar collapsed state: hide badge and user footer text
- sidebar-env-badge hidden when collapsed
- sidebar-user-text collapses like nav link spans
- User icon stays centered when collapsed via sidebar-user-footer flex

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:56:44 -04:00
Mathieu MoranandCopilot b172b89318 Simplify delete confirmation to native window.confirm
Bootstrap Modal async was unreliable. Replace with synchronous
window.confirm() which always works and needs no dependencies.

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:52:03 -04:00
Mathieu MoranandCopilot 7aa9ffda16 Add delete with confirmation for trips and dashcam
- Confirmation modal in server base.html (window.confirmDelete promise helper)
- Trip delete: trash button in Replay column (server-only via data-deletable on
  trip-list div), DELETE /api/v1/portal/trip/<id>, removes computed trip only
  (raw tracking_points kept)
- Dashcam delete: trash button next to download, DELETE /api/v1/portal/dashcam/<id>,
  removes from DB + disk (best-effort file removal)
- history.js: delete button gated on data-deletable='1' attribute; click handler
  intercepts before replay logic
- db.py: delete_trip(), delete_dashcam_file() helpers

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:40:22 -04:00