ラベル BLE の投稿を表示しています。 すべての投稿を表示
ラベル BLE の投稿を表示しています。 すべての投稿を表示

2021年10月1日金曜日

IoT Gateway for BLE meets ESP32

IoT Gateway for BLE meets ESP32

Video

Environments

  • ESP32 devkitc v4
  • IoT Gateway for BLE v1.17
  • CloudMQTT
  • Chrome

Sketch

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 255;

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

void onButton() {
  // sedning integer 255 via notify
  pCharacteristic->setValue((uint8_t*)&value, 4);
  pCharacteristic->notify();
}

void setup() {
  Serial.begin(115200);
  pinMode(0, INPUT_PULLUP);

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  // notify value no button pushed
  if (deviceConnected) {
    static uint8_t lastPinState = 1;
    uint8_t pinState = digitalRead(0);
    if (!pinState && lastPinState) {
      onButton();
    }
    lastPinState = pinState;
  }
  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
}

Websocket

<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
  <script type="text/javascript">
    var wsbroker = "m11.cloudmqtt.com";
    var wsport = 30707
    var username = "xxxxxxx"
    var password = "xxxxxxx"

    var client = new Paho.MQTT.Client(wsbroker, wsport, "myclientid_" + parseInt(Math.random() * 100, 10));
    client.onConnectionLost = function (responseObject) {
      console.log("connection lost: " + responseObject.errorMessage);
    };
    client.onMessageArrived = function (message) {
      console.log(message.destinationName, ' -- ', message.payloadString);
      var counter = $("#counter");
      var num = parseInt(counter.text());
      num++;
      counter.text(num);
    };
    var options = {
      useSSL: true,
      timeout: 3,
      onSuccess: function () {
        console.log("MQTT connected");
        client.subscribe('#', {qos: 1});
      },
      onFailure: function (message) {
        console.log("MQTT disconnected: " + message.errorMessage);
      }
    };
    options.userName = username;
    options.password = password;
    function init() {
      client.connect(options);
    }
    </script>
    <style type="text/css">
      #counter {
        color: #40e0d0
      }
      .container {
        width: 100%;
        margin: 0 auto;
      }
      h1 {
        text-align: center;
      }
    </style>
  </head>
  <body onload="init();">
    <div class="container">
      <header>
        <h1 id="msg">Pushed button count: <span id="counter">0</span></h1>
      </header>
    </div>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/FitText.js/1.2.0/jquery.fittext.min.js" integrity="sha512-e2WVdoOGqKU97DHH6tYamn+eAwLDpyHKqPy4uSv0aGlwDXZKGwyS27sfiIUT8gpZ88/Lr4UZpbRt93QkGRgpug==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script>
    $("#msg").fitText(1.2);
  </script>
</html>

2015年11月30日月曜日

BL600 で RSSI の強度を上げる方法

概要

前回の記事で nrf51822 で RSSI の強度を上げる方法を紹介しました
この場合は例えば nRF51 DK ボード上で動かしていたり、USB ドングルを使って開発している場合にはいいのですが、BL600 等のサードパーティ製の製品上で動かす場合に有効になりませんでした
今回は BL600 上で動作する nRF51 系のアプリで RSSI を強くする方法を紹介します

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0
  • SoftDevice S130

設定方法

ずばり以下を設定する
自分は advertising_init メソッド内でコールしました

nrf_gpio_cfg_output(20);
nrf_gpio_pin_clear(20);

初めこれでできたときは「魔法とはこれか」と思ったくらいビックリしました
詳しいことは良くわからないのですが、どうやら nRF51 DK の P0.20 ピンが VDD 周りの制御をしているらしく、そこに信号を送らないようにすればいいとか、詳しい方ヘルプ、、、

参考サイト

2015年11月27日金曜日

nrf51822 で RSSI の強度を上げる方法

概要

アドバタイジング時に RSSI の強度を強くしたり弱くしたりすることで検知させたいデバイスの距離を操作することができます
nrf51822 で RSSI の強度を設定する方法を紹介します

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0

設定方法

適当な場所で以下をコールすれば OK です
自分は advertising_init 内でコールして問題ありませんでした

err_code = sd_ble_gap_tx_power_set(4);
APP_ERROR_CHECK(err_code);

sd_ble_gap_tx_power_set メソッドは ble_gap.h のヘッダファイルに含まれているのでない場合は include してください

設定できるパラメータは -40, -30, -20, -16, -12, -8, -4, 0, 4 だけです
これ以外を指定すると APP_ERROR_CHECK でエラーとなりアプリが停止します
試した感じだと -40 が弱く 4 が強い設定になります
-40 とかは本当に弱く 20 cm くらい離れると検知できません
4 は正確には計測していませんが、5m くらいは余裕でした

