vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

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)で取得できるかな、と思っていたのですが...。

  1. Notificationで値を更新していないとNullが返る
  2. 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以降でこんなに時間がかかったのは、私が端末を新しく購入していなかったせいですが。

ともかくこれでAndroidiOSMacはそれぞれ連携させることができるようになりました。
まだトップのページに戻したときにBluetoothの接続や設定をリセットする、という部分など放置している部分もあるので、おいおい対応していく予定です。

参考