OBD_TEMPERATURE.md 10 KB

CarDash — OBD-II and temperature design note

Status: updated in CarDash 1.0.6; OBD hardware still needs a real-car test. Last updated: 2026-10-31

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. Settings has separate OBD-II, Speed and Temperature cards. Speed can be hidden, sourced from OBD-II or GPS, and temperature can be hidden, sourced from OBD-II ambient or GPS + the Open-Meteo weather provider.

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 speed 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 while the OBD temperature source is selected.
D8 Speed and temperature each have an independent show/hide switch in Settings.
D9 Source switches appear only while an OBD-II adapter is selected; GPS/weather is the fallback.
D10 Temperature starts off on fresh installs; upgrades that already used OBD or weather keep it on.
D11 The picker defaults to devices that look like Bluetooth Classic OBD-II adapters; Show all paired devices is the explicit escape hatch.
D12 Every Bluetooth binder call runs on one serialized native worker, never on the MethodChannel/main thread.
D13 Connection attempts stop after three failures; the card offers Retry connection. There is no infinite reconnect loop.

3. Permissions

Feature Manifest Runtime
Bluetooth 29–30 BLUETOOTH, BLUETOOTH_ADMIN (maxSdkVersion="30") install-time
Bluetooth 31+ BLUETOOTH_CONNECT requested from the OBD-II card
Weather INTERNET, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION location requested when temperature/weather is enabled

There is no BLUETOOTH_SCAN. The app lists bonded devices only and requests Bluetooth permission from the OBD-II 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, speed and temperature visibility, source choices and weather preference are persisted in SettingsRepository.

4. Architecture

Kotlin
  TelemetryBridge  MethodChannel "com.wasp.cardash_launcher/telemetry"
    - bluetoothState / listBondedDevices / ensureBluetoothPermission
    - connectObd(mac) / disconnectObd / getObdState
    - ensureLocationPermission / getLocation (last-known or single fix + Geocoder)
    - all Bluetooth binder work runs on the cardash-bluetooth worker
  ObdService       one worker thread over BluetoothSocket/RFCOMM SPP
    - handshake ATZ, ATE0, ATL0, ATS0, ATSP0
    - request/response serialized, prompt-terminated
    - 12 s connect watchdog, three attempts, then retryExhausted

Dart
  NativeTelemetryBridge  platform boundary
  ObdController          connection lifecycle, 1 s snapshot polling, status
  ObdEconomy             MAF/speed integrator with 5 km window
  TelemetrySettingsController
                         persisted speed/temperature visibility and source
  WeatherController      permission, one-shot location, 15 min weather cache
  WeatherService         keyless Open-Meteo current temperature_2m
  TelemetryBridge models BluetoothDeviceInfo / LocationReading / ObdState
  SettingsRepository     adapter address, speed/temperature flags, weather

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. A failure after three attempts sets retryExhausted; the card then shows Retry connection and the controller does not auto-retry on the next resume.

5. Home UI

  • Temperature row immediately below the date, only while the Temperature switch is on:
    • OBD source: Outside, 7°C
    • Weather source: Sofia, 7°C (or Current location, 7°C)
    • Red below 3°C, otherwise normal ink.
    • OBD ambient is preferred while its source switch is on. 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.
  • Speed card in the left column above the media-volume card, only while the Speed switch is on and a fresh source value exists:

    ● OBD-II                         LIVE
    44 km/h
    Throttle                     22%
    [========------------------]
    Rolling economy        7.5 L/100 km
    

When the GPS source is selected (or OBD is unavailable), the card reads GPS / FROM LOCATION with the GPS speed instead.

  • The card and row disappear cleanly when off/no adapter/no fresh data. The 16-button grid keeps its own space.

6. Settings cards

OBD-II

  • Adapter dropdown containing None and paired Bluetooth Classic devices that look like OBD-II adapters (known name, SPP UUID, or a plausible major class). Show all paired devices reveals phones, headsets and other bonded devices.
  • Find/Allow requests Bluetooth permission and refreshes the list.
  • Connection status: Off / starting / turning off / unavailable / Connecting / Connected / waiting for data / error.
  • Bluetooth-off action: Open Bluetooth settings. On return, the controller checks the adapter state and reuses the cached list; it does not run a bond query on every resume.
  • After three failed connection attempts: Retry connection.
  • Read-only note: CarDash never writes to the ECU.
  • This card contains adapter settings only.

Speed

  • Show/hide switch for the speed card.
  • While an OBD-II adapter is selected: Use OBD-II for speed. Off selects GPS.
  • The source switch is hidden while no adapter is selected; GPS is then used.

Temperature

  • Show/hide switch for the temperature row.
  • While an OBD-II adapter is selected: Use OBD-II for temperature. Off selects GPS + Open-Meteo weather.
  • Enabling temperature or choosing the weather source asks for location permission. OBD temperature still works if permission is denied.

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 Temperature switch to be on 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?...&current=temperature_2m&timezone=auto

  1. 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 Temperature card shows the weather error while OBD temperature can still work.
  • Three consecutive connect/handshake failures stop the worker and expose Retry connection; this prevents an absent adapter from keeping the Bluetooth stack in a connect loop.
  • Bluetooth power transitions show a status instead of listing devices while the adapter is still turning on or off.
  • The OBD status is visible only in Settings; the dashboard stays quiet.

9. Testing

  • Dart unit tests cover the rolling economy math, the OBD card, adapter classification/filtering, bounded-retry state, resume caching and the speed/temperature preference controller.
  • Settings persistence covers the adapter address, visibility switches, source choices and weather preference.
  • 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.
  • 2026-10-30 — Split Settings into OBD-II, Speed and Temperature cards, added independent visibility toggles and OBD/GPS-weather source switches.
  • 2026-10-30 — Retired the two-minute process-start onboarding hint in favor of a ten-minute first-install window that never returns after an upgrade.
  • 2026-10-31 — After a freeze that followed a Bluetooth settings return, moved all Bluetooth binder calls to a serialized worker, filtered the adapter picker, bounded connection attempts to three, added a 12 s connect watchdog and made resume reuse the cached bonded list.