CoreBluetoothでNotificationを受け取る!

CoreBluetoothでWrightしてNotificationを受け取るまでの処理の流れです。

以前BLEを使ってスキャンするという記事を書きました。今回はその続きです。

処理の流れ

①セントラルマネージャの状態を判定

②ペリフェラルの検出デリゲートを呼ぶ

③ペリフェラルが検出される

④検出されたペリフェラルに接続する

⑤接続成功か失敗のメソッドどちらかに飛ぶ

⑥成功したらペリフェラルの持っているサービスを見つける

⑦サービス内のキャラクタリスティックを見つける

⑧キャラクタリスティックを発見したら書き込む

⑨書き込まれたことを検知してNotificationが帰ってくる

ざっとこんな感じの流れでNotificationを取得ができます。

では細かい説明を書いていきます!

④ペリフェラルへの接続

③スキャンまでは以前の記事で紹介しているので省略します。ペリフェラルへの接続は、

.connect(peripheral: CBPeripheral, options:[String : Any])

というメソッドで接続することができます。

    //ペリフェラル検出時に呼ばれる
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
            //メンバ変数にペリフェラルを持っておかないとエラーが発生してしまうので注意!
            myPeripheral = peripheral
            //ペリフェラルに接続
            myCentralManager.connect(myP, options: nil)
        }
    }

コメントにも書きましたが、接続するペリフェラルをメンバとして保持しておかないと接続できません!なぜダメなのかわかっていませんが参照が消えてしまう?ようです。

⑤接続確認

.connectメソッドを呼ぶと接続失敗か接続成功どちらかのメソッドに飛んでいきます.

接続成功


    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("接続成功")
     }

接続失敗

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
                print("接続失敗")
    }

⑥ペリフェラルのサービスを探す

成功時にペリフェラルのサービスを探しにいきます。

本来であれば[CBUUID]を指定して渡すのですが、nilを指定するとペリフェラルの持っている全てのサービスを見つけて、次のメソッドに渡します。

   func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("接続成功")
        myPeripheral.delegate = self
        //指定されたサービスを探すnilだと全部!!
        myPeripheral.discoverServices(nil)
    }

⑦サービス内のキャラクタリスティクスを見つける

⑦以降のメソッド呼び出しにはCBPeripheralDelegateというプロトコルに準拠させる必要があります。

⑥で指定したサービスを問題なく見つけることができたらこのメソッドが呼ばれます。

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        print("サービス発見!")
        let service: CBService = myPeripheral.services![0]
        myPeripheral.discoverCharacteristics(nil, for: service)
    }

それぞれのサービスの中にそれぞれ違うキャラクタリスティックを持っているので書き込みをしたいキャラクタリスティックを指定して変数に格納しています。

キャラクタリスティックを探索している部分本来は.discoverCharacteristicsに特定のCBUUIDを指定して探索を行うと思うのですが、ここでもnilを指定すると 第二引数に指定したサービスの持っているキャラクタリスティック全部を見つけます。

⑧キャラクタリスティックを発見したら書き込む

ちょっと長いです。注意点は16進数を渡す際に1byteの配列にしてあげルコとです。

あとは⑦番でキャラクタリスティックの指定なしで渡してしまったので自分の書き換えたいキャラクタリスティックを判定してペリフェラルのwriteValueで書き込みを行います。

値が書き換わった際にNotificationを受け取りたいので書き込む前にperipheral.setNotifyValueを仕込んでいます

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        print("キャラクタリスティック発見!")
        
        //1byteずつの配列に分割して指定する(任意のバイト文字列)
        let byteArray: [UInt8] = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]
        let data = Data(bytes: byteArray)

        //ペリフェラルの保持しているキャラクタリスティクスから特定のものを探す
        for i in service.characteristics!{
            if i.uuid.uuidString == "書き込みを行いたいUUID"{
            	
                //Notificationを受け取るよっていうハンドラ
                peripheral.setNotifyValue(true, for: i)
                //書き込み
                peripheral.writeValue(data , for: i, type: .withResponse)
            }
        }
    }

Note

16進数を1byteずつ分割して書き直すのがめんどくさい時は、Data型に変換用のメソッドを追加すると便利です。

How to convert Data to hex string in swiftここに先人の知恵があります。

extension Data {
    struct HexEncodingOptions: OptionSet {
        let rawValue: Int
        static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
    }
    
    func hexEncodedString(options: HexEncodingOptions = []) -> String {
        let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16)
        var chars: [unichar] = []
        chars.reserveCapacity(2 * count)
        for byte in self {
            chars.append(hexDigits[Int(byte / 16)])
            chars.append(hexDigits[Int(byte % 16)])
        }
        return String(utf16CodeUnits: chars, count: chars.count)
    }
}

⑨書き込まれたことを検知してNotificationが帰ってくる

少し長い道のりでしたがやっとNotificationを受け取ることができます。

        func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        //valueの中にData型で値が入っています。
        print(characteristic.value)
        print("更新通知を取得しました!")
    }

実行バージョンとか

Swift version 4.2.1

前回のBluetoothでスキャンの記事と今回の記事の4から9までをく見合わせていけば問題なく書き込めると思います。

うまくいかない場合UUIDや書き込む値などが間違えている可能性が高いので要注意です!私の場合はそこで間違えてました。

Swift

前の記事

RxBluetoothKitでスキャンする
今日から始めるSwift

次の記事

三項演算子