一度設定してからアプリ内で動的に変更することもできるようです

最後に

RSSI の強さが動的に変更できると具体的に何ができるか思いつきませんが、何かおもしろいことができるような気がします

参考サイト

2015年11月25日水曜日

nrf51822 でタイマーのクロック間隔を短くすることで起動時間を長く管理する方法

概要

前回 タイマーを使って起動時間を取得する方法を紹介しました
前回の問題点として管理できる時間が短いという点をあげました
今回はそれが解決でき、長い時間管理できるようになったので紹介します

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0
  • SoftDevice S130

実装方法

タイマーを使う方法は前回の記事を参照してください

まず前提として前回から判明したことが

  • 保存できるカウントの最大値は 24bit = 16, 777, 216 まで
  • 1 秒間のカウントで増加する値は 32, 768

であることがわかりました ( 参考 )

保存できるカウントを増やす方式は難しいため増加するカウントを減らすことで管理できる時間を増やせないか検討してみました

ずばり回答としては

#define APP_TIMER_PRESCALER 0

の値を増やすことです
この値を増やすことで影響するのが

APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);

の部分です
この初期化をしている第一引数で増加するカウントの 32, 768 を割り算しているようです
例えば APP_TIMER_PRESCALER を 16 とすると 1 秒間でカウントされる値は

32, 768 / 16 = 2, 048

になります
なので最大保存時間は

16, 777, 216 / 2, 048 = 8, 192 (sec) = 136 (min) = 2.27 (hour)

になります
一応この値が 32 でも正常に動作することは確認しています

カウントから時間 (秒) を取得する方法は

uint32_t time = p_ticks / (APP_TIMER_CLOCK_FREQ / APP_TIMER_PRESCALER);

で OK です

気になる点

では一体 APP_TIMER_PRESCALER の値はいくつまで設定できるのかと言うとおそらく 4,095 までいけるっぽいです ( かなり不確定、カウントの最大が 24 bit であることから逆算しているみたいです )
確かに 4,095 を設定しても問題なく動くことは確認しました

ただ、今度はタイマーを使っているバッテリーレベルの Characteristics が更新されなくなってしまいました
32 や 16 のときはほぼリアルタイムで read してもバッテリーレベルが更新されていたのですが 4, 095 を設定すると更新されなくなりました

詳しく調査していないので、予想ですがこの後タイマーを作成 (app_timer_create) してスタート (app_timer_start) すると思うのですが、スタートする際にタイマーのインターバルを決定する引数 ( BATTERY_LEVEL_MEAS_INTERVAL) がありこれが APP_TIMER_PRESCALER を使っているため、更新するインターバルの時間が伸びたんだと思います

なので、ちゃんとやるとしたら時間を取得するためだけのタイマーを作成、スタートして、そのポインタから時間を参照するようにしたほういいと思います
チュートリアル を見てもアプリごとに 10 個タイマーを持てるとあるので、そうするのがベターなんだと思います

Tips

わからないことは Nordic Developer Zone で質問してみましょう
基本英語です
もちろん過去ログは検索してから質問したほうがいいです
結構適当な英語でも頑張って解釈してくれるので、ちゃんした返事が返ってきます

2015年11月24日火曜日

nrf51822 でデバイスをスキャンして Macアドレスと RSSI を取得する方法

概要

nrf51822 に Central の機能を実装して BLE デバイスをスキャンしてみました
スキャンした際に対象デバイスの Mac アドレスと RSSI (受信信号強度) を取得してデバッグ出力してみます

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0
  • SoftDevice S130

実装方法

今回は Peripheral ではなく Central の機能を実装するので SoftDevice のバージョンは s120 or s130 が必要になります
なので、あらかじめ上記の SoftDevice を書き込んでおいてください
実装方法は SDK に含まれる Central のサンプルコードベースで説明します

スキャンできるように初期化する

まずスキャンの方法を設定します
サンプルコードでは基本的に main メソッド内で初期化用のメソッドをコールすることで初期化を行っています

スキャンの初期化の流れは ble_stack_init -> softdevice_ble_evt_handler_set -> ble_evt_dispatch -> on_ble_central_evt になります
簡単に説明するとスキャンを実行した際に発生するイベントを on_ble_central_evt メソッドでハンドリングする設定を行っています

このコールの流れは基本サンプルのままで OK です
これが設定されていればスキャンの基本の設定は完了です
次にハンドリングしたイベント内で実際に Macアドレスと RSSI を取得してみます

