ESP32藍芽學習--GATT協議學習

一月一星辰發表於2024-11-18

前言

在瞭解了基礎的藍芽相關概念後,接下來透過學習其GATT Server的例程,瞭解其如何透過藍芽註冊GATT服務來收發資料。

GATT Server例程解析

image.png
圖中可以看出,官方的例程當中,gatt_servergatt_server_service_table兩個例程都是用於GATT伺服器建立的,二者區別在於:

  • gatt_server :主要展示建立多個Service,逐條新增屬性
  • gatt_server_service_table :主要展示的是建立一個Service,並建立多種不同型別的characteristic
    兩種內容是都差不多,官方更加推薦使用第二種方式,下面也主要以gatt_server_service_table展開說明。

1. 初始化

  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);
  1. 釋放ESP_BT_MODE_CLASSIC_BT,釋放經典藍芽資源,預設藍芽是以經典藍芽啟動的:
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
  1. 按照預設配置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;
}
  1. 使能藍芽控制器,工作在 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;
}
  1. 初始化藍芽主機,使能藍芽主機:
//藍芽棧 `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;
}
  1. 註冊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;
}
  1. 註冊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;
}
  1. 設定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;

相關文章