# yaris-obd Revival Plan This document tracks the plan to revive and finish this project: a Raspberry Pi 3-based OBD-II + GPS car tracker for a 2013 Toyota Yaris, hosting a local web UI over a self-broadcast WiFi hotspot, with dashcam file retrieval and remote upload of itinerary/dashcam data. Keep this file up to date as phases complete or plans change, so any future session/agent can pick up the project without needing prior chat history. ## Hardware - **Board**: Raspberry Pi 3 Model B Rev 1.2, 906MB RAM. Confirmed too weak to run an AI coding agent — all agent-assisted dev work happens on a separate local machine; the Pi is only used for hardware bring-up/testing/deployment via SSH (`yaris@`). - **WiFi**: Dual-radio setup — **TP-Link Archer T3U (RTL8812BU)** USB dongle as `wlan1` and the onboard BCM43438 as `wlan0`. **2026-08-12 role swap**: the fast T3U now hosts the in-car AP ("vroom vroom yaris de course" on 5GHz at 10.42.0.1) for fast phone access (Android dev, video browsing); the weak onboard radio carries the internet uplink (home WiFi "BuzzBis" + phone hotspot, static IP 192.168.50.220, gw 192.168.50.1). Both radios active simultaneously. Tradeoff: away-from-home uploads (dashcam/trip sync) now run over the weak onboard radio — a background task, accepted by owner. A single radio cannot be AP + client at once, so the phone-hotspot uplink cannot also live on wlan1 while it hosts the AP. - **GPS/GSM**: Waveshare **SIM868 GSM/GPRS/GNSS/Bluetooth HAT**. GNSS provides live GPS fixes (confirmed working, ~7 satellites, ~80s cold-start TTFF). GSM/GPRS cellular is **not in use** (no SIM card available); a Huawei **E3372h-153 4G LTE modem** has been purchased as a future cellular uplink replacement (FEAT-04). SIM868 GPS antenna confirmed connected to correct u.FL port (was briefly disconnected — reconnected and verified 2026-08-11). - **OBD-II adapter**: a cheap unbranded USB adapter using a **PL2303** USB-to-serial chip, ELM327-compatible (works with the `python-obd` library). Originally Bluetooth, switched to USB. - **Power**: Hard-wired 5V/GND directly to GPIO header (red to Pin 2, black to Pin 6), bypassing the worn micro-USB connector. `vcgencmd get_throttled` returns `0x0` — no undervoltage or throttling. Pi runs at full 1.2 GHz (was ~600 MHz downclocked before the fix). - **Car**: 2013 Toyota Yaris. ### SIM868 HAT bring-up notes (important gotchas) - UART routing is a 3-position jumper: **A** = USB↔SIM868 (via onboard CP2102), **B** = Pi GPIO↔SIM868, **C** = USB↔Pi console. Must be on **B** for the Pi to talk to the module over its own GPIO UART. - Pi-side UART setup required: `enable_uart=1` and `dtoverlay=disable-bt` in `/boot/firmware/config.txt` (frees the full PL011 UART — `/dev/ttyAMA0`/`/dev/serial0` — from the onboard Bluetooth controller), `raspi-config nonint do_serial_cons 1` (stops a login shell from claiming the serial port), and disable the `hciuart` systemd service. - **The SIM868 module chip itself needs its PWRKEY button pressed and held (~1-2s) to power on** — this is separate from the board's 5V/PWR LED, which can be lit while the module chip is fully off. AT commands get zero response if the module chip isn't powered, even with perfect UART wiring/config. **Update 2026-08-09: this HAT revision DOES have the "P4" GPIO-power-on mod wire installed** (per the Waveshare FAQ/`GSM_PWRKEY.rar` script) — confirmed by pulsing physical header pin 7 (BCM GPIO4) LOW for 4s then HIGH while the module was unresponsive, after which it immediately answered `AT`. This is now automated in `modules/app_gps.py` (`_ensure_module_powered()`), which checks `AT` first and only pulses PWRKEY if the module isn't already responding (PWRKEY is a toggle — pulsing an already-on module would turn it back off, so this check-first behavior is required). No more manual button-pressing needed after a Pi power cycle. - Confirmed AT command baud rate: **115200**. - GNSS workflow: `AT+CGNSPWR?` to check GNSS power state, `AT+CGNSPWR=1` to power on, `AT+CGNSINF` to poll for a fix. Response format: `+CGNSINF: run,fix,utc_time,lat,lon,alt,speed,course,fixmode,reserved1,hdop,pdop,vdop,reserved2,gps_sat_view,gnss_sat_used,gnss_sat_view,cn0,hpa,vpa` — field 1 (`fix`) must be `'1'` for a valid fix. - **The GPS/GNSS antenna is separate from the GSM antenna** — both need to be physically connected to their respective u.FL connectors on the HAT. A real fix was obtained only after discovering the GPS antenna wasn't plugged in (only GSM was). - Confirmed real fix obtained near a window: ~80s cold-start TTFF, 5-7 satellites, HDOP ~1.2-1.6. ## Software Architecture Decisions 1. **Offline maps**: Pre-cache/download offline map tiles for the car's usual driving area so the live map works fully in the car with no internet (not just numeric GPS readout). Tooling: MapTiler Cloud free tier (raster XYZ tiles, `streets-v2` style) — one-time download on the dev machine via `scripts/download_map_tiles.py`, output shipped to the Pi as static PNGs. Bulk-downloading directly from `tile.openstreetmap.org` was ruled out — it violates the OSM tile usage policy. Bounding box is a required CLI arg (the user's own driving area, not committed here), zoom 8-15 by default (~14k tiles for a city-plus-suburbs-sized area, well under MapTiler's 100k/month free quota). 2. **WiFi hotspot mode**: **Dual-radio** — the fast TP-Link Archer T3U (wlan1) hosts the in-car AP ("vroom vroom yaris de course" on 5GHz at 10.42.0.1), while the onboard BCM43438 (wlan0) carries all internet uplink traffic (home WiFi, phone hotspot) and is never taken down by the uplink manager. The hotspot is *only* for local web-UI/Android access while driving — it is not part of the upload path. Remote-server uploads happen independently via home WiFi (when in range), phone hotspot (Phase 22, now on wlan0), or cellular fallback (decision #5). Phone-hotspot auto-connects on wlan0 within ~60s of appearing, falls back to home-wifi when it disappears. **2026-08-12 swap** from the previous arrangement (uplink on wlan1, AP on wlan0) to put the fast radio on the AP for interactive phone work; the weak radio's uplink slowness is accepted because away-from-home upload is a background task. 3. **Local/remote persistence**: Keep SQLite locally (reliable local write path per trip). The original plan paired this with MongoDB Atlas for remote sync (`modules/app_db.py`, fixed in Phase 7) — **superseded by decision #4/Phase 9**: the actual remote store is the self-hosted Postgres server, uploaded via `modules/app_upload.py`. `app_db.py`'s Mongo path is not wired into `app.py` and is effectively dead code at this point, kept only for reference. 4. **Remote server**: Self-hosted — a custom server/API (e.g. on a home server or VPS) receives itinerary and dashcam uploads, rather than 3rd-party managed cloud services. 5. **Remote upload uplink**: Three paths, tried in order: (1) WiFi/Ethernet via `requests`, (2) phone hotspot via wlan0 when configured and in range (Phase 22), (3) SIM868 GSM/GPRS cellular fallback (AT-command HTTP client, tracking batches only — no dashcam video). Cellular is **untested** (no SIM card). A Huawei E3372h-153 4G LTE modem has been purchased to replace the 2G-only SIM868 cellular path once a SIM is available (FEAT-04). ## Phases ### Phase 1: Real GPS integration — ✅ DONE `modules/app_gps.py` rewritten to read real SIM868 GNSS data via serial AT commands (`/dev/serial0` @ 115200 baud) instead of hardcoded fake coordinates. Background daemon thread polls `AT+CGNSINF` every 2s, parses fix data, emits `gps_location` via socketio only on an actual fix. Verified against real hardware (see bring-up notes above). ### Phase 2: OBD fixes for USB adapter — ✅ DONE `modules/app_obd.py` fixed (commit "Fix app_obd.py bugs and adapt for USB OBD-II adapter", pushed to `origin/shameless-aislop`): - `connection` is never left undefined on failed connect (was a `NameError` crash). - Added `_find_obd_port()`/`connect_obd()`: auto-detects `/dev/ttyUSB*` (the PL2303 adapter), honors an `OBD_PORT` env override, falls back to python-obd's own port auto-scan (still covers the old `/dev/rfcomm*` Bluetooth path). - Fixed stale-response bug: `obd_speed()`/`obd_rpm()` now re-query every loop iteration instead of reusing one response forever. - Fixed `obd_rpm()` using the wrong lock, never updating the global `rpm`, and emitting to the wrong socketio event (`obd_speed` instead of `obd_rpm`). - Fixed `kill_obd()` being a no-op — now uses `threading.Event` flags actually checked in the polling loops. - Fixed inverted thread-start logic (threads were starting on failed connection, not success). - Fixed `obm_error` → `obd_error` typo (frontend listens for `obd_error`). - Added `obd` (python-obd) to `requirements.txt`. - **Still untested**: a real connection to the actual car (adapter has only been tested standalone on the Pi so far — correctly reports "no connection to car" with nothing attached). ### Phase 3: Web UI must work fully offline — ✅ DONE The whole point of the project is a Pi hotspot with **no internet in the car**, but the current UI silently depended on the internet for core functionality: - `templates/index.html`/`dash.html`/`dtc.html` loaded Google Fonts, FontAwesome, Leaflet JS/CSS, and the Socket.IO client from public CDNs — none of this loads offline, breaking layout, the websocket connection itself, and the map entirely. - `static/js/yaris.js` loaded live map tiles from `tile.openstreetmap.org` (also needs internet). - A local `static/js/chart.min.js` existed but was unused — `index.html` loaded Chart.js from `cdn.jsdelivr.net` instead (turned out the local copy was a stale Chart.js **v2.8.0** while `yaris.js`'s config uses v3/v4-style `scales: {x:{...}, y:{...}}` syntax — that mismatch is exactly why the CDN was used instead. Replaced the local file with a current v4.5.0 UMD build). **What was done**: - Vendored Socket.IO client (4.8.1, matches Flask-SocketIO 5.6.1's Engine.IO v4 protocol) under `static/vendor/socketio/`, and Leaflet (1.9.4 JS/CSS + marker/layers images) under `static/vendor/leaflet/`. - FontAwesome 5.12.0 was already vendored locally at `static/fonts/` (self-consistent, just never referenced) — repointed all three templates to it instead of the CDN. - Dropped the Google Fonts (Nunito) `` entirely rather than self-hosting 12 font-weight files — Bootstrap's `--bs-font-sans-serif` variable already falls back cleanly to system fonts (`-apple-system, Segoe UI, Roboto, ...`) after Nunito, so this is a clean no-cost simplification. - Replaced `static/js/chart.min.js` with a current Chart.js v4.5.0 UMD build (auto-registering, same drop-in behavior as the old CDN `