2015年5月20日水曜日

BluetoothSMARTデバイスをmbed で開発する(6)


はじめに

前回はmbedを使用したBLEデバイスとAndroid 4.4および5.0でBLE接続に必要な実装について掲載いたしましたが、今回は4種類のGATTアクセスに着目して、それぞれの動作と実装方法について紹介いたします。

関連記事
BluetoothSMARTデバイスをmbed で開発する(1)
BluetoothSMARTデバイスをmbed で開発する(2)
BluetoothSMARTデバイスをmbed で開発する(3)
BluetoothSMARTデバイスをmbed で開発する(4)
BluetoothSMARTデバイスをmbed で開発する(5)

BluetoothSMARTデバイスをmbed で開発する(7)
BluetoothSMARTデバイスをmbed で開発する(8)

ARMは、ARM Limited(またはその子会社)のEUまたはその他の国における登録商標です。mbedは、ARM Limited(またはその子会社)のEUまたはその他の国における商標です。All rights reserved.

注意事項 - 本記事に掲載されているソースコードについて

2015年の後半に次世代 mbed である mbed OS がリリースされるというアナウンスが出ています。 mbed OSでは新たに Bluetooth スタックのサポートが追加され API が大幅に変更されることが予測されます。 mbed OS上では本記事の内容が利用できなくなる可能性がありますので予めご了承ください。


お知らせ -- nRF51822 mbedプラットフォームの差異について

当ブログの記事ではmbedのプラットフォームとしてnRF51822-mKITを選択しておりますが、国内で入手可能な製品において記事の通りにビルドすると動作しないものが見つかっています。
これは該当する製品が nRF51 Softdevice の Version5.x世代に設計されたもので、それ以降のSoftdeviceにおいてデフォルト使用となっている 32kHzのサブ水晶が未搭載であることに起因しています。
本ブログで掲載している HRM1017ブレイクアウト基板は基板上に32kHzのサブ水晶を搭載してあることから nRF51822-mKIT互換として動作しますが、もしお手持ちのmbed製品で動作しない場合は製品を製造したベンダのプラットフォームを選択するようにしてください。



mbed BLE & Android プログラミング -- 4種類のGATT アクセス

今回は4種類のGATTアクセスについてmbed / Android両面で機能と実装方法について記載を進めていきます。ハードウェアは前回の記事と同じものを使用します。




・GATT Read


BLEデバイスからCharacteristicデータを取得する手順です。アプリ側から取得したいタイミングでデータ読み出しができることから、お手軽にデータ読み出しを行うときに用いられます。
ただしデバイス内にあるデータを読み取るだけなのでデータ更新を知ることはできません。そのため必要に合わせて定期的に読み出しを行うことにより最新のデータを取得するようにします。

<mbed側のコード>

mbedでGATT読み出し処理を行うコードを抜粋しました。 全ソースはこちら

GATT 読み出しはmbed BLEスタックがデバイス内に保持しているCharacteristicの値を自動的に応答するだけなのでmbed側で処理を実装する必要ありません。Characteristicの値を更新したい時のみupdateCharacteristicValue()を用いて値更新を行います。更新された値は次回のGATT読み出し時に取得されます。
サンプルコードでは 1秒ごとにCharacteristicの値を更新するようになっています。

GattCharacteristic  gDataCharacteristic (UUID_CHAR_DATA, gRwData, sizeof(gRwData), sizeof(gRwData),
        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ );

void tickerCallback(void)
{
    /* Update Characteristic */
    ble.updateCharacteristicValue(battLevel.getValueAttribute().getHandle(),
                                        (uint8_t *)&batt,
                                        sizeof(batt));
}


<Android側のコード>

AndroidでGATTから読み出しを行うためのコードを抜粋しました。 全ソースはこちら
ReadGatt()はGATT読み出しを行うローカル関数です。GATT接続時に保存してあったCharacteristic情報を用いて readCharacteristic()を呼び出します。
GATTから読み出したデータはコールバック関数onCharacteristicRead()にて取得します。BLEデバイスからの応答があるとコールバック関数が呼ばれますので、status値が"GATT_SUCCESS"であることを確認したうえで getValue()を用いて読み出しデータの取得を行います。


