Reorder roadmap phases by execution

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-09 17:53:17 -04:00
co-authored by Copilot
parent 7621c2a6e7
commit 0540a1a8eb
+98 -95
View File
@@ -676,90 +676,15 @@ returns HTTP 200 with no "relay" text over the real network) and the remote serv
CORS-hook removal is baked into the image at build time - confirmed `/health` still returns CORS-hook removal is baked into the image at build time - confirmed `/health` still returns
`{"status":"ok"}` and the response no longer carries any `Access-Control-*` header). `{"status":"ok"}` and the response no longer carries any `Access-Control-*` header).
**Update 2026-08-09**: Phase 17 (OBD hardening) was completed next; see its section below. **Update 2026-08-09**: Phase 13 (OBD hardening) was completed next; see its section below.
Phase 14 is the recommended next software phase because Phases 15/16 and the later list-based Phase 15 is the recommended next software phase because Phases 16/17 and the later list-based
features depend on its shared UI foundation. Phase 13 remains independent but requires a careful features depend on its shared UI foundation. Phase 14 remains independent but requires a careful
hardware/filesystem deployment pass rather than ordinary application deployment. hardware/filesystem deployment pass rather than ordinary application deployment.
### Phase 13: Power-loss resilience & watchdog — 🚧 PLANNED ### Phase 13: OBD real-hardware readiness (code hardening) — ✅ DONE
**New phase, not code-dependent on anything else — OS/hardware-level, can be done anytime, **Reordered from Phase 17 on 2026-08-09** because it was completed directly after Phase 12;
ideally before the Pi lives in the car full-time.** A car cuts power abruptly at every key-off; the phase numbers now follow actual execution order instead of leaving completed work below
nothing in the project currently protects against that, and repeated unclean power loss is a several still-planned phases.
classic cause of SD card corruption on embedded Pi projects. Two complementary angles:
- **Filesystem hardening**: convert the Pi's root filesystem to read-only with a writable overlay
(`overlayroot`/`raspi-config`'s built-in overlay FS option), so an abrupt power cut can no longer
corrupt the OS itself. App data that must persist (`db/sql/`, `dash/videos/`, logs) needs to
live on an explicitly writable partition/bind-mount carved out of the overlay, since those
still need real writes.
- **Clean shutdown before power loss**: either (a) a small delayed-shutdown power module/circuit
(holds 5V for a few seconds after ignition-off so the Pi can run `shutdown -h now` cleanly), or
(b) if the SIM868 HAT or another ADC/GPIO source can sense ignition/voltage state, a software
voltage-drop watcher that triggers a clean shutdown proactively. Exact approach TBD - depends
on what's easiest to wire into the existing power setup.
- **Hardware watchdog**: enable the Pi's built-in `bcm2835_wdt` watchdog (via `systemd`'s
`RuntimeWatchdogSec`/`watchdog-device` support) so a fully hung app or kernel gets a hard reboot
automatically instead of sitting dead in the car until someone notices.
### Phase 14: UI design-system foundation — 🚧 PLANNED
**New phase, pulled forward from the old "do the whole redesign at the end" plan.** Building the
Phase 15/16 file browsers (and later the trip-history/Pi-health/settings pages) against the
current old admin-template style just means rebuilding the same UI twice once the full redesign
(Phase 22) happens. Instead, do a *small, scoped* slice of the redesign right now: a shared page
layout/shell plus one reusable list/table component (sortable columns, per-row action buttons,
delete-with-confirmation pattern, empty-state messaging) that every subsequent list-based feature
in this plan (dashcam browser, video browser, trip history, settings) can be built on directly,
instead of writing bespoke list/delete UI code from scratch each time. Not a full redesign of
every existing page yet - just establishes the pattern once so nothing built after this point
needs to be redone in Phase 22.
### Phase 15: Dashcam USB file browser (list + delete) — 🚧 PLANNED
Replace the current all-or-nothing "Import videos" flow's blind spot (no visibility into what's
actually on the camera) with a real file browser for the mounted USB dashcam device, built on the
Phase 14 shared list component:
- New endpoint(s) in `modules/app_dash.py` to list every video file on the mounted dashcam
(relative path, size, modified time) without copying anything - reuses the existing
`find_video_files()` walk, pointed at the dashcam's mount point instead of the local
destination folder.
- Delete support: individual file delete and a "delete all" bulk action, each requiring the
mount to be briefly remounted read-write (currently always `ro`) - only for the duration of
the delete call, then back to read-only (or unmounted) afterward, to minimize risk of
filesystem corruption from an unclean power loss while writable.
- UI: a `/dash` card showing total file count + total size on the dashcam, a scrollable file
list with a delete button per row, a "Delete all" button behind a confirmation dialog (real
data loss risk - no undo).
- Explicitly deferred/out of scope: no in-browser preview/playback of files still on the
dashcam itself (only after import - see Phase 16). No changes to the USB
select-mode/data-communication toggle behavior - the user replugs power manually to return the
camera to recording mode, as they already do.
### Phase 16: Local imported-video browser + player — 🚧 PLANNED
Same file-browser treatment for videos already imported to the Pi's own disk
(`dash/videos/`, populated by `import_dashcam()`), built on the Phase 14 shared list component:
- List endpoint (reuses `find_video_files()` against `app_dash.DEFAULT_DESTINATION`) showing
relative path, size, modified time, and upload status (cross-check against
`app_sql.dashcam_file_marked_uploaded()` - so it's visible at a glance which imported clips
have already reached the remote server and which are still pending the Pi's own background
uploader).
- Delete support: **local Pi copy only** - remote server copies are intentionally left alone
as the safe backup/archive (explicit user decision - see 2026-08-09 discussion). No
"also delete from server" option is being built for now.
- In-browser video player: since the dashcam records **MP4** (confirmed with the user), this
is a plain HTML5 `<video>` element - no server-side transcoding needed. Reuses the same
Range-request-capable file-serving pattern already proven in the (now-removed) relay's
`/api/dash/file/<path>` endpoint (needed for correct seeking/scrubbing, not just playback).
- UI: a `/dash` card (or a dedicated sub-page if the list gets long) with a file list, a
"Play" action that opens the video inline, and a delete button per row with a confirmation
prompt.
- **QOL: video thumbnails.** Scrolling a list of `VID_00234.MOV`-style filenames to find a
specific clip is rough. Grab one low-res frame per file via `ffmpeg` (cheap, done once per file
and cached, not on every page load) so the browser shows an actual thumbnail per row instead of
just a filename.
- **QOL: retention/auto-cleanup policy.** Once a file is confirmed uploaded to the remote server,
it's safe to free local space. Add an optional automatic cleanup (configurable via the Phase 21
Settings page - e.g. "delete local copies older than N days once uploaded", off by default)
so the Pi's limited storage doesn't need to be babysat manually as the dashcam backlog grows.
### Phase 17: OBD real-hardware readiness (code hardening) — ✅ DONE
**Independent of everything else in this plan — only touches `app_obd.py`/`app_events.py`, no UI **Independent of everything else in this plan — only touches `app_obd.py`/`app_events.py`, no UI
dependency for the backend part, so this can be done anytime/in parallel with the phases above.** dependency for the backend part, so this can be done anytime/in parallel with the phases above.**
Nothing has been installed or tested in the actual 2013 Yaris yet - `modules/app_obd.py` has Nothing has been installed or tested in the actual 2013 Yaris yet - `modules/app_obd.py` has
@@ -823,6 +748,84 @@ all in `modules/app_obd.py` / `modules/app_events.py`:
`active`, `/` returned HTTP 200, and the log showed four autonomous reconnect attempts with the `active`, `/` returned HTTP 200, and the log showed four autonomous reconnect attempts with the
correct "ELM327 handshake succeeded, but the car ECU did not respond" diagnosis and no traceback. correct "ELM327 handshake succeeded, but the car ECU did not respond" diagnosis and no traceback.
### Phase 14: Power-loss resilience & watchdog — 🚧 PLANNED
**New phase, not code-dependent on anything else — OS/hardware-level, can be done anytime,
ideally before the Pi lives in the car full-time.** A car cuts power abruptly at every key-off;
nothing in the project currently protects against that, and repeated unclean power loss is a
classic cause of SD card corruption on embedded Pi projects. Two complementary angles:
- **Filesystem hardening**: convert the Pi's root filesystem to read-only with a writable overlay
(`overlayroot`/`raspi-config`'s built-in overlay FS option), so an abrupt power cut can no longer
corrupt the OS itself. App data that must persist (`db/sql/`, `dash/videos/`, logs) needs to
live on an explicitly writable partition/bind-mount carved out of the overlay, since those
still need real writes.
- **Clean shutdown before power loss**: either (a) a small delayed-shutdown power module/circuit
(holds 5V for a few seconds after ignition-off so the Pi can run `shutdown -h now` cleanly), or
(b) if the SIM868 HAT or another ADC/GPIO source can sense ignition/voltage state, a software
voltage-drop watcher that triggers a clean shutdown proactively. Exact approach TBD - depends
on what's easiest to wire into the existing power setup.
- **Hardware watchdog**: enable the Pi's built-in `bcm2835_wdt` watchdog (via `systemd`'s
`RuntimeWatchdogSec`/`watchdog-device` support) so a fully hung app or kernel gets a hard reboot
automatically instead of sitting dead in the car until someone notices.
### Phase 15: UI design-system foundation — 🚧 PLANNED
**New phase, pulled forward from the old "do the whole redesign at the end" plan.** Building the
Phase 16/17 file browsers (and later the trip-history/Pi-health/settings pages) against the
current old admin-template style just means rebuilding the same UI twice once the full redesign
(Phase 22) happens. Instead, do a *small, scoped* slice of the redesign right now: a shared page
layout/shell plus one reusable list/table component (sortable columns, per-row action buttons,
delete-with-confirmation pattern, empty-state messaging) that every subsequent list-based feature
in this plan (dashcam browser, video browser, trip history, settings) can be built on directly,
instead of writing bespoke list/delete UI code from scratch each time. Not a full redesign of
every existing page yet - just establishes the pattern once so nothing built after this point
needs to be redone in Phase 22.
### Phase 16: Dashcam USB file browser (list + delete) — 🚧 PLANNED
Replace the current all-or-nothing "Import videos" flow's blind spot (no visibility into what's
actually on the camera) with a real file browser for the mounted USB dashcam device, built on the
Phase 15 shared list component:
- New endpoint(s) in `modules/app_dash.py` to list every video file on the mounted dashcam
(relative path, size, modified time) without copying anything - reuses the existing
`find_video_files()` walk, pointed at the dashcam's mount point instead of the local
destination folder.
- Delete support: individual file delete and a "delete all" bulk action, each requiring the
mount to be briefly remounted read-write (currently always `ro`) - only for the duration of
the delete call, then back to read-only (or unmounted) afterward, to minimize risk of
filesystem corruption from an unclean power loss while writable.
- UI: a `/dash` card showing total file count + total size on the dashcam, a scrollable file
list with a delete button per row, a "Delete all" button behind a confirmation dialog (real
data loss risk - no undo).
- Explicitly deferred/out of scope: no in-browser preview/playback of files still on the
dashcam itself (only after import - see Phase 17). No changes to the USB
select-mode/data-communication toggle behavior - the user replugs power manually to return the
camera to recording mode, as they already do.
### Phase 17: Local imported-video browser + player — 🚧 PLANNED
Same file-browser treatment for videos already imported to the Pi's own disk
(`dash/videos/`, populated by `import_dashcam()`), built on the Phase 15 shared list component:
- List endpoint (reuses `find_video_files()` against `app_dash.DEFAULT_DESTINATION`) showing
relative path, size, modified time, and upload status (cross-check against
`app_sql.dashcam_file_marked_uploaded()` - so it's visible at a glance which imported clips
have already reached the remote server and which are still pending the Pi's own background
uploader).
- Delete support: **local Pi copy only** - remote server copies are intentionally left alone
as the safe backup/archive (explicit user decision - see 2026-08-09 discussion). No
"also delete from server" option is being built for now.
- In-browser video player: since the dashcam records **MP4** (confirmed with the user), this
is a plain HTML5 `<video>` element - no server-side transcoding needed. Reuses the same
Range-request-capable file-serving pattern already proven in the (now-removed) relay's
`/api/dash/file/<path>` endpoint (needed for correct seeking/scrubbing, not just playback).
- UI: a `/dash` card (or a dedicated sub-page if the list gets long) with a file list, a
"Play" action that opens the video inline, and a delete button per row with a confirmation
prompt.
- **QOL: video thumbnails.** Scrolling a list of `VID_00234.MOV`-style filenames to find a
specific clip is rough. Grab one low-res frame per file via `ffmpeg` (cheap, done once per file
and cached, not on every page load) so the browser shows an actual thumbnail per row instead of
just a filename.
- **QOL: retention/auto-cleanup policy.** Once a file is confirmed uploaded to the remote server,
it's safe to free local space. Add an optional automatic cleanup (configurable via the Phase 21
Settings page - e.g. "delete local copies older than N days once uploaded", off by default)
so the Pi's limited storage doesn't need to be babysat manually as the dashcam backlog grows.
### Phase 18: Trip history & replay — 🚧 PLANNED ### Phase 18: Trip history & replay — 🚧 PLANNED
**New phase.** Right now the `tracking` table (Phase 7) is just a raw stream of GPS/speed points **New phase.** Right now the `tracking` table (Phase 7) is just a raw stream of GPS/speed points
with no concept of a discrete "trip" - there's no way to look back and see "trip #47, left home with no concept of a discrete "trip" - there's no way to look back and see "trip #47, left home
@@ -830,11 +833,11 @@ with no concept of a discrete "trip" - there's no way to look back and see "trip
- Trip boundary detection: group existing tracking rows into discrete trips based on speed>0 - Trip boundary detection: group existing tracking rows into discrete trips based on speed>0
gaps (a simple, dependency-free heuristic - e.g. a gap of N minutes with no motion ends a trip), gaps (a simple, dependency-free heuristic - e.g. a gap of N minutes with no motion ends a trip),
computed either live as rows are logged or in a batch pass over existing data. Does not need to computed either live as rows are logged or in a batch pass over existing data. Does not need to
wait on Phase 17's OBD hardening - can ship with GPS-only boundary detection first, refine with wait on Phase 13's OBD hardening - can ship with GPS-only boundary detection first, refine with
ignition-on/off signals from OBD later if useful. ignition-on/off signals from OBD later if useful.
- A `trips` table (start/end time, start/end coordinates, distance, duration) in - A `trips` table (start/end time, start/end coordinates, distance, duration) in
`modules/app_sql.py`. `modules/app_sql.py`.
- A new history page/list (built on the Phase 14 shared list component) showing past trips, and - A new history page/list (built on the Phase 15 shared list component) showing past trips, and
a map replay view (reusing the existing offline-tile Leaflet setup from Phase 3/4) that draws a map replay view (reusing the existing offline-tile Leaflet setup from Phase 3/4) that draws
the recorded route for a selected trip. the recorded route for a selected trip.
@@ -843,31 +846,31 @@ with no concept of a discrete "trip" - there's no way to look back and see "trip
and RAM usage are all cheap to read (`vcgencmd measure_temp`, `psutil`/`/proc` stats) and cheap to and RAM usage are all cheap to read (`vcgencmd measure_temp`, `psutil`/`/proc` stats) and cheap to
display, but currently invisible - the only way to notice a problem (thermal throttling in a hot display, but currently invisible - the only way to notice a problem (thermal throttling in a hot
car, an SD card filling up) today is to SSH in and check manually. Add a small backend endpoint car, an SD card filling up) today is to SSH in and check manually. Add a small backend endpoint
and a dashboard card (built on the Phase 14 component set) surfacing these at a glance. Also and a dashboard card (built on the Phase 15 component set) surfacing these at a glance. Also
feeds directly into Phase 20's alerting. feeds directly into Phase 20's alerting.
### Phase 20: Alerts/notifications — 🚧 PLANNED ### Phase 20: Alerts/notifications — 🚧 PLANNED
**New phase - depends on Phases 16/17/19 existing first**, since it needs real data sources to **New phase - depends on Phases 13/17/19 existing first**, since it needs real data sources to
alert on. Right now, finding out about a problem (check-engine light on, dashcam SD card nearly alert on. Right now, finding out about a problem (check-engine light on, dashcam SD card nearly
full, upload backlog growing unbounded, Pi overheating, GPS fix lost for a long stretch) requires full, upload backlog growing unbounded, Pi overheating, GPS fix lost for a long stretch) requires
manually opening the web UI. Add a lightweight push-notification integration - no new manually opening the web UI. Add a lightweight push-notification integration - no new
infrastructure needed, just an outbound webhook to something like `ntfy.sh` or a Telegram bot - infrastructure needed, just an outbound webhook to something like `ntfy.sh` or a Telegram bot -
fired on a handful of conditions: fired on a handful of conditions:
- DTC/MIL status changes to "on" (from Phase 17's expanded OBD data). - DTC/MIL status changes to "on" (from Phase 13's expanded OBD data).
- Dashcam USB storage or local `dash/videos/` disk usage crossing a configurable threshold. - Dashcam USB storage or local `dash/videos/` disk usage crossing a configurable threshold.
- Upload backlog (pending tracking rows or dashcam files) growing past a configurable size, - Upload backlog (pending tracking rows or dashcam files) growing past a configurable size,
suggesting the Pi's been offline/uploading too slowly for a while. suggesting the Pi's been offline/uploading too slowly for a while.
- Pi CPU temperature crossing a configurable threshold (from Phase 19). - Pi CPU temperature crossing a configurable threshold (from Phase 19).
- GPS fix lost for longer than a configurable duration while the car appears to be moving - GPS fix lost for longer than a configurable duration while the car appears to be moving
(OBD speed > 0 with no fix - needs Phase 17's data). (OBD speed > 0 with no fix - needs Phase 13's data).
Webhook target/thresholds configurable via the Phase 21 Settings page rather than hardcoded. Webhook target/thresholds configurable via the Phase 21 Settings page rather than hardcoded.
### Phase 21: Settings page — 🚧 PLANNED ### Phase 21: Settings page — 🚧 PLANNED
**New phase - depends on Phase 14's shared components, and benefits from Phases 16/20 existing** **New phase - depends on Phase 15's shared components, and benefits from Phases 17/20 existing**
so there's real configuration to consolidate. Several settings currently only exist as so there's real configuration to consolidate. Several settings currently only exist as
SSH-edited files or scattered individual toggles (dashcam upload pause switch on `/dash`, SSH-edited files or scattered individual toggles (dashcam upload pause switch on `/dash`,
cellular APN in `db/upload_creds.env`, hotspot SSID/password via `nmcli` directly) or don't exist cellular APN in `db/upload_creds.env`, hotspot SSID/password via `nmcli` directly) or don't exist
yet (retention days from Phase 16, alert webhook/thresholds from Phase 20). Add one dedicated yet (retention days from Phase 17, alert webhook/thresholds from Phase 20). Add one dedicated
Settings page consolidating anything reasonably safe to expose over the local-only web UI: Settings page consolidating anything reasonably safe to expose over the local-only web UI:
upload toggle, cellular APN, local video retention policy, alert webhook URL + thresholds. WiFi upload toggle, cellular APN, local video retention policy, alert webhook URL + thresholds. WiFi
SSID/password changes likely stay out of scope (higher-risk to expose over a UI that could lock SSID/password changes likely stay out of scope (higher-risk to expose over a UI that could lock
@@ -878,14 +881,14 @@ The current `templates/`/`static/` UI is an old, mostly-unmodified admin dashboa
(SB Admin 2-style Bootstrap theme) with leftover placeholder cruft never replaced with real (SB Admin 2-style Bootstrap theme) with leftover placeholder cruft never replaced with real
content - e.g. `index.html`'s "Placeholder button", dropdown menus full of "Action" / "Another content - e.g. `index.html`'s "Placeholder button", dropdown menus full of "Action" / "Another
action" / "Something else here" stub links, and a `/dtc` page that's just an empty "Output" card action" / "Something else here" stub links, and a `/dtc` page that's just an empty "Output" card
with nothing wired up to it at all. Phase 14 already pulled the *foundational* piece of this with nothing wired up to it at all. Phase 15 already pulled the *foundational* piece of this
work forward (shared layout/list component, used by every phase since); this phase is the work forward (shared layout/list component, used by every phase since); this phase is the
remaining cleanup once all the functional phases above have landed: remaining cleanup once all the functional phases above have landed:
- Replace/remove all remaining placeholder buttons, dropdown menus, and stub links that don't do - Replace/remove all remaining placeholder buttons, dropdown menus, and stub links that don't do
anything. anything.
- Wire up the actually-empty `/dtc` page to show real DTC/MIL data from Phase 17. - Wire up the actually-empty `/dtc` page to show real DTC/MIL data from Phase 13.
- Add the new dashboard cards for Phase 17's expanded OBD data (coolant temp, fuel level, - Add the new dashboard cards for Phase 13's expanded OBD data (coolant temp, fuel level,
throttle) and Phase 19's Pi health card, using the Phase 14 component patterns. throttle) and Phase 19's Pi health card, using the Phase 15 component patterns.
- Final visual polish pass across every page now that the full feature set (dashcam/video - Final visual polish pass across every page now that the full feature set (dashcam/video
browsers, trip history, settings, alerts) exists, rather than redesigning against a moving browsers, trip history, settings, alerts) exists, rather than redesigning against a moving
target. target.
@@ -900,7 +903,7 @@ unavailable to browser JS - see the Phase 11 update). Explicitly **not urgent**
the end of the roadmap, after all web-side work (Phases 12-22) is done. Scope, at a high level the end of the roadmap, after all web-side work (Phases 12-22) is done. Scope, at a high level
(not yet designed in detail): (not yet designed in detail):
- **Dashcam file transfer**: browse/select/delete dashcam clips already imported to the Pi (same - **Dashcam file transfer**: browse/select/delete dashcam clips already imported to the Pi (same
data as the Phase 16 local video browser, just a native client instead of/alongside the web UI), data as the Phase 17 local video browser, just a native client instead of/alongside the web UI),
download them to the phone, and upload to the remote server over cellular while the phone stays download them to the phone, and upload to the remote server over cellular while the phone stays
on the Pi's hotspot for the download leg - the true simultaneous dual-network relay the web on the Pi's hotspot for the download leg - the true simultaneous dual-network relay the web
version could never deliver. version could never deliver.