Search code examples
iosbluetoothbluetooth-lowenergycore-bluetoothobd-ii

How to use BLE OBDII Peripheral


I'm trying to create an iOS app that will read from an OBDII Bluetooth 4 (LE) device. I purchased a Vgate icar3 bluetooth(4.0) elm327 OBDII. I plug it into my car and find IOS-VLink peripheral that advertises some services. I can then get the characteristics for those services. These are the services I find:

<CBService: 0x1780729c0, isPrimary = YES, UUID = Battery>
<CBService: 0x178072a80, isPrimary = YES, UUID = 1803>
<CBService: 0x178072ac0, isPrimary = YES, UUID = 1802>
<CBService: 0x178072b00, isPrimary = YES, UUID = 1811>
<CBService: 0x178072b40, isPrimary = YES, UUID = 1804>
<CBService: 0x178072b80, isPrimary = YES, UUID = 18F0>
<CBService: 0x178072bc0, isPrimary = YES, UUID = Device Information>
<CBService: 0x178072c00, isPrimary = YES, UUID = E7810A71-73AE-499D-8C15-FAA9AEF0C3F2>

But I have no idea what the 1803, 1802, 1811, 1804 and 18F0 services are or how to use them. Here is my simple program to find out what is being advertised.

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {

var centralManager = CBCentralManager()
var peripherals = [CBPeripheral]()
@IBOutlet weak var outputTextView: UITextView!

override func viewDidLoad() {
    super.viewDidLoad()
    centralManager.delegate = self
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    print("connected to \(peripheral.name ?? "unnamed")")
    peripheral.delegate = self
    peripheral.discoverServices(nil)
}

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    if central.state == .poweredOn {
        central.scanForPeripherals(withServices: nil, options: nil)
    }
}

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    if peripheral.name == "IOS-Vlink" {
        peripherals.append(peripheral)
        print(peripheral.name ?? "peripheral has no name")
        print(peripheral.description)
        central.connect(peripheral, options: nil)
        central.stopScan()
    }

}

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    guard let services = peripheral.services else {
        return
    }
    for service in services {
        print(service.description)
        peripheral.discoverCharacteristics(nil, for: service)
    }
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    guard let chars = service.characteristics else {
        return
    }
    for char in chars {
        print(char.description)
    }
}
}

Solution

  • I figured it out. The "E7810A71-73AE-499D-8C15-FAA9AEF0C3F2" service has a characteristic with a uuid of "BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F". If you write an AT command to that characteristic then it calls peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) and then get the results in the value property. Here is the code sending a simple ATZ command. At this point it is simply using the correct OBDII ELM327 commands.

    class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
    
    var centralManager = CBCentralManager()
    var peripherals = [CBPeripheral]()
    @IBOutlet weak var outputTextView: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        centralManager.delegate = self
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("connected to \(peripheral.name ?? "unnamed")")
        peripheral.delegate = self
        peripheral.discoverServices([CBUUID(string:"E7810A71-73AE-499D-8C15-FAA9AEF0C3F2")])
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            central.scanForPeripherals(withServices: nil, options: nil)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if peripheral.name == "IOS-Vlink" {
            peripherals.append(peripheral)
            print(peripheral.name ?? "peripheral has no name")
            print(peripheral.description)
            central.connect(peripheral, options: nil)
            central.stopScan()
        }
    
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else {
            return
        }
        for service in services {
            peripheral.discoverCharacteristics([CBUUID(string:"BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F")], for: service)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let chars = service.characteristics else {
            return
        }
        guard chars.count > 0 else {
            return
        }
        let char = chars[0]
        peripheral.setNotifyValue(true, for: char)
        peripheral.discoverDescriptors(for: char)
    
        print (char.properties)
        peripheral.writeValue("ATZ\r\n".data(using: .utf8)!, for: char, type: CBCharacteristicWriteType.withResponse)
    
        peripheral.readValue(for: char)
        if let value = char.value {
            print(String(data:value, encoding:.utf8) ?? "bad utf8 data")
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let value = characteristic.value {
            print(String(data:value, encoding:.utf8)!)
        }
    }
    func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
        print(characteristic.descriptors ?? "bad didDiscoverDescriptorsFor")
    }
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print(error)
        }
        print("wrote to \(characteristic)")
        if let value = characteristic.value {
            print(String(data:value, encoding:.utf8)!)
        }
    }
    }