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の接続や設定をリセットする、という部分など放置している部分もあるので、おいおい対応していく予定です。