# 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@`). - **GPS/GSM**: Waveshare **SIM868 GSM/GPRS/GNSS/Bluetooth HAT**. Also has unused cellular (GSM/GPRS) data capability, earmarked as a fallback internet uplink (see Remote Server phase). - **OBD-II adapter**: a cheap unbranded USB adapter using a **PL2303** USB-to-serial chip, presumed ELM327-compatible (works with the `python-obd` library). Originally the project used a Bluetooth OBD adapter; this has been switched to USB. - **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**: Auto-switch — the Pi joins home WiFi when in range (for syncing/updates), and falls back to broadcasting its own AP otherwise. **Important scope clarification**: the hotspot is *only* for local web-UI viewing on a phone/laptop while driving — it is not part of the upload path. Remote-server uploads happen independently via home WiFi (when in range) or the SIM868 cellular fallback (decision #5) — the phone connecting to the hotspot never relays/carries upload traffic. 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**: Use the SIM868's GSM/GPRS cellular connection as a fallback uplink for uploads when no WiFi is available (e.g. while driving), not just "sync when on home WiFi." ## 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 `