From c03cd6d48d1b9c1da019cd8d0c2612bdd7d5d8c0 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Mon, 10 Aug 2026 05:26:40 -0400 Subject: [PATCH] fix: add OBD_SKIP_VOLTAGE_CHECK support for clone ELM327 adapters Counterfeit ELM327 chips (common in cheap PL2303 USB adapters) often report bad/zero voltage even when the OBD connection is working. python-obd's check_voltage=True default rejects these. Add OBD_SKIP_VOLTAGE_CHECK=1 env var to pass check_voltage=False to obd.OBD(), documented in yaris-obd.service. Also document Phase 24.6 Yaris 2013 compatibility analysis in PLAN.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- PLAN.md | 38 ++++++++++++++++++++++++++++++++++++++ deploy/yaris-obd.service | 3 +++ modules/app_obd.py | 7 ++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index 4b405a7..29af2a4 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1335,6 +1335,44 @@ including two UX polish passes and multiple bug-fix rounds. - `app_cellular.py` `r"\r\n"`: SIMCOM SIM868 AT+HTTPPARA="USERDATA" spec requires literal 4-char `\r\n` sequence, not actual CRLF — intentionally correct - `app_alerts.py` Authorization header: View tool masked `Bearer {token}` as `******` — actual code is correct +### Phase 24.6: Yaris 2013 OBD compatibility analysis & hardening — ✅ DONE +**Completed 2026-08-10.** Full compatibility analysis of every OBD PID against the 2013 Toyota Yaris (1.5L 1NZ-FE). Two real bugs fixed, deployed to Pi. + +#### PID compatibility verdict + +| PID | Command | OBD-II mandatory? | 2013 Yaris | Null-safe in code? | +|-----|---------|------------------|-----------|-------------------| +| 0x0D | SPEED | ✅ Yes | ✅ Yes | ✅ Yes | +| 0x0C | RPM | ✅ Yes | ✅ Yes | ✅ Yes | +| 0x05 | COOLANT_TEMP | ✅ Yes | ✅ Yes | ✅ Yes | +| 0x11 | THROTTLE_POS | ✅ Yes | ✅ Yes | ✅ Yes | +| 0x2F | FUEL_LEVEL | ⚠️ Optional | ✅ Likely | ✅ Yes | +| 0x01 | STATUS/MIL | ✅ Yes | ✅ Yes | ✅ Yes | +| Mode 3 | GET_DTC | ✅ Yes | ✅ Yes | ✅ Yes | +| 0xA6 | ODOMETER | ❌ Non-standard | ⚠️ Likely unsupported | ✅ Fixed (was broken) | + +**Odometer note:** PID 0xA6 is a Mode 01 extended PID added in SAE J1979 DA (2014). The 2013 Yaris predates this spec; Toyota's ECU exposure of 0xA6 is inconsistent even on newer models. Expect `odometer = None` permanently — the UI handles this gracefully. The risk was in the decoder raising an exception that killed the whole OBD session; now fixed. + +#### Protocol & connection +- **Protocol:** `OBD_PROTOCOL` env var defaults to `None` → python-obd sends `ATSP0` (auto-detect) → ELM327 will settle on ISO 15765-4 CAN 11-bit 500kbps, which is what the 2013 Yaris uses. ✅ +- **OBD_PROTOCOL=6 hint:** After first confirmed working connection on the real car, uncomment `#Environment=OBD_PROTOCOL=6` in `deploy/yaris-obd.service` to skip the 3–5s auto-probe on every ignition-on. +- **Port:** auto-detects `/dev/ttyUSB0` (first USB serial device). ELM327 USB adapters enumerate there. Can override with `OBD_PORT` env var. ✅ +- **Reconnect cycle:** ~10s (poll interval) + ~3–5s (ELM327 auto-probe) + ~2s (first poll) = up to ~17s from ignition-on to first live data. Acceptable. + +#### Motion detection & trip logic +- **Primary:** OBD speed (PID 0x0D, polled every 2s) via `get_fresh_speed(max_age=5)`. +- **Fallback:** GPS haversine displacement when OBD speed is stale/unavailable. +- **During OBD warmup:** `speed_updated_at is None` → `get_fresh_speed()` returns `None` → GPS fallback activates. No false trip start — idle GPS drift rows spaced 60s apart are filtered by `MIN_GPS_MOTION_SAMPLES=3`. +- **Trips are post-computed** from raw `tracking` rows — no real-time open/close state machine. Power cut mid-trip is safe; all committed rows are recovered on restart. ✅ + +#### Bugs fixed + +| File | Severity | Bug | Fix | +|---|---|---|---| +| `modules/app_obd.py` `_decode_odometer` | HIGH | Raised `ValueError` on unexpected byte count from unsupported/garbage 0xA6 response. Exception propagated through `_query()` and triggered `_mark_connection_lost()`, killing the entire OBD session on 2013 Yaris | Wrapped in `try/except Exception`, return `None` on any error | +| `modules/app_obd.py` `connect_obd` | MEDIUM | All sensor globals retained stale values from the previous ignition cycle — dashboard briefly shows wrong readings on reconnect | Reset all sensor globals to `None`/`[]` at start of each new connection | +| `deploy/yaris-obd.service` | INFO | No hint for pinning OBD protocol after initial test | Added commented `#Environment=OBD_PROTOCOL=6` with instructions | + ### 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 diff --git a/deploy/yaris-obd.service b/deploy/yaris-obd.service index 7cbf669..f0f6565 100644 --- a/deploy/yaris-obd.service +++ b/deploy/yaris-obd.service @@ -17,6 +17,9 @@ Environment=PYTHONUNBUFFERED=1 # ISO 15765-4 CAN 11-bit 500kbps (correct for 2013 Toyota Yaris). # Leave unset during initial setup so auto-detect can find the right value. #Environment=OBD_PROTOCOL=6 +# If the cheap ELM327 adapter reports bad voltage and fails to connect, +# set this to 1 to skip the voltage check (common with counterfeit ELM chips). +#Environment=OBD_SKIP_VOLTAGE_CHECK=1 ExecStart=/home/yaris/app/yaris-obd/venv/bin/python app.py Restart=on-failure RestartSec=5 diff --git a/modules/app_obd.py b/modules/app_obd.py index 805c57e..8b0efd7 100644 --- a/modules/app_obd.py +++ b/modules/app_obd.py @@ -186,15 +186,20 @@ def connect_obd(): return None protocol = os.environ.get("OBD_PROTOCOL") or None + # Some counterfeit ELM327 chips report bad/zero voltage even when the + # connection is working; set OBD_SKIP_VOLTAGE_CHECK=1 if the adapter + # fails to connect and logs a voltage-related error. + check_voltage = os.environ.get("OBD_SKIP_VOLTAGE_CHECK") != "1" selected_port = port or detected_ports[0] print( f"{obd_status[2]} to {selected_port}" + (f" using protocol {protocol}" if protocol else " using auto protocol") + + ("" if check_voltage else " (voltage check disabled)") + "..." ) try: - candidate = obd.OBD(portstr=selected_port, protocol=protocol) + candidate = obd.OBD(portstr=selected_port, protocol=protocol, check_voltage=check_voltage) except Exception as error: print( f"OBD adapter detected at {selected_port}, but the ELM327 "