2017年4月12日水曜日

ESP32を2つ使い、GATT clientとGATT server間で情報をやりとりする方法


GATT clientのサンプルコードを書き換えて、前回作成したGATT serverに接続して、notifを取得できました。
方法を共有します。

使ったもの

  • ESP32モジュール2つ
  • esp-idfをインストールしたPC
  • シリアルモニタプログラム
    ログの確認に使いました。
    ubuntuのscreenコマンドを利用しました

GATT server

前回の投稿で作成したプログラムを利用しています。
詳しくはこちらをご覧ください。

プログラム: esp32-idf-samples/gatt_server_notif_switch/main/gatt_server_notif_switch.c
記事: ESP32からBLE GATTのnotifを発信し、nodejs(noble)で受信する方法

GATT client

利用したプログラムはこちらです。

esp32-idf-samples/gatt_client_listen_notif_then_output_to_led/main/gattc_demo.c

このプログラムは、esp-idfのgatt clientに関するサンプルプログラムの一部を変更したものです。
変更内容を説明します。

通知を受け取るGATT serverに合わせて設定変更

device_name, service_id, characteristic_idをそれぞれGATT serverとして利用するデバイスに合わせます。
今回は下記デバイスのnotifを受信します。

自作デバイスのnotif発信情報
device_name: ESP_GATTS_SWITCH
service_id: 0xff
characteristic_id: 0xff01

上記の設定を反映するために、編集を定義・編集します。
static esp_gatt_srvc_id_t alert_service_id = {
..
            .uuid = {.uuid16 = 0xff,}, //origin: 0x1811
..
};

static uint16_t listen_char_id = 0xff01;

static const char device_name[] = "ESP_GATTS_SWITCH"; //origin: "Alert Notification"

gatt_profile_a_event_handlerの内容を、先程定義した変数に変更します。
static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
..
    case ESP_GATTC_GET_CHAR_EVT:
..
        if (p_data->get_char.char_id.uuid.uuid.uuid16 == listen_char_id ) { //origin: 0x2a46
..

..
}

これで、自作したデバイスのnotifを受信できるようになりました。


受け取った通知の内容に応じて、LEDを光らせるための変更

ログを確認しなくても通知の受信を確認したいので、下記のように動作させとうと思います。

通知の値が0: LEDをON
通知の値が1: LEDをOFF

GPIOを使うためのライブラリと定数を宣言します。
#include "esp_system.h"

#define GPIO_OUTPUT_IO_0    2 // for LED
#define GPIO_OUTPUT_IO_1    19
#define GPIO_OUTPUT_PIN_SEL  ((1<<GPIO_OUTPUT_IO_0) | (1<<GPIO_OUTPUT_IO_1))


初期化とLEDの操作のための関数を定義します。
static void init_led() {
    gpio_config_t io_conf;
    //disable interrupt
    io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
    //set as output mode
    io_conf.mode = GPIO_MODE_OUTPUT;
    //bit mask of the pins that you want to set
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    //disable pull-down mode
    io_conf.pull_down_en = 0;
    //disable pull-up mode
    io_conf.pull_up_en = 0;
    //configure GPIO with the given settings
    gpio_config(&io_conf);

    printf("led initlalized");
}

static void switch_led(bool value) {
    int out_value = 0;
    if (value) {
        out_value = 1;
    }
    printf("out value %d\n", out_value);
    gpio_set_level(GPIO_OUTPUT_IO_0, out_value);
}


通知がきたら、値に応じてLEDの光り方を変化させます。
static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
..
    case ESP_GATTC_NOTIFY_EVT:
        switch_led(0 == (*(uint32_t *)p_data->notify.value % 2));
..
}


定義した初期化の関数をmainで呼びます。
void app_main()
{
..
    init_led();
}

これで、受信した通知の値によってLEDの光り方が変わるようになりました。

プログラムの書き込み

GATT clientとして動作させたいESP32をPCに接続して、下記のコマンドでプログラムを書き込みます。
cd [this gatt client project directory]
make flash

動作確認

それぞれのデバイスを単体で動作させると、何が起きているのか分かりにくいので、うまく動かない時はログを見ながら動作確認することをお勧めします。

ubuntu環境下では、ターミナルを2つ立ち上げ、それぞれでscreenコマンドを起動すると、2つのESP32からのログを確認できます。

ターミナルで下記のコマンドを実行します。
screen /dev/ttyUSB0

別ターミナルで下記のコマンドを実行します。
screen /dev/ttyUSB1

GATT接続が成功した時のログはこのような感じです。
左がGATT client、右がGATT serverです。



GATT serverのスイッチを押すと、双方のログが更新されると共に、GATT clientのLEDが点灯します。



共有する情報は以上です。

参考

how to send BLE notification to client
GATT SERVER API: esp_ble_gatts_send_indicate

変更履歴

2017.04.26
デバイス名比較に関する修正が反映されたので、それに関する記述を削除しました。