private void ReadGatt() {
  if (mDataCharacteristic != null) {
    mGatt.readCharacteristic(mDataCharacteristic);
  }
}
  
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic,int status) {
   
  Log.d(TAG, "onCharacteristicRead: " + status);
  if (status == BluetoothGatt.GATT_SUCCESS) {
    // READ成功
    byte[] read_data = characteristic.getValue();
    Log.d(TAG, "data = "  + read_data[0]  + "," + read_data[1]  + "," + read_data[2]  + "," + read_data[3]  + "," + read_data[4]);
  }
}




・GATT Write


BLEデバイスに対してCharacteristicデータを送るにはGATT Writeを用います。アプリがGATT Writeを行うとデータがデバイス内のCharacteristicに転送されます。
BLEの規格上は GATT Writeにはデバイス側から応答を待って終了するものと、そのまま終了するものの2種類の手順があります。しかし書き込み完了を待たずに次の処理に進んでしまうと、書き込みデータが失われてしまう場合があるため、Androidでは応答を待つタイプのみが利用可能です。

<mbed側のコード>

mbedでGATT書き込みを処理するコードを抜粋しました。 全ソースはこちら
初期設定コード内のCharacteristic登録時に "BLE_GATT_CHAR_PROPERTIES_WRITE"のフラグを使用し書き込み可能なCharacteristicであることを設定します。またGATT書き込み要求を受け取るためのコールバック関数を用意し、書き込まれたデータを取得します。

GattCharacteristic  gDataCharacteristic (UUID_CHAR_DATA, gRwData, sizeof(gRwData), sizeof(gRwData),
      GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE);

void DataWrittenCallback(const GattCharacteristicWriteCBParams *params)
{
    DEBUG("DataWritten\n\r");
    lcd.locate(0,1);
    lcd.printf("Wr:");
    lcd.printf("%02x%02x",params->data[0],params->data[1]);

}


<Android側のコード>

AndroidでGATT書き込み行うためのコードを抜粋しました。 全ソースはこちら
WriteGatt()はGATT書き込みを行うローカル関数です。まず byte配列で書き込むデータを準備し、GATT接続時に取得したCharacteristic情報に書き込みデータをセットします。次にwriteCharacteristic()を用いてGATT書き込みを実行することによりデータの書き込みが行われます。
BLEデバイスへGATT書き込みが完了するとコールバック関数 onCharacteristicWrite()が呼び出されますのでStatus値が"GATT_SUCCESS"であることを確認して書き込みを完了します。
なお書き込み完了を待たずに次のGATTアクセスを行うとAndroid側が不正動作に陥る場合がありますので必ず書き込み完了を確認するようにしてください。

private void WriteGatt() {
  if (mDataCharacteristic != null) {

    byte[] WrtVal = new byte[4];
    mDataCharacteristic.setValue(WrtVal);
    boolean status = mGatt.writeCharacteristic(mDataCharacteristic);
    if (status == false) {
      Log.e(TAG, "@@@@@ writeCharacteristic failed");
    }
  }
}

@Override
public void  onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
  Log.d(TAG, "onCharacteristicWrite: " + status);
  switch(status) {
    case BluetoothGatt.GATT_SUCCESS:
      Log.e(TAG, "onCharacteristicWrite: GATT_SUCCESS");
      break;
    case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
      Log.e(TAG, "onCharacteristicWrite: GATT_WRITE_NOT_PERMITTED");
      break;
       
    case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
      Log.e(TAG, "onCharacteristicWrite: GATT_REQUEST_NOT_SUPPORTED");
      break;
      
    case BluetoothGatt.GATT_FAILURE:
      Log.e(TAG, "onCharacteristicWrite: GATT_FAILURE");
      break;

    case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
      break;
 
    case BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION:
      break;

    case BluetoothGatt.GATT_INVALID_OFFSET:
      break;
    }
  
}


・GATT Notification


デバイスからCharacteristicデータの取得に用います。GATT Readと異なるのはスマートフォンのアプリ側から読み出し操作を行わず、デバイス側から通知という形でデータを受け取ることです。つまりデバイス側が送るべきデータを準備できたときのみデータ転送が行われます。
なおデバイスから意図しない通知が行われると困りますので、通知を許可するための機能としてCCCD(Client Characteristic Configuration descriptor)というものがあります。図では CCCDのUUIDとして"00002902-0000-1000-8000-00805f9b34fb"と記載しましたが 16bitUUIDである"0x2902" を用いることもできます。

