ESP32 の BLE SPP Client 読み解き

ネットワーク
スポンサーリンク

Bluetooth 初心者が,ESP-IDF の GATT CLIENT SPP demo のコードを読み解いてみたので,分かったことを紹介します.

スポンサーリンク

はじめに

前回,SERVER 側を読み解いたのに続き,今回は CLIENT 側です.通常,CLIENT はパソコンやスマホが担当しますが,今回のものは SERVER と同じく,ESP32 で動作します.

モジュール分割

前回と同じく,次の 2 つにもモジュール分割を行いました.

一般的な BLE クライアント処理
GAP や GATT 関係のイベント処理を担います.
UART 伝送特有の処理
今回のアプリケーションに特化した処理を担います.

分割の過程で自分が理解しやすくなるように一部コードの書き換えも行いました.結果は github にアップしてあります.

以降では,それぞれのモジュールについてコードの中身を順に追っていきます.説明をスムーズにするため,一部ソースコードと順番を入れ替えています.

一般的な BLE クライアント処理

コード全体はこちらです.


GATT クライアントの状態を管理する構造体です.元のコードは構造体の変数は使わずグローバル変数を使っていましたが,書き直す際に全てこの構造体の変数を使う形に直しました.

オリジナルに対して,is_scanningis_registered を追加しています.これらはサーバ側がリセットした場合に動作を継続するために使用します.詳細は,以降で説明します.


接続するサービスの定義.


デバイスをスキャンするときの設定を行います.各項目の意味は追ってません.


接続が切れた際の処理です.


デバイスのスキャンや接続に関するイベントハンドラです.デバイスに接続するまでの処理の流れとしては次のような感じです.

  1. デバイスが見つかると ESP_GAP_BLE_SCAN_RESULT_EVT イベントが発生.
  2. 見つかったデバイスが SPP SERVER の場合,handle_scan_result_event の中で esp_ble_gap_stop_scanning が呼ばれる.
  3. ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT イベントが発生.
  4. esp_ble_gattc_open を呼び,デバイスに接続.
  5. 以降,gattc_profile_event_handler の中で処理が進みます.

見つかったデバイスが SPP SERVER かチェックして,一致していれば esp_ble_gap_stop_scanning を呼びます.

オリジナルにはない処理として,gattc_spp_status()->is_scanning による条件分岐を行っています.詳細は追えていないのですが,デバイスがリセットした場合に ESP_GAP_BLE_SCAN_RESULT_EVT イベントが 2 回発生するようなので,それへの対策です.ちなみに,これを行わないと,「BT: bta_dm_ble_scan stop scan failed」というエラーが発生します.


デバイスが見つかった後の,GATT の読み書き処理に関するイベントハンドラです.ESP-IDF のライブラリに登録して使います.

イベントの発生順序は下記のような感じです.

  1. 接続完了後,ESP_GATTC_CONNECT_EVT イベントが発生.esp_ble_gattc_search_service を呼んで,デバイスが提供するサービスを読み出し.
  2. ESP_GATTC_SEARCH_RES_EVT イベントに続いて,ESP_GATTC_SEARCH_CMPL_EVT イベントが発生したら,esp_ble_gattc_send_mtu_req を呼んで,デバイスとの通信の MTU を取得.
  3. ESP_GATTC_CFG_MTU_EVTイベントが発生したら,取得済みのサービス内容を gattc_spp_status()->db にコピー.続いて,サーバーからクライアントへのデータ送信を有効化する書き込みを実施.
  4. データ送信の有効化の過程で ESP_GATTC_REG_FOR_NOTIFY_EVT イベントとESP_GATTC_WRITE_DESCR_EVT イベントが発生.

ESP_GATTC_CFG_MTU_EVT イベントのハンドラです.まず,サービス内容のコピーを行っています.内容からするとこの処理は ESP_GATTC_SEARCH_CMPL_EVT イベントのハンドラの中の方が自然な気がしますが,オリジナルコードでもここで行われていました.

その後,サーバーからクライアントへのデータ送信を有効化する書き込みを行っていきますが,ここが少しややこしいです.

具体的には,NOTIFY に対するハンドラの登録とサーバーに対する NOFITY の有効化をコールバックを挟みながら順に行っています.しかも,NOTIFY イベントに対するハンドラの登録は,別タスクから実施しています.流れを書くと下記のようになっています.

  1. NOTIFY イベントハンドラを登録する Characteristic のインデックスを cmd_regist_queue に追加.
  2. spp_client_regist_task タスクが NOFITY イベントハンドラを追加.
  3. ハンドラが追加されると ESP_GATTC_REG_FOR_NOTIFY_EVT イベントが発生.
  4. ESP_GATTC_REG_FOR_NOTIFY_EVT イベントのハンドラの中で esp_ble_gattc_write_char_descr を呼び,NOTIFY を有効化.
  5. 書き込みが完了すると ESP_GATTC_WRITE_DESCR_EVT イベントが発生.
  6. ESP_GATTC_WRITE_DESCR_EVT イベントハンドラの中で,別の Characteristic のインデックスを
    cmd_regist_queue に追加.
  7. 以下同様.

オリジナルにはない処理として,gattc_spp_status()->is_registered による条件分岐を行っています.デバイスがリセットした場合に,NOTIFY イベントハンドラを二重に登録するのを防止しています.コードを整理して,イベントハンドラの登録処理と NOTIFY を有効化処理を分離すれば,これはなくせると思います.(オリジナルコードでは両者が結びついているので,デバイスリセット時にイベントハンドラを登録しなくすると NOTIFY の有効化も行われなくなってしまいます)


NOTIFY の有効化を行います.


SPP_IDX_SPP_DATA_NTY_VAL の NOTIFY の有効化に続いて,SPP_IDX_SPP_STATUS_VAL の NOTIFY の有効化を行います.handle_configure_mtu_event の説明の「6.」に相当します.


GATT に関するイベントハンドラです.ESP-IDF のライブラリに登録して使います.今のところ,深追いできていません.SPP_PROFILE_NUM を 2 以上にしたくなったら,ちゃんと理解しとく必要がありそうです.


キューの内容に基づいて NOTIFY イベントハンドラの登録を行います.

UART 伝送特有の処理

コード全体はこちらです.


リモートから届いた UART データをローカルにに書き出します.


ローカルの UART データを受け取って,リモートに送信するタスクです.オリジナルコードに対して esp_ble_gattc_write_char を呼ぶまでのチェックを少し強化してあります.


メイン関数です.BLE のコントローラやホストを初期化して,イベントハンドラを登録しています.

まとめ

サーバ側よりもコードはコンパクトで,ESP32 でクライアントを実装するのもそれほど難しくはなさそうです.処理の流れはシンプルなので,個々の処理がイベントハンドラやタスクをまたいで連なっていくのに慣れれば,あとの見通しは良いと思います.

参考になれば幸いです.

コメント