Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
    }
}
}
like image 898
Daniel Burton Avatar asked Feb 04 '23 23:02

Daniel Burton


2 Answers

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)!)
    }
}
}
like image 75
Daniel Burton Avatar answered Feb 07 '23 11:02

Daniel Burton


If you find the assigned service on https://www.bluetooth.com/specifications/gatt/services they you can follow the service link to get information on the characteristic(s). From the service description, the characteristic link can be followed for more info.

For example: The 0x1804 service is for transmit power level. It is a read-only service providing a org.bluetooth.characteristic.tx_power_level characteristic which, if linked to, can be seen to provide a signed 8 bit db level between -100 and 20.

Another example: Service 0x1811 is for the alert notification service. It provides 5 separate characteristics, org.bluetooth.characteristic.supported_new_alert_category, org.bluetooth.characteristic.new_alert, org.bluetooth.characteristic.supported_unread_alert_category, org.bluetooth.characteristic.unread_alert_status, and org.bluetooth.characteristic.alert_notification_control_point. The service description gives a short explanation for each characteristics and links to further details on the values for that characteristic. These characteristics probably don't make sense, and are likely not supported, if your device is not capable of messaging via voice, SMS, IM or email.

like image 21
CuriousRabbit Avatar answered Feb 07 '23 13:02

CuriousRabbit