その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

Core Bluetooth トラブルシューティング

iOSでBLEを利用するアプリを開発していると、「スキャンで見つからない」「つながらない」といった場面はよく出てきますし、相手が新規開発デバイスだとそっちを疑いたくなることもあるわけですが、けっこうiOS側での「あるある」な実装ミスや勘違いというのが多くあります。


そんなよくあるトラブル、その解決のためのチェックポイント等をまとめました。

(本題に入る前に・・・)

このトラブルシューティング集は、下記書籍の執筆にあたりまとめていたものです。最終的に書籍内では ここに書いてある分量の2倍ぐらい*1のトラブル&対策について書いてあります。

iOS×BLE Core Bluetoothプログラミング
堤 修一 松村 礼央
ソシム
売り上げランキング: 1,106


iOS x BLE というニッチな内容で480ページ!下記に紹介記事があります。どうぞよろしくお願いします!

トラブル1: スキャンに失敗する

→ スキャンの直前に CBCentralManager を初期化していないか?

たとえば「1回目のスキャンに失敗するけど、2回目ではたいていうまく繋がる」という場合には、CBCentralManager の初期化タイミングが遅く、スキャンを開始するタイミングでまだ `CBCentralManagerStatePoweredOn` になってない、という可能性があります。


この実装ミスは、再試行ではうまくいくだけに、「何かハードか電波の調子悪いのかなー」ぐらいに思い過ごしがちなので要注意です。1発でバシッと繋がるようになるのはユーザー体験の改善効果としても大きいと思うし、修正も簡単なので、ぜひ今一度ご確認を。

→ サービスを指定している場合、そのサービスをペリフェラル側でアドバタイズしているか?

セントラル側で `scanForPeripheralsWithServices:options:` の第1引数にサービスのリストを渡している場合に、ペリフェラル側でそのサービスを提供はしていてもアドバタイズメントデータに入れていない と、スキャン時に発見できないことになります。


たとえばを `scanForPeripheralsWithServices:options:` の第1引数に `nil` を渡してみるとそのペリフェラルが見つかるようになる、といった場合はこのケースに当てはまっている可能性があります。

トラブル2: 接続に失敗する

→ 発見したCBPeripheral の参照を保持しているか?

Core Bluetooth に慣れていないと忘れがちなのが、スキャンにより発見した CBPeripheral オブジェクトを、strong のプロパティなり配列に格納するなりして参照を保持しないと解放されてしまう可能性がある、という点です。


`centralManager:didDisconnectPeripheral:error:` の引数に入ってくる CBPeripheral オブジェクトは、必要であれば(接続したりするのであれば)きちんとその参照を保持する必要があります。


これを怠ると、`connectPeripheral:options:` をコールしても失敗するとか、デリゲートメソッドが呼ばれない、といった事態を引き起こします。

トラブル3: サービスまたはキャラクタリスティックが見つからない

→ UUIDが間違っていないか?

`discoverServices:` , `discoverCharacteristics:forService:` で検索対象のサービス/キャラクタリスティックの CBUUID オブジェクト(の配列)を渡しているのであれば、いったん確認のため `nil` を渡してみます。


これで見つかるようなら、渡してるUUIDが間違っている、と考えられます。

→ ペリフェラル側でGATTを変更したのではないか?

iOSアプリと連携する新規ハードウェア開発をしているとものすごくあるあるで、ハマる人が多いのですが、開発途中でペリフェラル側(外部デバイス)で GATT の内容を変更すると、iPhone の Settings から Bluetooth を Off/On しないと変更が反映されない というのがあります。


このことを知らないと、たとえばキャラクタリスティックの `value` が取れず、

  • BLE の接続状態を疑う
  • Central / Peripheral 間での UUID の食い違いを疑う
  • etc...

と、無駄なデバッグ作業をしてしまいかねません。


で、とりいそぎ上にも書いた通り iPhone の off/on で GATT の変更を反映することはできるのですが、正しい解決方法をAppleの中の人に聞いてきたので、下記記事を併せてご参照ください。

トラブル4: Writeで失敗する

→ CBCharacteristicWriteType を間違って指定していないか?

`writeValue:forCharacteristic:type:` の第3引数に指定する CBCharacteristicWriteType (レスポンスありの場合は `CBCharacteristicWriteWithResponse`、なしの場合は `CBCharacteristicWriteWithoutResponse`) は、GATTの当該キャラクタリスティックのプロパティ設定と合ってないとエラーになります。


アプリ側では「確認のためレスポンス欲しいなー」ってな動機で WriteWithResponse を指定したくなる場合もあるかもしれませんが、GATT側では WriteWithoutResponse となっていると失敗します。

トラブル5: バックグラウンドでのスキャンが動作しない

→ サービスを指定しているか?

バックグラウンドでのスキャン時は、`scanForPeripheralsWithServices:options:` の第1引数にスキャン対象とするサービスを1つ以上渡す必要があります。

> they must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter.

→ もう少し待ってみる

フォアグラウンドのときと比べ、バックグラウンドでのスキャンは間隔が長くなります。


Appleのドキュメントでは具体的な数値を発見できなかったのですが、わふうさんの記事によると12分に1回とか。

おわりに

以上、日々の開発の中で書きためていたトラブルシューティングでした。他にもあれば追記していきます。


*1:※ざっくり感覚値です