iOSでBLEを利用するアプリを開発していると、「スキャンで見つからない」「つながらない」といった場面はよく出てきますし、相手が新規開発デバイスだとそちらを疑いたくなることもあるわけですが、iOS側での「あるある」な実装ミスや勘違い というのも多くあります。
そんなよくあるトラブル、その解決のためのチェックポイント等をまとめました。
実際に開発をしていく中で書きためてきたものなので、かなり実践的な内容になっているかと思います。
##(本題に入る前に・・・)
このトラブルシューティング集は、下記書籍の執筆にあたりまとめていたものです。最終的に書籍内では ここに書いてある分量の2倍ぐらい(※ざっくり感覚値です)のトラブル&対策について書いてあります。
ソシム
売り上げランキング: 1,106
iOS x BLE というニッチな内容で480ページ! 3/26発売です。どうぞよろしくお願いします!
(書籍発売から半年ぐらいしたら、こちらの記事も書籍と同様にフルバージョンにしようと思います。)
##トラブル1: スキャンに失敗する
###→ スキャンの直前に CBCentralManager を初期化していないか?
たとえば「1回目のスキャンに失敗するけど、2回目ではたいていうまく繋がる」という場合には、CBCentralManager の初期化タイミングが遅く、スキャンを開始するタイミングでまだ CBCentralManagerStatePoweredOn
になってない、という可能性があります。
- (IBAction)scanBtnTapped:(UIButton *)sender {
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil];
// スキャン開始
[self.centralManager scanForPeripheralsWithServices:nil
options:nil];
}
(これだとセントラルマネージャの準備が間に合わない可能性がある)
この実装ミスは、再試行ではうまくいくだけに、「何かハード側の調子悪いのかな」「電波だしそういうもんなのかな」ぐらいに思い過ごしがちなので要注意です。1発でバシッと繋がるようになるのはユーザー体験の改善効果としても大きく、修正も簡単なので、ぜひ今一度ご確認を。
###→ サービスを指定している場合、そのサービスをペリフェラル側でアドバタイズしているか?
セントラル側で scanForPeripheralsWithServices:options:
の第1引数にサービスのリストを渡している場合に、ペリフェラル側でそのサービスを提供はしていてもアドバタイズメントデータに入れていない と、スキャン時に発見できないことになります。
たとえばを scanForPeripheralsWithServices:options:
の第1引数に nil
を渡してみるとそのペリフェラルが見つかるようになる、といった場合はこのケースに当てはまっている可能性があります。
(もしくは、単純にサービスUUIDを間違って指定している可能性も)
##トラブル2: 接続に失敗する
###→ 発見したCBPeripheral の参照を保持しているか?
Core Bluetooth に慣れていないと忘れがちなのが、スキャンにより発見した CBPeripheral オブジェクトを、strong のプロパティなり配列に格納するなりして参照を保持しないと解放されてしまう可能性がある、という点です。
centralManager:didDisconnectPeripheral:error:
の引数に入ってくる CBPeripheral オブジェクトは、必要であれば(接続したりするのであれば)きちんとその参照を保持する必要があります。
- (void) centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
// strong属性のプロパティにセット
self.peripheral = peripheral;
// 接続開始
[self.centralManager connectPeripheral:peripheral
options:nil];
}
これを怠ると、connectPeripheral:options:
をコールしても失敗するとか、デリゲートメソッドが呼ばれない、といった事態を引き起こします。
##トラブル3: サービスまたはキャラクタリスティックが見つからない
###→ UUIDが間違っていないか?
discoverServices:
, discoverCharacteristics:forService:
で検索対象のサービス/キャラクタリスティックの CBUUID オブジェクト(の配列)を渡しているのであれば、いったん確認のため nil
を渡してみます。
[peripheral discoverServices:nil];
[peripheral discoverCharacteristics:nil forService:service];
これで見つかるようなら、渡してる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 となっていると失敗します。
[self.peripheral writeValue:data
forCharacteristic:self.characteristic
type:CBCharacteristicWriteWithResponse];
(これでペリフェラル側で当該キャラクタリスティックをWithoutResponseとして定義していると、Writeに失敗する)
##トラブル5: バックグラウンドでのスキャンが動作しない
###→ サービスを指定しているか?
バックグラウンドでのスキャン時は、scanForPeripheralsWithServices:options:
の第1引数にスキャン対象とするサービスを1つ以上渡す必要があります。
they must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter.
[self.centralManager scanForPeripheralsWithServices:nil
options:nil];
(バックグラウンドでこのようにスキャン開始しても動作しない)
###→ もう少し待ってみる
フォアグラウンドのときと比べ、バックグラウンドでのスキャンは間隔が長くなります。
Appleのドキュメントでは具体的な数値を発見できなかったのですが、わふうさんの記事によると12分に1回とか。