前言
在瞭解了基礎的藍芽相關概念後,接下來透過學習其GATT Server的例程,瞭解其如何透過藍芽註冊GATT服務來收發資料。
GATT Server例程解析
圖中可以看出,官方的例程當中,gatt_server
和gatt_server_service_table
兩個例程都是用於GATT伺服器建立的,二者區別在於:
gatt_server
:主要展示建立多個Service,逐條新增屬性gatt_server_service_table
:主要展示的是建立一個Service,並建立多種不同型別的characteristic
兩種內容是都差不多,官方更加推薦使用第二種方式,下面也主要以gatt_server_service_table
展開說明。
1. 初始化
- NVS初始化,主要可以用來儲存一些資訊:
/* Initialize NVS. */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
- 釋放
ESP_BT_MODE_CLASSIC_BT
,釋放經典藍芽資源,預設藍芽是以經典藍芽啟動的:
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
- 按照預設配置
BT_CONTROLLER_INIT_CONFIG_DEFAULT
,初始化 藍芽控制器:
//初始化藍芽控制器,此函式只能被呼叫一次,且必須在其他藍芽功能被呼叫之前呼叫
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
- 使能藍芽控制器,工作在 BLE mode:
//如果想要動態改變藍芽模式不能直接呼叫該函式,先disable關閉藍芽再使用該API來改變藍芽模式
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
- 初始化藍芽主機,使能藍芽主機:
//藍芽棧 `bluedroid stack` 包括了BT和 BLE 使用的基本的define和API
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
- 註冊GATT GAP回撥函式:
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
return;
}
// 藍芽是透過GAP建立通訊的,所以在這個回撥函式中定義了在廣播期間藍芽裝置的一些操作
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(TAG, "gap register error, error code = %x", ret);
return;
}
- 註冊service:
/*
當呼叫esp_ble_gatts_app_register()註冊一個應用程式Profile(Application Profile),
將觸發ESP_GATTS_REG_EVT事件,
除了可以完成對應profile的gatts_if的註冊,
還可以呼叫esp_bel_create_attr_tab()來建立profile Attributes 表
或建立一個服務esp_ble_gatts_create_service()
*/
ret = esp_ble_gatts_app_register(ESP_APP_ID);
if (ret){
ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
-
設定mtu:
MTU: MAXIMUM TRANSMISSION UNIT
最大傳輸單元,
指在一個PDU 能夠傳輸的最大資料量(多少位元組可以一次性傳輸到對方)。PDU:Protocol Data Unit
協議資料單元,
在一個傳輸單元中的有效傳輸資料。
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret){
ESP_LOGE(TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
2. 回撥函式
gatts_event_handler
/*
引數說明:
event:
esp_gatts_cb_event_t 列舉型別,表示呼叫該回撥函式時的事件(或藍芽的狀態)
gatts_if:
esp_gatt_if_t (uint8_t) 這是GATT訪問介面型別,
通常在GATT客戶端上不同的應用程式用不同的gatt_if(不同的Application profile對應不同的gatts_if) ,
呼叫esp_ble_gatts_app_register()時,
註冊Application profile 就會有一個gatts_if。
param: esp_ble_gatts_cb_param_t 指向回撥函式的引數,是個聯合體型別,
不同的事件型別採用聯合體內不同的成員結構體。
*/
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
/* 判斷是否是 GATT 的註冊事件 */
if (event == ESP_GATTS_REG_EVT) {
/* 確定底層GATT執行成功 觸發ESP_GATTS_REG_EVT時,完成對每個profile 的gatts_if 的註冊 */
if (param->reg.status == ESP_GATT_OK) {
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
} else {
ESP_LOGE(GATTS_TABLE_TAG, "reg app failed, app_id %04x, status %d",
param->reg.app_id,
param->reg.status);
return;
}
}
// 如果gatts_if == 某個Profile的gatts_if時,呼叫對應profile的回撥函式處理事情。
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
/* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
if (gatts_if == ESP_GATT_IF_NONE || gatts_if == heart_rate_profile_tab[idx].gatts_if) {
if (heart_rate_profile_tab[idx].gatts_cb) {
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
這個函式主要作用:匯入GATT的profiles
這個函式註冊完成之後,就會在ble 協議任務函式中執行,將程式前面定義的 profiles 匯入:
#define PROFILE_NUM 1
#define PROFILE_APP_IDX 0
#define ESP_APP_ID 0x55
#define SAMPLE_DEVICE_NAME "ESP_GATTS_DEMO"
#define SVC_INST_ID 0
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {
[PROFILE_APP_IDX] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
在這個profiles中,有個回撥函式gatts_profile_event_handler
這也是本示例的關鍵函式,下面我們會來分析。
gap_event_handler
GAP 定義了在廣播期間藍芽裝置的一些操作,藍芽是透過GAP建立通訊的
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
#ifdef CONFIG_SET_RAW_ADV_DATA
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
#else
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT://廣播資料設定完成事件標誌
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);//開始廣播
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT://廣播掃描相應設定完成標誌
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
#endif
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT://開始廣播事件標誌
/* advertising start complete event to indicate advertising start successfully or failed */
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TABLE_TAG, "advertising start failed");
}else{
ESP_LOGI(GATTS_TABLE_TAG, "advertising start successfully");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT://停止廣播事件標誌
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed");
}
else {
ESP_LOGI(GATTS_TABLE_TAG, "Stop adv successfully");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:// 裝置連線事件,可獲取當前連線的裝置資訊
ESP_LOGI(GATTS_TABLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
param->update_conn_params.status,
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
default:
break;
}
}
[!NOTE] 說明
GAP 的回撥函式有很多,透過列舉esp_gap_ble_cb_event_t
可檢視。
gatts_profile_event_handler
核心部分,GATT回撥函式gatts_profile_event_handler
,
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) {
/* 展示了一個Service的建立 GATT註冊事件,新增 service的基本資訊,設定BLE名稱 */
case ESP_GATTS_REG_EVT:{
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
if (set_dev_name_ret){
ESP_LOGE(GATTS_TABLE_TAG, "set device name failed, error code = %x", set_dev_name_ret);
}
#ifdef CONFIG_SET_RAW_ADV_DATA
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
if (raw_adv_ret){
ESP_LOGE(GATTS_TABLE_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
}
adv_config_done |= ADV_CONFIG_FLAG;
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
if (raw_scan_ret){
ESP_LOGE(GATTS_TABLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
}
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#else
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret);
}
adv_config_done |= ADV_CONFIG_FLAG;
//config scan response data
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "config scan response data failed, error code = %x", ret);
}
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#endif
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
if (create_attr_ret){
ESP_LOGE(GATTS_TABLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
}
}
break;
case ESP_GATTS_READ_EVT://GATT讀取事件,手機讀取開發板的資料
// ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT");
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT, handle = %d", param->write.handle);
break;
case ESP_GATTS_WRITE_EVT://GATT寫事件,手機給開發板的傳送資料,不需要回復
if (!param->write.is_prep){
// the data length of gattc write must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
ESP_LOGI(GATTS_TABLE_TAG, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len);
esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len);
if (heart_rate_handle_table[IDX_CHAR_CFG_A] == param->write.handle && param->write.len == 2){
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001){
ESP_LOGI(GATTS_TABLE_TAG, "notify enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
{
notify_data[i] = i % 0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
sizeof(notify_data), notify_data, false);
}else if (descr_value == 0x0002){
ESP_LOGI(GATTS_TABLE_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i % 0xff;
}
// if want to change the value in server database, call:
// esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_A], sizeof(indicate_data), indicate_data);
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
sizeof(indicate_data), indicate_data, true);
}
else if (descr_value == 0x0000){
ESP_LOGI(GATTS_TABLE_TAG, "notify/indicate disable ");
}else{
ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");
esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len);
}
}
/* send response when param->write.need_rsp is true*/
if (param->write.need_rsp){
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
}
}else{
/* handle prepare write */
example_prepare_write_event_env(gatts_if, &prepare_write_env, param);
}
break;
case ESP_GATTS_EXEC_WRITE_EVT://GATT寫事件,手機給開發板的傳送資料,需要回復
// the length of gattc prepare write data must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
example_exec_write_event_env(&prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
case ESP_GATTS_CONF_EVT://GATT配置事件
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d", param->conf.status, param->conf.handle);
break;