ESP32 の BLE SPP Server 読み解き

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

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

スポンサーリンク

はじめに

少し前に,ESP-IDF の github にGATT SERVER SPP demoが登録されました.これを使うと,Bluetooth Low Energy (BLE) を使って UART 通信を離れた場所に伝送することができます.

機能としてはシンプルなものですが,独自の BLE プロファイルを定義するサンプルとして良さそうだったので,読み解いてみました.

前提知識として,下記の本に目を通しておくのがオススメです.

私の場合,BLE に関してほぼ知識ゼロから始めましたが,あらかじめこの本を読んでおくとで BLE の全体像をつかむことができ,さらに詳しい内容を調べるにあたって助けられました.

モジュール分割

コードを読んでいくと,本質的には 3 つほどのモジュールに分割できそうな事が見えてきましたので,ソースコードを次の 3 つに分割してみることにしました.

一般的な BLE サーバー処理
デバイスの GATT サービス定義や,GATT 関係のイベント処理を担います.
文字列バッファ管理
細切れになった文字列バッファの管理を担います.
UART 伝送特有の処理
今回のアプリケーションに特化した処理を担います.

分割の過程で自分が理解しやすくなるように一部コードの書き換えも行いました.

結果は github にアップしてあります.

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

一般的な BLE サーバー処理

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


Advertising パケットとして送信するデータを定義しています.元のコードは値のみが呪文のように記載されていたので,コメントで意味を追記しました.Advertising パケットの構造については「Advertisement packet format」で検索すると分かりやすい解説が見つかると思います.


Advertising パケットの送信設定を行います.各項目の意味は追ってません.


GATT のサービス定義です.デバイスは Characteristic の形で状態を公開し,クライアントはこれらの状態に対して読み書きを行うことで,アプリケーションとしての機能を実現します.

今回のアプリケーションの場合,以下のような Characteristic が宣言されています.

SPP_IDX_SPP_DATA_RECV_*
リモートから UART データを受信する Characteristic.
SPP_IDX_SPP_DATA_NOTIFY_*
リモートに UART データを送信する Characteristic.SPP_IDX_SPP_DATA_NOTIFY_CFG によって送信を ON/OFF 設定できるようになっています.

アプリケーションにおける GATT サーバの状態を管理する構造体です.元のコードは構造体のものは使わずグローバル変数を使っていましたが,書き直す際に全てこの構造体の変数を使う形に直しました.


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


先ほどの gatts_event_handler から spp_profile_tab 経由で呼ばれる関数です.下記のイベントについては,定型処理を書いておけば良さそうです.

  • ESP_GATTS_REG_EVT
  • ESP_GATTS_MTU_EVT
  • ESP_GATTS_CONNECT_EVT
  • ESP_GATTS_DISCONNECT_EVT
  • ESP_GATTS_CREAT_ATTR_TAB_EVT

これ以外の,ESP_GATTS_READ_EVTESP_GATTS_*WRITE_EVT にはアプリケーション特有の処理を書くことになります.


Advertising に関するイベントハンドラです.ESP-IDF のライブラリに登録して使います.定型処理を書いておけば良さそうです.


GATT のイベントハンドラの WRITE の処理をする部分を抜き出した関数です.対象が SPP_IDX_SPP_DATA_RECV_VAL の場合は,リモートからの UART データの受信処理を行っています.データサイズが大きいとパケット分割されて送られてくるので,最初に param->write.is_prep == true で場合分けしています.ローカル UART に対する書き出し処理内容は別ファイルに定義しています.

今読み返すと,まず最初に switch (res) がきた方が自然な気がしますが,元のコードの構造がこうなっていたので,とりあえず踏襲しています.

対象が SPP_IDX_SPP_DATA_NOTIFY_CFG の場合は,送信の ON/OFF 設定を行っています.


GATT のイベントハンドラの EXEC WRITE の処理をする部分を抜き出した関数です.分割して送信されてきた書き込み内容を反映する処理を行います.ローカル UART に対する書き出し処理内容は別ファイルに定義しています.

文字列バッファ管理

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


文字列バッファをリンクトリストで管理するための構造体です.あとのコード内容は,ここの構造体から推測できると思いますので,説明は割愛します.

元のコードの場合,このバッファ管理に関する複数のグローバル変数があったため,コードの見通しを悪くしているように感じます.追っていけばやっていることはシンプルです.

UART 伝送特有の処理

コード全体はこちらです.
UART を伝送するというアプリケーション特有の処理は基本的にこのファイルに固めてみました.


ローカルの UART データをリモートに送信する処理を行っています.一つのパケットに収まらない場合,複数のパケットに分割して送信しています.

その際,先頭の 4 バイトに分割に関する情報を埋め込みます.この処理はこのサンプル特有の処理だと思いますので,接続相手のクライアントはこのデータ形式に対応している必要がありそうです.


ローカルの UART データを受け取って,先ほどのデータ送信関数を呼び出すタスクです.


リモートから届いた UART データをローカルにに書き出します.パケットが分割されている場合は,少し前に出てきたリンクトリスト使って,一端溜め込んだ後,一気に書き込むようになっています.

リモートに分割送信する場合は先頭 4 バイトに分割情報を埋め込んでいましたが,受信するデータは特にそういったことは行われていないようです.この非対称性が何からきているのかは理解できていません.


コマンド処理用の関数です.現状,機能は実装されていません.ボーレートの設定をクライアントから行えるようにする機能等を追加すると,便利かも.


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

まとめ

BLE は他のネットワークプロトコルでは見慣れない用語体系がバックにあることもあって,いきなりコードを見ると一見さんお断り的な敷居の高さを感じます.ただ,やっていることはシンプルなので,コードを整理しながら巷にある BLE の解説文書を読んでいくけば,割と理解しやすいように感じます.

私の場合,BLE の各種用語になれることが一番大変でした.そこさえ乗り越えれば,あとは見通し良かったです.

参考になれば幸いです.

コメント

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