Macアドレスと RSSI を取得するロジックを追加

修正は on_ble_central_evt メソッドだけになります
このメソッドは発生したイベント ID を取得して switch 文でイベントを分岐しています

static void on_ble_central_evt(ble_evt_t * p_ble_evt) {
    uint32_t                err_code;
    const ble_gap_evt_t   * p_gap_evt = &p_ble_evt->evt.gap_evt;

    switch (p_ble_evt->header.evt_id) {
    case BLE_GAP_EVT_ADV_REPORT: {
            // ...
            break;
        }
    case BLE_GAP_EVT_TIMEOUT:
        // ...
        break;

    case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
        // ...
        break;

    case BLE_GAP_EVT_DISCONNECTED:
        // ...
        break;

    default:
        break;
    }
}

今回はこの中の BLE_GAP_EVT_ADV_REPORT の switch 文を修正します
Mac アドレスと RSSI は引数として渡ってきた ble_evt_t のポインタから取得します

取得方法は以下の通りです

ble_gap_addr_t pa = p_gap_evt->params.adv_report.peer_addr;
int8_t rssi = p_gap_evt->params.adv_report.rssi;
printf("Mac Address: %02x:%02x:%02x:%02x:%02x:%02x \r\n", pa.addr[5], pa.addr[4], pa.addr[3], pa.addr[2], pa.addr[1], pa.addr[0]);
printf("RSSI %d \r\n", rssi);

これをそのまま BLE_GAP_EVT_ADV_REPORT の switch 文中に記載すれば OK です
Mac アドレスは LSB (下位ビットから格納する方式) になっているので、添字の大きい方から参照します

ドットや矢印でいろいろと参照していますが流れとしては

  1. ble_evt_t から evt.gap_evt で GAP (General Access Profile) のイベントを取得
  2. イベントの params フィールドからパラメータを参照
  3. パラメータの adv_report フィールドからアドバタイジング情報を参照
  4. アドバタイジング情報の peer_addr フィールドから Mac アドレス情報を参照
  5. アドレス情報の addr[6] フィールドが Macアドレスが格納されている配列になっている

という感じです
RSSI は adv_report から直接数値が参照できます
この辺りはリファレンスを参照すると、この構造体のこのフィールドを参照しているという流れがわかると思います

スキャンを開始する

これもサンプルをそのまま使っているのであれば特に変更する箇所はありません
main メソッド内 scan_start というメソッドをコールしていれば Central のスキャンを開始することができます

int main(void) {
    uint32_t err_code;
    bool erase_bonds;

    // Some Initialize ...
    ble_stack_init();
    // Some Initialize ...

    // Some Initialize ...
    scan_start();
    // Some Initialize ...

    for (;; ) {
        power_manage();
    }
}

scan_start を辿ってみるとわかると思いますが結構いろいろな設定をしています
スキャンの間隔やホワイトリストを使って、信頼するデバイスだけスキャンするようにするとか、タイムアウトをどうするかなど設定できます
興味があれば、scan_start メソッドのサンプルとリファレンスを見ながらいろいろとパラメータチューニングしてみてください

コードが記載できたら Keil でビルドして nRFgo Studio でアプリケーションを焼きこんでみましょう
焼きこみが完了すれば自動的に nRF51 DK が BLE デバイスとなりスキャンを開始します

試す

アドバタイズしている BLE デバイスは結構街中に飛んでいるのでスキャンを開始すればデバイスが見つかるかもしれません
ない場合はスマホアプリで Peripheral になれるアプリがあるので、それを使ってみてください
自分は SensorTag CC2650 を使いました

デバッグは nRF51 DK を PC に USB で接続して TeraTerm でシリアルモニタをすればいいと思います
ボーレートは 38400 を設定してください

今回のサンプルは printf しているだけなので、デバイスが見つかればそのまま出力されると思います

最後に

コンシューマ向けの BLE デバイスは基本は Peripheral のみが実装されており、iPhone とか Android などのスマホが Central になっています
nrf51822 は Central にもなることでできるのでちょっとおもしろい実装ができます

nrf51822 には pstorage という仕組みもあってデバイス上にデータも保存できるので、スキャンした情報を保存しておくこともできます

pstorage の話もどっかでできればなぁと思っています
あと、サラッと書いたデバッグの方法とかも書いたほうがいいかなと思っています

2015年11月17日火曜日

nrf51822 で Real Time Clock を使って起動時間を取得する方法

概要

