これまでAndroid - Unlimited Hand間をBluetoothで接続して動作させることはやったことがありました(記事)が、今度社内でペアリングをしないでBluetoothで接続する必要が出てきたので、Bluetooth Low Energy(BLE)を使用してみることにしました。
こちらの特徴は通常は電力消費が抑えられていることとなりますが、ペアリングを行わなくても、非暗号化通信限定でPeripheralとCentral間で通信が行える特徴もあります。
ただ、Android標準APIを使ってみて分かったのですが結構癖があるAPIとなっているので、その辺を中心に纏めたいと思います。

基本情報

使用したAndroid標準APIは以下のものとなります。
  • サポートするAndroidバージョン: 21(Lollipop)以上

Peripheralが自分を公開するまでのシーケンス

Peripheralが自分をCentralで発見できるようにするために、Advertisingを開始する必要があります。
以下のシーケンスはAdvertisingするまでのシーケンスとなります。
上記のシーケンスに記載している番号毎に実施していることは以下のようになります。 >
番号概要
BluetoothManager.openGattServerにてBluetoothGattServerインスタンスを取得
BluetoothGattServerインスタンスはCentralからのコネクション確立要求を受けるために必要で、Peripheralの公開とは直接的には関係ないですが、やっておかないとCentralがPeripheralを発見した後のコネクション確立ができないので、公開する前に体制を整えておきます。
BluetoothAdapter.getBoluetoothLeAdvertiserを取得
Peripheralの公開のために、BluetoothLeAdvertiser.startAdvertisingをコール
これによりOS内部で本Peripheralを公開するための処理を開始します
3-1BluetoothGattServerCallback.onStartSuccess / onStartFailureにて公開開始の結果が得られます

CentralがPeripheralからデータを取得するまでのシーケンス

Centralが公開されているPeripheralを見つけ、そのPeripheralからデータ(Characteristic)を取得するまでのシーケンスとなります。
上記のシーケンスに記載している番号毎に実施していることは以下のようになります。
番号概要
BluetoothAdadpter.getLeScannerにてBluetoothLeScannerインスタンスを取得
BluetoothLeScanner.startScanにて近くのAdvertisingしているPeripheralのスキャンを開始
デバイス名称などでフィルタリングすることも可能(詳細は別途記載予定)
2-1一致するPeripheralが見つかったらScanCallback.onScanResultがコールされ、検索結果が通知される。
ScanRecord.getDeviceなどにより見つかったPeripheralの情報を取得する
以下、見つかったPeripheralへの接続処理となります
BluetoothDevice.connectGattを実行して、PeripheralとGATTプロファイルによる接続を試みます。
ただ、このメソッドは若干曲者で間違った(?)使い方をするとハマります。
1) このメソッドではPeripheralとの接続は行われない。コール後、別途BluetoothGatt.connectを実行する必要があります
2) BluetoothGattCallback.onConnectionStateChangeでコネクションの状態をハンドリングしたい場合はautoConnectは無効(false)にしないとならないです
特に2はAndroid Developersに明記されていないのでハマりました(確認した際のバージョン: Android 7.0)。
Peripheralとの接続処理
このメソッドの戻り値はPeripheralとの接続要求を受け付けたことを示しているのみで、コネクション自体は非同期が確立されます。
そのため、Peripheralとのコネクションが必要なBluetoothGatt.discoverServicesは、BluetoothGatt.connect直後に実施しても失敗します(が、メソッドの戻り値はtrueで返却され、ログを見ると以下のようなログが出力されていて失敗したことが分かります)
「E BtGatt.GattService: discoverServices() - No connection for XX:XX:XX:XX:XX:XX(MACアドレス)...」
BluetoothGatt.discoverServicesはBluetoothGattCallback.onConnectionStateChangeでコネクション確立を受けてから実行する必要があります。
4-1このタイミングでPeripheralとのコネクションが確立されたことをハンドリングできます。コネクションの確立が必須な機能(例 Serviceの検索など)は、このタイミング以降で実施する必要があります
以下、接続したPeripheralからの情報取得処理となります
Peripheralから情報を取得するためにServiceの検索を実施します
5-1Serviceが見つかるとBluetoothGattCallback.onServicesDiscoveredがコールされます
BluetoothGattCallback.onServicesDiscoveredのパラメータで渡されたBluetoothGattインスタンスを使用して、BluetoothGatt.getService(s)をコールすると、Peripheralが保有するService一覧を取得できます
BluetoohGattService.getCharacteristic(s)をコールすることでServiceに紐づいたCharacteristicの一覧が取得できます
BluetoohGattService.getCharacteristic(s)で取得したCharacteristicにはデータが設定されていないことがあります。
その場合BluetoothGatt.readCharacteristic(引数で指定するのはBluetoohGattService.getCharacteristic(s)で取得したCharacteristic)を実行することで非同期でデータを取得できます
ただし、BluetoothGatt.readCharacteristicは1回コールしたら、BluetoothGattCallback.onCharacteristicReadがコールされるまで、コールしない方がいいです。連続コールしたら、1回しかコールバックされないことがあったので、コールバックされるまで実行しないように実装した記憶があります。
BluetoothGatt.readCharacteristicを実行するとPeripheralとの通信が発生し、Peripheral側でBluetoothGattServerCallback.onCharacteristicReadRequestがコールされます。データを返却することを許可する場合、BluetoohGattServer.sendResponseでデータを返却する必要があります。コールしないとCentral側のBluetoothGattCallback.onCharacteristicReadがコールされません。
8-1PeripheralからCharacteristicのデータを取得した際にコールされます
BluetoothGattCallback.onCharacteristicReadで渡されたBluetoothGattCharacteristicインスタンスを使用して、BluetoothGattCharacteristic.getValueを実行することでCharacteristicのデータを取得できます

