Search code examples
iosbluetooth-lowenergyobd-ii

OBD2 BLE Communication - how to send AT commands and receive data


I have a OBD2 dongle and need to get the speed of a vehicle through it (using BLE and iOS device). The documentation that came with the dongle didn't mention services and characteristics but with some debugging I discovered a few. Let's call them service 1, 2, 3.

  1. Service 1 has one characteristic with Read property and WriteWithoutResponse property
  2. Service 2 has one characteristic with Read property and Notify property
  3. Service 3 has two characteristics: Characteristic A with Read property and Notify property and Characteristic B with Write property and WriteWithoutResponse property

How to know which characteristic/s I need to send AT commands and receive the corresponding data and then how to initiate this communication. I need to be able to get the speed in particular. Thank you in advance.

I have tried the following but nothing happened:

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {

    for characteristic in service.characteristics! {

        if characteristic.properties.contains(.notify) {
            peripheral.setNotifyValue(true, for: characteristic)
        }

        if characteristic.properties.contains(.write) {
            let commandString = "010D\r"
            if let commandData = commandString.data(using: .utf8) {
                peripheral.writeValue(commandData, for: characteristic, type: .withoutResponse)
                peripheral.writeValue(commandData, for: characteristic, type: .withResponse)
            }
        }

    }
}

And then expecting something in:

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {

    if let value = characteristic.value {
        let text = String(data: value, encoding: String.Encoding.utf8)
        self.onNotifyReceived(text)
    }

} 

Solution

  • First off, if you're talking via e.g., an ELM327 – which is among the most common OBD2 chips – you better not start with PIDs (like 010D), but rather initialize it with the proper AT command sequence (see the respective manual for details on this).

    Next, peripheralDidUpdateNotificationState is the wrong delegate method. This is triggered whenever you subscribe or unsubscribe to a characteristic, not whenever its value changes. You want to implement the delegate method peripheralDidUpdateValueForCharacteristic instead.

    That said, the trouble is: OBD2 BLE adapters don't use any fixed GATT profiles. The way most (if not all) BLE OBD2 adapters work, is that they offer one service with one or two characteristics:

    • A write characteristic. This one is where the mobile device can write its AT commands (in the case of, e.g., an ELM327) and PIDs to.
    • A notify characteristic. This is the one where the results from the car (ECUs) are returned.

    Once you have access to these characteristics, you can implement the OBD2 serial protocol (e.g. using a command queue that writes and waits for the response, before transmitting the finished command to the application layer).

    Some BLE adapters merge these two into one characteristic. If you want to support arbitrary adapters, you will have to add a 'select your adapter' screen where you probe the found adapters, remember the characteristics, and then communicate.

    That way it's possible to write apps that work with all kinds of BLE OBD2 adapters (and not only support a selected handful of vendors), such as OBD2 Expert (Disclaimer: I'm the author of that software).