nrf51822 上で起動時間を取得するために RTC (RealTimeClock) の仕組みを使ってみました
RTC の値はデバイスの電源の ON/OFF でリセットされる値になります

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0

実装方法

nRF51 DK に含まれる App Timer (以下タイマー) という機能を使って実現します

nRF51 SDK に含まれているサンプルを見るといろいろなサンプルでこのタイマーを使っているので基本はそれをベースにすれば OK です
今回はバッテリーレベル用のタイマーを定義して、それを例に
流れを説明していきます

初期化

まず、タイマーを使うために APP_TIMER_INIT というマクロをコールする必要があります
サンプルではだいたいこんな感じで実装されていると思います

APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);

基本はこれで問題ないです

タイマーの生成

次にタイマーを生成します
生成するには app_timer_create をコールします

uint32_t err_code;
err_code = app_timer_create(&m_battery_timer_id, APP_TIMER_MODE_REPEATED, battery_level_meas_timeout_handler);
APP_ERROR_CHECK(err_code);
static app_timer_id_t m_battery_timer_id;

app_timer_id_t は typedef されている型で実体は uint32_t になります
この ID に対するタイマーを生成していることになります
また、 battery_level_meas_timeout_handler ですがこれは関数になっており、タイマーがタイムアウトしたときにコールされる関数を指定できます
この辺もサンプルを見ると流れが把握できると思いますが、この関数内でバッテリーレベルの再取得を実施し Characteristics の value の更新を行っています

タイマーが生成できたらタイマーをスタートします

タイマーを開始する

タイマーを開始するには app_timer_start をコールします

uint32_t err_code;
err_code = app_timer_start(m_battery_timer_id, BATTERY_LEVEL_MEAS_INTERVAL, NULL);
APP_ERROR_CHECK(err_code);

これをコールしたタイミングから RTC によるカウントが開始されます

現在のカウントを取得する

タイマーを開始したら、現在のカウントを取得してみます
現在のカウントを取得するには app_timer_cnt_get をコールします

uint32_t p_ticks;
app_timer_cnt_get(&p_ticks);
printf("%d \r\n", p_ticks);

こんな感じでカウントしている値を取得できます

ここでちょっとはまったのは app_timer_cnt_get の返り値を参照してもずっと 0 でした
リファレンスを見たのですが、この関数の返り値は正常に取得できたかのフラグを return しているだけなので常に 0 が返っていました
関数側では、受け取った引数のポインタに対して書き込みを行っているので、引数として渡した変数を参照する必要がありました

で実際にこの値を定期的に出力してみたのですが、値を見ると 1, 2, 3, … のような秒数を刻む値ではなく 3256, 6785, 125678, … のようにガンガン値が増えていました
ちょっとこの値に説明します

カウント値について

以下もしかすると、間違いがあるかもしれません

この値について目視でどの程度増えているのか計測してみたのですが、だいたい 30 秒で 1,000,000 カウント増えている感じでした
なので 1 秒間で約 33,000 ほど増えていることになります

何かそれっぽい値を定義している箇所がないか調べてみたのですが、APP_TIMER_CLOCK_FREQ というマクロが定義されておりこの値が 32768 になっていました
おそらくですが 1 秒間にこの APP_TIMER_CLOCK_FREQ の値だけ増加しており、app_timer_cnt_get ではそれが取得できているのだと思います
なので、時間を取得したいのであれば、取得した値を APP_TIMER_CLOCK_FREQ で割り算すればそれっぽい起動時間が取得できると思います

また、値の最大値なのですが、約 16, 800,000 くらいになるとまた 0 にリセットされてカウントが始まりました
( 16, 800, 000 / 33,000 = 510 (sec) = 8 min 30 sec )
なので何回リセットされたかどうかも管理しないとダメそうな感じです
最大値を伸ばす方法とリセットされた回数を取得できる方法があるかどうかは、すいません、調査しきれていません、、、

ちょっとこの辺が曖昧な記載なのは、そこまで深く調べていないので自信がないためです
間違いがや追加の情報あればご指摘いただけると助かります

最後に

とりあえず nrf51822 で起動時間っぽい値を取得できるようになりました
完全に使うためにはもう少し実装が必要そうですが、ベースとなる呼び出し方は理解できたと思います

10分くらいでリセットされるタイマーだとちょっと管理が大変なので、半日分くらいは管理できるようにしたいなと思っています

2015年11月11日水曜日

nRF51822 の Advertise で Scan レスポンスパケットを設定する方法

概要

Scan レスポンスパケットは Advertising でスキャンした後にレスポンスとして Peripheral が Central に送信できるおまけパケットです
Advertising パケットは上限が 31 バイトになっておりその上限を超えて何かデータを送りたいときなどに使います
データの構造は Advertising パケットと同様になっています

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0