<mbed側のコード>

mbed側でNotificationを行うためのコードを抜粋しました。 全ソースはこちら
初期設定コード内のCharacteristic登録時に "BLE_GATT_CHAR_PROPERTIES_NOTIFY"のフラグを使用します。また通知許可/停止を受け取るためのコールバック関数を用意し、通知処理の開始もしくは停止を行えるようにします。
サンプルコードでは通知許可を受け取るとmbedのタイマー機能Tickerを起動し、1秒ごとにCharacteristicの値を更新するようになっています。

GattCharacteristic  gDataCharacteristic (UUID_CHAR_DATA, gRwData, sizeof(gRwData), sizeof(gRwData),
        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

void UpdatesEnabledCallBack(Gap::Handle_t handle)
{
    DEBUG("UpdatesEnabled\n\r");
    // ユーザ処理コード
}

void UpdatesDisabledCallBack(Gap::Handle_t handle)
{
    DEBUG("UpdatesDisabled\n\r");
    // ユーザ処理コード
}

<Android側のコード>

Android側でNotificationを受けるためのコードを抜粋しました。 全ソースはこちら
NotificationGatt()およびDisNotificationGatt()はNotificationの有効化/無効化を行うためのローカル関数です。GATT接続後に保存していた Characteristic情報 mDataCharacteristicを用いてCCCD(Client Characteristic Configuration descriptor)を取得し、Enableビットをセット/クリアするとともにAndroidフレームワーク側にNotification通知許可を発行します。
コールバック関数 onCharacteristicChanged()はBLEデバイスからNotificationが来たときに呼び出されます。ここでBLEデバイスが通知したデータの受け取りをおこなっています。


// Notification通知許可
private void NotificationGatt() {

  // Androidフレームワークに対してGATT Notification通知登録を行う
  boolean registered = mGatt.setCharacteristicNotification(mDataCharacteristic, true);

  // Characteristic の Notification 有効化
  BluetoothGattDescriptor descriptor = mDataCharacteristic.getDescriptor(
        UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
  descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
  mGatt.writeDescriptor(descriptor);
}

// Notification通知停止
private void DisNotificationGatt() {
  // Characteristic の Notification 有効化
  BluetoothGattDescriptor descriptor = mDataCharacteristic.getDescriptor(
        UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
  descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
  mGatt.writeDescriptor(descriptor);

  // Androidフレームワークに対してGATT Notification通知解除を行う
  boolean registered = mGatt.setCharacteristicNotification(mDataCharacteristic, false);
}

// Notification/Indicateの受信コールバック
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
      BluetoothGattCharacteristic characteristic) {
  
  Log.d(TAG, "onCharacteristicChanged");
  // Characteristicの値更新通知
  if (DEVICE_NOTIFY_CHARACTERISTIC_UUID.equals(characteristic.getUuid().toString())) {
    byte[] read_data = characteristic.getValue();
    Log.d(TAG, "data = "  + read_data[0]  + "," + read_data[1]  + "," + read_data[2]  + "," + read_data[3]  + "," + read_data[4]);
  }
};



・GATT Indicate


Notification と同様にデバイスからの通知に用います。基本的な動作はNotificationと同じですがIndicateでは通知したデータが受け取られたかどうかをBLEデバイス側で応答確認できます。
なお Indicateと Notificationは内部的に同じ機構を用いており、通知時にどちらが使用されたか判定ができません。そのため Indicateと Notification同時に動作させないよう気をつけてください。

<mbed側のコード>

mbedでIndicationを行うコードを抜粋しました。 全ソースはこちら
基本的な部分はNotificationと同じですが設定するフラグが "BLE_GATT_CHAR_PROPERTIES_INDICATE"に変わります。また通知許可/停止以外にIndicate応答を受け取るためのコールバック関数を用意し、通知処理の完了が判定できるようにします。
サンプルコードでは通知を行うときにLEDを点灯し、通知応答が届いたときにLEDを消灯することにより、実際に通知と応答が判るようになっています。

GattCharacteristic  gDataCharacteristic (UUID_CHAR_DATA, gRwData, sizeof(gRwData), sizeof(gRwData),
        GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE);

void UpdatesEnabledCallBack(Gap::Handle_t handle)
{
    DEBUG("UpdatesEnabled\n\r");
    // ユーザ処理コード
}

void UpdatesDisabledCallBack(Gap::Handle_t handle)
{
    DEBUG("UpdatesDisabled\n\r");
    // ユーザ処理コード
}

void ConfirmationRcvCallBack(Gap::Handle_t handle)
{
    DEBUG("ConfirmationReceived\n\r");
    // ユーザ処理コード
}

<Android側のコード>

Android側でIndicateを受けるためのコードを抜粋しました。 全ソースはこちら
アプリ本体のコードはNotificationとほぼ同じです。違いとしては通知を有効にするときに用いる設定値が"ENABLE_INDICATION_VALUE"に置き換わります。ただし通知停止のときに関してはCCCDのEnableフラグを落すだけなので同じ"DISABLE_NOTIFICATION_VALUE"を用います。

// Notification通知許可
private void NotificationGatt() {

  // Androidフレームワークに対してGATT Notification通知登録を行う
  boolean registered = mGatt.setCharacteristicNotification(mDataCharacteristic, true);

  // Characteristic の Notification 有効化
    BluetoothGattDescriptor descriptor = mDataCharacteristic.getDescriptor(
        UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
        mGatt.writeDescriptor(descriptor);
}

// Notification通知停止
private void DisNotificationGatt() {
  // Characteristic の Notification 有効化
  BluetoothGattDescriptor descriptor = mDataCharacteristic.getDescriptor(
        UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
  descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
  mGatt.writeDescriptor(descriptor);

  // Androidフレームワークに対してGATT Notification通知解除を行う
  boolean registered = mGatt.setCharacteristicNotification(mDataCharacteristic, false);
}

// Notification/Indicateの受信コールバック
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
      BluetoothGattCharacteristic characteristic) {
  
  Log.d(TAG, "onCharacteristicChanged");
  // Characteristicの値更新通知
  if (DEVICE_NOTIFY_CHARACTERISTIC_UUID.equals(characteristic.getUuid().toString())) {
    byte[] read_data = characteristic.getValue();
    Log.d(TAG, "data = "  + read_data[0]  + "," + read_data[1]  + "," + read_data[2]  + "," + read_data[3]  + "," + read_data[4]);
  }
};



まとめ

GATTアクセスの基本を一つ一つマスターしていくことにより、難解なBLEデータ通信への理解も少しずつ深まってきたかと思います。次回はBLEデバイスの電力を減らす方法について掲載する予定です。


140 180 Bluetooth , GAP , GATT , mbed , Nordic , nRF51822 , ツール作成

記載されている会社名、および商品名等は、各社の商標または登録商標です。

4 コメント:

  1. mbedのNotificationとIndicationのソースコードのリンクが逆になってません?

    返信削除
  2. ご指摘ありがとうございます。
    この部分を気づかれるということは、実際にmbed コードをお試し頂けたというですね、ありがとうございます。
    こちらの公開フォルダ取り違え原因でしたので、先ほど修正を行いました。

    返信削除
  3. 今更のツッコミですが・・・申し訳ないです・・・。orz

    えーと、NotificationやIndication時は、onDataSentのCallbackを使用すればサンプルソース内で使用しているTickerは必要なくなりますね。

    https://docs.mbed.com/docs/ble-intros/en/latest/AdvSamples/HighData/
    にある、Server to ClientのトコのBLEDevice::onDataSentに関して書かれている箇所がありますが、そのチョイ下に大体こんな感じで・・・・っていうのが載ってます。

    自分も先ほどやっとこさNotificationをやってみて、上記のドキュメントを「やっと」読みました・・・。orz

    onDataSent Callbackを使用すれば、コネクション・インターバルの速さでデータを通知することが出来るので、そちらの方が適切かも知れません。

    https://developer.mbed.org/users/mbed_tw_hoehoe/code/BLE_Nano_MPU9250/
    こちらに私の拙いサンプルを更新してありますので・・・。ご参考になれば・・・。
    ならないかも知れませんが・・・。orz

    返信削除
  4. I have a question of Android side. If more Characteristic have, I call the NotificationGatt() more times. but remote device's discriptor is changed the first call NotificationGatt(). I don't know why?

    返信削除

Related Posts Plugin for WordPress, Blogger...