Problem: How to Get Real Vehicle Condition via OBD-II
Fleet vehicle owners often face this: the car runs, but fuel consumption suddenly spikes, or the Check Engine light comes on while the nearest service is 500 km away. Monitoring via OBD-II gives a complete picture, but implementing an app runs into numerous nuances: adapter protocols, background polling on Android and iOS, DTC decoding. We have been solving these tasks for 5+ years and have accumulated over 50 projects.
An OBD-II port exists in every car manufactured since the mid-1990s. Through it, an ELM327-compatible adapter (Viecar, KONNWEI, Veepeak) via Bluetooth or Wi-Fi sends PID requests — and the app receives engine RPM, load, coolant temperature, speed, battery voltage, and DTC error codes. The task sounds simple, but implementation hits several non-trivial points.
What Data Can Be Obtained via OBD-II?
The SAE J1979 standard defines Mode 01 (current data) and Mode 03 (error codes). Not all PIDs are supported by all vehicles — first request PID 0x00 (supported PIDs 01-20), then 0x20, 0x40, 0x60 to build a map of available parameters.
| PID | Parameter | Formula |
|---|---|---|
| 0x04 | Engine load | A * 100 / 255 % |
| 0x05 | Coolant temperature | A - 40 °C |
| 0x0C | Engine RPM | (256*A + B) / 4 RPM |
| 0x0D | Speed | A km/h |
| 0x11 | Throttle position | A * 100 / 255 % |
| 0x42 | Battery voltage | (256*A + B) / 1000 V |
| 0x5E | Fuel consumption | (256*A + B) / 20 L/h |
Error codes (Mode 03) return a list of DTCs in a 2-byte format per code. The first two bits define the system: 00 — engine (P), 01 — transmission (P1xxx), 10 — chassis (C), 11 — body (B). Decoding codes into readable descriptions requires a database — open-source options: CSV from the hfreire/ecu-can-bus-decoder repository or a paid database from OBD Solutions.
What Errors Occur When Working with ELM327 and How to Avoid Them?
ELM327 adapters communicate via AT commands over a serial port. Connection via Classic Bluetooth — BluetoothSocket on Android with UUID 00001101-0000-1000-8000-00805F9B34FB (SPP profile). On iOS, Classic Bluetooth is closed for third-party apps; the only path is BLE ELM327 adapters (Viecar EA400-P, OBDLink CX) via Core Bluetooth.
The most common error when working with ELM327 is sending the next PID request without waiting for the > (prompt) in the response. The adapter buffers commands unpredictably, and instead of the RPM value, the app receives ? or NO DATA. The correct polling cycle is sequential, with a timeout for expecting the prompt of ~200 ms:
class OBDConnection(private val socket: BluetoothSocket) { private val input = socket.inputStream.bufferedReader() private val output = socket.outputStream suspend fun sendCommand(command: String): String = withContext(Dispatchers.IO) { output.write("$command\r".toByteArray()) val sb = StringBuilder() var char: Int while (input.read().also { char = it } != -1) { val c = char.toChar() sb.append(c) if (c == '>') break } sb.toString().trim().removeSuffix(">").trim() } suspend fun readPID(mode: String, pid: String): String { return sendCommand("$mode$pid") } } Initialization of the adapter before polling is mandatory: ATZ (reset), ATE0 (disable echo), ATL0 (no line feeds), ATSP0 (auto protocol selection). Without ATE0, parsing responses is significantly harder — the command returns in the stream along with the response.
Using coroutines with asynchronous timeouts reduces the total polling cycle time by 2 times compared to blocking threads, especially when polling 15+ PIDs.
How Does the Development Process Work?
We approach the project systematically: first, we analyze requirements and compatibility with the client's vehicles, design the architecture, then implement with Kotlin/Android or Swift/iOS. After testing on real adapters, we publish the app to the stores. The entire process includes:
- Analytics and compiling a PID map for target vehicles
- Designing modules: OBD connection, database, notifications
- Implementation using Jetpack Compose or SwiftUI
- Testing on physical adapters (multiple models)
- Deployment to Google Play and App Store
App Architecture
On Android — a foreground service with a low-priority notification (otherwise Android 8+ kills the process after a few minutes). The Service manages the connection to the adapter and the polling loop; the UI subscribes via StateFlow. On iOS — foreground-only, since Core Bluetooth works in the background only for Heart Rate and some other profiles; polling only runs while the screen is active.
| Parameter | Android | iOS |
|---|---|---|
| OBD-II connection | Classic Bluetooth (SPP) | BLE (only BLE adapters) |
| Background polling | Foreground service | Only with active screen |
| Push notifications | Firebase | APNs |
| Tools | Kotlin, Jetpack Compose | Swift, SwiftUI |
Polling frequency: RPM and speed — every 100-200 ms, temperature and fuel consumption — every 1-2 seconds. Do not poll all PIDs at the same frequency — this overloads the adapter and noticeably slows down the CAN bus. For push notifications about approaching service, we use calendar and odometer.
What's Included in Our Work
We deliver the project turnkey: source code, architecture and connection documentation, user instructions, and assistance with store publication. We provide a 3-month warranty on bug fixes after delivery.
Additionally: TPMS and Cabin Camera
Tire pressure sensors (TPMS) in most cars work through a separate radio frequency protocol (315/433 MHz) and are not accessible via OBD-II. For monitoring them, external BLE sensors (e.g., Meneea, Fobo Tire Plus) that attach to the valve and transmit pressure and temperature are required. They are integrated via standard Core Bluetooth / Android BLE API.
A basic mobile application with ELM327 connection, monitoring 10-15 PIDs, and reading DTCs: 3-4 weeks. A full-featured app with trip history, geolocation, fuel consumption calculation, and TPMS: 6-8 weeks. The cost is calculated individually after clarifying target platforms and the list of supported parameters. Contact us to discuss your project and get a preliminary estimate.
Get a consultation on fleet monitoring — we'll help you choose the optimal set of sensors and adapters.







