Commit Graph
100 Commits
Author SHA1 Message Date
Mathieu MoranandCopilot 264d38a6dc cleanup: fix duplicate app_gps import, remove debug prints from index route
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:38:49 -04:00
Mathieu MoranandCopilot d96a971f15 fix: diagnose.sh DB backup line shows literal $DB_PATH
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:31:50 -04:00
Mathieu MoranandCopilot 437434b972 feat: add scripts/diagnose.sh — system diagnostic & recovery tool
Read-only health check (no args) or auto-fix mode (--fix):
  - Service: active state, crash loop detection, recent log
  - Hardware: OBD USB adapter (/dev/ttyUSBx), SIM868 UART (/dev/serial0),
    dashcam USB mount, permissions
  - Network: hotspot IP, web UI reachability, internet, upload server
  - Storage: disk usage, SQLite integrity check, row counts, upload backlog,
    log file size
  - OBD status: parsed from log (last connect/disconnect/speed/errors)
  - Python env: venv present, key packages importable
  - Pi health: throttling flags decoded, CPU temp, free memory
  - Manual recovery command cheatsheet printed at the end

Exit code = number of critical issues found (0 = healthy).

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:31:03 -04:00
Mathieu MoranandCopilot c03cd6d48d fix: add OBD_SKIP_VOLTAGE_CHECK support for clone ELM327 adapters
Counterfeit ELM327 chips (common in cheap PL2303 USB adapters) often report
bad/zero voltage even when the OBD connection is working. python-obd's
check_voltage=True default rejects these. Add OBD_SKIP_VOLTAGE_CHECK=1 env
var to pass check_voltage=False to obd.OBD(), documented in yaris-obd.service.

Also document Phase 24.6 Yaris 2013 compatibility analysis in PLAN.md.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:26:40 -04:00
Mathieu MoranandCopilot 10dea22f9a fix: Yaris 2013 OBD compatibility hardening
- _decode_odometer: catch all exceptions and return None instead of raising.
  PID 0xA6 (odometer) is not guaranteed on 2013 Toyota Yaris — a garbage
  response or unsupported PID previously raised ValueError through python-obd's
  decoder, which app_obd._query() caught and treated as a lost connection,
  killing the entire OBD session. Now it gracefully returns None.

- connect_obd: reset all sensor globals (rpm, coolant_temp, fuel_level,
  throttle_position, mil_on, dtc_codes, odometer, timestamps) on each new
  connection so the UI doesn't display stale readings from the previous
  ignition cycle while fresh polls are completing.

- yaris-obd.service: add commented OBD_PROTOCOL=6 hint for post-confirmation
  pinning (eliminates 3-5s auto-probe delay after first confirmed connection).

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:22:36 -04:00
Mathieu MoranandCopilot a56e4252d3 docs: document Phase 24.5 code audit in PLAN.md
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:17:32 -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 f30143c7b7 docs: document Phase 24 bug fixes and UI polish in PLAN.md
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:07:20 -04:00
Mathieu MoranandCopilot 2f1e35414d Fix sidebar arrow direction: use CSS rotation not JS icon swap
The CSS already handles the arrow direction with transform:rotate(180deg) on
.app-sidebar.collapsed .sidebar-collapse-btn. The JS icon className swap was
fighting with CSS rotation causing a double-flip. Remove JS icon manipulation,
let CSS handle it. Also suppress the button transition on page load.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:04:57 -04:00
Mathieu MoranandCopilot 3c3c8bba71 Fix sidebar collapse flash on page navigation
Suppress CSS transitions during initial state restore from localStorage.
Re-enable after two rAF frames so the toggle button still animates smoothly.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:00:22 -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 72e4c1b1d7 Fix trip delete button: layout and click handler
- text-nowrap on action cell so Replay+trash stay on same line
- Delete button inline with Replay (no whitespace between)
- Explicit deletable guard in click handler

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:45:05 -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
Mathieu MoranandCopilot a13d74e18e Fix remote portal map tiles and geocode
- history.js reads tile URL from data-tile-url attribute on #history-map div,
  falling back to local offline tiles (Pi behavior unchanged)
