# CarDash — OBD-II and temperature design note _Status: implemented in CarDash 0.6.0; OBD hardware still needs a real-car test. Last updated: 2026-10-29_ ## 1. Scope CarDash optionally reads live data from a Bluetooth Classic ELM327 adapter in the car's OBD-II port and shows it on the home page. When OBD does not provide an ambient temperature, an optional location-based weather fallback can fill the temperature row. ### Implemented values | Value | PID | Notes | |---|---|---| | Vehicle speed, km/h | `0D` | Digital home readout | | Throttle, % | `49`, fallback `11` | Horizontal bar | | MAF, g/s | `10` | Rolling fuel-economy input | | Ambient temperature, °C | `46` | OBD temperature row | The economy figure is a **5 km rolling average** in L/100 km. It uses petrol constants (stoichiometric AFR 14.7, 745 g/L), ignores MAF on a closed-throttle coast above 10 km/h, and shows `—` until at least 0.5 km has accumulated. ### Out of scope - RPM, coolant, module voltage, MIL/DTC contents, fuel level, range. - Mode `04`/`08` writes; CarDash is read-only. - Wi-Fi, USB and BLE adapters. - Cabin temperature. - Background OBD polling or background location. ## 2. Decisions | # | Decision | |---|---| | D1 | Bluetooth Classic SPP only; only devices already paired in Android Settings. No scan UI. | | D2 | Throttle uses accelerator `49`, falling back to plate `11`. | | D3 | Metric L/100 km only. | | D4 | Small live card above the volume card; temperature row below the date. | | D5 | Rolling window resets on disconnect or a sample gap over 30 s. | | D6 | No fuel level or range. | | D7 | OBD ambient wins over weather whenever it is present and fresh. | ## 3. Permissions | Feature | Manifest | Runtime | |---|---|---| | Bluetooth 29–30 | `BLUETOOTH`, `BLUETOOTH_ADMIN` (`maxSdkVersion="30"`) | install-time | | Bluetooth 31+ | `BLUETOOTH_CONNECT` | requested from the Car data card | | Weather | `INTERNET`, `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` | location requested when the weather toggle is enabled | There is no `BLUETOOTH_SCAN`. The app lists bonded devices only and requests Bluetooth permission from the Car data card before listing them. Location is one-shot. Coordinates and place names are held only in memory for the current weather value; they are not persisted. The adapter address and the weather toggle are persisted in `SettingsRepository`. ## 4. Architecture ``` Kotlin TelemetryBridge MethodChannel "com.wasp.cardash_launcher/telemetry" - listBondedDevices / isBluetoothEnabled / ensureBluetoothPermission - connectObd(mac) / disconnectObd / getObdState - ensureLocationPermission / getLocation (last-known or single fix + Geocoder) ObdService one worker thread over BluetoothSocket/RFCOMM SPP - handshake ATZ, ATE0, ATL0, ATS0, ATSP0 - request/response serialized, prompt-terminated - latest snapshot polled by Dart Dart NativeTelemetryBridge platform boundary ObdController connection lifecycle, 1 s snapshot polling, status ObdEconomy MAF/speed integrator with 5 km window WeatherController toggle, permission, one-shot location, weather cache WeatherService keyless Open-Meteo current temperature_2m TelemetryBridge models BluetoothDeviceInfo / LocationReading / ObdState SettingsRepository obd.adapterAddress, weather.locationTemperature ``` The native `ObdService` polls speed, throttle and MAF roughly once per second; ambient temperature is refreshed every 30 poll cycles. A snapshot carries `updatedAt`; the Dart side treats data older than five seconds as stale and hides the live card. Connection failures surface as a short Settings status and keep the dashboard quiet. ## 5. Home UI - **Temperature row** immediately below the date: - OBD: `Outside, 7°C` - Weather: `Sofia, 7°C` (or `Current location, 7°C`) - Red below 3°C, otherwise normal ink. - OBD ambient is always preferred. If OBD is disconnected or has no ambient value, the location weather fallback is used when enabled and available. - If OBD has no temperature and weather is off/unavailable, no row is shown. - **OBD card** in the left column above the media-volume card, only while fresh telemetry exists: ``` ● OBD-II LIVE 44 km/h Throttle 22% [========------------------] Rolling economy 7.5 L/100 km ``` - The card and row disappear cleanly when off/no adapter/no fresh data. The 16-button grid keeps its own space. ## 6. Settings — Car data - **OBD-II adapter**: dropdown containing `None` and paired Bluetooth Classic devices. `Find`/`Allow` requests Bluetooth permission and refreshes the list. - Connection status: Off / Connecting / Connected / waiting for data / error. - **Location temperature** toggle: “Show temperature from current location if no OBD-II connected.” Enabling it asks for location permission. - Read-only note: CarDash never writes to the ECU. Selecting a device persists its MAC address and connects immediately when the app is active. Selecting `None` disconnects. Polling stops when CarDash leaves the foreground and reconnects on resume. ## 7. Weather fallback `WeatherController`: 1. Requires the toggle to be enabled and location permission granted. 2. Gets one location fix through `TelemetryBridge.getLocation`. 3. Reverse-geocodes a display name with Android `Geocoder`. 4. Queries Open-Meteo: `https://api.open-meteo.com/v1/forecast?...¤t=temperature_2m&timezone=auto` 5. Refreshes every 15 minutes while enabled. Any failure clears the temperature and hides the row. No API key, account or coordinates are stored. ## 8. Failure and freshness - Missing PID → null, never zero. - OBD data older than 5 s → stale; hide live card and ambient. - Ambient `-40`/`215` sentinel values are rejected. - Weather failure → no row. - Permission denial is a normal state; the Settings card shows the error and offers the relevant Allow action. - The OBD status is visible only in Settings; the dashboard stays quiet. ## 9. Testing - Dart unit tests cover the rolling economy math and the OBD card. - Settings persistence covers the adapter address and weather toggle. - The API 35 emulator verifies the location permission path, reverse geocoding, Open-Meteo fetch and the temperature row (`Ukiah, 17°C` during implementation). - The Android bridge compiles for API 29/35. - **Not yet verified:** a real ELM327 socket, PID responses, clone timing and reconnect behavior in a car. This is the main remaining soak test. ## 10. Decision log - 2026-10-29 — Classic SPP, bonded-only, read-only subset: speed, throttle, MAF economy, ambient. - 2026-10-29 — Added Open-Meteo/location fallback at the user's request; INTERNET and location permissions accepted for this feature only. - 2026-10-29 — Implemented in 0.6.0 with Car data Settings, live OBD card and temperature row; hardware verification deferred to a car.