13 件のコメント :

  1. 参考にさせて頂いています。どうもありがとうございます。
    タクトスイッチ以外で、条件でClientのLEDを点灯させるのに
    以下のような文で動作させようとしましたが、コンパイルはできても動作はしませんでした。

    if (adc1_get_voltage(ADC1_TEST_CHANNEL) > 3000) {
    ble_indicate(1);
    }

    どのような文にすればいいか教えていただけませんか?

    宜しくお願いします。

    返信削除
    返信
    1. コメントありがとうございます。
      原因は分かりませんが、自分だったら下記の確認をしてみます。

      1. adc1_get_voltageは期待する値を取得しているのでしょうか?
      adcとして期待する値が取れていないのかもしれません。
      下記のような記述でadcの値をprintして、シリアルモニタで確認してみてください。

      printf("adc_value: %d\n", adc1_get_voltage(ADC1_TEST_CHANNEL));


      2. adc1のピンは合っていますか?
      adc1は(2017/8/24時点では)下記のように、不規則にピンが割り当てられているようです。

      ACD1_CHANNEL: GPIO
      0: 36
      1: 37
      2: 38
      3: 39
      4: 32
      5: 33
      6: 34
      7: 35

      参考: esp-idf: Analog to Digital Converter #adc1_channel_t


      どうでしょう?

      削除
    2. ご回答どうもありがとうございました。

      説明が不足しておりました。

      IO_32に3.3Vを接続したところ

      The adc1 value:4095..

      と表示されますので、期待される値(>3000)を取得しているようです。
      それよりも、BLE Sever側から BLE Client側のIO_2のLEDを光らせるトリガーの呼び出しがうまくいかないようです。

      ble_indicate(1);

      ではClient側のLEDは光らせられないのでしょうか?

      gpio_task;

      でも

      237:3: error: statement with no effect [-Werror=unused-value]
      gpio_task;
      ^
      cc1.exe: some warnings being treated as errors

      のようなエラーがでてコンパイルできませんでした。

      宜しくお願いします。

      削除
    3. 電圧は期待通り計測できているようで良かったです。

      gpio_taskは引数を1つ取る関数なので、「gpio_task(true);」のように記述すると、コンパイルはできると思います。
      しかし、想定していない呼び出し方なので期待通りに動くかは分かりません。

      この記事はgatt_serverから信号を送り、gatt_clientのLEDを光らせますが、それができないということですよね?
      先程自分が公開している下記のコードの組み合わせで記事の内容を実現しようとしたところ、esp-idfのアップデートに対応していなくて動きませんでした。
      (具体的にはGATTとして接続出来ませんでした。)
      gatt_server_gpio_notif
      gatt_client_listen_notif_then_output_to_led

      修正を試みましたが、思っていたよりも比較する項目が多く、すぐには対応できません。
      そのため、自分の修正を気長にお待ちいただくか、ご自身で試行錯誤される必要がありそうです。
      (お急ぎでしたら、技術支援の業務として自分にプログラムの修正を依頼するという手もあります。)

      お手数をおかけします。

      削除
  2. ご回答どうもありがとうございます。

    gatt_server側でADCからのデータを受信して、そのデータが所定の数値を超えていたら、gatt_client側のLEDを光らせたいと思いました。

    最初の書き込みで
    ble_indicate(1);
    と書いていましたが、考えてみると、IO_0はたくとスイッチでGNDに接続されていましたので、ONは「1」ではなく、「0」ではないかと思い
    ble_indicate(0);
    と書き込んだところ、無事LEDは光りました。どうもありがとうございました。

    わからないところがありましたら、また質問させていただくことがあると思いますが、よろしくおねがいします。

    返信削除
    返信
    1. 期待通りに動いたようで良かったです。

      自分の知っていることや、30分位の調査で分かることでしたら、お答えしますので、よろしくお願いします。

      削除
  3. 了解しました。ご回答どうもありがとうございました。

    返信削除
  4. 先日、無事gatt serverからclientにスイッチ信号を送れて、目的は達成したのですが、今度はclientからのスイッチ信号をserver側に送りたいという要望を受けました。
    esp_ble_gatts_send_indicateはserver側の関数であり、client側の関数を探しましたが、見つかりませんでした。ご存知でしたらお教え願います。
    宜しくお願いします。

    返信削除
    返信
    1. 下記のプログラムを使い、「PCのnodejsプログラムをclient」「ESP32をserver」としてclientからserverの情報送信をしたことはあります。
      そのため、「clientでwrite」「serverでwriteされた値の読み取り」をすれば、期待する動作を実現できると思います。
      write-and-listen.js
      gatt_server_gpio_notif.c

      上記のnodejsのプログラムでしているwriteをESP32にさせればよいのですが、ここで問題になるのが、clientのwriteに関する関数の使い方がよく分からないことです。
      下記のドキュメントにwrite関数は示されているのですが、esp-idfのexamplesをザックリ探してみてもwriteをしている箇所が見つからないため、使い方に関する試行錯誤が必要になりそうです。
      GATT_CLEINT_API

      自分の持っている情報は以上です。
      いかがでしょう?

      削除
    2. ご回答どうもありがとうございます。

      以下のサイトの
      https://github.com/asukiaaa/esp32-idf-samples/tree/master/gatt_client_car_controller

      gattc_demo.c
      の中にある。
      esp_ble_gattc_write_char(
      gattc_if_to_write,
      conn_id_to_write,
      &srvc_id_to_write,
      &write_descr_id,
      sizeof(sending_values),
      (uint8_t *) sending_values,
      ESP_GATT_WRITE_TYPE_RSP,
      ESP_GATT_AUTH_REQ_NONE);
      は参考にならないでしょうか?

      削除
    3. 情報ありがとうございます。
      自分が書いたプログラムなのに、すっかり忘れていました。
      はい、そのプログラムのように、接続したBLEデバイスの情報を変数として保持し、esp_ble_gattc_write_charで利用すれば、clientからserverに対して情報を渡せます。

      自分はそのプラグラムでラジコンのコントローラを作りました。

      削除
    4. なにか似ているなぁと思ったら、asukiさんが書かれたプログラムでしたか、納得です。
       参考にさせていただきます。どうもありがとうございました。

      削除
    5. うまくいくと良いですね。
      (雰囲気が似ているのはesp-idfのサンプルコードをベースに作っているからかもしれません。)

      削除