Move BUG-23 to Resolved with commit 78aea2d and verification (Pi trips
57 -> 49 after rebuild). Add matching entry to PLAN.md Phase 29.
267 lines
16 KiB
Markdown
267 lines
16 KiB
Markdown
# yaris-obd Bug Tracker
|
||
|
||
Standalone bug/issue tracker. One `## BUG-XX` per issue. Numbering sequential.
|
||
**Status**: `OPEN` → `IN PROGRESS` → `CLOSED` (with commit ref + verification).
|
||
When CLOSED, move the entire entry to Resolved — don't duplicate.
|
||
|
||
---
|
||
|
||
## Open bugs
|
||
|
||
### BUG-19 — Dashboard OBD values slow to appear while the car is running
|
||
- **Status**: `OPEN` 2026-08-13
|
||
- **Phase**: 29
|
||
- **Symptom**: on the Pi dashboard, the speed/RPM/coolant/fuel/throttle cards stay on
|
||
"—" for a long time after the page loads (or after ignition) before populating, even
|
||
though the car is running and OBD is connected.
|
||
- **Cause**: the value cards were populated **only** by socket.io push events from the
|
||
OBD polling threads — the dashboard never pulled a current snapshot on load, so a
|
||
freshly loaded page had to wait for the next poll tick (plus any reconnect/probe
|
||
latency). `/api/obd/status` only returned the connection state, not the actual
|
||
values, so there was no REST fallback either.
|
||
- **Fix**: emit a full current-value snapshot on socket connect, and extend
|
||
`/api/obd/status` to return live speed/RPM/coolant/fuel/throttle/MIL so the dashboard
|
||
fills the cards immediately on load (and every 15 s) via REST. Needs in-car
|
||
verification.
|
||
|
||
---
|
||
|
||
## Resolved
|
||
|
||
### BUG-23 — Pi and server trip counts diverge (server drops short trips the Pi keeps)
|
||
- **Status**: `CLOSED` 2026-08-15
|
||
- **Commit**: `78aea2d`
|
||
- **Fix**: the Pi's `build_trip_summaries()` now applies the same minimum-trip filter as the
|
||
server (`duration < 30s OR distance < 0.05km` → reject), so GPS lock-in drift while the
|
||
car is stationary no longer spawns false trips. It also sorts normalized rows by
|
||
`datetime` (the server orders by `recorded_at ASC`), so clock-rollback rows can't make a
|
||
trip span a backwards time jump. `TRIP_BUILD_VERSION` bumped 3 → 4 to force a Pi rebuild.
|
||
- **Verification**: unit tests updated and pass (12/12), including a new regression test for
|
||
short GPS-drift rejection. On the Pi, trips dropped **57 → 49** after rebuild
|
||
(`trip_build_state` = (8562, 4)). The remaining gap vs the portal's 10 is the 41
|
||
intentionally-deleted trips (server-side `deleted_trip_markers`) plus ~2 trips from the
|
||
server holding 279 more raw points than the Pi — both documented in the original analysis,
|
||
not regressions.
|
||
|
||
### BUG-22 — Trip max speed ignores GPS-only stretch (shows OBD max only)
|
||
- **Status**: `CLOSED` 2026-08-14
|
||
- **Commit**: `90cfd7b`
|
||
- **Fix**: `max_speed_kmh` was computed from OBD speeds only, so the top speed reached during
|
||
the 20-min OBD dropout wasn't reflected. Both trip builders now union OBD speeds with
|
||
GPS-derived speeds computed over a trailing ~30s window (`GPS_SPEED_WINDOW=6`) to average out
|
||
GPS position jitter — raw 5s segments swing wildly (e.g. 58→141→58 km/h, peaks ~162), so an
|
||
unsmoothed union overshoots the real top speed. Also bumped `TRIP_BUILD_VERSION` to force a
|
||
Pi-side trip rebuild.
|
||
- **Verification**: trip #9 max went 111 (OBD-only) → 162 (raw union) → **119** (smoothed), and
|
||
trip #8 → **127.8**, identical on the server and the Pi. The 135.6 the scrubber showed was a
|
||
single noisy 5s GPS sample, not the sustained top speed.
|
||
|
||
### BUG-21 — OBD speed drops out mid-drive, replay shows 0 km/h (GPS-only)
|
||
- **Status**: `CLOSED` 2026-08-14
|
||
- **Commit**: `7c8f27b`
|
||
- **Fix**: the Pi's OBD adapter (ELM327) stopped returning speed for ~20 min on 2026-08-13
|
||
(last OBD reading 22:23:50 UTC, back at 22:43:41 UTC), so the portal replay flatlined at
|
||
0 km/h for that stretch. The server's `get_trip_route()` now mirrors the Pi's `route_points`
|
||
GPS-speed fallback — when OBD speed is missing it derives speed from the previous point's
|
||
movement (haversine / elapsed time, ≥8 m, ≤220 km/h). The Pi's own replay already did this;
|
||
only the portal was missing it.
|
||
- **Verification**: trip #9 points at 22:23:55 previously returned speed 0.0 — now return
|
||
GPS-derived speeds (e.g. 61.9 km/h at 22:23:55, labelled `gps`). API rebuilt + healthy.
|
||
- **Follow-up**: investigate why the OBD adapter dropped for ~20 min (reconnect on stalled
|
||
reads).
|
||
|
||
### BUG-20 — Server trip IDs inflate far beyond the real trip count
|
||
- **Status**: `CLOSED` 2026-08-14
|
||
- **Commit**: `9af71e2`
|
||
- **Fix**: `trips.id BIGSERIAL` burned a sequence value on every upsert in
|
||
`compute_trips_for_device()` (which runs after every upload batch), so IDs climbed far past
|
||
the real trip count. Now the sequence is clamped to `MAX(id)` after each recompute
|
||
(`server/modules/db.py`), and a one-time migration (`server/sql/migrate_phase29_trip_ids.sql`)
|
||
renumbered existing trips compactly and reset the sequence.
|
||
- **Verification**: before — `COUNT=10`, `MAX(id)=27091`; after — `MIN(id)=1`, `MAX(id)=10`,
|
||
sequence `last_value=10` (next insert gets 11). API container rebuilt and healthy.
|
||
|
||
### BUG-18 — Pi needs offline timekeeping (no RTC)
|
||
- **Status**: `CLOSED` 2026-08-14
|
||
- **Commit**: `bf4f3d3`
|
||
- **Fix**: DS3231 RTC module installed on the free I²C bus (SDA pin 3, SCL pin 5, 3.3V pin 1,
|
||
GND pin 6) with a CR2032 (charge diode removed). Pi config: `dtparam=i2c_arm=on` +
|
||
`dtoverlay=i2c-rtc,ds3231` in `/boot/firmware/config.txt`, `i2c-tools` installed,
|
||
`fake-hwclock` disabled, `i2c-dev` persisted via `modules-load.d`. CR1220 also installed in
|
||
the SIM868 HAT holder (secondary RTC backup).
|
||
- **Verification**: DS3231 probes at `0x68` (`UU`), its 24C32 EEPROM at `0x57`, `/dev/rtc0`
|
||
registered. Power-cycle test passed — after a full unplug/replug the boot log reads
|
||
`setting system clock to 2026-08-14…` from the RTC (was `2000-01-01` before), and
|
||
`hwclock -r` matches system time; the `SET TIME!` recovery line no longer appears (stable
|
||
oscillator on CR2032). (Debugging note: the first scans were an empty bus due to a data-line
|
||
wiring issue, resolved during re-soldering.) SIM868 HAT RTC was also set to the correct time
|
||
via `AT+CCLK` (was factory-default 2004) — its CR1220 hold will be confirmed on the next
|
||
power cycle.
|
||
|
||
### BUG-17 — Server trip timestamps wrong after an offline period
|
||
- **Status**: `CLOSED` 2026-08-14
|
||
- **Commit**: `bf4f3d3`
|
||
- **Fix**: root cause was BUG-18 — no RTC, so offline the Pi ran on a stale `fake-hwclock`
|
||
time and NTP snapped the clock forward mid-trip. Fixed by installing the DS3231 (BUG-18).
|
||
- **Verification**: RTC power-cycle test passed (BUG-18). Historical trips 25136/25137 keep
|
||
their bad timestamps (raw tracking rows were recorded with a wrong clock and can't be
|
||
retroactively corrected) — future trips will be correct.
|
||
|
||
### BUG-15 — Phone can't host the hotspot and connect to the Pi's AP at once
|
||
- **Status**: `CLOSED` 2026-08-13
|
||
- **Commit**: `ab79820`
|
||
- **Resolution**: Confirmed a **phone-OS limitation**, not a Pi bug. A single phone
|
||
can't cleanly be both the Pi's internet uplink (hotspot/AP host) and its WiFi client
|
||
(on the Pi's AP) at the same time — doing so routes the hotspot's traffic back through
|
||
the no-internet AP, creating a routing loop (`NameResolutionError` on uploads).
|
||
- **Workaround (documented)**: the phone must be **LTE-only** (WiFi off) to serve as the
|
||
Pi's uplink. Reach the Pi UI from that phone over the hotspot at
|
||
`http://<pi-hotspot-ip>` (Settings → "In-car hotspot"), or from a second device on the AP.
|
||
- **Pi-side hardening shipped**: `phone-hotspot` profile forces public DNS
|
||
(`8.8.8.8`/`1.1.1.1`), and the Settings page surfaces the correct URL per uplink mode.
|
||
|
||
### BUG-16 — Trip missing from Pi local history (present on server)
|
||
- **Status**: `CLOSED` 2026-08-13
|
||
- **Commits**: `e5ac14e`, `f5f20c3`
|
||
- **Fix**: two root causes:
|
||
1. `connect_obd()` set `_car_ever_connected` **without a `global` declaration**, so the
|
||
module-level flag was never set and `was_car_ever_connected()` always returned `False` —
|
||
local trip building had been silently blocked ever since BUG-10 landed. Added the `global`
|
||
declaration and now persist the flag to the settings table (`car_ever_connected`) so it
|
||
survives reboots.
|
||
2. Speed-only rows (NULL GPS, from FEAT-12) made trips start/end with NULL coordinates,
|
||
violating the `trips.*GPSX NOT NULL` constraint. The trip builder now uses the first/last
|
||
GPS-bearing row for start/end coordinates, and skips a trip that never got any fix.
|
||
- **Verification**: backfilled the persisted flag on the Pi (the car has been connected
|
||
before), triggered a rebuild — the local `trips` table now contains the 2026-08-13 drive
|
||
(e.g. a 48.5 km trip) plus several earlier trips that had been silently dropped.
|
||
`trip_build_state` advanced to LAST_TRACKING_ID=7009. Timestamps for the 08-13 data are
|
||
still wrong — that's BUG-17 (no RTC).
|
||
|
||
### BUG-13 — Server Web UI shows raw coordinates instead of geocoded location on trips
|
||
- **Status**: `CLOSED` 2026-08-12
|
||
- **Commit**: `8309819`
|
||
- **Fix**: Server `_geocode_cached()` now always returns a non-empty string with
|
||
coordinate fallback when Nominatim/reverse_geocoder fail. `/api/geocode` always
|
||
returns HTTP 200 with `{label}` (never 204). Batch endpoint includes all keys.
|
||
Nominatim params + reverse_geocoder mode aligned with Pi. Pi also hardened against
|
||
empty reverse_geocoder label.
|
||
- **Verification**: Deployed to Unraid (Docker rebuild) and Pi (git pull + restart).
|
||
Both healthy. Unraid now uses SSH deploy key for git fetch (was failing on HTTPS auth).
|
||
|
||
### BUG-14 — Geocode slow/unreliable — location labels fail to load during scrubbing
|
||
- **Status**: `CLOSED` 2026-08-12
|
||
- **Commit**: `0ddfe39`
|
||
- **Fix**: Ditched Nominatim entirely — it was slow (1 req/sec rate limit, ~4s
|
||
timeouts) and often blocked/throttled from Docker/Cloudflare IPs. Replaced with
|
||
reverse_geocoder exclusively: an offline Python library that maps lat/lon →
|
||
city/province instantly with no network. JS `preload()` switched from N individual
|
||
GET requests to single `POST /api/geocode/batch`. Server batch endpoint simplified
|
||
to sequential loop with lru_cache + dedup.
|
||
- **Verification**: `POST /api/geocode/batch` returns `{"Montreal, Quebec",
|
||
"Longueuil, Quebec"}` instantly on Pi. Server Docker rebuilt and healthy.
|
||
|
||
|
||
### BUG-01 — WiFi/hotspot unreachable from phone while cell data is on
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Fix**: Dual-radio — wlan0="yaris 911" at 10.42.0.1 (password "yaris911!"),
|
||
wlan1=uplink. Never touches wlan0. Phone-hotspot auto-connect on wlan1 within ~60s,
|
||
falls back to home-wifi. Settings page shows in-car access card.
|
||
- **Verification**: iOS + Android connect to AP, `yaris.local` resolves. Phone-hotspot
|
||
uplink tested: auto-connects when hotspot enabled, falls back when disabled. wlan0 AP
|
||
stays up throughout.
|
||
|
||
### BUG-02 — Micro-USB power port on the Pi is physically damaged
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Fix**: Hard-wired 5V/GND directly to GPIO header — red to Pin 2 (5V),
|
||
black to Pin 6 (GND), bypassing the worn micro-USB port.
|
||
- **Verification**: `vcgencmd get_throttled` returns `0x0` — no undervoltage,
|
||
no throttling. Pi runs at full 1.2 GHz. GPS has fix.
|
||
|
||
### BUG-03 — DTC/MIL codes never persisted
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `e2f2314`
|
||
- **Fix**: `save_dtc_state()` persists MIL+codes each poll. `/api/dtc` falls back to
|
||
`get_persisted_dtc_state()` when OBD offline. Cleared codes tracked via IS_SET timestamps.
|
||
- **Verification**: Deployed to Pi, `/api/dtc` returns persisted snapshot, stats show `active_dtc: 0`.
|
||
|
||
### BUG-04 — Server merges two trips into one
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `96fb897`
|
||
- **Fix**: Replaced time-gap splitting with Pi's motion-aware logic: OBD > 1 km/h primary,
|
||
GPS >= 8m × 3 consecutive fallback, 5-min inactivity gap.
|
||
- **Verification**: Server trips cleared + recomputed. 17 trips on both Pi and server.
|
||
|
||
### BUG-05 — Server route replay speed_source always "gps"
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `e2f2314`
|
||
- **Fix**: `"obd" if speed > 0 else "gps"` matching Pi logic.
|
||
- **Verification**: Docker rebuild, `/health` OK LAN + Cloudflare.
|
||
|
||
### BUG-11 — Dashboard GPS shows "Stale fix" while Status shows "Live fix"
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `9733036`
|
||
- **Fix**: `renderGpsStatus()` now reads `status.latitude`/`status.longitude` from
|
||
the `/api/gps/status` response (not just socket events). Status page now uses the
|
||
same 30s staleness threshold (`fix_age_seconds > 30` → "Stale fix") instead of
|
||
calling any fix "Live" regardless of age.
|
||
- **Verification**: Deployed to Pi, dashboard GPS card now shows lat/lon coordinates
|
||
even when fix is stale. Both pages agree on fix status.
|
||
|
||
### BUG-06 — Server timestamps are shifted (naive local time stored as UTC)
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `7ce2a1e` (upload fix), manual migration on server
|
||
- **Fix**:
|
||
1. `_to_tz_aware()` in `app_upload.py` converts naive SQLite timestamps to
|
||
TZ-aware ISO before upload (`2026-08-10 19:27:30` → `2026-08-10T19:27:30-04:00`).
|
||
2. One-time SQL migration on Unraid: shifted 3,593 tracking rows +4h EDT,
|
||
cleared deleted-trip markers, recomputed 43 trips from corrected data.
|
||
- **Verification**: Server trips now show Aug 10 at 7:27 PM / 7:58 PM (was 3:27 / 3:58).
|
||
|
||
### BUG-12 — Dashboard OBD shows "—" while Status page shows correct OBD state
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `91281b1`
|
||
- **Fix**: Added `/api/obd/status` endpoint. Dashboard calls `refreshObdStatus()`
|
||
on load + every 15s. Socket events still update in real-time. Side fix: `Number()`
|
||
wrap on API string lat/lon prevents crash. Socket `gps_location` now passes lat/lon
|
||
to `renderGpsStatus` (`a4a92cc`).
|
||
- **Verification**: Dashboard shows "Adapter only" matching Status page. GPS lat/lon
|
||
display correctly. Confirmed on live Pi.
|
||
|
||
### BUG-02 — Micro-USB power port on the Pi is physically damaged
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Fix**: Hard-wired 5V/GND directly to GPIO header — red to Pin 2 (5V),
|
||
black to Pin 6 (GND), bypassing the worn micro-USB port.
|
||
- **Verification**: `vcgencmd get_throttled` returns `0x0` — no undervoltage,
|
||
no throttling. GPS has fix. Pi runs at full 1.2 GHz.
|
||
|
||
### BUG-07 — Trip replay on portal sometimes does nothing
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `fa93384`
|
||
- **Fix**: Full rewrite of `static/js/history.js`: unified AbortController cancels fetch
|
||
+ all downstream rendering on trip switch; 300ms debounce prevents double-tap stacking;
|
||
loading spinner on map + `.table-active` row highlight give clear feedback; geocode
|
||
preload starts immediately before map rendering so cache is warm for the scrubber;
|
||
`signal.aborted` guards after every async step (route build, fitBounds, timeline init).
|
||
- **Verification**: Deployed to Pi and server. Rapid trip-switching no longer freezes.
|
||
Verified on both platforms 2026-08-11.
|
||
|
||
### BUG-08 — No trip deletion on local Pi
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Commit**: `c5c478b`
|
||
- **Fix**: `deletable` macro, `window.confirmDelete`, `DELETE /api/trip/<id>`, configurable
|
||
`data-delete-url` in shared `history.js`. Raw tracking preserved.
|
||
- **Verification**: Trip 1967 deleted (18→17 trips), 2028 tracking rows preserved.
|
||
|
||
### BUG-09 — Server trip scrubber geocoding is slow
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Fix**: `preloadGeocodes()` batch-fetches all unique coords (concurrency 4) on trip load.
|
||
`geocodePoint()` checks in-memory cache first.
|
||
- **Verification**: `preloadGeocodes` present in history.js on both Pi and server Docker container.
|
||
|
||
### BUG-10 — GPS-only false trips when car not connected
|
||
- **Status**: `CLOSED` 2026-08-11
|
||
- **Fix**: `_car_ever_connected` flag in `app_obd.py`. `build_trip_summaries()` returns
|
||
empty list when car was never connected.
|
||
- **Verification**: Pi bench-test shows 0 trips. No OBD = no GPS-only trips.
|