iOSのBLEでハマった事 + Tips

# 自己紹介

* tomoya0x00
    * GitHub/Twitter/Qiita/mstdn.jp
* メインは組み込み系
* 去年の4月に車載業界から小規模ベンチャーへ転職
* 転職してから業務でAndroid/iOSのアプリ開発もやるように
    * 実務経験は少ないのでお手柔らかに…

---

# 何にハマったのか？

---

# BLEデバイス（Peripheral）がアプリから見つからない！

---

# どういうこと？

* BLEデバイスは自社製
    * 電池消費削減のため、スマホとは常時接続では無く必要時だけ接続
    * 搭載Serviceは独自サービスとBattery Service
* フォアグラウンドのスキャンでは見つかる
* **バックグラウンドのスキャンでだけ見つからない！**

---

# バックグラウンド特有の制限に引っかかっている？

* 同一デバイスからの複数アドバタイズイベントは1回にまとめられてしまう
    * 一度も検知されないので違う
* スキャン間隔が広がるので検知に時間がかかる
    * いくら待っても検知されないので違う
* スキャン時に必ずService UUIDを指定する必要がある
    * 独自サービスUUIDを指定しており、フォアグラウンドでは検知できてるので違う

参照：[iOS×BLE Core Bluetoothプログラミング](https://www.amazon.co.jp/dp/4883379736)
参照：[Core Bluetooth Background Processing for iOS Apps](https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html)
参照：[scanForPeripherals(withServices:options:)](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518986-scanforperipherals)

---

# 仮説

* アドバタイズ間隔が広すぎるとバックグラウンドで検知できないのでは？
    * Android版nRF ConnectでアドバタイズをCLONEして間隔変更
        * 独自サービスUUID指定だと見つからない
        * **Battery Service UUID指定だとバックグランドでも検出できた**
        * でも、アドバタイズ間隔は関係無いっぽい

※nRF ConnectはiOSよりAndroid版の方が高機能なのでお勧めです

---

# あれ？おかしいぞ？

アドバタイズのRaw data
![rawdata.jpg](https://qiita-image-store.s3.amazonaws.com/0/24788/932f170a-faa0-3cc9-4d7a-b451f06869de.jpeg)

---

# アドバタイズのペイロードに乗り切らないはず

* アドバタイズのペイロードは31byte
    * 各Data Typeの使用サイズ
        * LEN(1byte) + TYPE(1byte) + VALUE(1byte～)
    * Flags
        * 1+1+1 = 3byte
    * Complete Local Name
        * 1+1+8 = 10byte
    * Complete List of 128-bit Service Class UUIDs
        * 1+1+16 = 18byte
    * Complete List of 16-bit Service Class UUIDs
        * 1+1+2 = 4byte
* 何かがスキャンレスポンスに乗っかっているっぽい
    * アドバタイズに乗り切らないデータをスキャンレスポンスで送ることができる
    * Android版nRF Connectではアドバタイズとスキャンレスポンスを区別できないのでどうするか

---

# BLEスニファー

BLEの電波をキャッチして通信内容が見れる

![blesniffer.png](https://qiita-image-store.s3.amazonaws.com/0/24788/673be860-818b-62ff-5c79-db92b8d67472.png)

[Bluefruit LE Sniffer - Bluetooth Low Energy (BLE 4.0) - nRF51822](https://www.adafruit.com/product/2269)
参照：[技適マークつき BLE パケットスニファを入手する ](http://dsas.blog.klab.org/archives/52259630.html)

---

# スニファイ結果を見てみる - アドバタイズ

**Device Nameしか無いぞ！？**

![advertise.png](https://qiita-image-store.s3.amazonaws.com/0/24788/0108e8d7-d0c6-ac83-eea9-88ca6cf6622c.png)

---

# スニファイ結果を見てみる - スキャンレスポンス

**Service UUIDは全てこちらにある！！**

![scanrsp.png](https://qiita-image-store.s3.amazonaws.com/0/24788/4a7ebdc2-4978-f6f7-859d-a6498b5c96bc.png)

---

# 結論

* **バックグラウンドではスキャンレスポンスは無視される**
* Android版nRF ConnectでCLONEしたアドバタイズが検出できたのは？
    * Battery Service UUIDだけ、アドバタイズに乗っていたからだった

---

# で、どうするの？

* バックグラウンドでのスキャンはService UUID指定が必須
    * つまり、今回のBLEデバイスはバックグラウンドで検出不可能
* BLEデバイスのファームウェア書き換えは厳しい
    * 数が多い＆時間が無い

---

# 打つ手無しなのか…

---

# まだ手はあるっ！！

---

# Retrieve a list of known peripherals

* iOSは過去に接続したことのあるBLEデバイスを記録している
    * 接続時にBLEデバイスのIdentifierを記録して、これを指定すればスキャンせずに接続要求できる

![ReconnectingToAPeripheral_2x.png](https://qiita-image-store.s3.amazonaws.com/0/24788/d4fe3a7a-72b4-bab4-cce6-69e3de73b39e.png)

参照：[Best Practices for Interacting with a Remote Peripheral Device](https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/BestPracticesForInteractingWithARemotePeripheralDevice/BestPracticesForInteractingWithARemotePeripheralDevice.html)

---

# 無事にバックグラウンドでも接続できるようになった！

---

# めでたしめでたし

---

# 最初からベストプラクティスに従っておけば良かった…

--- 

# Tips

* Delegate地獄が辛い
    * [RxBluetoothKit](https://github.com/Polidea/RxBluetoothKit)がオススメ
* iOS側でデバイス名が更新されない
    * CBPeripheral::nameはキャッシュされてる
    * advertisementDataから直接LocalName参照が良い
* iOS版のnRF Connect、表示される情報が少ない
    * iOSで取得できる情報が少ないため
    * Android版を使いましょう
* なんだか動きがおかしい
    * iOSのBT OFF->ON