背景 (なぜ Scanパケットを設定したかったのか)

ちょっと長くなりますが背景を詳しく説明します

サンプルのせいにするわけではないですが nRF51 SDK に含まれている各種サンプルでは基本的に Scanパケットを設定していません
で、Central 側の実装を Python ベースの gattlib というライブラリを使って実装していたのですが、Advertising パケットをスキャンした際に DeviceName が含まれずに困っていました
具体的には DiscoveryService.discover というメソッドを使うと DeviceName が取得できない状況でした

切り分けのため TI 社が提供する SensorTag や iPhone で実装した Peripheral デバイスを使ってスキャンしてみたのですが、その場合はうまく DeviceName が取得できました
なので、nRF51822 上に実装しているアプリが悪そうだ、ということでいろいろ調べたところ Scan レスポンスパケットを設定してあげることで、nRF51822 でも DeviceName を取得することができるようになりました

更に余談になってしまうのですが、hcitool の lescan というコマンドを使っていると DeviceName が (unknown) と表示されることにも気付きました
unknown がちょくちょく出ていましたが、ちゃんと DeviceName が表示されることもあったので初めは無視していました
また、nRF51822 の場合でも SensorTag の場合でも unknown が表示されていたので「こんなもんなのか」と思っていたのですが、discover すると nRF51822 だけ DeviceName が取得できないのはおかしいということに気が付き詳しく調べてみたところ Scan レスポンスパケットという仕様にたどり着き設定してみたらうまくいった感じです

ということは今思うと SensorTag は Scan レスポンスパケットを設定しておりかつ、gattlib の DiscoveryService.discover は Advertising パケットの DeviceName を見ているわけではなく、Scan レスポンスパケットの DeviceNam を見ている ( のではと今になって予想しています、コードレベルまで見ていないので正解がどうかは不明です )

長くなりましたが、まとめると

  • gattlib の DiscoveryService.discover で DeviceName が取得したかった

ために設定した感じです

設定方法

前置きかなり長くなりましたが実装方法です
nRF51 SDK のサンプルでは多くの場合、 main メソッド内で各処理の初期化を行っています
その中の advertising_init というメソッドで Advertising パケットの設定をしておりそこを修正します

具体的なコードの全貌は以下のとおり

static void advertising_init(void) {
    uint32_t      err_code;
    ble_advdata_t advdata;
    ble_advdata_t rspdata;

    // Build advertising data struct to pass into @ref ble_advertising_init.
    memset(&advdata, 0, sizeof(advdata));
    memset(&rspdata, 0, sizeof(rspdata));

    advdata.name_type               = BLE_ADVDATA_FULL_NAME;
    advdata.include_appearance      = true;
    advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    advdata.uuids_complete.p_uuids  = m_adv_uuids;

    rspdata.name_type               = BLE_ADVDATA_FULL_NAME;

    ble_adv_modes_config_t options = {0};
    options.ble_adv_fast_enabled  = BLE_ADV_FAST_ENABLED;
    options.ble_adv_fast_interval = APP_ADV_INTERVAL;
    options.ble_adv_fast_timeout  = APP_ADV_TIMEOUT_IN_SECONDS;

    err_code = ble_advertising_init(&advdata, &rspdata, &options, on_adv_evt, NULL);
    APP_ERROR_CHECK(err_code);
}

ポイントは rspdata 変数を宣言して rspdata.name_type = BLE_ADVDATA_FULL_NAME; として DeviceName を設定するところです

冒頭でも説明していますが、Advertising パケットと Scan レスポンスパケットはデータ構造が同じです
なので、すでに宣言している advdata を使いたいところですがこれを使うとうまく動作しませんでした

また、 rspdata に DeviceName だけ表示するように設定していますが、これもポイントで他のパラメータも advdata と同じように設定したところ、これまたうまく動作しませんでした
( うまく動作しない明確な理由がわからない状態です、すいません )

なのでとりあえず上記のように、記載することで Scan レスポンスパケットを設定することができました

最後に

これで再度 hcitool lescan をすると nRF51822 で実装した BLE デバイスに unknown が表示されなくなっていることが確認できると思います
Advertising + Scan レスポンスパケット両方で DeviceName を設定するようにしたためです

ただ、他のデバイスを見るとほとんどのデバイスで unknown になることがあるので、おすすめ実装的にはやっぱりどっちかだけで DeviceName を送信するほうがいいのかもしれません
まぁ確かに同じデータを 2 回送るのであれば無駄といえば無駄ですからね
とは言え Central 側の実装も考慮しなければいけないと考えるととりあえず両方設定しておいたほうが無難といえば無難なのでしょうか

