AutoTach is an Android Auto app that turns your car's infotainment screen into a live, color-coded tachometer. It connects to an OBD-II Bluetooth adapter, reads engine RPM directly off the CAN bus, and renders a full-screen tach on the dashboard display that shifts color in real time as the revs climb. Green at idle, amber through the powerband, red near redline.
Built and tested against the Carista EVO adapter (ELM327 v2.2 compatible), but the parser is generic and works with any standards-compliant ELM327 device.
IMG_0388.MOV
IMG_0391.MOV
- Android Auto integration via
CarAppService+Session+Screenwith aNavigationTemplate, so the app renders full-screen on the head unit while the driver is moving — hosted through the Android for Cars App Library (androidx.car.app). - Custom Canvas rendering to the car's
Surfacedriven by aSurfaceCallback— segmented RPM and speed bars with per-segment HSV interpolation (green → amber → red) - MVVM — an
ObdRepositoryacts as the apps 'Model' or dat layer, exposingStateFlows that can be read directly on the car service side (since it's not an activity), and read on the phone side through a Viewmodel class that wraps the repository state and methods in an activity-safe manner. Finally the phoneFragments and the Android AutoScreenact as views that simply observe. - Single-Activity UI with
Fragments (MainFragment,ConnectFragment), hosted by a single main activity - Live OBD-II data parsed from an ELM327 BLE adapter (engine RPM
010C, speed010D), with multi-frame byte reassembly and full ELM327 error-string handling — see Architecture and The Protocol Stack below.
The app follows MVVM with a clean three-layer separation underneath the ViewModel. Each layer owns one protocol concern and knows nothing about the layers above it. The phone UI (for setup / pairing) and the Android Auto Screen (for the in-car tach display) are both thin observers of the same ViewModel state:
Head unit ◀── renders ─── CarAppService / Screen ┐
│ observes
Phone UI ──── observes ───────────────────────── ▶ ViewModel ── owns ──▶ ObdReader ── owns ──▶ BluetoothCommunicator
OBD-II / PIDs BLE GATT + ELM327
BluetoothCommunicator(transport): owns theBluetoothGatt, runs the GATT callbacks, enables CCCD notifications, sends raw AT command strings, and exposes asuspend fun sendCommand(cmd: String): String. It has no idea what a PID is.ObdReader(domain): wraps the communicator and exposessuspend fun getRpm()/getSpeed(). All hex parsing, error detection, and PID formulas live here. It has no idea what GATT is.ObdViewModel(presentation): owns theObdReader, exposesStateFlows for both the phone UI and the Android AutoScreento observe, and survives configuration changes.
Between the Bluetooth connection, the PID-polling loop, and two UIs that redraw on every state change, there's a lot going on off the main thread. Kotlin coroutines and StateFlow are what keep it coordinated.
StateFlowas the source of truth — RPM, speed, connection status, and the device list are allStateFlows. New subscribers immediately get the latest value.repeatOnLifecycle(STARTED)on the phone side, so Fragment collectors pause with the view and never leak past it.suspend fun sendCommandwraps the callback-based BLE GATT API in a linear one — the caller writesval response = sendCommand("010C")and awaits the parsed response- ELM327 init handshake as a coroutine — after notifications are enabled,
scope.launch { performHandshake() }sends the 7-command init sequence (ATZ,ATE0,ATL0, …) one after another usingsendCommand, so a serial protocol reads as a top-to-bottom loop instead of a callback chain.
- Language: Kotlin
- Android Auto: Android for Cars App Library (
androidx.car.app) - Architecture: MVVM with
AndroidViewModel,Fragment,ViewBinding - Bluetooth: Android BLE GATT API directly — no
RxAndroidBle, no Nordic wrapper - Min SDK: 24 / Target SDK: 35 / JVM: 17
git clone https://github.com/hobbbbies/autotach.git
cd autotachOpen the project in Android Studio (Giraffe or newer). Gradle sync will pull dependencies.
BLE doesn't work in the emulator. You'll need a real Android device and an ELM327-compatible OBD-II adapter (Carista EVO, OBDLink, generic clone, etc.) plugged into a car's OBD-II port.
To test the Android Auto experience without leaving the driveway, install the Desktop Head Unit (DHU) from the Android SDK — it simulates a car's head unit on your desktop and renders the in-car UI live.