CentralからPeripheralのcharacteristicのデータ書き換えるまでのシーケンス

Peripheralが公開しているCharacteristicをCentral側で書き換えることもできます。
※ ただし、PeripheralがそのCharacteristicに対して書込みを許容している場合のみとなります。

その際のシーケンスは以下のようになります(CentralとPeripheralの間にGATTコネクションが確立している前提とします)。
上記のシーケンスに記載している番号毎に実施していることは以下のようになります。
番号概要
対象となるCharacteristicに変更した値を設定
ただし、このタイミングではPeripheralへの変更提案は行われていないです
BluetoothGatt.writeCharacteristicに先ほど変更を加えたBluetoothGattCharacteristicインスタンスを指定してPeripheralへ変更提案を行う
2-1 CentralからのCharacteristicの変更要求がBluetoothGattServerCallback.onCharacteristicWriteRequestで通知される。
この変更要求を受け付ける場合はパラメータで指定されたBluetoothGattCharacteristicインスタンスに対してsetValue(パラメータで指定されたvalueを指定)することで変更されます。
2-2 該当のCharacteristic生成時にプロパティにPROPERTY_WRITE_NO_RESPONSEが設定されていない場合、BluetoothGattServer.sendResponseを使用してCentralに応答を返却してあげる必要があります。
PeripheralにてBluetoothGattServer.sendResponseがコールされると本メソッド(BluetoothGattCallback.onCharacteristicWriteがコールされる。
ただし、引数statusがGATT_SUCCESSになっているからといっても、実際にCharacteristicが更新されているか保証されません(※)。 ※ Peripheral側でBluetoothGattCharacteristic.setValueをコールしていない、または別の値を設定している場合、意図したようには更新されません。


最初、同じような名前のメソッドがBluetoothGatt・BluetoothGattService・BluetoothGattCharacteristicにあって、どのメソッドまたはどの順番でコールすればよいか不明でした。
また、HWを使用するために非同期処理が多くなりがちで結構使い辛い印象でした。

コメントの投稿