相変わらず BLE の実装は大変です

2015年11月6日金曜日

nRF51822 + nRF51 DK を使って BLE デバイスの開発環境を構築

概要

nRF51822 とは NordicSemiconductor 社が提供する BLE モジュールです
nRF51822 には専用の開発用のボード (nRF51 DK) がありこれを使うことで BLE デバイスの開発を行うことができます
今回は開発環境の構築方法を紹介します

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • MicroUSB x 1

各種インストール

各種インストールの作業は Windows 上で行っていきます

アプリ焼きこみ用のツール nRFgo Studio のインストール

開発したアプリをモジュールに焼きこむためのツールです

  • https://www.nordicsemi.com/chi/node_176/2.4GHz-RF/nRFgo-Studio にアクセス
  • 「DOWNLOADS」タブを選択
  • nRFgo Studio-Win64 を選択しダウンロード
  • ダウンロードした nrfgostudio_win-64_1.21.0_installer.msi を実行しインストール
  • インストール中の Choose Setup Type は Typical を選択
  • ドライバをインストールする確認ダイアログが続けて表示されるのですべて OK にする
  • 続けて nRF Tools をインストールする旨が表示されるので続けてインストール
  • J-Link のドライバをインストール

で nRFgo Studio がインストールできます
同時に J-Link のドライバもインストールするのを忘れないようにしてください
2015/11/05 時点での最新版は 1.21.0 でした

開発用の IDE Keil のインストール

Keil という組み込み開発で使用する IDE をインストールします

  • https://www.keil.com/download/ にアクセス
  • 「Download」タブを選択
  • 「Product Downloads」を選択
  • 「MDK-ARM」を選択
  • ダウンロードするために個人情報を入力 (keil だけダウンロードに必要)
  • ダウンロードのページに遷移したら MDK517.EXE を選択しダウンロード
  • ダウンロードした MDK517.EXE を実行しインストール

で Keil の μVision という IDE がインストールできます
デフォルトの設定だと C ドライブ直下に C:\Keil_v5 というディレクトリ名でインストールされます
IDE を起動する場合は C:\Keil_v5\UV4\UV4.exe を実行してください
2015/11/05 時点での最新版は 5.17.0 でした

nRF51 SDK のインストール

開発に必要な SDK をインストールします

ダウンロードが完了したら解凍して適当な場所に配置すれば OK です
nRFgo Studio から参照します
zip をダウンロードする際に他の最新版があればそちらをダウンロードしても OK です
2015/11/05 時点での最新版は 9.0.0 でした

この作業は nRF Studio のインストール時に完了しています
最新版をインストールしたい場合にだけ実施してください

  • https://www.segger.com/jlink-software.html にアクセス
  • Software and documentation pack for Windows の最新版を Download
  • ダウンロードが完了したら zip を解凍し Setup_JLink_V502i.exe を実行しインストール
  • インストール中の「Install USB Driver for J-Link」にチェック

でドライバがインストールされるはずです
2015/11/05 時点での最新版は 502i でした

配線

開発ボードと Windows を接続していきます
開発ボードへの電源の供給は PC から行います

配線は非常に簡単で開発ボード側の MicroUSB と PC の USB ポートを接続すれば OK です
基本はこれだけで開発できます

他にはデバッグ用のシリアル通信や nRF51822 モジュールへの書き込みを行うための配線がありますが、とりあえず BLE デバイスを開発するだけであれば上記で十分です

配線が完了したら開発ボードの電源を ON にしましょう

サンプルの焼きこみ

では開発ボードにサンプルアプリを焼きこんでみましょう
今回は簡単な Peripheral アプリを焼きこんで、ちゃんとアドバタイズされるか確認してみます

配線を完了し開発ボードの電源が入っている状態で nRFgo Studio を起動します
起動したら左ペインの「Device Mangaer」に nRF51 development boards があり、その中に Segger … で始まるデバイスが認識されていることを確認してください
これが表示されない場合は開発ボードがうまく Windows 側に認識されていないので、ドライバのインストールを再度実施してください

SoftDevice の焼きこみ

Segger を選択した状態で右ペインに移動します
まずは SoftDevice を焼きこんでいきます
SoftDevice とは簡単にいうとミドルウェアのようなもので、開発するアプリはこの SoftDevice 上で動作します

