- _decode_odometer: catch all exceptions and return None instead of raising.
PID 0xA6 (odometer) is not guaranteed on 2013 Toyota Yaris — a garbage
response or unsupported PID previously raised ValueError through python-obd's
decoder, which app_obd._query() caught and treated as a lost connection,
killing the entire OBD session. Now it gracefully returns None.
- connect_obd: reset all sensor globals (rpm, coolant_temp, fuel_level,
throttle_position, mil_on, dtc_codes, odometer, timestamps) on each new
connection so the UI doesn't display stale readings from the previous
ignition cycle while fresh polls are completing.
- yaris-obd.service: add commented OBD_PROTOCOL=6 hint for post-confirmation
pinning (eliminates 3-5s auto-probe delay after first confirmed connection).
Co-authored-by: Copilot <[email protected]>
- app_alerts.py: null guard on list_local_videos() result (.get('files', []))
- app_cellular.py: remove erroneous .encode(errors='replace') on str response body
- app_settings.py: initialize apn='' when isinstance check fails (NameError guard)
- app_upload.py: move json import to top of file instead of inside except handler
- server/app.py: wrap int(request.args.get('limit')) in try/except ValueError
- server/app.py: wrap os.path.commonpath() in try/except ValueError (cross-root paths)
- tests/test_app_settings.py: add missing trips_upload_enabled/video_convert_enabled
to valid_payload() (fields added in Phase 23.5 but test not updated)
All 63 tests pass.
Co-authored-by: Copilot <[email protected]>
The CSS already handles the arrow direction with transform:rotate(180deg) on
.app-sidebar.collapsed .sidebar-collapse-btn. The JS icon className swap was
fighting with CSS rotation causing a double-flip. Remove JS icon manipulation,
let CSS handle it. Also suppress the button transition on page load.
Co-authored-by: Copilot <[email protected]>
Suppress CSS transitions during initial state restore from localStorage.
Re-enable after two rAF frames so the toggle button still animates smoothly.
Co-authored-by: Copilot <[email protected]>
- sidebar-env-badge hidden when collapsed
- sidebar-user-text collapses like nav link spans
- User icon stays centered when collapsed via sidebar-user-footer flex
Co-authored-by: Copilot <[email protected]>
Bootstrap Modal async was unreliable. Replace with synchronous
window.confirm() which always works and needs no dependencies.
Co-authored-by: Copilot <[email protected]>
- text-nowrap on action cell so Replay+trash stay on same line
- Delete button inline with Replay (no whitespace between)
- Explicit deletable guard in click handler
Co-authored-by: Copilot <[email protected]>
- history.js reads tile URL from data-tile-url attribute on #history-map div,
falling back to local offline tiles (Pi behavior unchanged)
- server history.html sets data-tile-url to live OSM tiles
- maxZoom raised to 19 (OSM supports it, local tiles were limited to 15)
- server geocode returns 'label' key to match what history.js/yaris.js expect
Co-authored-by: Copilot <[email protected]>
formatSpeed and formatDistance return '—' on null/undefined values.
Server trips may have null avg/max speed if no speed data was recorded.
Co-authored-by: Copilot <[email protected]>
- /api/trips now returns {trips:[...]} to match what history.js expects (result.trips)
- odo_start_km null guard in history.js (server trips have no odometer data)
- require_login returns 401 JSON for /api/* paths instead of HTML redirect
Co-authored-by: Copilot <[email protected]>
- GET/POST /account/password with CSRF token and bcrypt re-hash
- 'Password' link in sidebar next to Logout
- change_password.html template
Co-authored-by: Copilot <[email protected]>
- PLAN.md: Phase 23.5 documents all work from this session
- README: hardware table adds T3U, features add geocoding/offline maps/T3U, status updated
- pics/ui.png: desktop + mobile side-by-side screenshot (updated)
Co-authored-by: Copilot <[email protected]>
- AbortSignal.timeout not supported on all mobile browsers
- Manual AbortController + setTimeout works everywhere
- Fixes location not showing on dashboard and history first point
Co-authored-by: Copilot <[email protected]>
- Cache key uses 4 decimal places (~11m) instead of 3 (~111m) for correct street
- Dashboard geocodes on first GPS fix, then again every >50m moved (throttled 15s)
- Removed stale console.log
Co-authored-by: Copilot <[email protected]>
- Try Nominatim first (road + city), 4s timeout
- Fall back to reverse_geocoder (city + region) if no internet
- LRU cache (512 entries, ~111m precision) — repeated lookups are instant
- No external calls from browser, all via /api/georecode on Pi
Co-authored-by: Copilot <[email protected]>
- Banner appears on dashboard only when uplink mode is 'phone'
- Shows the Pi's current IP so you can reach it from your phone's browser
- Polls every 15s, hides automatically when not on phone hotspot
Co-authored-by: Copilot <[email protected]>
- Remove all Nominatim/external calls from frontend
- Add /api/geocode?lat=&lon= route using reverse_geocoder (20MB offline KD-tree)
- JS hits /api/geocode only — works with no internet at all
- Falls back to raw coords if Pi route fails
- Add reverse_geocoder to requirements.txt
Co-authored-by: Copilot <[email protected]>
- 5s timeout on Nominatim fetch (AbortSignal.timeout)
- On network error, non-200, or empty result: show 'lat, lon' (5 decimal places)
- Applies to both dashboard live location and history scrubber
Co-authored-by: Copilot <[email protected]>
- Location name (road + city) appears below time/speed in the timeline panel
- Debounced 600ms so dragging the scrubber doesn't spam Nominatim
- Deduped by ~3 decimal lat/lon so stationary replay doesn't re-fetch
- Cleared when a new trip is loaded
Co-authored-by: Copilot <[email protected]>
- Dashboard: recenter button in GPS card header (hidden until first fix)
- Dashboard: estimated location name under lat/lon via Nominatim reverse geocode (throttled 30s)
- History: recenter button fits map back to trip bounds
- Both buttons use crosshairs icon, hidden until data is available
Co-authored-by: Copilot <[email protected]>
- data-trip-id on the <tr> itself
- click handler checks row if no button found
- cursor: pointer on rows for discoverability
Co-authored-by: Copilot <[email protected]>
- formatDistance/formatSpeed: km/km/h only, no mi/mph
- Replay button hidden on mobile (d-none d-sm-table-cell)
- Action column header hidden on mobile to match
- data_table macro supports optional 'class' on column headers
Co-authored-by: Copilot <[email protected]>
- PRIMARY_INTERFACE=wlan1, FALLBACK_INTERFACE=wlan0
- resolve_interface() checks /sys/class/net at startup and picks whichever exists
- INTERFACE is module-level, so all functions use the correct iface automatically
- restore_fallback() now also tries home-wifi-fb / Hotspot on wlan0 if wlan1 is primary but failed
- install_netwatch.sh creates home-wifi-fb profile on wlan0 (lower autoconnect-priority=5)
- wlan0 stays managed=yes (fallback) instead of being hard-disabled
- Status JSON now includes 'interface' field for diagnostics
Co-authored-by: Copilot <[email protected]>
- INTERFACE = 'wlan1' in yaris_uplink.py
- install_netwatch.sh binds home-wifi, Hotspot, phone-hotspot to wlan1
- Disables wlan0 (onboard) via nmcli device set managed no
- WLAN variable at top of script for easy revert to wlan0 if needed
Co-authored-by: Copilot <[email protected]>
- /api/local-video/file?download=1 serves file as attachment
- Download link (green DL button) added to each row in local-videos.js
- Dashcam USB files skipped: no persistent mount for direct serve;
use Import then download from Pi Videos
Co-authored-by: Copilot <[email protected]>
- ffmpeg -progress pipe:1 streams out_time_ms lines; we parse them in the
reading loop so % = out_time_s / duration_s (actual encode position)
- find_video_files() skips *.converting.mp4 temp files so they are never
listed, uploaded, or shown in the UI
Co-authored-by: Copilot <[email protected]>
- OBD odometer now logged to tracking rows on every GPS snapshot
- trips table: START_ODOMETER/END_ODOMETER columns (migration)
- build_trip_summaries: picks odometer from first/last tracking row
- list_trips: uses real OBD odo, falls back to cumulative distance
- overview stats: odometer_km (max seen from OBD), video_count fixed
- history page: Odometer column shows start→end km per trip
- app_upload: TRIPS_UPLOAD_SETTING_KEY, is_trips_upload_enabled()
- _upload_tracking_batch: skips when trips upload is off
- settings: separate 'Upload trips & GPS' toggle from dashcam toggle
Co-authored-by: Copilot <[email protected]>
File size ratio was wrong since output bitrate != input bitrate.
Now uses ffprobe to get video duration, then elapsed/duration for %.
Co-authored-by: Copilot <[email protected]>
Instead of full re-render every 10s, patch converting badges in-place.
Only does a full renderFiles() when file count changes (conversion done)
or on the final poll when all conversions finish.
Co-authored-by: Copilot <[email protected]>
- New POST /api/local-video/convert route triggers queue_convert(force=True)
- queue_convert() now returns True/False/None and accepts force param
to bypass the auto-convert toggle (manual always works)
- Convert button shown for non-MP4, non-converting files in video list
- Clicking spins the button and refreshes list after 1s to show badge
Co-authored-by: Copilot <[email protected]>
- _convert_bg now runs in real threading.Thread (not gevent greenlet)
so ffmpeg proc.wait() doesn't block gevent event loop
- _device_lock released before copy loop; only held for mount/unmount
so /api/dash/files and other routes don't block during long copies
- Date now shows time (Aug 9, 10:30 PM) on mobile video rows
- Spacing added above converting spinner badge
Co-authored-by: Copilot <[email protected]>
- Import now returns immediately with {status: 'started'}
- Backend runs copy in a daemon thread, tracks job state in _import_job
- New GET /api/dash/import/status endpoint for polling
- UI polls every 3s, shows progress bar until done
- Page reload mid-import resumes polling automatically
- Prevents double-import with 'busy' guard
Co-authored-by: Copilot <[email protected]>
- yaris.css: split mobile rules by list ID — video-list shows thumb+actions,
dashcam-file-list shows dash-col-file+actions; thead display:none !important
- yaris.js: check data !== '-' before appending rpm/°C/% units so disconnected
state shows — instead of '- rpm'
Co-authored-by: Copilot <[email protected]>
Inline style.width on td was overriding the mobile media query width rule.
Use dash-thumb-td class for desktop (9rem) and mobile (5rem !important).
Co-authored-by: Copilot <[email protected]>
Bootstrap d-flex sets display:flex !important which beats the HTML hidden
attribute. Switch to style.display='none'/'flex' for both status bars.
Co-authored-by: Copilot <[email protected]>
- yaris.css: use .dash-row class on all rows; only td:first-child and
.dash-actions-td visible on mobile (<576px); buttons never overflow
- dash.js: add dash-row class + dash-actions-td wrapper div to dashcam
file rows; import status moved to card footer at top with spinner + ETA
- local-videos.js: add dash-row class to video rows
- dash.html: import status bar (spinner + ETA + result) in device card
at top; removed old footer import status from files card
Co-authored-by: Copilot <[email protected]>
Replace column-hiding approach (still caused 691px overflow) with full
table reflow: on <576px each tr becomes a flex row, all tds hidden except
first (thumb+name) and .dash-actions-td (buttons). Buttons can never
overflow because the row is constrained to viewport width.
Co-authored-by: Copilot <[email protected]>
- yaris.css: on mobile (<576px) hide file/size/date/upload cols, show
filename inside thumb cell, icon-only buttons, dash-actions-td nowrap
- local-videos.js: filename in thumb cell for mobile; Converting badge with
spinner; auto-refresh every 10s while any file is converting
- app_video.py: CONVERT_SETTING, is_convert_enabled(), queue_convert(),
_convert_bg() — h264_v4l2m2m + aac faststart, deletes original on success
- app_dash.py: call queue_convert() after each successful copy on import
- app_settings.py: expose video_convert_enabled in get/save settings
- settings.html: toggle for 'Convert imported videos for web playback'
Co-authored-by: Copilot <[email protected]>
- local-videos.js: action buttons go in a div.data-list-actions inside td
so td stays a table cell (not flex); thumbnail td gets fixed width 9rem
- app.py: video route now pipes ffmpeg with -c copy -movflags
frag_keyframe+empty_moov so browsers get a fragmented MP4 stream that
starts playing immediately without needing the moov atom at the end
Co-authored-by: Copilot <[email protected]>
- app_sql.py: add get_overview_stats() with single SQL query (trip count,
total km, duration, max speed, active DTC count)
- app.py: add /api/stats route (was missing after scp overwrote the file)
- local-videos.js: remove leftover old closePlayer/playVideo/renderFiles
duplicate block that still had scrollIntoView and references to the old
panel elements; new modal-based code is the only path now
Co-authored-by: Copilot <[email protected]>
- app.py: replace send_file with manual Range-aware chunked generator;
returns 206 on partial requests so browsers can seek/buffer properly.
Thumbnail route returns 202 while ffmpeg is generating.
- app_video.py: thumbnail generation moved to background gevent greenlet
(_generate_thumbnail_bg). get_thumbnail_path returns immediately with
RuntimeError('generating') if not cached; ffmpeg only ever runs once
per video (result cached in dash/thumbnails/).
- dash.html: replaced bottom card player with Bootstrap modal (modal-xl,
centered, dark bg) — video pops up in place regardless of scroll position.
- local-videos.js: rewritten to use Bootstrap modal; thumbnail loader
polls with fetch() and retries on 202 every 2s (up to 15x) instead of
setting img.src directly (which can't detect 202).
Co-authored-by: Copilot <[email protected]>
Phase 23 section updated with full list of changes from all polish passes:
Overview stats card, speed chart rework, mobile dashcam table fixes,
trip history mobile layout, notification badge fix, footer text.
README project status updated to reflect Phases 1-23 complete.
Co-authored-by: Copilot <[email protected]>
- Replace 8-point scatter chart with proper 60-point line chart
- Blue gradient fill (rgba 35%→2%), tension 0.4 smooth curve
- No legend (obvious), no X axis grid, soft Y axis gridlines
- Y axis auto-scales: max(60, sessionMax*1.2) rounded to 10s
- Ticks: '0 km/h' labels on Y, time strings on X (max 6)
- chart.update('none') for live updates (no animation stutter)
- Speed history card header now shows current speed + session max
even when the graph is collapsed
- Footer bar inside expanded graph: session avg, point count, last reading time
- makeGradient() refreshed on every update in case canvas was resized
- obd_rpm now appends ' rpm' to the displayed value
Co-authored-by: Copilot <[email protected]>
Dashboard:
- OBD stat cards more compact (py-2, h6 health values, 'OBD' label fits)
- obd-val class prevents Disconnected wrapping
- Speed graph collapsed by default with toggle chevron button
- GPS map + coordinates merged into single card with footer coords
- Pi health rows use h6 (less tall), alert py-2
Dashcam:
- Device status is now a compact single-line bar
- Files on dashcam section is collapsible (hidden by default)
- Import status moved into the dashcam files card footer
- Imported videos on Pi moved to top, open by default
- Column headers trimmed (Select→empty, Actions→empty, Preview→empty)
Trip history:
- col-12 on mobile so list stacks above the map
- Shorter column labels (Distance→km, Duration→Time, Max speed→Max)
- history-map height reduced for mobile (22rem on small screens)
Settings:
- Cellular + ntfy merged into one collapsible card
- Alert thresholds collapsible (advanced)
- Phone hotspot instructions shortened
- Upload retention uses inline row layout (compact)
- All cards use shadow-sm, smaller headers
CSS:
- obd-val: word-break + smaller font for OBD status values
- collapse-chevron: smooth rotation transition
- #map height 18rem (was 20rem)
- history-map 32rem desktop / 22rem mobile (was 36rem)
Co-authored-by: Copilot <[email protected]>
- Replace SB Admin 2 sidebar with a clean fixed sidebar (desktop)
and a sticky bottom tab navigation (mobile, ≤767px)
- Sidebar collapses to icon-only mode with state persisted in localStorage
- Logo is a functional home link on all viewports
- Mobile: full-width content, bottom nav replaces sidebar
- Dashboard: 6 OBD stat cards (status, speed, RPM, coolant, throttle, fuel)
wired to live socket events; removed all placeholder buttons/dropdowns
- GPS info section cleaned up (lat/lon display instead of X/Y paragraphs)
- DTC page: live /api/dtc wiring — MIL indicator + DTC table populated from API
- New /api/dtc route added to app.py (mil + codes)
- yaris.js: added obd_coolant_temp / obd_fuel_level / obd_throttle listeners
- yaris.css: full rewrite — layout variables, sidebar, bottom nav, OBD cards,
border-start utilities, mobile padding for bottom nav
Co-authored-by: Copilot <[email protected]>
Prefer a configured phone hotspot for outbound synchronization, preserve secure write-only credentials, and recover through home WiFi or the local Pi hotspot.
Co-authored-by: Copilot <[email protected]>
Recorded Phase 12's deployment step (Pi restart + unraid server
docker compose build/up since server/app.py's CORS removal is baked
into the image at build time - a plain restart wouldn't pick it up)
and its verification (both confirmed on real infrastructure: Pi's
/dash returns HTTP 200 with no relay text, server's /health has no
Access-Control-* headers anymore).
Added a handoff note for whoever picks this up next: Phase 17 (OBD
hardening) was the next phase in progress, but no code was written
yet - only read app_events.py/app_gps.py for context and started
(but didn't finish, due to a tool permission interruption) installing
the python-obd package locally to check its actual API surface
before writing the DTC/protocol/diagnostics code. Reset that todo
back to pending to reflect no actual progress was made. Phases 13
and 14 are equally ready to start (no blocking deps) if a different
starting point makes more sense.
Co-authored-by: Copilot <[email protected]>
Removes the phone-relay feature entirely per the Phase 11 finding
that it hit a fundamental phone-OS limitation (a browser page can't
route WiFi and cellular traffic simultaneously, no code fix possible)
- see PLAN.md for the full test writeup.
- app.py: dropped /api/dash/pending, /api/dash/file/<path>,
/api/dash/relay/config, /api/dash/relay/heartbeat,
/api/dash/relay/stop, /api/dash/relay/mark_uploaded routes and
their now-unused imports (os, send_file, abort, safe_join).
- modules/app_upload.py: dropped list_pending_dashcam_files(),
relay_heartbeat()/relay_stop()/is_relay_active(),
RELAY_LEASE_SECONDS/_relay_lease_until, and the relay-lease check
inside _upload_dashcam_files()/get_status(). Kept the chunked
upload protocol, dashcam_hash_cache/_hash_dashcam_file() (still
used by the Pi's own background uploader), and DASHCAM_CHUNK_SIZE -
none of that was relay-specific.
- templates/dash.html: removed the 'Relay via phone' card.
- static/js/dash.js: removed all relay JS (relayOneFile,
fetchChunkWithRetry, uploadChunkWithRetry, heartbeat timers,
pending-file list rendering).
- server/app.py: removed the CORS after_request hook, added solely
for the phone's cross-origin browser calls (the Pi's own Python
requests-based client was never subject to CORS).
- readme.md: dropped the phone-relay feature bullet, updated the
project-status blurb to explain the removal + point at the planned
native Android app as the real fix for that use case.
- PLAN.md: marked Phase 12 done with a verification writeup.
Verified: py_compile on all touched Python files, node --check on
dash.js, grepped all touched files for any leftover relay/RELAY
reference (none), and smoke-tested by importing app.py + hitting it
with Flask's test client - app starts cleanly, every relay route is
gone from url_map, and both / and /dash still render HTTP 200 with
zero 'relay' occurrences in /dash's HTML.
Co-authored-by: Copilot <[email protected]>
Reordered/renumbered the roadmap based on dependency analysis:
- Phase 12 (remove relay) stays first - pure cleanup, no deps.
- New Phase 13: power-loss resilience + hardware watchdog - OS-level,
independent, should land before the Pi lives in the car full-time
(cars cut power abruptly at every key-off, a real SD-corruption risk
nothing in the project currently addresses).
- New Phase 14: UI design-system foundation - pulls forward a small
slice of the old end-of-roadmap redesign (shared layout + reusable
list/table component) so the dashcam/video browsers, trip history,
and settings pages are all built on it once instead of being built
in the old template and then rebuilt during the redesign later.
- Phases 15/16 (dashcam + local video browsers) now explicitly build
on Phase 14's foundation. Folded two more QOL ideas directly into
Phase 16: ffmpeg thumbnails per video, and an optional
upload-confirmed local retention/auto-cleanup policy.
- Phase 17 (OBD hardening) explicitly marked independent/parallel-
frins to other phases - backend-only work, no UI dependency.
- New Phase 18: trip history + map replay (trips table + GPS-gap
boundary detection + list/replay UI reusing the offline Leaflet map).
- New Phase 19: Pi health monitoring card (CPU temp/disk/uptime/RAM).
- New Phase 20: alerts/notifications via a simple outbound webhook
(ntfy.sh/Telegram) for DTC-on, storage full, backlog growing,
overheating, lost GPS fix.
- New Phase 21: consolidated Settings page (upload toggle, cellular
APN, retention policy, alert config) - reduces routine SSH-editing.
- Phase 22: renamed 'Finish web UI redesign' - now scoped as the
remaining cleanup after Phase 14's foundation + all functional
phases land, rather than one big redesign at the very end.
- Phase 23: native Android app, unchanged content, still last/low
priority per explicit user instruction.
Updated the session's todo tracking table to match the new phase
IDs/order and added todo_deps reflecting the same dependency chain.
Co-authored-by: Copilot <[email protected]>
Formatting-only pass, no information changed:
- Added missing checkmarks to Phase 7 and Phase 9 headers (were plain
'DONE' text while every other completed phase uses '✅ DONE').
- Added a status icon to the un-numbered SIM868 cellular fallback
section ('⚠️ built, UNTESTED').
- Added a consistent '🚧 PLANNED' icon to Phases 12-17, which had no
status icon at all.
- Fixed a stale cross-reference in the Phase 6 update ('see Phase 9
update below' -> 'see Phase 11 update below' - the phone-relay
content it points to has always lived under Phase 11).
Co-authored-by: Copilot <[email protected]>
Phase 16: complete web UI redesign - the current template is full of
never-replaced placeholder cruft (stub buttons, empty /dtc page); do
one real design pass once the functional phases land rather than
keep bolting onto the old admin-template style.
Phase 17: native Android companion app - the real fix for the
phone-relay goal that a web page could never deliver (true
simultaneous WiFi-to-Pi + cellular-to-server via
bindProcessToNetwork), plus a trip/location info view. Explicitly
deprioritized to the end of the roadmap per user - not urgent.
Also expanded Phase 15 (OBD hardening) scope to pull richer dashboard
data (coolant temp, fuel level, throttle, DTC/MIL status) alongside
speed/RPM, noting odometer/mileage support is not guaranteed on this
vehicle/year and needs real-car confirmation.
Co-authored-by: Copilot <[email protected]>
Retested the hotspot+cellular phone-relay scenario more rigorously
(netwatch explicitly disabled during tests so it wouldn't yank the Pi
back to home WiFi mid-test - an earlier failed run turned out to be
caused by that, not a phone issue). Confirmed with WiFi fully off
(pure cellular) that requests to the public server still failed while
associated with the Pi's hotspot, and that this is a fundamental
OS-level 'single default network for the whole device' limitation -
not something a web page's JS can work around, only a native app could
(bindProcessToNetwork). Documented the decision to remove the feature.
Added Phase 12 (remove phone-relay), Phase 13 (dashcam USB file
browser: list+delete), Phase 14 (local imported-video browser +
HTML5 player), Phase 15 (OBD code hardening ahead of first real
car test - background reconnect loop, exception handling, protocol
override, better diagnostics).
Co-authored-by: Copilot <[email protected]>
Live-tested the away-from-home hotspot+cellular relay scenario. Found the
netwatch reconnect bug (documented in Phase 6 update) during this test.
Confirmed the relay architecture itself needs no code changes - the
TypeError: Failed to fetch was a phone-OS network-switching behavior
(won't route internet traffic over cellular while connected to a
no-internet WiFi), not a CORS/code issue - ruled out via a plain page
navigation test. Testing this fully requires being genuinely away from
home or manually forcing cellular use on the phone.
Co-authored-by: Copilot <[email protected]>
Real-hardware finding: netwatch repeatedly reported 'home WiFi not found'
4 times over 13min while sitting right next to the router. Root cause:
a plain 'nmcli con up home-wifi' straight out of AP mode relies on NM's
scan cache for wlan0, which is stale/empty coming out of AP mode (the
radio can't scan while acting as an AP). A single con-up attempt didn't
wait for a fresh scan to land before giving up.
Fix: explicitly drop the AP connection first to free the radio, force a
fresh scan with a settle delay, and retry the connect attempt (longer
30s timeout) up to 2 times before conceding and restoring the hotspot.
Bumped the systemd unit's TimeoutStartSec to 180s to give the retry loop
enough headroom (was defaulting to 90s).
Co-authored-by: Copilot <[email protected]>
Root-caused a "GPS not working" report to the SIM868 module chip being
fully powered off (the HAT's 5V rail can be live while the chip itself
stays off until PWRKEY is triggered) - confirmed by getting zero AT
response at any baud rate while the app's GPS thread had the serial
port open normally.
Discovered this specific HAT revision has the "P4" GPIO power-on mod
wire installed (Waveshare FAQ / GSM_PWRKEY.rar script), letting PWRKEY
be triggered via physical pin 7 (BCM GPIO4) instead of a manual button
press. Verified live: pulsing it low for 4s then high brought the
module from zero AT response to responding immediately.
modules/app_gps.py: added _module_responds()/_pulse_pwrkey()/
_ensure_module_powered(), wired into _poll_loop() before the existing
GNSS power-on check. Checks AT first and only pulses PWRKEY if the
module isn't already responding, since PWRKEY is a toggle (pulsing an
already-on module would turn it back off).
Also fixed a related logging gap found while debugging: print()
diagnostics from app_gps.py were invisible in yaris-obd.log for
months because Python fully-buffers stdout when it isn't a TTY.
deploy/yaris-obd.service now sets PYTHONUNBUFFERED=1.
requirements.txt: added RPi.GPIO.
PLAN.md: documented both findings and the Pi deploy gotchas (systemd
unit file changes need a manual copy + daemon-reload; requirements.txt
changes need a manual venv pip install - neither is picked up by a
plain git pull + restart).
Co-authored-by: Copilot <[email protected]>
Retook pics/ui.png to show the current dashboard, including the
Dashcam nav item added in later phases. Also switched the readme's
image link from a hardcoded old-commit GitHub URL to a relative path
so it won't go stale again.
Co-authored-by: Copilot <[email protected]>