ESP32-WROVERでBLE通信(Custom Characteristic編)
前回(ESP32-WROVERでBLE通信)でサンプルコードやテンプレートプロジェクトでPC-ESP間でBLE通信しましたが、私的にはPC-BLEデバイス間で単純なデータR/Wをしたいのでそれをやっていきます。
ESP32のテンプレートプロジェクトでは心拍計サービスでしたが、他の標準サービスでデータR/W的な物はないか調べてもドンピシャというのは無さそうです(Bluetooth.com/ja-jp)。
ですのでCustomサービスを作ってみました。
では、始めましょう!!!
BLEはGATTサービスの内容を変えることで他のサービスやCustomサービスに対応する事ができます。ですので「ESP32-WROVERでBLE通信」を元にしてGATTサービスを対応していきますので、このページから始める方は前回(ESP32-WROVERでBLE通信)を参考にしてプロジェクト作ってから始めて下さい。
プロジェクト名は前回の「GattSecurityServerExample」から「GattSecuriyServerCustom」にしています。
このテンプレートプロジェクトのGATTサーバー定義がどこにあるかというと138行目辺りの”heart_rate_gatt_db”がそうです。
まずこの”heart_rate_gatt_db”の中身を確認してみます。
基本的にGATTサーバーは以下のような構造をしています。
これと”esp_gatts_attr_db”を対応させてみます。
UUID | Permission | 値 | |
サービス | 0x2800(Primary Service) | Read | 0x180D(心拍数サービス) |
特性 | 0x2803(Characteristic Declaration) | Read | Notify |
特性 | 0x2A37(Heart Rate Measurement) | Read | Null |
ディスクリプター | 0x2902(Client Characteristic Configuration Descriptor(CCCD)) | Read/Write | Heart Measurement CCC |
特性 | 0x2803(Characteristic Declaration) | Read | Read |
特性 | 0x2A38(Body Sensor Location) | 暗号化Read | Sensor location Value |
特性 | 0x2803(Characteristic Declaration) | Read | Read/Write |
特性 | 0x2A39(Heart Rate Control Point) | 暗号化Read/Write | Heart Ctrl Point |
よくわかりませんね。私もそれほどわかっていないので悪しからず。
わかっていないながらも多少解説するとNotify(通知)はサーバー(ESP32)がデータを一方的に送ってきますがCCCDのNotify bitをOnした場合のみNotify(通知)します。ReadやWriteはクライアント(PC)側から要求します。
今回はNotifyやCCCDは使わないのでHeart Rate Control Pointといったところをカスタム化します。
以下が今回のカスタムサービスです。
UUID | Permission | 値 | |
サービス | 0x2800(Primary Service) | Read | 787767d1-a760-4568-90c8-85525e7add92(ベンダー固有UUID) |
特性 | 0x2803(Characteristic Declaration) | Read | Read |
特性 | 54bde750-f4df-4909-9be0-61083b10b171(ベンダー固有UUID) | 暗号化Read | Read Data |
特性 | 0x2803(Characteristic Declaration) | Read | Write |
特性 | 11078b7f-42e5-4e91-abb9-f247e622159a(ベンダー固有UUID) | 暗号化Write | Write Data |
サービスのところで元々は規定の心拍数サービスなのでUUIDは16bitの0x180Dでしたが今回はカスタムサービスなので128bitUUIDになっています。
BLEにおいてはBLEの負担低減のため16bitを使用できますがUUIDは本来BLE限らずいろいろと使われるグローバルで一意な事が高い確率で保証される128bitの数字です。
ちなみにBLEではxxxxxxxx-0000-1000-8000-00805f9B34FBのxxxxxxxxのところに0000180dが入り最終的に128bitになります。
カスタムサービスの場合、それは使えないので128bitになります。
UUIDはOnline UUID Generatorなどで生成できます。サイトにはVersion1とVersion4がありますが説明文によるとVersion1がタイムスタンプとそのコンピューターのMACアドレスから生成されて、Version4は安全な乱数ジェネレーターで生成される、とあります。
同様に特性でも2つのUUIDを生成してそれぞれRead Data、Write Dataとしました。
これを具体的にコードに移植します。”heart_rate_gatt_db”を”custom_gatt_db”にして以下のようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/// Custom service Database Description - Used to add attributes into the database static const esp_gatts_attr_db_t custom_gatt_db[CUSTOM_IDX_NB] = { // Custom Service Declaration [CUSTOM_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(custom_svc), sizeof(custom_svc), (uint8_t *)custom_svc}}, // Read Data Characteristic Declaration [CUSTOM_IDX_READ_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}}, // Read Data Characteristic Value [CUSTOM_IDX_READ_DATA] = {{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_128, (uint8_t *)&read_data_uuid, ESP_GATT_PERM_READ_ENCRYPTED, sizeof(read_data), sizeof(read_data), (uint8_t *)read_data}}, // Write Data Characteristic Declaration [CUSTOM_IDX_WRITE_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}}, // Write Data Characteristic Value [CUSTOM_IDX_WRITE_DATA] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_128, (uint8_t *)&write_data_uuid, ESP_GATT_PERM_WRITE_ENCRYPTED, sizeof(write_data), sizeof(write_data),(uint8_t *)write_data}}, }; |
1 2 3 4 5 6 7 8 9 10 11 12 |
///Attributes State Machine enum { CUSTOM_IDX_SVC, CUSTOM_IDX_READ_CHAR, CUSTOM_IDX_READ_DATA, CUSTOM_IDX_WRITE_CHAR, CUSTOM_IDX_WRITE_DATA, CUSTOM_IDX_NB, }; |
ここで使われるUUID 3個、read_data、write_dataを定義します。GATTプロファイルはリトルエンディアンなので下位のバイトから並べます。
1 2 3 4 5 6 7 8 |
/// Custom Service static const uint8_t custom_svc[16] = {0x92,0xdd,0x7a,0x5e,0x52,0x85, 0xc8,0x90, 0x68,0x45, 0x60,0xa7, 0xd1,0x67,0x77,0x78}; static const uint8_t read_data_uuid[16] = {0x71,0xb1,0x10,0x3b,0x08,0x61, 0xe0,0x9b, 0x09,0x49, 0xdf,0xf4, 0x50,0xe7,0xbd,0x54}; static const uint8_t write_data_uuid[16] = {0x9a,0x15,0x22,0xe6,0x47,0xf2, 0xb9,0xab, 0x91,0x4e, 0xe5,0x42, 0x7f,0x8b,0x07,0x11}; /// custom Service - Read Data,Write Data static uint8_t read_data[13] = {"initial value"}; static uint8_t write_data[13]; |
デバイス名を「ESP_BLE_SECURITY_CUSTOM」(任意)にします。
1 |
#define CUSTOM_DEVICE_NAME "ESP_BLE_SECURITY_CUSTOM" |
その他、名前を変えたところも合わせて名前を変えます。また使わなくなった変数も削除します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#define CUSTOM_SVC_INST_ID ... static uint16_t custom_handle_table[CUSTOM_IDX_NB]; ... static void gatts_profile_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_REG_EVT: esp_ble_gap_set_device_name(CUSTOM_DEVICE_NAME); esp_ble_gatts_create_attr_tab(custom_gatt_db, gatts_if, CUSTOM_IDX_NB, CUSTOM_SVC_INST_ID); ... case ESP_GATTS_CREAT_ATTR_TAB_EVT: { if (param->create.status == ESP_GATT_OK){ if(param->add_attr_tab.num_handle == CUSTOM_IDX_NB) { memcpy(custom_handle_table, param->add_attr_tab.handles, sizeof(custom_handle_table)); esp_ble_gatts_start_service(custom_handle_table[CUSTOM_IDX_SVC]); ... |
できたらbuild & flashします。
ESP32側の準備ができたところで前回の(ESP32-WROVERでBLE通信)でやったようにESP32 BLEにペアリング、接続をしていきます。
では、
PC(Windows10)の「Windowsの設定」→「デバイス」→「Bluetoothとその他のデバイス」を選択。
「Bluetoothまたはその他のデバイスを追加する」→「Bluetooth」をクリック。
「ESP-BLE-SECURITY_CUSTOM」が見つかるのでクリック、ペアリングができます。
ペアリングができたので前回も使った「Bluetooth GATT クライアント」のGitHubにある完全なサンプルを使用して接続してみます。
「1)Client: Discover servers」を選んで「Start enumerating」をクリックして現れる「ESP_BLE_SECURITY_CUSTOM」を選択します。
「2) Client: Connect to a server」を選んで「Connect」をクリックして「Choose a service」をクリックするとさきほど設定したカスタムサービスが現れます。
「Custom Service: 787767d1-….」を選択して「Choose a characteristic」をクリックすると「Custom Characteristic: 54bde750-…」と「Custom Characteristic:11078b7f-…」が現れます。
「Custom Characteristic: 54bde750-…」の方がReadなのでこちらを選択して「Read Value」をクリックすると初期値”initial value”が読み取れます。
Read特性はできました!!!
しかしWrite特性は何もコードを記述していないので動きません。Writeした値をそのままReadできるようにするコードを足していきます。
GATTのWriteイベントは”gatts_profile_event_handler”の”case ESP_GATTS_WRITE_EVT:”を呼び出すのでここに「write_data」→「read_data」のコードを足します。
1 2 3 4 5 |
case ESP_GATTS_WRITE_EVT: for(uint8_t x = 0; x < sizeof(write_data); x++) { read_data[x] = write_data[x]; } |
しかし再度build & flashして、ライト特性「Custom Characteristic: 11078b7f-…」にデータを入力して「Write Value as UTF-8」をクリックしても「write_data」が「read_data」に反映されません。
“case ESP_GATTS_WRITE_EVT”の”write_data”を”param->write.value”に変えても結果は同じです。”case ESP_GATTS_READ_EVT”に何かコードを足さなければいけなさそうですが、どこにデータをセットすればいいのでしょうか。param->readにvalueメンバーは無いのです。
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * @brief ESP_GATTS_READ_EVT */ struct gatts_read_evt_param { uint16_t conn_id; /*!< Connection id */ uint32_t trans_id; /*!< Transfer id */ esp_bd_addr_t bda; /*!< The bluetooth device address which been read */ uint16_t handle; /*!< The attribute handle */ uint16_t offset; /*!< Offset of the value, if the value is too long */ bool is_long; /*!< The value is too long or not */ bool need_rsp; /*!< The read operation need to do response */ } read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */ |
ESP-IDFのサンプルコードなどをいろいろ見てみるとサンプルコード”gatt_securiy_server”ではなくサンプルコード”gatt_server”の”case ESP_GATTS_READ_EVT”に以下のようなコードがあります。
1 2 3 4 5 6 7 8 9 10 11 12 |
case ESP_GATTS_READ_EVT: { ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); esp_gatt_rsp_t rsp; memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); rsp.attr_value.handle = param->read.handle; rsp.attr_value.len = 4; rsp.attr_value.value[0] = 0xde; rsp.attr_value.value[1] = 0xed; rsp.attr_value.value[2] = 0xbe; rsp.attr_value.value[3] = 0xef; esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,ESP_GATT_OK, &rsp); break; |
これを流用してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
case ESP_GATTS_READ_EVT: ; esp_gatt_rsp_t rsp; memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); rsp.attr_value.handle = param->read.handle; for(x = 0; x < sizeof(read_data); x++) { rsp.attr_value.value[x] = write_data[x]; } rsp.attr_value.len = sizeof(read_data); esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp); break; case ESP_GATTS_WRITE_EVT: ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT, write value:"); esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len); for(x = 0; x < sizeof(write_data); x++) { write_data[x] = *(param->write.value + x); } break; |
あと上で設定したGATTプロファイルの内、[CUSTOM_IDX_READ_DATA]のところの{ESP_GATT_AUTO_RSP}を{ESP_GATT_RSP_BY_APP}に変えます。これはその字のままAutoでレスポンスをするのでなくアプリでレスポンスするということです。
1 2 3 4 |
// Read Data Characteristic Value [CUSTOM_IDX_READ_DATA] = {{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_128, (uint8_t *)&read_data_uuid, ESP_GATT_PERM_READ_ENCRYPTED, sizeof(read_data), sizeof(read_data), (uint8_t *)read_data}}, |
再度、build & flashします。
今度は書いたデータが読み出されました。
これで一応、終わりなのですが、ここのRead ResultにあるUnknown formatとは何なのでしょうか。
UWPアプリのサンプルコードを確認してみたりするとこれはディスクリプターの1つの特性提示フォーマットディスクリプター(Characteristic presentation format descriptor)を参照して特性提示フォーマットディスクリプターが無いとUnknown formatとしていました。
特性提示フォーマットディスクリプターは7byteで1byte目に値のフォーマットを示します。
フォーマットは28種類あり例えばunsigned 32bit integer→0x08、UTF8 string→0x19などです。
このディスクリプターをGATTプロファイルに追加してみます。
1 2 3 4 5 6 7 |
// Read Data Characteristic Value [CUSTOM_IDX_READ_DATA] = ・・・ ・・・ [CUSTOM_DATA_IDX_CHARACTOR_PRESENT_FORMAT] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_present_format_uuid, ESP_GATT_PERM_READ, sizeof(character_present_format),sizeof(character_present_format), (uint8_t *)character_present_format}}, |
追加したディスクリプターで使っている変数もソースファイル、ヘッダーファイルに追加します。
1 2 |
static const uint16_t character_present_format_uuid = ESP_GATT_UUID_CHAR_PRESENT_FORMAT; static const uint8_t character_present_format[7] = {ESP_GATT_CPF_FORMAT_UTF8S, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
1 2 3 4 5 6 7 8 9 10 11 |
・・・ CUSTOM_IDX_READ_DATA, CUSTOM_DATA_IDX_CHARACTOR_PRESENT_FORMAT, CUSTOM_IDX_WRITE_CHAR, CUSTOM_IDX_WRITE_DATA, CUSTOM_IDX_NB, }; #define ESP_GATT_CPF_FORMAT_UINT32 0x08 #define ESP_GATT_CPF_FORMAT_UTF8S 0x19 |
今度はUnknown formatとはなりませんし、”1″を書いた値を読むとunsigned 32bitに指定しているので49(0x31)と表示されます。
プロジェクト全てはGitHubにあります。