Refresh docs and reorder roadmap
Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -1398,89 +1398,9 @@ including two UX polish passes and multiple bug-fix rounds.
|
|||||||
- **Dispatcher hook fix (`deploy/90-yaris-uplink`)** — previously ignored wlan1 events; now listens for both `wlan0` and `wlan1` so uplink transition recording remains accurate on current hardware.
|
- **Dispatcher hook fix (`deploy/90-yaris-uplink`)** — previously ignored wlan1 events; now listens for both `wlan0` and `wlan1` so uplink transition recording remains accurate on current hardware.
|
||||||
- **README hardware photo** — added a current hardware build image (`pics/yaris-obd-hardware.jpg`) at the bottom of `readme.md`; saved without EXIF metadata and resized for lighter page load.
|
- **README hardware photo** — added a current hardware build image (`pics/yaris-obd-hardware.jpg`) at the bottom of `readme.md`; saved without EXIF metadata and resized for lighter page load.
|
||||||
|
|
||||||
### Phase 25: Pre-install Yaris 2013 compatibility test — 🚧 PLANNED (core-system gate)
|
**Roadmap ordering note (2026-08-10):** the late phases are intentionally sequenced as
|
||||||
**Added 2026-08-09; intentionally immediately before the Android app.** Bench tests, synthetic
|
Phase 25.5a–25.5d (configuration/sync/status work) -> Phase 25 (real-car acceptance gate) ->
|
||||||
routes, attached-hardware tests, and stationary Pi runs cannot prove the complete system works
|
Phase 26 (Android, always last).
|
||||||
when temporarily connected to and powered by the actual 2013 Toyota Yaris. This phase is the
|
|
||||||
pre-installation acceptance gate for the complete core Pi/web/server system after Phase 24. It is
|
|
||||||
verification and targeted correction—not permanent installation or another redesign—and the
|
|
||||||
system is not considered ready to install until the results are recorded here. Permanent mounting,
|
|
||||||
cable routing, trim work, and final installation are explicitly outside the roadmap and will be
|
|
||||||
handled by the owner after this phase passes.
|
|
||||||
|
|
||||||
Prepare a temporary, removable test setup before driving:
|
|
||||||
- Connect the Pi, SIM868 HAT/antennas, USB OBD adapter, dashcam, and representative intended power
|
|
||||||
source without permanently mounting or routing anything. Secure every temporary component and
|
|
||||||
cable so it cannot interfere with pedals, steering, airbags, visibility, occupants, or loose-item
|
|
||||||
safety during the controlled drive; remove the setup after testing.
|
|
||||||
- Resolve the Phase 19/20 undervoltage/throttling warning before the test; confirm
|
|
||||||
`vcgencmd get_throttled` shows no current power fault at idle and under load with the temporary
|
|
||||||
representative power setup. This validates electrical compatibility, not final cable routing.
|
|
||||||
- Back up the Pi SQLite database and record baseline row/trip/video/upload counts on the Pi and
|
|
||||||
remote server. Use a uniquely timestamped test window so generated records can be reconciled
|
|
||||||
without deleting unrelated history.
|
|
||||||
- Verify the service, watchdog, storage mounts, hotspot, `http://yaris.local/`, alert engine, GPS
|
|
||||||
antenna view, dashcam recording mode, free space, and remote server health before moving.
|
|
||||||
- Use a second person for UI/log observations while the vehicle is moving, or record diagnostics
|
|
||||||
for review afterward. The driver must not operate or troubleshoot the UI while driving.
|
|
||||||
|
|
||||||
Run a controlled route containing ignition-off/on, stationary idle, low-speed streets, normal road
|
|
||||||
speed, several complete stops, turns, a short GPS-obstructed segment if safely available, and a
|
|
||||||
clean trip end:
|
|
||||||
- **OBD/ECU:** confirm the ELM327 reaches `CAR_CONNECTED`, record negotiated Toyota protocol and
|
|
||||||
adapter firmware, and verify real speed/RPM/coolant/fuel/throttle/MIL/DTC values. Compare wheel
|
|
||||||
speed against the instrument cluster at several safe speeds. Record unsupported PIDs (especially
|
|
||||||
odometer PID A6) as capabilities, not failures, and verify reconnect after ignition cycling and a
|
|
||||||
deliberate USB disconnect/reconnect.
|
|
||||||
- **GPS and motion logging:** confirm fixes while stationary and moving, route coordinates on the
|
|
||||||
offline map, OBD-speed-first motion detection, GPS displacement fallback when OBD is deliberately
|
|
||||||
unavailable, one-minute idle heartbeat behavior, and no false trip from stationary GPS drift.
|
|
||||||
- **Trip boundary/replay:** after the inactivity timeout, verify exactly one trip (or the expected
|
|
||||||
number for deliberate ignition gaps), correct start/end time and coordinates, plausible
|
|
||||||
distance/average/max speed, km/mi and km/h/mph values, route polyline, start/end markers, timeline
|
|
||||||
scrubber, play/pause marker position, and stopped portions of the route.
|
|
||||||
- **Local web UI:** test the final Phase 23 interface on the actual phone over the Pi/phone network at
|
|
||||||
`http://yaris.local/`; verify the explicit **In-car / Local Pi** identity, dashboard telemetry,
|
|
||||||
DTC page, trip history, dashcam library, settings, alerts, responsive layout, reconnect behavior,
|
|
||||||
and no new browser console/API errors.
|
|
||||||
- **Dashcam workflow:** create a known short real clip, switch the camera safely into USB storage
|
|
||||||
mode, detect/list it without confusing the OBD adapter, import only that selected file, verify
|
|
||||||
thumbnail/playback/Range seeking, preserve the source until checks finish, and confirm the camera
|
|
||||||
returns to recording afterward. Test deletion only with the disposable clip.
|
|
||||||
- **Alerts:** safely exercise conditions that can be simulated without damaging hardware (upload
|
|
||||||
backlog, temporary GPS loss while OBD reports motion, and disposable threshold overrides).
|
|
||||||
Confirm local bell transition/recovery/cooldown behavior and offline alert-state reconciliation
|
|
||||||
with the Phase 24 server after connectivity returns. Do not intentionally overheat, undervolt,
|
|
||||||
fill storage, or trigger a real MIL. With a real SIM, explicitly determine whether the SIM868
|
|
||||||
transport can reliably deliver ntfy notifications while WiFi/Ethernet is unavailable, including
|
|
||||||
authentication, timeout, retry, and recovery. If that cannot be made reliable on the actual
|
|
||||||
modem/network, remove the ntfy feature and its settings rather than retain a transport that fails
|
|
||||||
in the only situation where it is useful.
|
|
||||||
- **Power/reliability:** perform multiple normal ignition/power cycles, confirm automatic boot,
|
|
||||||
service/watchdog startup, SIM868 power-on, OBD/GPS recovery, no SQLite corruption, and no current
|
|
||||||
undervoltage/throttling. The deferred Phase 14 clean-shutdown limitation must be explicitly
|
|
||||||
accepted or resolved with representative delayed-power/ignition-sense hardware before declaring
|
|
||||||
the design ready for permanent installation.
|
|
||||||
|
|
||||||
Verify the complete remote path after connectivity returns:
|
|
||||||
- Confirm every test-window tracking row uploads exactly once and the selected dashcam clip resumes
|
|
||||||
or completes without corruption; compare counts, timestamps, hashes, and byte sizes locally and
|
|
||||||
remotely.
|
|
||||||
- On the Phase 24 portal, verify the explicit **Remote server** identity, authenticated
|
|
||||||
vehicle-scoped trip summary/replay, route and timeline agreement with the local Pi, archived clip
|
|
||||||
thumbnail/playback/Range seeking/download, synchronized alert history/current state without
|
|
||||||
duplicate transitions after an offline interval, and absence of local-only OBD/cellular/Pi
|
|
||||||
controls.
|
|
||||||
- The Android companion is intentionally outside this core acceptance gate and remains the final,
|
|
||||||
low-priority Phase 26. Its own hotspot/cellular binding, trip display, and disposable clip relay
|
|
||||||
require separate real-phone/in-car acceptance when that phase is implemented.
|
|
||||||
|
|
||||||
Acceptance requires a written result for every item, captured negotiated protocol/PID support,
|
|
||||||
measured trip/count/hash comparisons, relevant logs/screenshots, no unexplained tracebacks or data
|
|
||||||
loss, and cleanup of every disposable record/file while preserving the real drive. Any defect found
|
|
||||||
must be fixed, redeployed uncommitted, and the affected in-car test repeated before the final commit
|
|
||||||
and **ready-to-install** sign-off. Afterward, the owner handles permanent installation outside the
|
|
||||||
phase roadmap.
|
|
||||||
|
|
||||||
### Phase 25.5a: Device configuration sync backend — ✅ COMPLETE (connection-driven server sync)
|
### Phase 25.5a: Device configuration sync backend — ✅ COMPLETE (connection-driven server sync)
|
||||||
**Server API and Pi sync thread implemented.** Server stores device config; Pi fetches when internet available and applies changes without restart.
|
**Server API and Pi sync thread implemented.** Server stores device config; Pi fetches when internet available and applies changes without restart.
|
||||||
@@ -1570,310 +1490,395 @@ updates its display-facing values without a manual restart once sync lands.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Phase 25.5c: Multi-device provisioning + force-sync UX — ✅ COMPLETE
|
### Phase 25.5c: Multi-device provisioning + force-sync UX — ✅ COMPLETE
|
||||||
Extend the current single-device portal page into a real multi-device management
|
Extend the current single-device portal page into a real multi-device management
|
||||||
surface: register devices from the server, mint/rotate API keys, help the Pi
|
surface: register devices from the server, mint/rotate API keys, help the Pi
|
||||||
consume those credentials without SSH-heavy manual steps, and expose an honest
|
consume those credentials without SSH-heavy manual steps, and expose an honest
|
||||||
**force sync** action that works with the system's pull-based/offline-first
|
**force sync** action that works with the system's pull-based/offline-first
|
||||||
architecture.
|
architecture.
|
||||||
|
|
||||||
#### Goals
|
#### Goals
|
||||||
- **Multi-device server view**: stop assuming one `DEVICE_NAME` in the portal;
|
- **Multi-device server view**: stop assuming one `DEVICE_NAME` in the portal;
|
||||||
show all registered devices and let the user select which one is being edited
|
show all registered devices and let the user select which one is being edited
|
||||||
- **Device registration from the portal**: create a new device row and return a
|
- **Device registration from the portal**: create a new device row and return a
|
||||||
one-time raw API key directly in the browser UI (same behavior as
|
one-time raw API key directly in the browser UI (same behavior as
|
||||||
`server/scripts/create_device.py`, but web-managed)
|
`server/scripts/create_device.py`, but web-managed)
|
||||||
- **Credential handoff to the Pi**: make it possible to put `YARIS_SERVER_URL`,
|
- **Credential handoff to the Pi**: make it possible to put `YARIS_SERVER_URL`,
|
||||||
`YARIS_DEVICE_NAME`, and `YARIS_API_KEY` onto the Pi from the app UI rather
|
`YARIS_DEVICE_NAME`, and `YARIS_API_KEY` onto the Pi from the app UI rather
|
||||||
than editing `db/upload_creds.env` manually over SSH
|
than editing `db/upload_creds.env` manually over SSH
|
||||||
- **Force sync button**: let the user request an immediate config pull without
|
- **Force sync button**: let the user request an immediate config pull without
|
||||||
pretending the cloud server can magically push into an offline car
|
pretending the cloud server can magically push into an offline car
|
||||||
|
|
||||||
#### Proposed implementation
|
#### Proposed implementation
|
||||||
1. **Server: device management model + portal**
|
1. **Server: device management model + portal**
|
||||||
- Add a device list page/card in the remote portal, with:
|
- Add a device list page/card in the remote portal, with:
|
||||||
- registered devices table
|
- registered devices table
|
||||||
- "Add device" action
|
- "Add device" action
|
||||||
- "Rotate API key" action
|
- "Rotate API key" action
|
||||||
- per-device "Edit settings" action
|
- per-device "Edit settings" action
|
||||||
- Replace the single-device lookup helper with:
|
- Replace the single-device lookup helper with:
|
||||||
- list devices
|
- list devices
|
||||||
- fetch one device by id
|
- fetch one device by id
|
||||||
- update one device's identity/config
|
- update one device's identity/config
|
||||||
- Keep raw API keys **write-only/one-time reveal**:
|
- Keep raw API keys **write-only/one-time reveal**:
|
||||||
- show only immediately after create/rotate
|
- show only immediately after create/rotate
|
||||||
- never store raw keys server-side
|
- never store raw keys server-side
|
||||||
- only store hash as today
|
- only store hash as today
|
||||||
|
|
||||||
2. **Server: browser APIs**
|
2. **Server: browser APIs**
|
||||||
- Add login-protected endpoints for:
|
- Add login-protected endpoints for:
|
||||||
- list devices
|
- list devices
|
||||||
- create device + return one-time API key
|
- create device + return one-time API key
|
||||||
- rotate device API key + return one-time API key
|
- rotate device API key + return one-time API key
|
||||||
- get/update selected device config
|
- get/update selected device config
|
||||||
- request force sync for a selected device
|
- request force sync for a selected device
|
||||||
- Device settings UI should populate from the selected device row, not from a
|
- Device settings UI should populate from the selected device row, not from a
|
||||||
single hardcoded portal `DEVICE_NAME`
|
single hardcoded portal `DEVICE_NAME`
|
||||||
|
|
||||||
3. **Pi: credential management UI**
|
3. **Pi: credential management UI**
|
||||||
- Add a local Settings section for remote server credentials:
|
- Add a local Settings section for remote server credentials:
|
||||||
- server URL
|
- server URL
|
||||||
- device name
|
- device name
|
||||||
- API key
|
- API key
|
||||||
- connection test button
|
- connection test button
|
||||||
- Persist those values in the existing gitignored
|
- Persist those values in the existing gitignored
|
||||||
`db/upload_creds.env` (or an equivalent sqlite-backed secret store if we
|
`db/upload_creds.env` (or an equivalent sqlite-backed secret store if we
|
||||||
later choose to migrate away from env files)
|
later choose to migrate away from env files)
|
||||||
- After saving:
|
- After saving:
|
||||||
- reload upload client / sync client config
|
- reload upload client / sync client config
|
||||||
- allow enabling **Sync with server** only when creds test cleanly
|
- allow enabling **Sync with server** only when creds test cleanly
|
||||||
|
|
||||||
4. **Force sync semantics**
|
4. **Force sync semantics**
|
||||||
- **Constraint**: the server cannot directly reach a Pi behind hotspot/NAT,
|
- **Constraint**: the server cannot directly reach a Pi behind hotspot/NAT,
|
||||||
and the car is often offline. So a server-side force sync button cannot be
|
and the car is often offline. So a server-side force sync button cannot be
|
||||||
a true push.
|
a true push.
|
||||||
- Proposed behavior:
|
- Proposed behavior:
|
||||||
- server sets a per-device `force_sync_requested_at` flag/version bump
|
- server sets a per-device `force_sync_requested_at` flag/version bump
|
||||||
- Pi checks that flag during normal online polling
|
- Pi checks that flag during normal online polling
|
||||||
- local Pi UI also gets a **Sync now** button that triggers an immediate
|
- local Pi UI also gets a **Sync now** button that triggers an immediate
|
||||||
fetch against the server using the saved credentials
|
fetch against the server using the saved credentials
|
||||||
- Result:
|
- Result:
|
||||||
- from server: "sync on next online contact"
|
- from server: "sync on next online contact"
|
||||||
- from Pi: "sync immediately if internet is currently available"
|
- from Pi: "sync immediately if internet is currently available"
|
||||||
|
|
||||||
#### Data model changes
|
#### Data model changes
|
||||||
- Add server columns such as:
|
- Add server columns such as:
|
||||||
- `last_config_sync_at`
|
- `last_config_sync_at`
|
||||||
- `last_config_sync_version`
|
- `last_config_sync_version`
|
||||||
- `force_sync_requested_at`
|
- `force_sync_requested_at`
|
||||||
- `api_key_rotated_at`
|
- `api_key_rotated_at`
|
||||||
- Optionally add a lightweight pairing/claim model later if copying raw API keys
|
- Optionally add a lightweight pairing/claim model later if copying raw API keys
|
||||||
manually into the Pi UI feels too clunky
|
manually into the Pi UI feels too clunky
|
||||||
|
|
||||||
#### UI/UX shape
|
#### UI/UX shape
|
||||||
- **Remote portal**
|
- **Remote portal**
|
||||||
- left: device list / selector
|
- left: device list / selector
|
||||||
- right: settings form for selected device
|
- right: settings form for selected device
|
||||||
- actions: Add device, Rotate key, Copy creds, Request sync
|
- actions: Add device, Rotate key, Copy creds, Request sync
|
||||||
- status badges: last seen, last config version, last sync requested, maybe
|
- status badges: last seen, last config version, last sync requested, maybe
|
||||||
last successful contact
|
last successful contact
|
||||||
- **Pi local app**
|
- **Pi local app**
|
||||||
- server credentials card in Settings
|
- server credentials card in Settings
|
||||||
- Test connection button
|
- Test connection button
|
||||||
- Sync now button
|
- Sync now button
|
||||||
- clear warning when local edits are locked because server sync is enabled
|
- clear warning when local edits are locked because server sync is enabled
|
||||||
|
|
||||||
#### Security / operational notes
|
#### Security / operational notes
|
||||||
- Never display a stored API key after the creation/rotation response is gone
|
- Never display a stored API key after the creation/rotation response is gone
|
||||||
- Keep key rotation explicit; rotating a key should warn that the old Pi config
|
- Keep key rotation explicit; rotating a key should warn that the old Pi config
|
||||||
will stop authenticating until updated
|
will stop authenticating until updated
|
||||||
- Avoid logging raw keys in Flask logs, JS errors, or shell output captured in
|
- Avoid logging raw keys in Flask logs, JS errors, or shell output captured in
|
||||||
committed docs
|
committed docs
|
||||||
- Because the Pi is usually offline, all flows must degrade gracefully when the
|
- Because the Pi is usually offline, all flows must degrade gracefully when the
|
||||||
remote portal requests work the Pi cannot act on yet
|
remote portal requests work the Pi cannot act on yet
|
||||||
|
|
||||||
#### Suggested execution order
|
#### Suggested execution order
|
||||||
1. Server multi-device DB/API refactor
|
1. Server multi-device DB/API refactor
|
||||||
2. Remote portal device list + register/rotate key UI
|
2. Remote portal device list + register/rotate key UI
|
||||||
3. Pi local server-credentials UI + connection test
|
3. Pi local server-credentials UI + connection test
|
||||||
4. Pi/local immediate **Sync now** button
|
4. Pi/local immediate **Sync now** button
|
||||||
5. Server "request sync" flag + per-device status indicators
|
5. Server "request sync" flag + per-device status indicators
|
||||||
6. End-to-end validation:
|
6. End-to-end validation:
|
||||||
- create device on server
|
- create device on server
|
||||||
- copy/paste creds into Pi UI
|
- copy/paste creds into Pi UI
|
||||||
- test connection
|
- test connection
|
||||||
- enable sync
|
- enable sync
|
||||||
- change device identity on server
|
- change device identity on server
|
||||||
- verify Pi receives it
|
- verify Pi receives it
|
||||||
- rotate key and verify old key stops working until Pi updated
|
- rotate key and verify old key stops working until Pi updated
|
||||||
|
|
||||||
#### Open constraint to keep in mind
|
#### Open constraint to keep in mind
|
||||||
- If you want **zero manual copy/paste** for API key provisioning, that becomes
|
- If you want **zero manual copy/paste** for API key provisioning, that becomes
|
||||||
a pairing problem (claim code / QR / short-lived token). That is doable, but
|
a pairing problem (claim code / QR / short-lived token). That is doable, but
|
||||||
it is a bigger scope than simply exposing key create/copy fields in the two
|
it is a bigger scope than simply exposing key create/copy fields in the two
|
||||||
existing UIs. My recommendation is to ship copy/paste provisioning first, then
|
existing UIs. My recommendation is to ship copy/paste provisioning first, then
|
||||||
add a pairing flow only if it still feels too annoying.
|
add a pairing flow only if it still feels too annoying.
|
||||||
|
|
||||||
#### Implementation progress (2026-08-10)
|
#### Implementation progress (2026-08-10)
|
||||||
- **Server-side multi-device API/UI**
|
- **Server-side multi-device API/UI**
|
||||||
- Added device listing, create-device, rotate-key, per-device config, and
|
- Added device listing, create-device, rotate-key, per-device config, and
|
||||||
request-sync endpoints in the remote portal
|
request-sync endpoints in the remote portal
|
||||||
- Reworked `/settings/device` into a multi-device management page with a
|
- Reworked `/settings/device` into a multi-device management page with a
|
||||||
registered-device list, selected-device editor, one-time API-key reveal, and
|
registered-device list, selected-device editor, one-time API-key reveal, and
|
||||||
copy-credentials helper
|
copy-credentials helper
|
||||||
- **Server schema**
|
- **Server schema**
|
||||||
- Added Phase 25.5c migration for `last_config_sync_at`,
|
- Added Phase 25.5c migration for `last_config_sync_at`,
|
||||||
`last_config_sync_version`, `force_sync_requested_at`, and
|
`last_config_sync_version`, `force_sync_requested_at`, and
|
||||||
`api_key_rotated_at`
|
`api_key_rotated_at`
|
||||||
- Added device sync acknowledgement endpoint so the Pi can report when a
|
- Added device sync acknowledgement endpoint so the Pi can report when a
|
||||||
config version was actually applied
|
config version was actually applied
|
||||||
- **Pi-side provisioning**
|
- **Pi-side provisioning**
|
||||||
- Added local Settings UI/API for `YARIS_SERVER_URL`, `YARIS_DEVICE_NAME`, and
|
- Added local Settings UI/API for `YARIS_SERVER_URL`, `YARIS_DEVICE_NAME`, and
|
||||||
`YARIS_API_KEY`
|
`YARIS_API_KEY`
|
||||||
- Added **Test connection** and **Sync now** actions in the Pi app
|
- Added **Test connection** and **Sync now** actions in the Pi app
|
||||||
- Reused the existing gitignored `db/upload_creds.env` store instead of
|
- Reused the existing gitignored `db/upload_creds.env` store instead of
|
||||||
inventing a second credential location
|
inventing a second credential location
|
||||||
- **Runtime behavior fixes**
|
- **Runtime behavior fixes**
|
||||||
- Upload client now reads credentials dynamically instead of only at import
|
- Upload client now reads credentials dynamically instead of only at import
|
||||||
- Device sync thread can restart after credentials are added later
|
- Device sync thread can restart after credentials are added later
|
||||||
- Enabling **Sync with server** now validates that remote credentials exist
|
- Enabling **Sync with server** now validates that remote credentials exist
|
||||||
|
|
||||||
#### Validation
|
#### Validation
|
||||||
- Local tests expanded to `72 passed`
|
- Local tests expanded to `72 passed`
|
||||||
- Deployed Phase 25.5c to unraid and applied `migrate_phase25_5c.sql`
|
- Deployed Phase 25.5c to unraid and applied `migrate_phase25_5c.sql`
|
||||||
- Deployed Phase 25.5c to the Pi at `192.168.50.213`; `yaris-obd.service`
|
- Deployed Phase 25.5c to the Pi at `192.168.50.213`; `yaris-obd.service`
|
||||||
restarted cleanly
|
restarted cleanly
|
||||||
- Pi local API checks:
|
- Pi local API checks:
|
||||||
- `GET /api/remote-config` shows the saved remote server credentials state
|
- `GET /api/remote-config` shows the saved remote server credentials state
|
||||||
- `POST /api/remote-config/test` succeeds with authenticated device creds
|
- `POST /api/remote-config/test` succeeds with authenticated device creds
|
||||||
- `POST /api/remote-config/sync-now` succeeds (`no_change` when already current)
|
- `POST /api/remote-config/sync-now` succeeds (`no_change` when already current)
|
||||||
- Server sync-ack metadata verified live: `last_config_sync_version` matched the
|
- Server sync-ack metadata verified live: `last_config_sync_version` matched the
|
||||||
current `config_version` for `yaris-2013`, and `last_config_sync_at` populated
|
current `config_version` for `yaris-2013`, and `last_config_sync_at` populated
|
||||||
- Temporary create/delete validation of the new device-provisioning helper
|
- Temporary create/delete validation of the new device-provisioning helper
|
||||||
succeeded on the live server (`phase25c-validate-temp`)
|
succeeded on the live server (`phase25c-validate-temp`)
|
||||||
|
|
||||||
#### Remaining polish ideas (not blockers)
|
#### Remaining polish ideas (not blockers)
|
||||||
- Exercise the authenticated browser flow for create/rotate/request-sync in the
|
- Exercise the authenticated browser flow for create/rotate/request-sync in the
|
||||||
live portal UI itself, not just the backing APIs/helpers
|
live portal UI itself, not just the backing APIs/helpers
|
||||||
- If copy/paste provisioning feels too clunky in practice, add a later QR /
|
- If copy/paste provisioning feels too clunky in practice, add a later QR /
|
||||||
claim-code pairing flow
|
claim-code pairing flow
|
||||||
- Main portal overview/history/dashcam pages are **not** multi-device yet:
|
- Main portal overview/history/dashcam pages are **not** multi-device yet:
|
||||||
the data model already separates rows by `device_id`, but those pages still
|
the data model already separates rows by `device_id`, but those pages still
|
||||||
read from the single configured `DEVICE_NAME`. Today that means the new
|
read from the single configured `DEVICE_NAME`. Today that means the new
|
||||||
multi-device management page can manage several devices, but the normal trip
|
multi-device management page can manage several devices, but the normal trip
|
||||||
history view still shows one configured car/device at a time until a later
|
history view still shows one configured car/device at a time until a later
|
||||||
portal-wide device selector/filter is implemented.
|
portal-wide device selector/filter is implemented.
|
||||||
|
|
||||||
### Phase 25.5d: GPS dashboard visibility / live-status debugging — 🚧 IN PROGRESS
|
### Phase 25.5d: GPS dashboard visibility / live-status debugging — ✅ COMPLETE
|
||||||
- Add an explicit Pi dashboard GPS status indicator (thread alive / live fix / stale fix / no fix)
|
- Add an explicit Pi dashboard GPS status indicator (thread alive / live fix / stale fix / no fix)
|
||||||
- Expose a local `/api/gps/status` endpoint so GPS health can be checked without relying on
|
- Expose a local `/api/gps/status` endpoint so GPS health can be checked without relying on
|
||||||
socket events alone
|
socket events alone
|
||||||
- Verify on the real Pi whether GPS is actually still producing fixes or whether the issue is
|
- Verify on the real Pi whether GPS is actually still producing fixes or whether the issue is
|
||||||
now hardware/module/fix availability rather than a front-end live-update regression
|
now hardware/module/fix availability rather than a front-end live-update regression
|
||||||
- **Live Pi check 2026-08-10**: after deploying the new endpoint, the real Pi reported
|
- **Live Pi check 2026-08-10**: after deploying the new endpoint, the real Pi reported
|
||||||
`thread_alive=true` but `has_fix=false`, `latitude=null`, `longitude=null`, and
|
`thread_alive=true` but `has_fix=false`, `latitude=null`, `longitude=null`, and
|
||||||
`fix_age_seconds=null`. That means the local app/socket path is still running, but there is
|
`fix_age_seconds=null`. That means the local app/socket path is still running, but there is
|
||||||
currently no GNSS fix to stream into the dashboard.
|
currently no GNSS fix to stream into the dashboard.
|
||||||
- **Follow-up recheck 2026-08-10**: the Pi later recovered a live fix without further code
|
- **Follow-up recheck 2026-08-10**: the Pi later recovered a live fix without further code
|
||||||
changes. `/api/gps/status` returned `thread_alive=true`, `has_fix=true`,
|
changes. `/api/gps/status` returned `thread_alive=true`, `has_fix=true`,
|
||||||
`latitude=45.544198`, `longitude=-73.536626`, and a fresh `fix_age_seconds` (~2.7s), which
|
`latitude=45.544198`, `longitude=-73.536626`, and a fresh `fix_age_seconds` (~2.7s), which
|
||||||
confirms the live-update path still works and the earlier issue was temporary lack of GNSS fix,
|
confirms the live-update path still works and the earlier issue was temporary lack of GNSS fix,
|
||||||
not a dashboard/socket regression.
|
not a dashboard/socket regression.
|
||||||
- **Dashboard fit polish 2026-08-10**: tightened the Pi dashboard layout so the GPS and Overview
|
- **Dashboard fit polish 2026-08-10**: tightened the Pi dashboard layout so the GPS and Overview
|
||||||
cards no longer stretch to the speed graph's full height, shortened the speed graph on
|
cards no longer stretch to the speed graph's full height, shortened the speed graph on
|
||||||
Pi-sized screens, and reduced excess map/footer whitespace that previously forced unnecessary
|
Pi-sized screens, and reduced excess map/footer whitespace that previously forced unnecessary
|
||||||
scrolling.
|
scrolling.
|
||||||
- **Next local UX follow-up 2026-08-10**: split the bulky Pi-health block out of the dashboard
|
- **Next local UX follow-up 2026-08-10**: split the bulky Pi-health block out of the dashboard
|
||||||
into a dedicated local **Status** page and expand it into a broader runtime diagnostics view:
|
into a dedicated local **Status** page and expand it into a broader runtime diagnostics view:
|
||||||
local `systemd` service states, server reachability/auth status, Wi-Fi uplink state, GPS fix
|
local `systemd` service states, server reachability/auth status, Wi-Fi uplink state, GPS fix
|
||||||
state, SIM868/cellular-HAT responsiveness, upload backlog, and OBD adapter presence.
|
state, SIM868/cellular-HAT responsiveness, upload backlog, and OBD adapter presence.
|
||||||
- **Implemented + verified 2026-08-10**: added a new local `/status` page plus `/api/status`
|
- **Implemented + verified 2026-08-10**: added a new local `/status` page plus `/api/status`
|
||||||
aggregator. The dashboard no longer carries the bulky Pi-health block; instead the new page
|
aggregator. The dashboard no longer carries the bulky Pi-health block; instead the new page
|
||||||
shows Pi health, remote-server reachability/auth, upload backlog, Wi-Fi/uplink mode, GPS fix
|
shows Pi health, remote-server reachability/auth, upload backlog, Wi-Fi/uplink mode, GPS fix
|
||||||
status, SIM868 UART/modem responsiveness, OBD presence, and key `systemd` units
|
status, SIM868 UART/modem responsiveness, OBD presence, and key `systemd` units
|
||||||
(`yaris-obd.service`, `NetworkManager.service`, `yaris-netwatch.timer`,
|
(`yaris-obd.service`, `NetworkManager.service`, `yaris-netwatch.timer`,
|
||||||
`yaris-uplink-wait.service`). Deployed to the real Pi (`192.168.50.213`) and confirmed the
|
`yaris-uplink-wait.service`). Deployed to the real Pi (`192.168.50.213`) and confirmed the
|
||||||
page/API served live values: server reachable+authenticated, uplink on Wi-Fi with internet,
|
page/API served live values: server reachable+authenticated, uplink on Wi-Fi with internet,
|
||||||
GPS thread alive with live fix, SIM868 responding on UART, OBD adapter present, and the local
|
GPS thread alive with live fix, SIM868 responding on UART, OBD adapter present, and the local
|
||||||
services reporting expected active/inactive states.
|
services reporting expected active/inactive states.
|
||||||
- **Next status-page polish 2026-08-10**: add quick action buttons directly on `/status` for
|
- **Next status-page polish 2026-08-10**: add quick action buttons directly on `/status` for
|
||||||
server test, sync-now, uplink reconnect, and app-service restart so the Pi can be diagnosed and
|
server test, sync-now, uplink reconnect, and app-service restart so the Pi can be diagnosed and
|
||||||
nudged from one screen instead of bouncing back to Settings/SSH.
|
nudged from one screen instead of bouncing back to Settings/SSH.
|
||||||
- **Implemented + verified 2026-08-10**: `/status` now includes quick actions for server test,
|
- **Implemented + verified 2026-08-10**: `/status` now includes quick actions for server test,
|
||||||
sync-now, uplink reconnect, and app restart. The first three reuse the existing local APIs; app
|
sync-now, uplink reconnect, and app restart. The first three reuse the existing local APIs; app
|
||||||
restart adds a minimal sudo-backed `systemctl restart --no-block yaris-obd.service` path,
|
restart adds a minimal sudo-backed `systemctl restart --no-block yaris-obd.service` path,
|
||||||
installed by `deploy/install_service.sh` via `/etc/sudoers.d/yaris-status-actions`. Verified on
|
installed by `deploy/install_service.sh` via `/etc/sudoers.d/yaris-status-actions`. Verified on
|
||||||
the real Pi by deploying the sudoers update, loading the new buttons, exercising
|
the real Pi by deploying the sudoers update, loading the new buttons, exercising
|
||||||
`/api/remote-config/test` (200/ok), `/api/remote-config/sync-now` (200/no_change), and
|
`/api/remote-config/test` (200/ok), `/api/remote-config/sync-now` (200/no_change), and
|
||||||
`/api/status/restart-app` (202/restart_scheduled), then confirming `yaris-obd.service`
|
`/api/status/restart-app` (202/restart_scheduled), then confirming `yaris-obd.service`
|
||||||
returned to `active` and `/api/status` recovered with live values. The uplink-reconnect button
|
returned to `active` and `/api/status` recovered with live values. The uplink-reconnect button
|
||||||
was left unclicked during verification because this Pi currently has a saved phone hotspot and
|
was left unclicked during verification because this Pi currently has a saved phone hotspot and
|
||||||
the action intentionally may switch networks/drop the active SSH session.
|
the action intentionally may switch networks/drop the active SSH session.
|
||||||
- **Dashboard card order tweak 2026-08-10**: swapped the local dashboard layout so the Overview
|
- **Dashboard card order tweak 2026-08-10**: swapped the local dashboard layout so the Overview
|
||||||
card now appears before the Live GPS map. Deployed to the real Pi and confirmed the rendered
|
card now appears before the Live GPS map. Deployed to the real Pi and confirmed the rendered
|
||||||
`/` HTML places `Overview` before `Live GPS`.
|
`/` HTML places `Overview` before `Live GPS`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
### Phase 25: Pre-install Yaris 2013 compatibility test — 🚧 PLANNED (core-system gate)
|
||||||
|
**Added 2026-08-09; intentionally immediately before the Android app.** Bench tests, synthetic
|
||||||
|
routes, attached-hardware tests, and stationary Pi runs cannot prove the complete system works
|
||||||
|
when temporarily connected to and powered by the actual 2013 Toyota Yaris. This phase is the
|
||||||
|
pre-installation acceptance gate for the complete core Pi/web/server system after Phase 24. It is
|
||||||
|
verification and targeted correction—not permanent installation or another redesign—and the
|
||||||
|
system is not considered ready to install until the results are recorded here. Permanent mounting,
|
||||||
|
cable routing, trim work, and final installation are explicitly outside the roadmap and will be
|
||||||
|
handled by the owner after this phase passes.
|
||||||
|
|
||||||
### Phase 26: Native Android companion app — 🚧 PLANNED (low priority, always last)
|
Prepare a temporary, removable test setup before driving:
|
||||||
Revives the phone-relay goal (Phase 11/12) properly this time, using a real native app instead of
|
- Connect the Pi, SIM868 HAT/antennas, USB OBD adapter, dashcam, and representative intended power
|
||||||
a web page - the only way to actually hold a WiFi connection to the Pi's hotspot and a cellular
|
source without permanently mounting or routing anything. Secure every temporary component and
|
||||||
connection to the internet open simultaneously (`ConnectivityManager.bindProcessToNetwork()`,
|
cable so it cannot interfere with pedals, steering, airbags, visibility, occupants, or loose-item
|
||||||
|
safety during the controlled drive; remove the setup after testing.
|
||||||
|
- Resolve the Phase 19/20 undervoltage/throttling warning before the test; confirm
|
||||||
|
`vcgencmd get_throttled` shows no current power fault at idle and under load with the temporary
|
||||||
|
representative power setup. This validates electrical compatibility, not final cable routing.
|
||||||
|
- Back up the Pi SQLite database and record baseline row/trip/video/upload counts on the Pi and
|
||||||
|
remote server. Use a uniquely timestamped test window so generated records can be reconciled
|
||||||
|
without deleting unrelated history.
|
||||||
|
- Verify the service, watchdog, storage mounts, hotspot, `http://yaris.local/`, alert engine, GPS
|
||||||
|
antenna view, dashcam recording mode, free space, and remote server health before moving.
|
||||||
|
- Use a second person for UI/log observations while the vehicle is moving, or record diagnostics
|
||||||
|
for review afterward. The driver must not operate or troubleshoot the UI while driving.
|
||||||
|
|
||||||
|
Run a controlled route containing ignition-off/on, stationary idle, low-speed streets, normal road
|
||||||
|
speed, several complete stops, turns, a short GPS-obstructed segment if safely available, and a
|
||||||
|
clean trip end:
|
||||||
|
- **OBD/ECU:** confirm the ELM327 reaches `CAR_CONNECTED`, record negotiated Toyota protocol and
|
||||||
|
adapter firmware, and verify real speed/RPM/coolant/fuel/throttle/MIL/DTC values. Compare wheel
|
||||||
|
speed against the instrument cluster at several safe speeds. Record unsupported PIDs (especially
|
||||||
|
odometer PID A6) as capabilities, not failures, and verify reconnect after ignition cycling and a
|
||||||
|
deliberate USB disconnect/reconnect.
|
||||||
|
- **GPS and motion logging:** confirm fixes while stationary and moving, route coordinates on the
|
||||||
|
offline map, OBD-speed-first motion detection, GPS displacement fallback when OBD is deliberately
|
||||||
|
unavailable, one-minute idle heartbeat behavior, and no false trip from stationary GPS drift.
|
||||||
|
- **Trip boundary/replay:** after the inactivity timeout, verify exactly one trip (or the expected
|
||||||
|
number for deliberate ignition gaps), correct start/end time and coordinates, plausible
|
||||||
|
distance/average/max speed, km/mi and km/h/mph values, route polyline, start/end markers, timeline
|
||||||
|
scrubber, play/pause marker position, and stopped portions of the route.
|
||||||
|
- **Local web UI:** test the final Phase 23 interface on the actual phone over the Pi/phone network at
|
||||||
|
`http://yaris.local/`; verify the explicit **In-car / Local Pi** identity, dashboard telemetry,
|
||||||
|
DTC page, trip history, dashcam library, settings, alerts, responsive layout, reconnect behavior,
|
||||||
|
and no new browser console/API errors.
|
||||||
|
- **Dashcam workflow:** create a known short real clip, switch the camera safely into USB storage
|
||||||
|
mode, detect/list it without confusing the OBD adapter, import only that selected file, verify
|
||||||
|
thumbnail/playback/Range seeking, preserve the source until checks finish, and confirm the camera
|
||||||
|
returns to recording afterward. Test deletion only with the disposable clip.
|
||||||
|
- **Alerts:** safely exercise conditions that can be simulated without damaging hardware (upload
|
||||||
|
backlog, temporary GPS loss while OBD reports motion, and disposable threshold overrides).
|
||||||
|
Confirm local bell transition/recovery/cooldown behavior and offline alert-state reconciliation
|
||||||
|
with the Phase 24 server after connectivity returns. Do not intentionally overheat, undervolt,
|
||||||
|
fill storage, or trigger a real MIL. With a real SIM, explicitly determine whether the SIM868
|
||||||
|
transport can reliably deliver ntfy notifications while WiFi/Ethernet is unavailable, including
|
||||||
|
authentication, timeout, retry, and recovery. If that cannot be made reliable on the actual
|
||||||
|
modem/network, remove the ntfy feature and its settings rather than retain a transport that fails
|
||||||
|
in the only situation where it is useful.
|
||||||
|
- **Power/reliability:** perform multiple normal ignition/power cycles, confirm automatic boot,
|
||||||
|
service/watchdog startup, SIM868 power-on, OBD/GPS recovery, no SQLite corruption, and no current
|
||||||
|
undervoltage/throttling. The deferred Phase 14 clean-shutdown limitation must be explicitly
|
||||||
|
accepted or resolved with representative delayed-power/ignition-sense hardware before declaring
|
||||||
|
the design ready for permanent installation.
|
||||||
|
|
||||||
|
Verify the complete remote path after connectivity returns:
|
||||||
|
- Confirm every test-window tracking row uploads exactly once and the selected dashcam clip resumes
|
||||||
|
or completes without corruption; compare counts, timestamps, hashes, and byte sizes locally and
|
||||||
|
remotely.
|
||||||
|
- On the Phase 24 portal, verify the explicit **Remote server** identity, authenticated
|
||||||
|
vehicle-scoped trip summary/replay, route and timeline agreement with the local Pi, archived clip
|
||||||
|
thumbnail/playback/Range seeking/download, synchronized alert history/current state without
|
||||||
|
duplicate transitions after an offline interval, and absence of local-only OBD/cellular/Pi
|
||||||
|
controls.
|
||||||
|
- The Android companion is intentionally outside this core acceptance gate and remains the final,
|
||||||
|
low-priority Phase 26. Its own hotspot/cellular binding, trip display, and disposable clip relay
|
||||||
|
require separate real-phone/in-car acceptance when that phase is implemented.
|
||||||
|
|
||||||
|
Acceptance requires a written result for every item, captured negotiated protocol/PID support,
|
||||||
|
measured trip/count/hash comparisons, relevant logs/screenshots, no unexplained tracebacks or data
|
||||||
|
loss, and cleanup of every disposable record/file while preserving the real drive. Any defect found
|
||||||
|
must be fixed, redeployed uncommitted, and the affected in-car test repeated before the final commit
|
||||||
|
and **ready-to-install** sign-off. Afterward, the owner handles permanent installation outside the
|
||||||
|
phase roadmap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 26: Native Android companion app — 🚧 PLANNED (low priority, always last)
|
||||||
|
Revives the phone-relay goal (Phase 11/12) properly this time, using a real native app instead of
|
||||||
|
a web page - the only way to actually hold a WiFi connection to the Pi's hotspot and a cellular
|
||||||
|
connection to the internet open simultaneously (`ConnectivityManager.bindProcessToNetwork()`,
|
||||||
unavailable to browser JS - see the Phase 11 update). Explicitly **not urgent** and intentionally
|
unavailable to browser JS - see the Phase 11 update). Explicitly **not urgent** and intentionally
|
||||||
kept at the end of the roadmap, after all web/server work and the Phase 25 core in-car acceptance
|
kept at the end of the roadmap, after all web/server work and the Phase 25 core in-car acceptance
|
||||||
test are done. Permanent vehicle installation remains owner-handled and is not an implementation
|
test are done. Permanent vehicle installation remains owner-handled and is not an implementation
|
||||||
phase. Scope, at a high level (not yet designed in detail):
|
phase. Scope, at a high level (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 17 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.
|
||||||
- **Trip/location info**: surface live GPS position, speed, and trip history (Phase 18) pulled
|
- **Trip/location info**: surface live GPS position, speed, and trip history (Phase 18) pulled
|
||||||
from the Pi while connected to its hotspot (reusing the existing `/api/gps`-style data feeds),
|
from the Pi while connected to its hotspot (reusing the existing `/api/gps`-style data feeds),
|
||||||
so the app is useful as a real "check on the car" companion, not just a file-transfer tool.
|
so the app is useful as a real "check on the car" companion, not just a file-transfer tool.
|
||||||
- Not yet scoped: distribution (sideload vs Play Store), whether it talks to the Pi directly over
|
- Not yet scoped: distribution (sideload vs Play Store), whether it talks to the Pi directly over
|
||||||
the hotspot only or also to the remote server directly for trip history when away from the car,
|
the hotspot only or also to the remote server directly for trip history when away from the car,
|
||||||
and how much of the existing REST API surface can be reused as-is vs needs new
|
and how much of the existing REST API surface can be reused as-is vs needs new
|
||||||
mobile-friendly endpoints. To be designed once Phases 12-24 (web-side cleanup + hardening,
|
mobile-friendly endpoints. To be designed once Phases 12-24 (web-side cleanup + hardening,
|
||||||
redesign, remote portal, and core in-car acceptance) are done.
|
redesign, remote portal, and core in-car acceptance) are done.
|
||||||
|
|
||||||
## Working Notes / Environment Gotchas
|
## Working Notes / Environment Gotchas
|
||||||
|
|
||||||
- **Pi credentials/SSH**: `ssh yaris@<pi-ip>`. Confirm current IP before connecting (varies by
|
- **Pi credentials/SSH**: `ssh yaris@<pi-ip>`. Confirm current IP before connecting (varies by
|
||||||
network — check locally, not recorded here) — usually reachable via `yaris.local` too (mDNS,
|
network — check locally, not recorded here) — usually reachable via `yaris.local` too (mDNS,
|
||||||
see Phase 6). **Current verified LAN IP (2026-08-10): `192.168.50.213`** after the TP-Link
|
see Phase 6). **Current verified LAN IP (2026-08-10): `192.168.50.213`** after the TP-Link
|
||||||
Archer T3U WiFi change.
|
Archer T3U WiFi change.
|
||||||
- **Local dev repo**: work happens on a separate dev machine, not on the Pi. Push to
|
- **Local dev repo**: work happens on a separate dev machine, not on the Pi. Push to
|
||||||
`origin/shameless-aislop`, the repo's current default branch (repo is public).
|
`origin/shameless-aislop`, the repo's current default branch (repo is public).
|
||||||
- **unraid server SSH**: `ssh [email protected]` (alias `unraid`). Real git checkout lives at
|
- **unraid server SSH**: `ssh [email protected]` (alias `unraid`). Real git checkout lives at
|
||||||
`/mnt/user/app/git/yaris-obd` (not `/mnt/user/appdata/...`, which is only the docker-managed data
|
`/mnt/user/app/git/yaris-obd` (not `/mnt/user/appdata/...`, which is only the docker-managed data
|
||||||
volume for the `db`/`dashcam` containers). **Re-verified 2026-08-10**: alias works and lands in
|
volume for the `db`/`dashcam` containers). **Re-verified 2026-08-10**: alias works and lands in
|
||||||
`/root`. Deploy with
|
`/root`. Deploy with
|
||||||
`cd /mnt/user/app/git/yaris-obd && git pull --ff-only && cd server && docker compose up -d --build`.
|
`cd /mnt/user/app/git/yaris-obd && git pull --ff-only && cd server && docker compose up -d --build`.
|
||||||
Container names: `yaris-obd-db` (Postgres 17), `yaris-obd-api`. For existing databases, also run
|
Container names: `yaris-obd-db` (Postgres 17), `yaris-obd-api`. For existing databases, also run
|
||||||
the mounted migration scripts manually, e.g.
|
the mounted migration scripts manually, e.g.
|
||||||
`docker compose exec db psql -U yarisobd -d yarisobd -f /docker-entrypoint-initdb.d/migrate_phase25_5a.sql`.
|
`docker compose exec db psql -U yarisobd -d yarisobd -f /docker-entrypoint-initdb.d/migrate_phase25_5a.sql`.
|
||||||
**Important 2026-08-10**: this checkout had a long-diverged local `shameless-aislop`
|
**Important 2026-08-10**: this checkout had a long-diverged local `shameless-aislop`
|
||||||
(`ahead 136, behind 146`), so `git pull --ff-only` failed. To avoid destroying local history, the
|
(`ahead 136, behind 146`), so `git pull --ff-only` failed. To avoid destroying local history, the
|
||||||
Phase 25.5a deploy was performed from a fresh temporary clone while reusing the same `.env` and
|
Phase 25.5a deploy was performed from a fresh temporary clone while reusing the same `.env` and
|
||||||
Docker data. Reconcile or replace the old checkout before relying on it for future ff-only deploys.
|
Docker data. Reconcile or replace the old checkout before relying on it for future ff-only deploys.
|
||||||
- **Pi deploy loop**: `ssh yaris@<pi-ip>` (alias `yarisobd`) ->
|
- **Pi deploy loop**: `ssh yaris@<pi-ip>` (alias `yarisobd`) ->
|
||||||
`cd ~/app/yaris-obd && git pull --ff-only` -> `sudo systemctl restart yaris-obd.service`
|
`cd ~/app/yaris-obd && git pull --ff-only` -> `sudo systemctl restart yaris-obd.service`
|
||||||
(passwordless sudo confirmed via `yaris ALL=(ALL) NOPASSWD: ALL`). App stdout/stderr goes to
|
(passwordless sudo confirmed via `yaris ALL=(ALL) NOPASSWD: ALL`). App stdout/stderr goes to
|
||||||
`~/app/yaris-obd/yaris-obd.log` (not journald - the systemd unit redirects both streams there),
|
`~/app/yaris-obd/yaris-obd.log` (not journald - the systemd unit redirects both streams there),
|
||||||
so use `tail`/`grep` on that file, not `journalctl`, to see application-level prints (upload
|
so use `tail`/`grep` on that file, not `journalctl`, to see application-level prints (upload
|
||||||
progress, OBD/GNSS errors, etc.) - `journalctl -u yaris-obd.service` only shows service
|
progress, OBD/GNSS errors, etc.) - `journalctl -u yaris-obd.service` only shows service
|
||||||
start/stop/crash lifecycle events. **Update 2026-08-09**: plain `print()` calls were silently
|
start/stop/crash lifecycle events. **Update 2026-08-09**: plain `print()` calls were silently
|
||||||
invisible in that log for months because Python fully-buffers stdout when it's not a TTY (a
|
invisible in that log for months because Python fully-buffers stdout when it's not a TTY (a
|
||||||
file, in this case) — `logging`-based lines (e.g. the `obd` library's own logger, which uses
|
file, in this case) — `logging`-based lines (e.g. the `obd` library's own logger, which uses
|
||||||
stderr) showed up fine, but bare `print()` diagnostics from `app_gps.py` never did. Fixed by
|
stderr) showed up fine, but bare `print()` diagnostics from `app_gps.py` never did. Fixed by
|
||||||
adding `Environment=PYTHONUNBUFFERED=1` to the systemd unit
|
adding `Environment=PYTHONUNBUFFERED=1` to the systemd unit
|
||||||
(`deploy/yaris-obd.service`) — **remember to also copy the updated unit file to
|
(`deploy/yaris-obd.service`) — **remember to also copy the updated unit file to
|
||||||
`/etc/systemd/system/yaris-obd.service` and run `sudo systemctl daemon-reload`** on the Pi, a
|
`/etc/systemd/system/yaris-obd.service` and run `sudo systemctl daemon-reload`** on the Pi, a
|
||||||
plain `git pull` + restart does NOT pick up systemd unit file changes (the live
|
plain `git pull` + restart does NOT pick up systemd unit file changes (the live
|
||||||
`/etc/systemd/system/` copy is independent of the repo's `deploy/` copy).
|
`/etc/systemd/system/` copy is independent of the repo's `deploy/` copy).
|
||||||
- **Pi deploy loop, requirements.txt changes**: after any `requirements.txt` change (e.g. adding
|
- **Pi deploy loop, requirements.txt changes**: after any `requirements.txt` change (e.g. adding
|
||||||
`RPi.GPIO`), also run `~/app/yaris-obd/venv/bin/pip install -r requirements.txt` on the Pi — a
|
`RPi.GPIO`), also run `~/app/yaris-obd/venv/bin/pip install -r requirements.txt` on the Pi — a
|
||||||
`git pull` alone doesn't install new dependencies into the venv.
|
`git pull` alone doesn't install new dependencies into the venv.
|
||||||
- **Pi memory budget**: ~900MB usable RAM, no swap. Any code path that might buffer a whole
|
- **Pi memory budget**: ~900MB usable RAM, no swap. Any code path that might buffer a whole
|
||||||
dashcam-sized file (hundreds of MB) in memory is unsafe on this hardware - stream, don't buffer
|
dashcam-sized file (hundreds of MB) in memory is unsafe on this hardware - stream, don't buffer
|
||||||
(see Phase 10).
|
(see Phase 10).
|
||||||
- **Git push auth**: if `git push` fails with a credential/auth error, run `gh auth login`
|
- **Git push auth**: if `git push` fails with a credential/auth error, run `gh auth login`
|
||||||
(device-code browser flow) then `gh auth setup-git` to wire git to use the `gh` CLI's stored
|
(device-code browser flow) then `gh auth setup-git` to wire git to use the `gh` CLI's stored
|
||||||
credentials — this fixed push auth issues in this environment without needing SSH keys or PATs.
|
credentials — this fixed push auth issues in this environment without needing SSH keys or PATs.
|
||||||
- **Pi environment gap**: Debian Bookworm blocks system-wide `pip install` (PEP 668,
|
- **Pi environment gap**: Debian Bookworm blocks system-wide `pip install` (PEP 668,
|
||||||
"externally-managed-environment"). Use a venv for the real app dependency install (Phase 5)
|
"externally-managed-environment"). Use a venv for the real app dependency install (Phase 5)
|
||||||
rather than `--break-system-packages` everywhere. (Resolved in Phase 5 — venv at
|
rather than `--break-system-packages` everywhere. (Resolved in Phase 5 — venv at
|
||||||
`/home/yaris/app/yaris-obd/venv`.)
|
`/home/yaris/app/yaris-obd/venv`.)
|
||||||
- **Pi → GitHub auth for `git pull` (private repo)**: the Pi's pre-existing
|
- **Pi → GitHub auth for `git pull` (private repo)**: the Pi's pre-existing
|
||||||
`~/.ssh/id_ed25519.pub` is an *inbound* key only (private half is on the dev machine) — can't
|
`~/.ssh/id_ed25519.pub` is an *inbound* key only (private half is on the dev machine) — can't
|
||||||
be reused for the Pi's own outbound git auth. Generated a dedicated keypair on the Pi
|
be reused for the Pi's own outbound git auth. Generated a dedicated keypair on the Pi
|
||||||
(`~/.ssh/yaris_deploy_key`), added it as a GitHub **read-only deploy key** on this repo, and
|
(`~/.ssh/yaris_deploy_key`), added it as a GitHub **read-only deploy key** on this repo, and
|
||||||
added an `~/.ssh/config` entry pinning `github.com` to that key. `[email protected]:...` (SSH
|
added an `~/.ssh/config` entry pinning `github.com` to that key. `[email protected]:...` (SSH
|
||||||
remote) then works standalone on the Pi, no PAT/interactive login needed.
|
remote) then works standalone on the Pi, no PAT/interactive login needed.
|
||||||
- **piwheels**: pip on the Pi automatically uses `piwheels.org` as an extra index (Raspberry Pi
|
- **piwheels**: pip on the Pi automatically uses `piwheels.org` as an extra index (Raspberry Pi
|
||||||
OS default `pip.conf`) — this is why `gevent` (which normally needs a C compile) installed in
|
OS default `pip.conf`) — this is why `gevent` (which normally needs a C compile) installed in
|
||||||
seconds from a prebuilt ARM wheel instead of taking many minutes to build from source.
|
seconds from a prebuilt ARM wheel instead of taking many minutes to build from source.
|
||||||
- **python-obd note**: `obd.OBD()` (or with `portstr=`) performs the ELM327 handshake at
|
- **python-obd note**: `obd.OBD()` (or with `portstr=`) performs the ELM327 handshake at
|
||||||
connection time; if there's no car on the other end, it raises rather than silently returning
|
connection time; if there's no car on the other end, it raises rather than silently returning
|
||||||
a dead handle — `app_obd.py`'s `connect_obd()` already catches this and sets `connection = None`.
|
a dead handle — `app_obd.py`'s `connect_obd()` already catches this and sets `connection = None`.
|
||||||
@@ -59,8 +59,8 @@ Dashboard — mobile (390 px) and desktop (1280 px):
|
|||||||
trip history with OSM/CARTO live map and route replay, dashcam archive browsing with inline
|
trip history with OSM/CARTO live map and route replay, dashcam archive browsing with inline
|
||||||
video player and Range-request seeking, overview stats dashboard, delete buttons for trips and
|
video player and Range-request seeking, overview stats dashboard, delete buttons for trips and
|
||||||
clips, and a device-settings page for editing the synced owner/car/avatar values that the Pi
|
clips, and a device-settings page for editing the synced owner/car/avatar values that the Pi
|
||||||
pulls from the server. The portal can register multiple devices, reveal a one-time API key at
|
pulls from the server. The portal can register/delete multiple devices, reveal a one-time API
|
||||||
creation/rotation time, and request a sync for a selected device. When the Pi has
|
key at creation/rotation time, and request a sync for a selected device. When the Pi has
|
||||||
**Sync with server** enabled, those identity changes hot-reload into the running local UI on the
|
**Sync with server** enabled, those identity changes hot-reload into the running local UI on the
|
||||||
next successful sync instead of waiting for a manual restart. Shared `history.js` code runs on
|
next successful sync instead of waiting for a manual restart. Shared `history.js` code runs on
|
||||||
both the Pi and the server — tile source and zoom limits are configured per-deployment via HTML
|
both the Pi and the server — tile source and zoom limits are configured per-deployment via HTML
|
||||||
@@ -71,8 +71,9 @@ Dashboard — mobile (390 px) and desktop (1280 px):
|
|||||||
diagnostics, plus quick actions for server test, sync-now, uplink reconnect, and app restart
|
diagnostics, plus quick actions for server test, sync-now, uplink reconnect, and app restart
|
||||||
- 🗄️ **Motion-aware local-first SQLite logging** — five-second motion checks without five-second
|
- 🗄️ **Motion-aware local-first SQLite logging** — five-second motion checks without five-second
|
||||||
parked writes, plus retry-safe remote sync and automatic backoff
|
parked writes, plus retry-safe remote sync and automatic backoff
|
||||||
- 🌡️ **Pi health monitoring** — dashboard temperature, RAM, disk space, uptime, and explicit
|
- 🌡️ **Pi health + diagnostics** — a dedicated local Status page shows temperature, RAM, disk,
|
||||||
undervoltage/throttling warnings from the Raspberry Pi firmware
|
uptime, undervoltage/throttling warnings, service state, uplink/server reachability, GPS state,
|
||||||
|
OBD presence, and safe quick actions
|
||||||
- 🔔 **Offline-first alerts** — a notification bell on every page reports check-engine state,
|
- 🔔 **Offline-first alerts** — a notification bell on every page reports check-engine state,
|
||||||
storage pressure, upload backlog, temperature/power faults, and GPS loss while moving. Optional
|
storage pressure, upload backlog, temperature/power faults, and GPS loss while moving. Optional
|
||||||
ntfy delivery is available but remains disabled until configured
|
ntfy delivery is available but remains disabled until configured
|
||||||
@@ -122,7 +123,14 @@ For the remote server (receives trip logs + dashcam uploads), see [server/README
|
|||||||
|
|
||||||
## Customizing device identity
|
## Customizing device identity
|
||||||
|
|
||||||
The Pi displays a username, car model, and avatar image. All customizable values are stored in `config/device.json` (no code edits needed).
|
The Pi displays an owner name, car model, device name, and avatar. You can manage those values in
|
||||||
|
two ways:
|
||||||
|
|
||||||
|
- **Locally on the Pi** through the Settings page
|
||||||
|
- **From the server portal** when **Sync with server** is enabled on the Pi
|
||||||
|
|
||||||
|
The local fallback/default source remains [config/device.json](./config/device.json), so the car
|
||||||
|
still boots with sensible identity values even when fully offline.
|
||||||
|
|
||||||
### Edit device configuration
|
### Edit device configuration
|
||||||
|
|
||||||
@@ -141,10 +149,10 @@ Edit [config/device.json](./config/device.json):
|
|||||||
|-------|----------|---------|
|
|-------|----------|---------|
|
||||||
| `owner_name` | Top-right of app topbar | Your name/username |
|
| `owner_name` | Top-right of app topbar | Your name/username |
|
||||||
| `car_model` | Left sidebar (next to car icon) | Vehicle model/year |
|
| `car_model` | Left sidebar (next to car icon) | Vehicle model/year |
|
||||||
| `device_name` | (Reserved) | Future server-side use in Phase 25.5 |
|
| `device_name` | Local settings / remote portal | Device identity used by server sync/provisioning |
|
||||||
| `avatar_path` | Top-right avatar circle | Profile picture (relative to `static/`) |
|
| `avatar_path` | Top-right avatar circle | Profile picture (relative to `static/`) |
|
||||||
|
|
||||||
After editing, restart the app:
|
If you edit the JSON file directly, restart the app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If running as a systemd service:
|
# If running as a systemd service:
|
||||||
@@ -187,15 +195,26 @@ rm db/sql/quoije_Yaris2013.db
|
|||||||
|
|
||||||
## Project status
|
## Project status
|
||||||
|
|
||||||
Phases 1–24.7 are complete. The system is fully built and audited, ready for the Phase 25 real-vehicle test:
|
The build is now at the endgame stage: the Pi app, remote server, and device-management/sync work
|
||||||
|
are implemented and verified on the real Pi/unraid setup. The remaining roadmap phases are the
|
||||||
|
temporary real-vehicle acceptance test and the low-priority Android companion.
|
||||||
|
|
||||||
- **Phases 1–23.5**: core Pi system — GPS, OBD, dashcam, trip history, web UI redesign, WiFi upgrade, cellular fallback, alerts, settings, upload
|
- **Phases 1–23.5**: core Pi system — GPS, OBD, dashcam, trip history, web UI redesign, WiFi upgrade, cellular fallback, alerts, settings, upload
|
||||||
- **Phase 24**: remote web portal at `https://yaris.ceaf.dev/` — login-protected, trip history with CARTO live map, dashcam archive, delete, overview stats
|
- **Phase 24**: remote web portal at `https://yaris.ceaf.dev/` — login-protected, trip history with CARTO live map, dashcam archive, delete, overview stats
|
||||||
- **Phase 24.5**: full code audit — 6 bugs fixed across Pi modules and server, 63 tests passing
|
- **Phase 24.5**: full code audit — 6 bugs fixed across Pi modules and server, 63 tests passing
|
||||||
- **Phase 24.6**: Yaris 2013 OBD compatibility hardening — `_decode_odometer` no longer kills the OBD session on unsupported PIDs, sensor globals reset on reconnect, `OBD_SKIP_VOLTAGE_CHECK` escape hatch for clone ELM327 adapters
|
- **Phase 24.6**: Yaris 2013 OBD compatibility hardening — `_decode_odometer` no longer kills the OBD session on unsupported PIDs, sensor globals reset on reconnect, `OBD_SKIP_VOLTAGE_CHECK` escape hatch for clone ELM327 adapters
|
||||||
- **Phase 24.7**: diagnostic script (`scripts/diagnose.sh`), map tile switch to CARTO, zoom limit fix
|
- **Phase 24.7**: diagnostic script (`scripts/diagnose.sh`), map tile switch to CARTO, zoom limit fix
|
||||||
|
- **Phase 24.8**: deployment/readme cleanup and uplink interface consistency
|
||||||
|
- **Phases 25.5a–25.5d**: offline-first device identity sync, remote portal device settings,
|
||||||
|
Pi-side hot-reload, multi-device provisioning, one-time API key rotation/registration, local
|
||||||
|
Settings UI for remote credentials, Sync now / Test connection, GPS live-status verification,
|
||||||
|
dedicated local Status page, and status-page quick actions
|
||||||
|
- **Current known limitation**: the remote portal's device-management page is multi-device, but the
|
||||||
|
main remote overview/history/dashcam browsing views still follow one configured `DEVICE_NAME`
|
||||||
|
until a later portal-wide device selector/filter is added
|
||||||
|
|
||||||
**Next**: Phase 25 — temporary real-vehicle test in the 2013 Yaris (see PLAN.md for the full acceptance checklist). Phase 26 (Android companion app) follows after that.
|
**Next**: Phase 25 — temporary real-vehicle test in the 2013 Yaris (see [PLAN.md](./PLAN.md) for
|
||||||
|
the full acceptance checklist). Phase 26 (Android companion app) follows after that.
|
||||||
|
|
||||||
Phase 14 is partially complete: the Pi's hardware watchdog is deployed and reboot-verified.
|
Phase 14 is partially complete: the Pi's hardware watchdog is deployed and reboot-verified.
|
||||||
Read-only-root storage hardening is deferred until persistent storage is moved off the SD root
|
Read-only-root storage hardening is deferred until persistent storage is moved off the SD root
|
||||||
@@ -205,12 +224,12 @@ The browser-based phone relay was removed after real-world testing confirmed tha
|
|||||||
cannot reliably use the Pi's WiFi and cellular data simultaneously. A native Android companion is
|
cannot reliably use the Pi's WiFi and cellular data simultaneously. A native Android companion is
|
||||||
planned later instead (Phase 26).
|
planned later instead (Phase 26).
|
||||||
|
|
||||||
The local Web UI includes a Settings page for safe runtime configuration: dashcam uploads,
|
The local web UI includes a Settings page for safe runtime configuration: dashcam uploads,
|
||||||
uploaded-video retention, cellular APN fallback, optional ntfy delivery, and alert thresholds.
|
uploaded-video retention, cellular APN fallback, optional ntfy delivery, alert thresholds, local
|
||||||
Notification secrets are write-only in the browser API, and network credentials that could lock
|
device identity, and remote server credentials/API key. Notification secrets remain write-only in
|
||||||
the Pi out remain SSH/file-managed. When cellular is configured, small ntfy notifications use the
|
the browser API. When cellular is configured, small ntfy notifications use the SIM868 first and
|
||||||
SIM868 first and WiFi/Ethernet only as fallback; that modem path is implemented and mock-tested but
|
WiFi/Ethernet only as fallback; that modem path is implemented and mock-tested but still needs
|
||||||
still needs real-SIM/carrier verification.
|
real-SIM/carrier verification.
|
||||||
|
|
||||||
The Pi can also join a configured phone hotspot for outbound internet when no SIM is available,
|
The Pi can also join a configured phone hotspot for outbound internet when no SIM is available,
|
||||||
including trip synchronization and WiFi dashcam uploads, with automatic recovery to home WiFi or
|
including trip synchronization and WiFi dashcam uploads, with automatic recovery to home WiFi or
|
||||||
|
|||||||
+33
-18
@@ -21,6 +21,13 @@ docker-compose stack on an unraid server (or any Docker host).
|
|||||||
staged on disk and reassembled + sha256-verified once all have arrived, then
|
staged on disk and reassembled + sha256-verified once all have arrived, then
|
||||||
recorded the same way as the single-shot endpoint. This is what the Pi
|
recorded the same way as the single-shot endpoint. This is what the Pi
|
||||||
actually uses for real dashcam files — see "Why chunked uploads" below.
|
actually uses for real dashcam files — see "Why chunked uploads" below.
|
||||||
|
- `GET /api/device-config` — device-authenticated config fetch used by the Pi's
|
||||||
|
identity-sync thread and local **Sync now** action.
|
||||||
|
- `POST /api/v1/device-config/ack` — device-authenticated config-sync
|
||||||
|
acknowledgement so the server can record the last applied version/time.
|
||||||
|
- `/settings/device` + `/api/v1/portal/devices*` — login-protected multi-device
|
||||||
|
management UI/APIs for create, delete, rotate key, edit config, and request
|
||||||
|
sync.
|
||||||
|
|
||||||
Both `/api/v1/tracking/batch` and the dashcam upload endpoints require two
|
Both `/api/v1/tracking/batch` and the dashcam upload endpoints require two
|
||||||
headers: `X-Device-Name` and `X-Api-Key` (see
|
headers: `X-Device-Name` and `X-Api-Key` (see
|
||||||
@@ -28,13 +35,11 @@ headers: `X-Device-Name` and `X-Api-Key` (see
|
|||||||
Postgres is **not** published to the LAN — only the `api` container talks to
|
Postgres is **not** published to the LAN — only the `api` container talks to
|
||||||
it, over the internal compose network.
|
it, over the internal compose network.
|
||||||
|
|
||||||
**CORS is enabled on every response** (`Access-Control-Allow-Origin: *`) so a
|
**CORS is enabled on every response** (`Access-Control-Allow-Origin: *`) for the
|
||||||
device's registered API key can be used directly from browser JS running on a
|
device-authenticated API surface. This keeps header-authenticated device calls
|
||||||
*different* origin — specifically, the Pi's phone-relay feature (see
|
usable from browser-based tooling and same-user cross-origin utilities without
|
||||||
[PLAN.md](../PLAN.md) Phase 11), where a phone's browser calls this API
|
introducing cookie/session theft risk, because device auth is explicit
|
||||||
straight from a page served by the Pi itself. This is safe here because
|
(`X-Device-Name`/`X-Api-Key`) rather than ambient session auth.
|
||||||
auth is header-based (`X-Device-Name`/`X-Api-Key`), not cookie/session-based,
|
|
||||||
so a permissive origin can't be used to steal a logged-in session.
|
|
||||||
|
|
||||||
## Deploying on unraid
|
## Deploying on unraid
|
||||||
|
|
||||||
@@ -65,12 +70,11 @@ so a permissive origin can't be used to steal a logged-in session.
|
|||||||
docker compose ps
|
docker compose ps
|
||||||
curl http://localhost:8098/health
|
curl http://localhost:8098/health
|
||||||
```
|
```
|
||||||
5. Sign in to the remote portal and open `/settings/device` to edit the synced
|
5. Sign in to the remote portal and open `/settings/device` to manage devices:
|
||||||
owner/car/device/avatar values for the configured `DEVICE_NAME`. If the Pi
|
create/delete devices, reveal a one-time API key, rotate keys, edit synced
|
||||||
has local **Sync with server** enabled, those values hot-reload into the
|
owner/car/device/avatar values, and request a sync. If the Pi has local
|
||||||
running Pi UI after the next successful sync. Phase 25.5c also adds a
|
**Sync with server** enabled, those values hot-reload into the running Pi UI
|
||||||
multi-device list, one-time API-key reveal, key rotation, and a request-sync
|
after the next successful sync.
|
||||||
action on that page.
|
|
||||||
|
|
||||||
### Upgrading an existing unraid deployment
|
### Upgrading an existing unraid deployment
|
||||||
|
|
||||||
@@ -87,10 +91,12 @@ docker compose up -d --build
|
|||||||
|
|
||||||
Phase 25.5a adds the device identity sync columns (`owner_name`, `car_model`,
|
Phase 25.5a adds the device identity sync columns (`owner_name`, `car_model`,
|
||||||
`device_name`, `avatar_path`, `config_version`, `config_updated_at`) used by the
|
`device_name`, `avatar_path`, `config_version`, `config_updated_at`) used by the
|
||||||
Pi/server config sync flow. The browser portal page at `/settings/device` edits
|
Pi/server config sync flow. Phase 25.5c adds multi-device provisioning/status
|
||||||
those values; the Pi applies them on its next successful sync when local
|
columns (`last_config_sync_at`, `last_config_sync_version`,
|
||||||
**Sync with server** is enabled, and Phase 25.5b now hot-reloads the in-process
|
`force_sync_requested_at`, `api_key_rotated_at`). The browser portal page at
|
||||||
UI fields instead of requiring an app restart.
|
`/settings/device` edits those values; the Pi applies them on its next
|
||||||
|
successful sync when local **Sync with server** is enabled, and Phase 25.5b
|
||||||
|
hot-reloads the in-process UI fields instead of requiring an app restart.
|
||||||
|
|
||||||
## Registering a device and getting an API key
|
## Registering a device and getting an API key
|
||||||
|
|
||||||
@@ -104,14 +110,23 @@ This prints a one-time API key — copy the `YARIS_DEVICE_NAME` /
|
|||||||
`YARIS_API_KEY` values into the Pi's upload-client config. Only the
|
`YARIS_API_KEY` values into the Pi's upload-client config. Only the
|
||||||
key's sha256 hash is stored server-side; the raw key is never shown again.
|
key's sha256 hash is stored server-side; the raw key is never shown again.
|
||||||
|
|
||||||
|
On the Pi, those credentials can now be entered through the local Settings page
|
||||||
|
instead of editing `db/upload_creds.env` over SSH. The same page also exposes
|
||||||
|
**Test connection** and **Sync now** actions.
|
||||||
|
|
||||||
The browser portal at `/settings/device` can now do the same workflow without
|
The browser portal at `/settings/device` can now do the same workflow without
|
||||||
shell access:
|
shell access:
|
||||||
|
|
||||||
- create a new device
|
- create a new device
|
||||||
|
- delete a non-default device
|
||||||
- reveal a one-time API key
|
- reveal a one-time API key
|
||||||
- rotate the API key later
|
- rotate the API key later
|
||||||
- request a sync for the selected device
|
- request a sync for the selected device
|
||||||
|
|
||||||
|
Current limitation: device management is multi-device, but the main remote
|
||||||
|
overview/history/dashcam browsing pages still scope themselves to one
|
||||||
|
configured `DEVICE_NAME` until a portal-wide device selector/filter is added.
|
||||||
|
|
||||||
## Exposing it outside the LAN
|
## Exposing it outside the LAN
|
||||||
|
|
||||||
Two options, either works with this API unchanged:
|
Two options, either works with this API unchanged:
|
||||||
@@ -149,7 +164,7 @@ byte-for-byte sha256-verified against the source on the Pi.
|
|||||||
|
|
||||||
The Pi's web UI has a setting to pause dashcam file uploads while keeping
|
The Pi's web UI has a setting to pause dashcam file uploads while keeping
|
||||||
GPS/trip log uploads running — no server-side change needed, the Pi simply
|
GPS/trip log uploads running — no server-side change needed, the Pi simply
|
||||||
stops calling `/api/v1/dashcam/upload` while the toggle is off.
|
stops calling the dashcam upload status/chunk endpoints while the toggle is off.
|
||||||
|
|
||||||
## Local development / testing without unraid
|
## Local development / testing without unraid
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user