右ペインの「Program SoftDevice」タグを選択します
そして「File to program」に SoftDevice を指定します
指定する SoftDevice はダウンロードした SDK に含まれています
例えば C ドライブ直下に SDK を配置したのであれば

C:\nRF51_SDK_9.0.0_2e23562\components\softdevice\s110\hex\s110_softdevice.hex

にあります
選択したら「Program」ボタンを押して焼きこみを開始します
ダイアログが表示され最終的に successfully になれば OK です

サンプルアプリのビルド

ここがちょっとだけ面倒です
サンプルアプリ用の .hex ファイルが SDK 内に同梱されておらずソースファイルから自分でビルドして作成する必要があります
まず Keil を開きましょう
開いたら SDK に同梱されているサンプルプロジェクトを開きます

Project -> Open Project

で以下を指定して開きます

C:\nRF51_SDK_9.0.0_2e23562\examples\ble_peripheral\ble_app_hrs\pca10028\s110\arm5_no_packs\ble_app_hrs_s110_pca10028.uvprojx

するとプロジェクトが開きます
今回は特にソースを修正する必要はないので開いたらいきなりビルドします

Project -> Build target

でビルドが始まります
プロジェクト内に存在する C のソールコードがコンパイルされ最終的に .hex が作成されれば OK です
.hex ファイルはarm5_no_packs\_build\nrf51422_xxac_s110.hex に作成されます

サンプルアプリの焼き込み

作成できたアプリを焼きこんでいきましょう
再度 nRFgo Studio に戻ります

今度は右ペインの「Program Application」タグを選択します
そして、FIle to program に先ほど作成した .hex ファイルを指定します

C:\nRF51_SDK_9.0.0_2e23562\examples\ble_peripheral\ble_app_hrs\pca10028\s110\arm5_no_packs\_build\nrf51422_xxac_s110.hex

選択したら「Program」を選択します
SoftDevice の書き込み同様ダイアログが表示され最終的に successfully になれば書き込み完了です

動作確認

今回は iPhone で確認してみます
スマホアプリに BLE を検知できるアプリがいくつかあるのでそれを使います

開発ボードの電源が入っている状態でアプリを立ち上げてみましょう
すると開発ボードに焼きこまれたアプリがBLEデバイスとして検知できると思います
今回の場合「Nordic_HRM」というデバイス名でアプリを書き込んでいるのでその名前で見つかると思います

最後に

紹介は以上です
今回 nRF51822 モジュールに直接書き込みはしませんでした
モジュールに書き込みを行いたい場合は Debug out という 10pin の I/F が開発ボードにあるので、ジャンパを使って nRF58122 が乗った別のボードと接続した状態で nRF Studio で Program すれば焼きこむことができると思います

あとは main.c なりソースの修正をしてビルドして再度 .hex ファイルを作成して焼き込めば開発できると思います
ただ、これだと print 文などをデバッグできないので作業効率が悪いです
やはり開発ボードとシリアル通信して print 文をデバッグした方がいいです
次回にでもシリアル通信の記事を紹介できればと思います

Tips

アプリを修正して Program してうまく動作しない場合は一旦「Erase all」してから再度 SoftDevice -> アプリの順番で書き込みを行ってみてください

2015年10月29日木曜日

gatttool を使って SensorTag CC2650 の KeyPressState の Notification を受信してみた

概要

bluez の gatttool と SensorTag CC2650 を使って SensorTag 側のボタンが押されたことを gatttool 側で受信してみました
たぶんこんなニッチな記事を日本語で書いているのはこれくらいだと思う、、、

環境

  • Raspberry Pi Type B Single Board Computer 512MB
  • Raspbian 8.0 (Jessie)
  • Kernel Version 4.1.7+
  • BlueZ 5.23-2+b1

事前作業

RaspberryPi の構築 や bluez のインストール を実施して gatttool が使えるようになるようにしてください

そもそも notification とは

Bluetooth Low Energy のデバイスから情報 (GATT) を読み込みするための方法の 1 つです
読み込みするには、他に read という方法がありこれはデバイスに対して直接データを読み込みにいく方法です
対して notification はデバイス側からデータをプッシュすることで受け取り側にデータを読みこませる方法です

notification のポイントとしては初めにデバイス側に notification の受信要求を実施させる必要がある点です
この受信要求はどうやっているかと言うとデバイス側のある書き込み可能領域に 0 or 1 のフラグを書き込みます
書き込みに成功すると書き込んだ側に対して初めて notification を送信することができます

  • read
    enter image description here

  • notification
    enter image description here

gatttool で notification の受信

