Auto Whisperer: Decoding Car Conversations with OBD

Saunik Singh
CARS24 Engineering Blog
5 min readMar 12, 2024

Rev up your engines, fellow tech petrolheads! In today’s blog post, we’re diving into the fascinating world of On-Board Diagnostics II (OBD II) implementation using the Android Kotlin language. Buckle up as we explore how to establish a Bluetooth connection, tap into those PID’s (Parameter IDs), and unlock a treasure trove of car data right from your Android device.

  1. Bluetooth Boogie: We’ll kick things off by syncing up our Android device with the OBD II interface via Bluetooth. It’s like a digital handshake between your smartphone and your car’s brain.
  2. PID Party: Ever wondered what’s going on under the hood? With OBD II in Android Kotlin, we’ll decode those PID’s like a seasoned detective. From engine RPM to coolant temperature, we’re decoding the language of your car’s inner workings.
  3. Driving Insights: Whether you’re a gearhead or just want to keep tabs on your ride, OBD II implementation in Kotlin empowers you with real-time insights. Detect malfunctions, monitor performance, and even impress your friends with your newfound automotive prowess.

In this guide, we’ll navigate the twists and turns of establishing a Bluetooth connection — because let’s face it, pairing devices can sometimes feel like navigating rush hour traffic. Once we’ve conquered that hurdle, we’ll shift gears and delve into the exhilarating world of PID decoding. Think of it as cracking the code to your car’s secret language — who knew those jumbles of letters and numbers held the key to unlocking a wealth of information?

So whether you’re a seasoned developer looking to expand your skill set or a newcomer eager to dive headfirst into the world of automotive diagnostics, this blog post is your roadmap to success. Strap in, rev those engines, and let’s embark on this coding adventure together. The open road of OBD II ELM 327 implementation awaits — and with Kotlin by our side, the possibilities are endless. Let’s drive! 🚗💨

The first step is to establish a Bluetooth connection. This involves pairing our Android device with the OBD II interface. Once paired, we can start reading and writing PID’s (parameter identification data). PID’s are unique identifiers associated with various systems in a car. By reading PID’s, we can gather information about the engine, fuel consumption, emissions, and much more.

An example of connecting an OBD Bluetooth device

private fun discoverAndConnect() {
if (ActivityCompat.checkSelfPermission(this,Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
requestBluetoothPermissions()
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
//to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
pairedDevices?.forEach { device ->
//Adjust the device name as needed
if (device.name == "OBDII") {
try {
//Create a Bluetooth socket to connect to the device
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("<Your Device UUID>"))
// Connect to the socket
socket?.connect()
// Get the input and output streams from the socket
outputStream = socket?.outputStream
inputStream = socket?.inputStream
} catch (e: IOException) {
Log.e(TAG, "Error connecting to OBD-II device: ${e.message}")
}
}
}
}

Reading PID’s in real-time allows us to monitor the car’s performance on our Android device. We can track important metrics like engine RPM, vehicle speed, coolant temperature, DTC, and throttle position. This information can be displayed in a user-friendly manner, making it easier to understand and analyse.

inputStream?.let {
val inputStreamReader = BufferedReader(InputStreamReader(inputStream))
val res = StringBuilder()
var c: Char
var b = inputStreamReader.read()
while (b > -1) { // -1 if the end of the stream is reached
c = b.toChar()
if (c == '>') { // read until '>' arrives
break
}
res.append(c)
b = inputStreamReader.read()
}
val data = res.toString().trim { it <= ' ' }
data
}
} ?: "Data Not Captured!",

Writing PID’s is also possible with the OBD II implementation in Android Kotlin. This enables us to interact with the car’s systems and perform specific actions. For example, we can turn on/off the check engine light or reset certain error codes.

Similar to the example below, we are requesting the (Show stored diagnostic trouble codes by sending PID 03 to the OBD device.

outputStream?.let {
it.write("03\r".toByteArray())
it.flush()
}

Apart from reading and writing PID's, the OBD II implementation in Android Kotlin allows us to identify any malfunctions or issues in the car. By retrieving diagnostic trouble codes (DTC’s), we can pinpoint the exact problem and take appropriate actions. This can save time and money by avoiding unnecessary visits to the mechanic. The PIDs listed below are used to identify DTC.

  1. PID 03: Show stored Diagnostic Trouble Codes
  2. PID 07: Show pending Diagnostic Trouble Codes (detected during current or last driving cycle)
  3. PID 0A: Permanent Diagnostic Trouble Codes (DTCs) (Cleared DTCs)

A sample of a DTC hexadecimal answer that was received and translated to a human-readable format is provided.

OBD provided the following: Hex Code: 43 02 00 31 01 08

The reasoning for the computation is shown below.

BitsDefinition A7-A6 Category

00: P — Powertrain

01: C — Chassis

10: B — Body

11: U — Network A5-B0Number (within category)

Here

  • “43” is likely the identifier or header of the message.
  • “02” could represent the length of the data payload (the number of bytes following).
  • “00” and “31” are data bytes.
  • “01” and “08” are also data bytes

Consequently, the real, human-readable outcome is

  1. P0031 (“HO2S Heater Control Circuit Low”)
  2. P0108 (Manifold Absolute Pressure/Barometric Pressure Circuit High Input)

In a similar vein As of right now, 3743 DTCs have been diagnosed overall. All {P (Powertrain), C (Chassis), B (Body), U (Network)} are included in this.

Each trouble code requires 2 bytes to describe. Encoded in these bytes are a category and a number. It is typically shown decoded into a five-character form like “P0108”, where the first character (here ‘P’) represents the category the DTC belongs to, and the remaining four characters are a hexadecimal representation of the number under that category. The first two bits (A7 and A6) of the first byte (A) represent the category. The remaining 14 bits represent the number. Of note is that since the second character is formed from only two bits, it can thus only be within the range 0–3.

In a similar vein, PIDs 05 and 06 can be used for component and system monitoring.

Furthermore, the OBD II implementation can provide valuable specifications about the car. We can retrieve information such as the VIN (Vehicle Identification Number), vehicle make and model, manufacturing year, and supported protocols. This information can be useful when buying a used car or verifying vehicle details.

In conclusion, implementing OBD II in Android Kotlin is a fascinating endeavor. It empowers us to establish a Bluetooth connection, read and write PID’s, identify malfunctions, and access valuable car specifications. Whether you are a car enthusiast, a mechanic, or someone who wants to monitor their vehicle’s performance, this implementation is worth exploring. So, grab your Android device and dive into the world of OBD II in Android Kotlin!

--

--