- server history.html sets data-tile-url to live OSM tiles
- maxZoom raised to 19 (OSM supports it, local tiles were limited to 15)
- server geocode returns 'label' key to match what history.js/yaris.js expect

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:34:49 -04:00
Mathieu MoranandCopilot e2a319ae08 Fix history.js null guard for speed and distance
formatSpeed and formatDistance return '—' on null/undefined values.
Server trips may have null avg/max speed if no speed data was recorded.

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:30:54 -04:00
Mathieu MoranandCopilot 693848aba1 Fix remote portal trip list
- /api/trips now returns {trips:[...]} to match what history.js expects (result.trips)
- odo_start_km null guard in history.js (server trips have no odometer data)
- require_login returns 401 JSON for /api/* paths instead of HTML redirect

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:27:57 -04:00
Mathieu MoranandCopilot 64b9481ce0 Phase 24: add password change page
- GET/POST /account/password with CSRF token and bcrypt re-hash
- 'Password' link in sidebar next to Logout
- change_password.html template

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:22:28 -04:00
Mathieu MoranandCopilot c4dfcbaf25 Phase 24: remote trip + dashcam portal
- Add Flask-Login browser session auth (separate from device X-Api-Key auth)
  - modules/user_auth.py: User model, require_login decorator, bcrypt helpers
  - /login with CSRF token, /logout
  - scripts/create_user.py to provision admin user

- Add server-side trip computation from tracking_points
  - db.compute_trips_for_device(): gap > 5 min = new trip, haversine distance,
    upserts into trips table, auto-triggered in bg thread after each upload batch
  - db.list_trips(), db.get_trip_route(): same JSON shape as Pi API
  - db.get_overview_stats(): aggregated totals for overview page
  - db.list_dashcam_files(), db.get_dashcam_file(): browse/stream helpers

- Add web portal to server/app.py
  - Overview page (/): stat cards + recent trips + recent clips
  - Trip history (/history): reuses Pi history.js + data-list.js unchanged
    via /api/trips + /api/trip/<id> compatibility aliases
  - Dashcam archive (/dashcam): file list + inline video player
  - /api/v1/portal/dashcam/stream/<id>: Range-request streaming, path traversal guard
  - /api/geocode: same Nominatim→reverse_geocoder offline fallback as Pi

- Add server templates (server/templates/)
  - base.html: shared shell with 'Remote server' badge in sidebar + mobile topbar
  - login.html, index.html, history.html, dashcam.html, components.html

- DB schema: users + trips tables added to init.sql (fresh deploys) +
  migrate_phase24.sql (existing deployments)

- Infrastructure: Dockerfile uses repo-root build context to copy shared
  static/ assets; docker-compose updated; SECRET_KEY + DEVICE_NAME in .env;
  requirements: Flask-Login, bcrypt, reverse_geocoder

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:58:45 -04:00
Mathieu MoranandCopilot 51c6be3a94 docs: update PLAN.md phase 23.5, README features/hardware/status, new screenshots
- PLAN.md: Phase 23.5 documents all work from this session
- README: hardware table adds T3U, features add geocoding/offline maps/T3U, status updated
- pics/ui.png: desktop + mobile side-by-side screenshot (updated)

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:43:09 -04:00
Mathieu MoranandCopilot 58faded602 fix(dashboard): parseFloat GPS coords before toFixed call
Co-authored-by: Copilot <[email protected]>
2026-08-10 03:31:55 -04:00
Mathieu MoranandCopilot a2ae15c2f0 fix(dashboard): geocode fires on first GPS fix, gated by distance not timer
Co-authored-by: Copilot <[email protected]>
2026-08-10 03:29:32 -04:00
Mathieu MoranandCopilot c5982e905f fix(geocode): replace AbortSignal.timeout with AbortController for compatibility
- AbortSignal.timeout not supported on all mobile browsers
- Manual AbortController + setTimeout works everywhere
- Fixes location not showing on dashboard and history first point

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:25:14 -04:00
Mathieu MoranandCopilot 11f07dec38 fix(geocode): accurate street name + always shows on dashboard
- Cache key uses 4 decimal places (~11m) instead of 3 (~111m) for correct street
- Dashboard geocodes on first GPS fix, then again every >50m moved (throttled 15s)
- Removed stale console.log

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:21:37 -04:00
Mathieu MoranandCopilot c188fb9d14 feat(geocode): street-level via Nominatim when online, city fallback offline
- Try Nominatim first (road + city), 4s timeout
- Fall back to reverse_geocoder (city + region) if no internet
- LRU cache (512 entries, ~111m precision) — repeated lookups are instant
- No external calls from browser, all via /api/georecode on Pi

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:18:36 -04:00
Mathieu MoranandCopilot e96c6a16bb fix(network): stable phone hotspot IP via dhcp-client-id=yaris-obd
- Phone DHCP caches by client-id so Pi always gets same lease
- Remove dashboard phone-hotspot banner (didn't work)

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:11:41 -04:00
Mathieu MoranandCopilot a6c6cddf30 feat(dashboard): show Pi IP banner when connected to phone hotspot
- Banner appears on dashboard only when uplink mode is 'phone'
- Shows the Pi's current IP so you can reach it from your phone's browser
- Polls every 15s, hides automatically when not on phone hotspot

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:06:39 -04:00
Mathieu MoranandCopilot 9f9974e197 fix(network): home-wifi-fb autoconnect=no, only activated by restore_fallback()
Co-authored-by: Copilot <[email protected]>
2026-08-10 02:56:25 -04:00
Mathieu MoranandCopilot 1299e35a88 fix(network): use wifi-sec shorthand in nmcli connection add
Co-authored-by: Copilot <[email protected]>
2026-08-10 02:53:41 -04:00
Mathieu MoranandCopilot 11e98de0ee feat(geocode): fully offline reverse geocode via local Pi API
- Remove all Nominatim/external calls from frontend
- Add /api/geocode?lat=&lon= route using reverse_geocoder (20MB offline KD-tree)
- JS hits /api/geocode only — works with no internet at all
- Falls back to raw coords if Pi route fails
- Add reverse_geocoder to requirements.txt

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:49:48 -04:00
Mathieu MoranandCopilot 91d475fd24 fix(map): show raw coords as fallback when geocode fails or times out
- 5s timeout on Nominatim fetch (AbortSignal.timeout)
- On network error, non-200, or empty result: show 'lat, lon' (5 decimal places)
- Applies to both dashboard live location and history scrubber

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:46:33 -04:00
Mathieu MoranandCopilot 548808fd83 feat(history): show reverse-geocoded location during trip scrubbing
- Location name (road + city) appears below time/speed in the timeline panel
- Debounced 600ms so dragging the scrubber doesn't spam Nominatim
- Deduped by ~3 decimal lat/lon so stationary replay doesn't re-fetch
- Cleared when a new trip is loaded

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:45:41 -04:00
Mathieu MoranandCopilot f1145a4dbb feat(map): reverse geocode location + recenter button on dashboard and history
- Dashboard: recenter button in GPS card header (hidden until first fix)
- Dashboard: estimated location name under lat/lon via Nominatim reverse geocode (throttled 30s)
- History: recenter button fits map back to trip bounds
- Both buttons use crosshairs icon, hidden until data is available

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:43:23 -04:00
Mathieu MoranandCopilot 08a3bef558 ui: tap any row to replay trip on mobile
- data-trip-id on the <tr> itself
- click handler checks row if no button found
- cursor: pointer on rows for discoverability

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:40:45 -04:00
Mathieu MoranandCopilot f27e2f2d4b ui: remove imperial units, hide Replay button on mobile
- formatDistance/formatSpeed: km/km/h only, no mi/mph
- Replay button hidden on mobile (d-none d-sm-table-cell)
- Action column header hidden on mobile to match
- data_table macro supports optional 'class' on column headers

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:38:50 -04:00
Mathieu MoranandCopilot 908e86d169 feat(network): wlan0 fallback if T3U dongle is absent
- PRIMARY_INTERFACE=wlan1, FALLBACK_INTERFACE=wlan0
- resolve_interface() checks /sys/class/net at startup and picks whichever exists
- INTERFACE is module-level, so all functions use the correct iface automatically
- restore_fallback() now also tries home-wifi-fb / Hotspot on wlan0 if wlan1 is primary but failed
- install_netwatch.sh creates home-wifi-fb profile on wlan0 (lower autoconnect-priority=5)
- wlan0 stays managed=yes (fallback) instead of being hard-disabled
- Status JSON now includes 'interface' field for diagnostics

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:35:06 -04:00
Mathieu MoranandCopilot e533f52e78 feat(network): switch all WiFi to wlan1 (TP-Link T3U dongle)
- INTERFACE = 'wlan1' in yaris_uplink.py
- install_netwatch.sh binds home-wifi, Hotspot, phone-hotspot to wlan1
- Disables wlan0 (onboard) via nmcli device set managed no
- WLAN variable at top of script for easy revert to wlan0 if needed

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:29:22 -04:00
Mathieu MoranandCopilot cc6e9c8de0 feat: add download button to Pi videos list
- /api/local-video/file?download=1 serves file as attachment
- Download link (green DL button) added to each row in local-videos.js
- Dashcam USB files skipped: no persistent mount for direct serve;
  use Import then download from Pi Videos

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:17:29 -04:00
Mathieu MoranandCopilot b711d421cc fix(videos): real ffmpeg progress via -progress pipe, exclude .converting.mp4
- ffmpeg -progress pipe:1 streams out_time_ms lines; we parse them in the
  reading loop so % = out_time_s / duration_s (actual encode position)
- find_video_files() skips *.converting.mp4 temp files so they are never
  listed, uploaded, or shown in the UI

Co-authored-by: Copilot <[email protected]>
2026-08-10 02:13:45 -04:00
Mathieu MoranandCopilot afce229310 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]>
2026-08-10 02:07:31 -04:00
Mathieu MoranandCopilot 414fb78543 fix(videos): base convert % on elapsed/duration not file size
File size ratio was wrong since output bitrate != input bitrate.
Now uses ffprobe to get video duration, then elapsed/duration for %.

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:50:49 -04:00
Mathieu MoranandCopilot 1fde8732c2 fix(videos): stop thumbnails reloading on every convert poll
Instead of full re-render every 10s, patch converting badges in-place.
Only does a full renderFiles() when file count changes (conversion done)
or on the final poll when all conversions finish.

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:49:03 -04:00
Mathieu MoranandCopilot 7d5755180a fix(videos): set explicit 4Mbps bitrate for hw encode
Default h264_v4l2m2m bitrate was ~200kbps producing unusable quality.
4M video + 128k audio = ~100MB per 3-min clip, proper dashcam quality.

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:43:28 -04:00
Mathieu MoranandCopilot e8ce33eded fix(videos): fix FileNotFoundError crashing convert thread
safe_video_path checks file existence — mp4 output doesn't exist yet.
Add _local_path_unchecked() for output paths that don't exist yet.

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:35:31 -04:00
Mathieu MoranandCopilot d9d1537a13 feat(videos): show conversion progress % and elapsed time
- _converting dict now tracks started_at, source_size, tmp_path per file
- list_local_videos() reads tmp file size to compute pct complete
- Desktop badge: 'Converting 42% 1m30s'
- Mobile badge: '42%' (compact, fits under thumbnail)

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:30:48 -04:00
Mathieu MoranandCopilot c995599512 feat(videos): add manual Convert button per MOV file on Pi
- New POST /api/local-video/convert route triggers queue_convert(force=True)
- queue_convert() now returns True/False/None and accepts force param
  to bypass the auto-convert toggle (manual always works)
- Convert button shown for non-MP4, non-converting files in video list
- Clicking spins the button and refreshes list after 1s to show badge

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:28:56 -04:00
Mathieu MoranandCopilot ba712bd6db fix(dashcam): prevent site freeze during import/convert
- _convert_bg now runs in real threading.Thread (not gevent greenlet)
  so ffmpeg proc.wait() doesn't block gevent event loop
- _device_lock released before copy loop; only held for mount/unmount
  so /api/dash/files and other routes don't block during long copies
- Date now shows time (Aug 9, 10:30 PM) on mobile video rows
- Spacing added above converting spinner badge

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:23:53 -04:00
Mathieu MoranandCopilot 77cd3fdadd feat(dashcam): show converting badge and date on mobile video rows
Co-authored-by: Copilot <[email protected]>
2026-08-10 01:18:03 -04:00
Mathieu MoranandCopilot 384050e78a fix(dashcam): show checkbox on mobile for file selection
Co-authored-by: Copilot <[email protected]>
2026-08-10 01:15:00 -04:00
Mathieu MoranandCopilot 71f8ce2993 feat(dashcam): async import with polling status
- Import now returns immediately with {status: 'started'}
- Backend runs copy in a daemon thread, tracks job state in _import_job
- New GET /api/dash/import/status endpoint for polling
- UI polls every 3s, shows progress bar until done
- Page reload mid-import resumes polling automatically
- Prevents double-import with 'busy' guard

Co-authored-by: Copilot <[email protected]>
2026-08-10 01:12:00 -04:00
Mathieu MoranandCopilot 7d124b0fa0 fix(dashcam): remove internal td borders on mobile rows
Co-authored-by: Copilot <[email protected]>
2026-08-10 01:01:17 -04:00
Mathieu MoranandCopilot 219d559de9 fix: dashcam mobile shows filename, RPM/fuel/throttle don't append unit when disconnected
- yaris.css: split mobile rules by list ID — video-list shows thumb+actions,
  dashcam-file-list shows dash-col-file+actions; thead display:none !important
- yaris.js: check data !== '-' before appending rpm/°C/% units so disconnected
  state shows — instead of '- rpm'

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:56:48 -04:00
Mathieu MoranandCopilot 522674c907 fix(dashcam): remove inline width from thumb td, control via CSS with !important
Inline style.width on td was overriding the mobile media query width rule.
Use dash-thumb-td class for desktop (9rem) and mobile (5rem !important).

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:53:02 -04:00
Mathieu MoranandCopilot 5b2854fbec fix(dashcam): use style.display instead of hidden attr (d-flex overrides hidden)
Bootstrap d-flex sets display:flex !important which beats the HTML hidden
attribute. Switch to style.display='none'/'flex' for both status bars.

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:51:27 -04:00
Mathieu MoranandCopilot b064b4f902 fix(dashcam): auto-hide import result bar after 10s
Co-authored-by: Copilot <[email protected]>
2026-08-10 00:48:31 -04:00
Mathieu MoranandCopilot ba760a04d6 fix(dashcam): proper mobile row reflow + import status at top with ETA
- yaris.css: use .dash-row class on all rows; only td:first-child and
  .dash-actions-td visible on mobile (<576px); buttons never overflow
- dash.js: add dash-row class + dash-actions-td wrapper div to dashcam
  file rows; import status moved to card footer at top with spinner + ETA
- local-videos.js: add dash-row class to video rows
- dash.html: import status bar (spinner + ETA + result) in device card
  at top; removed old footer import status from files card

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:46:05 -04:00
Mathieu MoranandCopilot 9f0f53a59c fix(dashcam): reflow table rows as flex cards on mobile so buttons are always visible
Replace column-hiding approach (still caused 691px overflow) with full
table reflow: on <576px each tr becomes a flex row, all tds hidden except
first (thumb+name) and .dash-actions-td (buttons). Buttons can never
overflow because the row is constrained to viewport width.

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:33:57 -04:00
Mathieu MoranandCopilot 01704d5429 feat(dashcam): mobile layout fix + optional hw-encode conversion on import
- yaris.css: on mobile (<576px) hide file/size/date/upload cols, show
  filename inside thumb cell, icon-only buttons, dash-actions-td nowrap
- local-videos.js: filename in thumb cell for mobile; Converting badge with
  spinner; auto-refresh every 10s while any file is converting
- app_video.py: CONVERT_SETTING, is_convert_enabled(), queue_convert(),
  _convert_bg() — h264_v4l2m2m + aac faststart, deletes original on success
- app_dash.py: call queue_convert() after each successful copy on import
- app_settings.py: expose video_convert_enabled in get/save settings
- settings.html: toggle for 'Convert imported videos for web playback'

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:30:59 -04:00
Mathieu MoranandCopilot dd2f95b9c1 revert(video): back to simple send_file for video serving (RPi3 can't transcode)
Co-authored-by: Copilot <[email protected]>
2026-08-10 00:21:19 -04:00
Mathieu MoranandCopilot a3659e8a8c fix(video): transcode audio to AAC for MP4 streaming (PCM unsupported in MP4)
Co-authored-by: Copilot <[email protected]>
2026-08-10 00:18:41 -04:00
Mathieu MoranandCopilot 51322d0e00 fix(dashcam): wrap action buttons in div, ffmpeg stream for MOV playback
- local-videos.js: action buttons go in a div.data-list-actions inside td
  so td stays a table cell (not flex); thumbnail td gets fixed width 9rem
- app.py: video route now pipes ffmpeg with -c copy -movflags
  frag_keyframe+empty_moov so browsers get a fragmented MP4 stream that
  starts playing immediately without needing the moov atom at the end

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:17:10 -04:00
Mathieu MoranandCopilot 5d9b2f6556 feat(dashboard): 3-col layout with Overview stats, speed chart fixes
- base.html: fix notification badge positioning (inline style, no overflow
  clip); remove py-4 from main; update footer text
- index.html: split bottom row into 3 cols (GPS / Overview / Speed);
  Overview card with 6 live stats from /api/stats; speed chart auto-expands
  on desktop; shown.bs.collapse triggers speedChart.resize()

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:13:23 -04:00
Mathieu MoranandCopilot ea03ef7f6c fix(dashcam): add missing dash-col CSS rules, correct MOV mime type and range clamp
- yaris.css: add dash-col-size/date/upload hide rules on mobile (<576px),
  dash-col-file truncation on tablet, btn-icon-mobile label hide
- app.py: clamp byte2 to file_size-1 to prevent Content-Length mismatch;
  detect MIME from file extension (.mov -> video/quicktime)

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:12:33 -04:00
Mathieu MoranandCopilot 375b93fcea fix: restore /api/stats route, remove dead player code, fix modal scroll
- app_sql.py: add get_overview_stats() with single SQL query (trip count,
  total km, duration, max speed, active DTC count)
- app.py: add /api/stats route (was missing after scp overwrote the file)
- local-videos.js: remove leftover old closePlayer/playVideo/renderFiles
  duplicate block that still had scrollIntoView and references to the old
  panel elements; new modal-based code is the only path now

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:07:14 -04:00
Mathieu MoranandCopilot 600fd41353 fix(dashcam): range-request video streaming, modal player, async thumbnails
- app.py: replace send_file with manual Range-aware chunked generator;
  returns 206 on partial requests so browsers can seek/buffer properly.
  Thumbnail route returns 202 while ffmpeg is generating.
- app_video.py: thumbnail generation moved to background gevent greenlet
  (_generate_thumbnail_bg). get_thumbnail_path returns immediately with
  RuntimeError('generating') if not cached; ffmpeg only ever runs once
  per video (result cached in dash/thumbnails/).
- dash.html: replaced bottom card player with Bootstrap modal (modal-xl,
  centered, dark bg) — video pops up in place regardless of scroll position.
- local-videos.js: rewritten to use Bootstrap modal; thumbnail loader
  polls with fetch() and retries on 202 every 2s (up to 15x) instead of
  setting img.src directly (which can't detect 202).

Co-authored-by: Copilot <[email protected]>
2026-08-10 00:01:49 -04:00
Mathieu MoranandCopilot 35ec705fe2 docs: update PLAN.md Phase 23 completion and README project status
Phase 23 section updated with full list of changes from all polish passes:
Overview stats card, speed chart rework, mobile dashcam table fixes,
trip history mobile layout, notification badge fix, footer text.
README project status updated to reflect Phases 1-23 complete.

Co-authored-by: Copilot <[email protected]>
2026-08-09 23:56:47 -04:00
Mathieu MoranandCopilot e2f7fa88df Speed history: rework chart and add live stats
- Replace 8-point scatter chart with proper 60-point line chart
  - Blue gradient fill (rgba 35%→2%), tension 0.4 smooth curve
  - No legend (obvious), no X axis grid, soft Y axis gridlines
  - Y axis auto-scales: max(60, sessionMax*1.2) rounded to 10s
  - Ticks: '0 km/h' labels on Y, time strings on X (max 6)
  - chart.update('none') for live updates (no animation stutter)
- Speed history card header now shows current speed + session max
  even when the graph is collapsed
- Footer bar inside expanded graph: session avg, point count, last reading time
- makeGradient() refreshed on every update in case canvas was resized
- obd_rpm now appends ' rpm' to the displayed value

Co-authored-by: Copilot <[email protected]>
2026-08-09 23:14:32 -04:00
Mathieu MoranandCopilot beddfec8a5 Phase 23 UX polish: compact layouts, collapsibles, test trip
Dashboard:
- OBD stat cards more compact (py-2, h6 health values, 'OBD' label fits)
- obd-val class prevents Disconnected wrapping
- Speed graph collapsed by default with toggle chevron button
- GPS map + coordinates merged into single card with footer coords
- Pi health rows use h6 (less tall), alert py-2

Dashcam:
- Device status is now a compact single-line bar
- Files on dashcam section is collapsible (hidden by default)
- Import status moved into the dashcam files card footer
- Imported videos on Pi moved to top, open by default
- Column headers trimmed (Select→empty, Actions→empty, Preview→empty)

Trip history:
- col-12 on mobile so list stacks above the map
- Shorter column labels (Distance→km, Duration→Time, Max speed→Max)
- history-map height reduced for mobile (22rem on small screens)

Settings:
- Cellular + ntfy merged into one collapsible card
- Alert thresholds collapsible (advanced)
- Phone hotspot instructions shortened
- Upload retention uses inline row layout (compact)
- All cards use shadow-sm, smaller headers

CSS:
- obd-val: word-break + smaller font for OBD status values
- collapse-chevron: smooth rotation transition
- #map height 18rem (was 20rem)
- history-map 32rem desktop / 22rem mobile (was 36rem)

Co-authored-by: Copilot <[email protected]>
2026-08-09 23:10:58 -04:00
Mathieu MoranandCopilot 6a519dbf9e PLAN: mark Phase 23 done
Co-authored-by: Copilot <[email protected]>
2026-08-09 22:59:08 -04:00
Mathieu MoranandCopilot 7edea446c5 Phase 23: UI redesign — responsive mobile + desktop layout
- Replace SB Admin 2 sidebar with a clean fixed sidebar (desktop)
  and a sticky bottom tab navigation (mobile, ≤767px)
- Sidebar collapses to icon-only mode with state persisted in localStorage
- Logo is a functional home link on all viewports
- Mobile: full-width content, bottom nav replaces sidebar
- Dashboard: 6 OBD stat cards (status, speed, RPM, coolant, throttle, fuel)
  wired to live socket events; removed all placeholder buttons/dropdowns
- GPS info section cleaned up (lat/lon display instead of X/Y paragraphs)
- DTC page: live /api/dtc wiring — MIL indicator + DTC table populated from API
- New /api/dtc route added to app.py (mil + codes)
- yaris.js: added obd_coolant_temp / obd_fuel_level / obd_throttle listeners
- yaris.css: full rewrite — layout variables, sidebar, bottom nav, OBD cards,
  border-start utilities, mobile padding for bottom nav

Co-authored-by: Copilot <[email protected]>
2026-08-09 22:57:12 -04:00
Mathieu MoranandCopilot cdb032b888 Add phone hotspot uplink
Prefer a configured phone hotspot for outbound synchronization, preserve secure write-only credentials, and recover through home WiFi or the local Pi hotspot.

Co-authored-by: Copilot <[email protected]>
2026-08-09 22:48:11 -04:00
Mathieu MoranandCopilot 5c707fe295 Plan phone hotspot uplink
Co-authored-by: Copilot <[email protected]>
2026-08-09 21:29:08 -04:00
Mathieu MoranandCopilot 3a6f12ba9d Add local settings management
Add cellular-first ntfy delivery with WiFi fallback and safe SIM868 HTTP header framing.

Co-authored-by: Copilot <[email protected]>
2026-08-09 21:26:15 -04:00
Mathieu MoranandCopilot b77ed838bd Clarify pre-install vehicle testing
Co-authored-by: Copilot <[email protected]>
2026-08-09 20:37:43 -04:00
Mathieu MoranandCopilot acf24be89e Plan final Yaris acceptance test
Co-authored-by: Copilot <[email protected]>
2026-08-09 20:35:52 -04:00
Mathieu MoranandCopilot 592a123c77 Expand remote portal dashcam scope
Co-authored-by: Copilot <[email protected]>
2026-08-09 20:33:42 -04:00
Mathieu MoranandCopilot b348a9944a Plan remote trip portal
Co-authored-by: Copilot <[email protected]>
2026-08-09 20:32:27 -04:00
Mathieu MoranandCopilot 96332adab6 Add offline and ntfy alerts
Co-authored-by: Copilot <[email protected]>
2026-08-09 20:28:42 -04:00
Mathieu MoranandCopilot 8b664667e5 Add Raspberry Pi health monitoring
Co-authored-by: Copilot <[email protected]>
2026-08-09 19:57:07 -04:00
Mathieu MoranandCopilot d3bc313e7f Add trip history and route replay
Co-authored-by: Copilot <[email protected]>
2026-08-09 19:48:06 -04:00
Mathieu MoranandCopilot 3a981f5c6d Add local dashcam video library
Co-authored-by: Copilot <[email protected]>
2026-08-09 19:04:47 -04:00
Mathieu MoranandCopilot c57457b934 Add selected dashcam imports
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:34:52 -04:00
Mathieu MoranandCopilot e058ea8a41 Fix FAT dashcam file deletion
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:23:52 -04:00
Mathieu MoranandCopilot 80d9c150fe Document dashcam browser completion
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:19:37 -04:00
Mathieu MoranandCopilot 5a520bf022 Add dashcam USB file browser
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:16:59 -04:00
Mathieu MoranandCopilot 796b34a0b2 Refresh project README
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:10:44 -04:00
Mathieu MoranandCopilot 84362206f2 Clarify final UI redesign sequencing
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:09:14 -04:00
Mathieu MoranandCopilot 68c9aeaf44 Complete shared UI foundation
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:06:42 -04:00
Mathieu MoranandCopilot c367504c13 Add shared UI shell and list component
Co-authored-by: Copilot <[email protected]>
2026-08-09 18:03:32 -04:00
Mathieu MoranandCopilot 606f67d077 Document watchdog deployment status
Co-authored-by: Copilot <[email protected]>
2026-08-09 17:57:26 -04:00
Mathieu MoranandCopilot 3d9706b466 Add Pi hardware watchdog setup
Co-authored-by: Copilot <[email protected]>
2026-08-09 17:55:13 -04:00
Mathieu MoranandCopilot 0540a1a8eb Reorder roadmap phases by execution
Co-authored-by: Copilot <[email protected]>
2026-08-09 17:53:17 -04:00
Mathieu MoranandCopilot 7621c2a6e7 Document Phase 17 deployment
Co-authored-by: Copilot <[email protected]>
2026-08-09 17:49:18 -04:00
Mathieu MoranandCopilot cdbe8c89b1 Harden OBD connection and telemetry
Co-authored-by: Copilot <[email protected]>
2026-08-09 17:47:52 -04:00
Mathieu MoranandCopilot 33ea6958f4 Document Phase 12 deployment + handoff note for next session
Recorded Phase 12's deployment step (Pi restart + unraid server
docker compose build/up since server/app.py's CORS removal is baked
into the image at build time - a plain restart wouldn't pick it up)
and its verification (both confirmed on real infrastructure: Pi's
/dash returns HTTP 200 with no relay text, server's /health has no
Access-Control-* headers anymore).

Added a handoff note for whoever picks this up next: Phase 17 (OBD
hardening) was the next phase in progress, but no code was written
yet - only read app_events.py/app_gps.py for context and started
(but didn't finish, due to a tool permission interruption) installing
the python-obd package locally to check its actual API surface
before writing the DTC/protocol/diagnostics code. Reset that todo
back to pending to reflect no actual progress was made. Phases 13
and 14 are equally ready to start (no blocking deps) if a different
starting point makes more sense.

Co-authored-by: Copilot <[email protected]>
2026-08-09 17:17:21 -04:00
Mathieu MoranandCopilot f17c5c8681 Phase 12: remove phone-relay dashcam upload feature
Removes the phone-relay feature entirely per the Phase 11 finding
that it hit a fundamental phone-OS limitation (a browser page can't
route WiFi and cellular traffic simultaneously, no code fix possible)
- see PLAN.md for the full test writeup.

- app.py: dropped /api/dash/pending, /api/dash/file/<path>,
  /api/dash/relay/config, /api/dash/relay/heartbeat,
  /api/dash/relay/stop, /api/dash/relay/mark_uploaded routes and
  their now-unused imports (os, send_file, abort, safe_join).
- modules/app_upload.py: dropped list_pending_dashcam_files(),
  relay_heartbeat()/relay_stop()/is_relay_active(),
  RELAY_LEASE_SECONDS/_relay_lease_until, and the relay-lease check
  inside _upload_dashcam_files()/get_status(). Kept the chunked
  upload protocol, dashcam_hash_cache/_hash_dashcam_file() (still
  used by the Pi's own background uploader), and DASHCAM_CHUNK_SIZE -
  none of that was relay-specific.
- templates/dash.html: removed the 'Relay via phone' card.
- static/js/dash.js: removed all relay JS (relayOneFile,
  fetchChunkWithRetry, uploadChunkWithRetry, heartbeat timers,
  pending-file list rendering).
- server/app.py: removed the CORS after_request hook, added solely
  for the phone's cross-origin browser calls (the Pi's own Python
  requests-based client was never subject to CORS).
- readme.md: dropped the phone-relay feature bullet, updated the
  project-status blurb to explain the removal + point at the planned
  native Android app as the real fix for that use case.
- PLAN.md: marked Phase 12 done with a verification writeup.

Verified: py_compile on all touched Python files, node --check on
dash.js, grepped all touched files for any leftover relay/RELAY
reference (none), and smoke-tested by importing app.py + hitting it
with Flask's test client - app starts cleanly, every relay route is
gone from url_map, and both / and /dash still render HTTP 200 with
zero 'relay' occurrences in /dash's HTML.

Co-authored-by: Copilot <[email protected]>
2026-08-09 17:12:30 -04:00
Mathieu MoranandCopilot 0b729adb28 Rework Phases 12-17 for efficiency, add QOL phases (power resilience, watchdog, trip history, Pi health, alerts, settings)
Reordered/renumbered the roadmap based on dependency analysis:
- Phase 12 (remove relay) stays first - pure cleanup, no deps.
- New Phase 13: power-loss resilience + hardware watchdog - OS-level,
  independent, should land before the Pi lives in the car full-time
  (cars cut power abruptly at every key-off, a real SD-corruption risk
  nothing in the project currently addresses).
- New Phase 14: UI design-system foundation - pulls forward a small
  slice of the old end-of-roadmap redesign (shared layout + reusable
  list/table component) so the dashcam/video browsers, trip history,
  and settings pages are all built on it once instead of being built
  in the old template and then rebuilt during the redesign later.
- Phases 15/16 (dashcam + local video browsers) now explicitly build
  on Phase 14's foundation. Folded two more QOL ideas directly into
  Phase 16: ffmpeg thumbnails per video, and an optional
  upload-confirmed local retention/auto-cleanup policy.
- Phase 17 (OBD hardening) explicitly marked independent/parallel-
  frins to other phases - backend-only work, no UI dependency.
- New Phase 18: trip history + map replay (trips table + GPS-gap
  boundary detection + list/replay UI reusing the offline Leaflet map).
- New Phase 19: Pi health monitoring card (CPU temp/disk/uptime/RAM).
- New Phase 20: alerts/notifications via a simple outbound webhook
  (ntfy.sh/Telegram) for DTC-on, storage full, backlog growing,
  overheating, lost GPS fix.
- New Phase 21: consolidated Settings page (upload toggle, cellular
  APN, retention policy, alert config) - reduces routine SSH-editing.
- Phase 22: renamed 'Finish web UI redesign' - now scoped as the
  remaining cleanup after Phase 14's foundation + all functional
  phases land, rather than one big redesign at the very end.
- Phase 23: native Android app, unchanged content, still last/low
  priority per explicit user instruction.

Updated the session's todo tracking table to match the new phase
IDs/order and added todo_deps reflecting the same dependency chain.

Co-authored-by: Copilot <[email protected]>
2026-08-09 16:57:56 -04:00
Mathieu MoranandCopilot 5877f16c9a PLAN.md: add missing status icons, fix stale phase cross-reference
Formatting-only pass, no information changed:
- Added missing checkmarks to Phase 7 and Phase 9 headers (were plain
  'DONE' text while every other completed phase uses '✅ DONE').
- Added a status icon to the un-numbered SIM868 cellular fallback
  section ('⚠️ built, UNTESTED').
- Added a consistent '🚧 PLANNED' icon to Phases 12-17, which had no
  status icon at all.
- Fixed a stale cross-reference in the Phase 6 update ('see Phase 9
  update below' -> 'see Phase 11 update below' - the phone-relay
  content it points to has always lived under Phase 11).

Co-authored-by: Copilot <[email protected]>
2026-08-09 16:50:42 -04:00
Mathieu MoranandCopilot e280f9ac96 Plan native Android app + full UI redesign; expand OBD data scope; deprioritize Android app
Phase 16: complete web UI redesign - the current template is full of
never-replaced placeholder cruft (stub buttons, empty /dtc page); do
one real design pass once the functional phases land rather than
keep bolting onto the old admin-template style.

Phase 17: native Android companion app - the real fix for the
phone-relay goal that a web page could never deliver (true
simultaneous WiFi-to-Pi + cellular-to-server via
bindProcessToNetwork), plus a trip/location info view. Explicitly
deprioritized to the end of the roadmap per user - not urgent.

Also expanded Phase 15 (OBD hardening) scope to pull richer dashboard
data (coolant temp, fuel level, throttle, DTC/MIL status) alongside
speed/RPM, noting odometer/mileage support is not guaranteed on this
vehicle/year and needs real-car confirmation.

Co-authored-by: Copilot <[email protected]>
2026-08-09 16:48:27 -04:00
Mathieu MoranandCopilot 9e96b4327b Document phone-relay hard limitation, plan 4 new phases
Retested the hotspot+cellular phone-relay scenario more rigorously
(netwatch explicitly disabled during tests so it wouldn't yank the Pi
back to home WiFi mid-test - an earlier failed run turned out to be
caused by that, not a phone issue). Confirmed with WiFi fully off
(pure cellular) that requests to the public server still failed while
associated with the Pi's hotspot, and that this is a fundamental
OS-level 'single default network for the whole device' limitation -
not something a web page's JS can work around, only a native app could
(bindProcessToNetwork). Documented the decision to remove the feature.

Added Phase 12 (remove phone-relay), Phase 13 (dashcam USB file
browser: list+delete), Phase 14 (local imported-video browser +
HTML5 player), Phase 15 (OBD code hardening ahead of first real
car test - background reconnect loop, exception handling, protocol
override, better diagnostics).

Co-authored-by: Copilot <[email protected]>
2026-08-09 16:44:11 -04:00