LoginSignup
0
0

More than 5 years have passed since last update.

RxBluetoothKitでBLEデバイスの複数サービス/キャラクタリスティックにアクセスする

Last updated at Posted at 2018-12-17

複数のBLEサービス/キャラクタリスティックにアクセスする必要があり、RxBluetoothKitを使って実現しました。

やりたいこと

以下のことを実行する必要がありました。
(1) ServiceA の CharacteristicA1 をread
(2) ServiceB の CharacteristicB1 をread
(3) ServiceB の CharacteristicB2 をsubscribeし、(2)の回数分だけ飛んでくるnotifyを受け取る

環境

Xcode: 10.1
Swift: 4.2.1
RxBluetoothKit: 5.1.4

RxBluetoothKit

ソースコード

1. Serviceの定義

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

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

2. Chracteristicの定義

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

    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. BLEデバイスのスキャン・接続

電源OFF・接続できないときにエラーとして返すため、タイムアウト3秒を設定しました。
またpoweredOn状態になったイベント、接続先デバイスを各1つに限定するために、take(1)を入れています。
※iOS11, 12では、Control CenterからBluetoothが完全オフにはできなくなり、「未接続」状態へ切り替えるようになっています。
その状態では CBCentralManagerOptionShowPowerAlertKey を設定していてもアラートは出ません。
poweredOn状態にもならないため、そのタイムアウトとして捕捉できます。

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

次にこれらを行います。
(1) ServiceA の CharacteristicA1 をread
(2) ServiceB の CharacteristicB1 をread
Observable.concat で複数の処理をまとめることができます。
※受け取りデータの変換処理は省略

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

                // notify受け取りの呼び出し
                getNotify(char: char, notifyCount: Int(data))

        }, onError: { (error) in
            // エラー処理
        }, onCompleted: {
        }, onDisposed: {
        })
}

5. readの結果に基づいたnotify受け取り

これを行います。
(3) ServiceB の CharacteristicB2 をsubscribeし、(2)の回数分だけ飛んでくるnotifyを受け取る
このメソッドの呼び出しは、先ほどの「4. 複数サービスの read」の onNext で行なっています。

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
            // エラー処理
        }, onCompleted: {
        }, onDisposed: {
        })
}

6. メモリリーク防止のため、Observableのdispose

take(n)を行えば、n回ぶん値を受け取ったあと、自動的にObservableがdisposeされます。
「5. readの結果に基づいたnotify受け取り」ではそれを実装していますが、「4. 複数サービスのread」では実装していません。
「4. 複数サービスのread」で実装すると、「5. readの結果に基づいたnotify受け取り」を行う前にBluetooth接続がdisposeされてしまい、エラーになってしまうためです。

ただし、そうするとObservableが解放されず、メモリリークが発生してしまいます。

そこで readData で生成したObservableをメンバ変数として保持し、次回アクセス時にdisposeするように対処しました。
これであれば、何度BLEアクセスを行なってもメモリリークは発生しなくなります。
(あまりきれいな対処ではありませんが…。もし良いアイデアがありましたらご意見いただきたいです)

func readData() {

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

    disposable =
    connect()
        .flatMap ... // 以下は「4. 複数サービスのread」参照
}

まとめ

個人的にRxを使ったのは初めてだったのですが、RxBluetoothKitを使うことにより、Core Bluetoothをそのまま使うよりもシンプルに書けるかと思います。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0