2017年4月5日水曜日

ESP32からBLE GATTのnotifを発信し、nodejs(noble)で受信する方法


下記動画のように、ボタンを押すと通知を飛ばす仕組みを作れました。



GATTの通知に関する情報が少ない気がするので、内容を共有します。

背景

ESP32のGATTに関するライブラリにはnotification用の関数が提供されています。
しかし、ESP32のbluetoothに関するサンプルコードには、notification(通知)に関する記述が見当たりません。(2017/4/4時点)
今回試行錯誤してみたところ、ESP32からindicateして、PCでそれを受信できました。

この記事ではその方法を説明します。

使ったもの

  • esp32
    BLEとwifiの通信ができるモジュールです。
  • esp-idf
    esp32などのプログラムを作成するツールキットです。
    PCにインストールしてください。
    こちらの記事が参考になるかもしれません。
    ubuntuからESP32にbluetooth接続して信号を送受信する方法
  • nodejs
    javascriptでweb以外のプログラムを書けるフレームワークです。
    お使いのPCにインストールしてください。
  • @abandonware/noble
    BLE操作に関するnodejsのライブラリです。
    2020.12.31: 本家のnobleは開発が止まっているので、フォークして保守が継続しているnobleのリンクに変更しました。

esp32のプログラム説明

gatt serverのサンプルプログラムをベースにして、notifを送信するためのプログラムを作成しました。

全体のコードはこちらです。

asukiaaa/esp32-idf-samples/gatt_server_notif_switch/main/gatt_server_notif_switch.c

変更箇所を説明します。

GATT indicate

esp_ble_gatts_send_indicateという関数を使うとnotifを送信できます。
その関数を都度使うのは面倒なので、ble_indicateという関数を定義して使いやすくしています。
通知先は、esp_gatt_for_indicateという名前のグローバル変数で管理します。
esp_gatt_if_t gatts_if_for_indicate = ESP_GATT_IF_NONE;

static void ble_indicate(int value) {
    if (gatts_if_for_indicate == ESP_GATT_IF_NONE) {
        printf("cannot indicate becaoute gatts_if_for_indicate is NONE\n");
        return;
    }
    printf("indicate %d to %d\n", value, gatts_if_for_indicate);
    uint16_t attr_handle = 0x002a;
    uint8_t value_len = 1;
    uint8_t value_arr[] = {value};
    esp_ble_gatts_send_indicate(gatts_if_for_indicate, 0, attr_handle, value_len, value_arr, false);
}

indicate先のgatts_if_for_indicateを、GATTの接続時に設定し、接続解除時に削除する処理を追加します。
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
    switch (event) {
..
..
    case ESP_GATTS_CONNECT_EVT:
        gatts_if_for_indicate = gatts_if;
        printf("set %d for gatts_if_for_indicate %d\n", gatts_if);
..
..
    case ESP_GATTS_DISCONNECT_EVT:
        gatts_if_for_indicate = ESP_GATT_IF_NONE;
        printf("set NONE for gatts_if_for_indicate\n");
..
..
}

これにより、GATTの接続が有効な状態でble_indicate()を呼ぶと、notificationのためのindicateを発行できます。

gpioのinputでボタン押しを検知して、indicateを実施

gpioのライブラリを利用して、IO0のボタンを操作したら先程作成したble_indicaate()を実行しています。

IO0に割り込み処理を設定するための値を用意します。
IO18の設定は、GPIOのサンプルにあったのを消さずに置いているだけで、このプログラムでは使っていません。
#define GPIO_INPUT_IO_0     0
#define GPIO_INPUT_IO_1     18
#define GPIO_INPUT_PIN_SEL  ((1<<GPIO_INPUT_IO_0) | (1<<GPIO_INPUT_IO_1))

#define ESP_INTR_FLAG_DEFAULT 0

gpioの割り込み時に実行するタスクとして、gpioの値をble_indicateに渡す関数を定義します。
static void gpio_task(void* arg) {
    uint8_t io_num;
    while (true) {
        if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
          int io_level = gpio_get_level(io_num);
          printf("GPIO[%d] val: %d\n", io_num, io_level);
          ble_indicate(io_level);
        }
    }
}

GPIOの初期化用の関数を定義します。
static void init_switch() {
    gpio_config_t io_conf;
    //interrupt of rising edge
    io_conf.intr_type = GPIO_PIN_INTR_POSEDGE;
    //bit mask of the pins, use GPIO here
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    //set as input mode
    io_conf.mode = GPIO_MODE_INPUT;
    //enable pull-up mode
    io_conf.pull_up_en = 1;
    //disable pull-down mode
    io_conf.pull_down_en = 0;
    //configure
    gpio_config(&io_conf);

    //change gpio intrrupt type for one pin
    gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);

    //create a queue to handle gpio event from isr
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    //start gpio task
    xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL);

    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
}

スイッチ操作に関する準備ができたら、作成したinit_switch()をapp_mainで呼び出し、スイッチ操作を有効にします。
void app_main()
{
..
..
  init_switch();

  return;
}

デバイス名の変更

好きなデバイス名に変更できます。
今回のプログラムではESP_GATTS_SWITCHに変更しました。
#define TEST_DEVICE_NAME            "ESP_GATTS_SWITCH"


プログラムの書き込み

esp-idfをインストール済みのPCにESP32を接続して、下記のコマンドでビルドとプログラムの書き込みを行ってください。
cd [esp32-idf-samples]/gatt_server_notif_switch
make flash

ESP32の準備ができました。

nodejsのプログラム説明

プログラム全体のコードはこちらです。

asukiaaa/esp32-nodejs-samples/listen-notification.js

nobleでnotificationの監視設定

nobleのcharacteristic.notifyで監視を開始し、characteristic.on('data')でデータを受信した時に行いたい処理を設定しています。
..
..
chara.notify(true, (error) => {
  if (error) {
    console.log('listen notif error', error)
  } else {
    console.log('listen notif')
  }
})
chara.on('data', (data, isNotif) => {
  console.log('receoved notif', data, isNotif)
})
..
..

実行

indicateするプログラムを書き込んだESP32を動作させた状態で、notificationを監視するプログラムを実行します。
cd [esp32-nodejs-project-dir]
npm install # for noble
sudo node listen-notification.js

npm installで失敗する場合は、nobleに必要なプログラムがPCに入っていない可能性があるので、nobleのページのインストール方法をご覧ください。

sandeepmistry/noble

ESP32との接続に成功すると、下記のようなログが表示されます。


この状態でESP32のIO0ボタンを押すと、ログに送信された内容が表示されます。


共有する情報は以上です。
間違いがありましたら、良ろしければコメントなどでお知らせください。
この情報が役に立てば嬉しいです。

参考

how to send BLE notification to client
GATT SERVER API

変更履歴

2017/04/12
「デバイス名の変更」を追加しました。

2020/12/31
本家のnobleの更新が止まっているので、フォークして保守が続いている@abandonware/nobleへのリンクに変更しました。

0 件のコメント :