Android6.0でBLEを使う(Peripheral編) その2
前回の続きです。
Central側からWriteRequestを送って、それをPeripheral側で受け取ります。
書き込みデータの読み取り(失敗)
Central側でwriteCharacteristic()によって書き込まれたデータを読み取り、それをTextViewにセットします。
ここで大いにつまづくこととなりました...。
onCharacteristicWriteRequest(失敗)
WriteRequestが送られると、接続したデバイスの取得に使用したonConnectionStateChangeと同じく、 BluetoothGattServerCallbackの中で以下のメソッドが呼ばれるはずなのですが、実際に動かしても反応は得られませんでした。
PeripheralActivity.java
~省略~ private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { ~省略~ @Override public void onCharacteristicWriteRequest (BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value){ // ここに処理を記述. } ~省略~
調べてみたところ、WriteRequestを送ることができるのはCharacteristicだけではなく、Descriptorもあり、onDescriptorWriteRequestが用意されていました。
コードに組み込んでみたところ確かに呼ばれはしたものの、1000ミリ秒ごとにRequestは発生しているはずなのに1度しか呼ばれませんでした。
PeripheralActivity.java
~省略~ private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { ~省略~ @Override public void onCharacteristicWriteRequest (BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value){ // ここに処理を記述. } @Override public void onDescriptorWriteRequest(BluetoothDevice device, int requestId , BluetoothGattDescriptor descriptor, boolean preparedWrite , boolean responseNeeded, int offset, byte[] value) { // ここに処理を記述. 接続後1回しか呼ばれない. } ~省略~
原因
まずonDescriptorWriteRequestが1回しか呼ばれない原因は、Central側でNotificationを有効にするために以下のコードを実行しており、 それ以降はCharacteristicに対して値をセットしていたためでした。
CentralActivity.java
~省略~ @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { ~省略~ // Characteristic の Notificationを有効化する. BluetoothGattDescriptor descriptor = mBleCharacteristic.getDescriptor( UUID.fromString(CHARACTERISTIC_CONFIG_UUID)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBleGatt.writeDescriptor(descriptor); // 接続が完了したらデータ送信を開始する. mIsBluetoothEnable = true; } ~省略~
そして、onCharacteristicWriteRequestが呼ばれない理由は、onDescriptorWriteRequestでsendResponseを実行しておらず、 Central側がそれを待っている状態になっていたためのようです。
PeripheralActivity.java
~省略~ private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { ~省略~ @Override public void onCharacteristicWriteRequest (BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value){ super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); // ここに処理を記述. if(responseNeeded){ mBtGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value); } } @Override public void onDescriptorWriteRequest(BluetoothDevice device, int requestId , BluetoothGattDescriptor descriptor, boolean preparedWrite , boolean responseNeeded, int offset, byte[] value) { super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); // Centralに対してResponseを返す. if(responseNeeded){ mBtGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value); } } ~省略~
このようにResponseを返してやることで、onCharacteristicWriteRequestが呼ばれるようになりました。
書き込みデータの取り出し
Requestに合わせてメソッドが呼ばれるようになったので、あとはデータの取り出しです。
失敗
onCharacteristicWriteRequestは引数としてBluetoothGattCharacteristicが渡されるため、 characteristic.getStringValue(0)で取得できるかな、と思っていたのですが...。
- Notificationで値を更新していないとNullが返る
- Notificationで値を更新した後はCentral側でセットした値ではなく、Notificationで更新した値が返る
という問題が発生しました。
対策
↑を見ていたところ、こんな記述がありました。
- characteristic: Characteristic to be written to.
- value: The value the client wants to assign to the characteristic
・・・あれ、もしかしてcharacteristicはCentral側で送ったデータが含まれているわけではない?
ということで、characteristicに対してsetValueを実行し、その引数を同じく引数として渡されたvalueをセットすることで、無事正しい値が取得できるようになりました。
PeripheralActivity.java
~省略~ private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { ~省略~ @Override public void onCharacteristicWriteRequest (BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value){ super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); // Centralから受け取った値をCharacteristicにセット. characteristic.setValue(value); // TextViewのアップデート. mStrReceivedNum = characteristic.getStringValue(offset); mHndBleHandler.sendEmptyMessage(MESSAGE_NEW_RECEIVEDNUM); if(responseNeeded){ mBtGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value); } } ~省略~
終わりに
最初にAndroidでBLEを試してから実に1年以上経ってしまいました。
長かったですねぇ...。
まぁLollipop以降でこんなに時間がかかったのは、私が端末を新しく購入していなかったせいですが。
ともかくこれでAndroid、iOS、Macはそれぞれ連携させることができるようになりました。
まだトップのページに戻したときにBluetoothの接続や設定をリセットする、という部分など放置している部分もあるので、おいおい対応していく予定です。