Last Updated: October 03, 2020
·
3.923K
· nozomuplus

Accessing multiple BLE services and characteristics using RxBluetoothKit

Using RxBluetoothKit is a good way to accessing Bluetooth device, especially accessing multiple services/characteristics.

Goal

(1) read CharacteristicA1 of ServiceA
(2) read CharacteristicB1 of ServiceB
(3) subscribe CharacteristicB2 of ServiceB, and get notify as same times as the value got in (2)

Environment

Xcode: 10.1
Swift: 4.2.1
RxBluetoothKit: 5.1.4

RxBluetoothKit

https://github.com/Polidea/RxBluetoothKit

SourceCode

1. Define Services

enum BLEService: String, ServiceIdentifier {
    case serviceA = "UUID of ServiceA"
    case serviceB = "UUID of ServiceB"

    var uuid: CBUUID {
        return CBUUID(string: self.rawValue)
    }
}

2. Define Chracteristics

enum BLECharacteristic: String, CharacteristicIdentifier {
    case charA1 = "UUID of CharacteristicA1"
    case charB1 = "UUID of CharacteristicB1"
    case charB2 = "UUID of CharacteristicB2"

    var uuid: CBUUID {
        return CBUUID(string: self.rawValue)
    }

    var service: ServiceIdentifier {
        switch self {
        case .charA1:
            return BLEService.serviceA
        case .charB1, .charB2:
            return BLEService.serviceB
        }
    }
}

3. Scan and Connect to BLE Device

I set 3 seconds timeouts in order to handle error, when the bluetooth is off or the central cannot connect to the peripheral.
I adopted take(1), in order to limit powered on event and device to connect to 1 each.
*In iOS 11 and 12, Control Center cannot kill the bluetooth, but switches to disconnected state.
In that state, no alert is shown even if CBCentralManagerOptionShowPowerAlertKey is true, but we can catch as a timeout of powered on event.

func connect() -> Observable<Peripheral> {
    let options = [CBCentralManagerOptionShowPowerAlertKey: true] as [String : AnyObject]
    let manager = CentralManager(queue: .main, options: options)

    return manager.observeState()
        .startWith(self.manager.state)
        .filter { $0 == .poweredOn }
        .timeout(3.0, scheduler: MainScheduler.instance)
        .take(1)
        .flatMap { _ in self.manager.scanForPeripherals(withServices: [BLEService.serviceA.uuid, BLEService.serviceB.uuid]) }
        .timeout(3.0, scheduler: MainScheduler.instance)
        .take(1)
        .flatMap { $0.peripheral.establishConnection() }
}

4. Read from Multiple Services/Characteristics

What to do here are:
(1) read CharacteristicA1 of ServiceA
(2) read CharacteristicB1 of ServiceB
By using Observable.concat, I can read from multiple services/characteristics.
*Omitted parsing values here

func readData() {
    connect()
        .flatMap {
            Observable.concat(
                $0.readValue(for: BLECharacteristic.charA1).asObservable(),
                $0.readValue(for: BLECharacteristic.charB1).asObservable()
            )}
        .subscribe(onNext: { (char) in
            switch char.uuid {
            case BLECharacteristic.charA1.uuid:
                let data = char.value
            case BLECharacteristic.charB1.uuid:
                let data = char.value

                // call get notify method
                getNotify(char: char, notifyCount: Int(data))

        }, onError: { (error) in
            // error handling
        }, onCompleted: {
        }, onDisposed: {
        })
}

5. Get Notify According to the Result of Read

What to do here is:
(3) subscribe CharacteristicB2 of ServiceB, and get notify as same times as the value got in (2)
I call this method in onNext in "4. Read from Multiple Services/Characteristics".

func getNotify(char : Characteristic, notifyCount : Int) {
    char.service.discoverCharacteristics([BLECharacteristic.charB2.uuid]).asObservable()
        .flatMap { Observable.from($0) }
        .flatMap { $0.observeValueUpdateAndSetNotification() }
        .take(notifyCount)
        .subscribe(onNext: { (char) in
            let data = char.value
        }, onError: { (error) in
            // error handling
        }, onCompleted: {
        }, onDisposed: {
        })
}

6. Dispose Observable to Prevent Memory Leak

If I take(n), observable will be disposed automatically after getting value n times.
I implemented take(n) in "5. Get Notify According to the Result of Read", but not in "4. Read from Multiple Services/Characteristics".
That's because if I implement in "4. Read from Multiple Services/Characteristics", the bluetooth connection will be disposed before "5. Get Notify According to the Result of Read", and I'll get an error.

But in that way, the observable will not be disposed and it will cause a memory leak.

My solution toward this problem is keeping observable as a member variable and to dispose it in the next access.

func readData() {

    if let d = disposable {
        d.dispose()
    }

    disposable =
    connect()
        .flatMap ... // see "4. Read from Multiple Services/Characteristics"
}

1 Response
Add your response

I found later that I should dispose also after getting values and in error handling.

over 1 year ago ·