前置きが長くなりましたが試していきます
まずは SensorTag の電源を ON にして connection 待機状態 (Advertising) にしましょう
CC2650 であれば緑の LED が点灯すれば OK です

では gatttool で接続していきます
コマンドは以下の通りです

  • gatttool -t public -b xx:xx:xx:xx:xx:xx --char-write-req --handle=0x4a --value=0100 --listen

「-b」のあとには Mac アドレスが入るので持っている SensorTag の Mac アドレスを入力してください
その他のパラメータはそのまま使ってください

簡単に説明すると --char-write-req で notification のための書き込み要求をしています
--handle で書き込みをするアドレスを指定して、そのアドレスに--value で書き込む値を指定します
最後に --listen することで書き込み後にデバイスからの notification を待つようにしています

コマンドを実行すると以下のように書き込みが成功した旨が表示されます

Characteristic value was written successfully

これが表示されれば notification を受け取る準備ができました
SensorTag の横にある (電源とは逆の方向にある) ボタンを押してみましょう
すると以下のようにボタンが押されたイベントを受信することができると思います

Notification handle = 0x0049 value: 01
Notification handle = 0x0049 value: 00

gatttool の場合は handle と value が表示されるだけのようです
0x0049 はボタンが押されたときの状態を管理するアドレスです
value の 00, 01 はそれぞれ「ボタンから離れた時の状態」「ボタンが押された時の状態」になります
長押ししてから話すと状態の遷移の様子がよくわかると思います

そもそも、その handler が書き込めるってどうやって発見したんだ

notification の仕組み自体を理解するまでも大変でしたが、0x0049 のハンドラに書き込みを行うことでなぜボタンを押したイベントを notification で受け取れるのか・・・
ということをどうやって知ったかですが正直いろいろ調べて試しました
その経験上こうやってやると、うまく調べられるという方法も紹介します

まず BLE 機器には「ここのこのアドレスに read するとこんな値が取れるよ」というアドレス一覧表みたいなのがあります
SensorTag の一覧表は以下になります
http://processors.wiki.ti.com/images/a/a8/BLE_SensorTag_GATT_Server.pdf

SensorTag に触ったことがある人であればこの表は一度は見たことがあると思います
で、実はこの表は CC2650 には完全に当てはまりません
例えば 0x73 というハンドラは表で見ると「Test Config」とあって RW で書き込めるはずですが、実際に書き込んでみると書き込めません

ではハンドラを探す方法ですがまずこの表の中で「Key Press State」を管理している UUID を探します
すると UUID は「0xFFE1」だということがわかります
find_key_press.png

そしてその下に「Client Characteristic Configuration」が RW で存在していると思います
Client Characteristic Configuration はクライアントが書き込み可能な領域として定義されているものです
そしてその Client Characteristic Configuration の UUID を見てみると「0x2902」だということがわかります
※もしかしたら UUID も SensorTag の型で違う可能性がありますが、経験上 UUID は他の型でも同じ場合が多いです

とりあえずこの UUID を覚えておきましょう
次に primary コマンドを使って SensorTag が持つサービスの一覧を表示します
その中で Key Press State を管理するサービスを探します

attr handle: 0x0047, end grp handle: 0x004b uuid: 0000ffe0-0000-1000-8000-00805f9b34fb

「これが何で Key Press State のサービスってわかるんだ」と思う方もいると思いますが、先ほどの表で「ffe0」で検索すると「Simple Key Service」というのがあると思います
「ffe0」はどこから持ってきたかというと primary コマンドで、ずらっと表示された中に uuid が 0000ffe0 で始まるものを探しました

サービスがわかったら、このサービスが持つハンドラの範囲 (0x0047 - 0x004b) に存在する UUID を更に調べます
調べるには char-desc というコマンドを使います

  • char-desc 0x47 0x4b

すると以下のような結果が返ってくると思います

handle: 0x0047, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0048, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0049, uuid: 0000ffe1-0000-1000-8000-00805f9b34fb
handle: 0x004a, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x004b, uuid: 00002901-0000-1000-8000-00805f9b34fb

この中に先ほど調べた「Client Characteristic Configuration」の uuid 「0x2902」があり、そのハンドラを見ると「0x004a」だということがわかります
また、「0x0049」というハンドラが notification 受信時に出力されたハンドラと一致することも確認できると思います

表と照らしあわせてみると分かりますが、表のハンドラは「0x6A」であるのに対して調べてみたわかったハンドラは「0x4A」でやはり CC2650 は表と違うことがわかると思います

最後に

BLE の情報は Web で探してもなかなか見つからず、解決するのに時間と根性をかなり費やすとつくづく感じさせられます