创建GATTS服务来控制开发板上的LED

创建GATTS服务来控制开发板上的LED

创建项目 使用 idf.py create-project 创建新项目 idf.py create-project gatts 随后使用更改项目配置,使其支持蓝牙 idf.py menuconfig 编辑gatts.c 包含要使用的头文件 #include <stdio.h>

创建项目

使用 idf.py create-project 创建新项目

idf.py create-project gatts

随后使用更改项目配置,使其支持蓝牙

idf.py menuconfig

编辑gatts.c

包含要使用的头文件

#include <stdio.h>
#include "esp_err.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "led.h"

定义日志前缀和蓝牙设备名称

#define GATTS_TAG   "GATTS_SERVER"
#define DEVICE_NAME "BLE_DEVICE"

定义profile相关

#define PROFILE_NUM           1
#define PROFILE_LED_APP_ID    0

定义服务 UUID 和 特征 UUID

这里将服务 UUID 定义为:Automation IO Service 0x1815

Automation IO Service

[[Assigned_Numbers.pdf#page=66&selection=39,0,39,21|Assigned_Numbers, 页面 66]]

#define LED_SERVICE_UUID 0x1815
#define LED_CHAR_UUID    0x01

定义全局变量,保存 LED 状态

static uint8_t led_state = 0x00;

定义 gatts 事件分发函数

static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatt_if, esp_ble_gatts_cb_param_t *params);

定义 gap 事件处理函数

static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *params);

定义处理 LED 服务的 gatts 事件处理函数

static void gatts_profile_led_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatt_if, esp_ble_gatts_cb_param_t *params);

定义结构体,用于实例化 gatts_profile 的各项属性

struct gatts_profile_instance = {
	esp_gatts_cb_t gatts_cb;         // 存储该 profile 的 gatts 回调函数
	uint16_t gatts_if;               // 存储该 profile 的 gatts 接口号
	uint16_t app_id;                 // 存储该 profile 的 app_id
	uint16_t conn_id;                // 存储该 profile 的 连接ID
	uint16_t service_handle;         // 储存该 profile 的 连接句柄
	esp_gatt_srvc_id_t service_id;   // 储存该 profile 的 服务ID
	uint16_t char_handle;            // 储存该 profile 的 特征句柄
	esp_bt_uuid_t char_uuid;         // 储存该 profile 的 特征 UUID
	esp_attr_value_t char_val;       // 储存该 profile 的 特征值变量
	esp_gatt_char_prop_t property;   // 储存该 profile 的 特征属性
	esp_gatt_perm_t perm;            // 储存该 profile 的 权限
	uint16_t descr_handle;           // 储存该 profile 的 描述符句柄
	esp_bt_uuid_t descr_uuid;        // 储存该 profile 的 描述符 UUID
}

实例化 gatts profile

创建一个结构体数组,来保存各服务的实例化。
因为通常情况下,蓝牙设备包含多个 profile ,可以将 profile 理解为 app。用 结构体数组实例化 profile 的各项属性,有助于代码模块化,便于维护以及更易读。

static struct gatts_profile_instance gl_profile_tab[] = {
	[PROFILE_LED_APP_ID] = {
		.gatts_cb = gatts_profile_led_event_handler,
		.gatts_if = ESP_GATT_IF_NONE
	}
};

创建原始广播数据

static uint8_t raw_adv_data_app_led[] = {
    /* device flag */
    0x02, 0x01, 0x06,
    /* complete local name */
    0x0C, 0x09, 'P', 'F', 'O', 'F', 'I', 'L', 'E', '-', 'L', 'E', 'D',
    /* tx power  -61db */
    0x02, 0x0A, 0xEB,
    /* 16-bit Service UUID */
    0x03, 0x03, 0x01, 0x00
}

定义广播参数

static esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,
    .adv_int_max = 0x40,
    .adv_type = ADV_TYPE_IND,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .channel_map = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY
};

创建处理 LED 的 gatts 事件处理函数

static void gatts_profile_led_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatt_if, esp_ble_gatts_cb_param_t *params)
{
    switch (event) {
		// GATTS APP 注册之后,会触发该事件
        case ESP_GATTS_REG_EVT:
            ESP_LOGI(GATTS_TAG, \
                    "Prifile LED registerd. This profile is use to control led on the board, status: %d, app_id: %" PRIu16 "", \
                     params->reg.status, params->reg.app_id);
			/* 为 profile 属性赋值 */
            gl_profile_tab[PROFILE_LED_APP_ID].service_id.is_primary = true; // 设置该服务为主服务
            gl_profile_tab[PROFILE_LED_APP_ID].service_id.id.inst_id = 0x00; // 设置实例ID
            gl_profile_tab[PROFILE_LED_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; // 设置UUID长度
            gl_profile_tab[PROFILE_LED_APP_ID].service_id.id.uuid.uuid.uuid16 = LED_SERVICE_UUID; // 设置服务的UUID
			// 创建 gatts 服务,最后的 4 表示服务最多包含四个属性,包含服务、特征、描述符
            esp_ble_gatts_create_service(gatt_if, \
                                         &gl_profile_tab[PROFILE_LED_APP_ID].service_id, \
                                         4);
            break;
		// 连接事件
        case ESP_GATTS_CONNECT_EVT:
            ESP_LOGI(GATTS_TAG, "Profile LED device connect, conn_id: %" PRIu16 "", params->connect.conn_id);
            break;
		// 断开连接事件,为了保证设备在断开连接之后能够重新连接,这里需要重新启动广播
        case ESP_GATTS_DISCONNECT_EVT:
            esp_ble_gap_start_advertising(&adv_params);
            break;
		// GATTS 服务创建之后,会执行
        case ESP_GATTS_CREATE_EVT:
			/* 为 profile 属性赋值 */
            gl_profile_tab[PROFILE_LED_APP_ID].service_handle = params->create.service_handle; // 从事件参数中获取服务句柄
            gl_profile_tab[PROFILE_LED_APP_ID].char_uuid.len = ESP_UUID_LEN_16; // 特征 UUID 长度
            gl_profile_tab[PROFILE_LED_APP_ID].char_uuid.uuid.uuid16 = LED_CHAR_UUID; // 特征 UUID
            gl_profile_tab[PROFILE_LED_APP_ID].perm = ESP_GATT_PERM_WRITE; // 权限,这里定义为 只写
            gl_profile_tab[PROFILE_LED_APP_ID].property = ESP_GATT_CHAR_PROP_BIT_WRITE; // 属性,这里定义为 可写
            gl_profile_tab[PROFILE_LED_APP_ID].char_val.attr_max_len = sizeof(uint8_t); // 特征值最大长度
            gl_profile_tab[PROFILE_LED_APP_ID].char_val.attr_len = sizeof(uint8_t);  // 特征值当前长度
            gl_profile_tab[PROFILE_LED_APP_ID].char_val.attr_value = &led_state; // 特征值变量,读取此变量用来更改 LED 状态 
			// 将 特征 添加到 gatts 服务中
            esp_ble_gatts_add_char(gl_profile_tab[PROFILE_LED_APP_ID].service_handle, \
                                   &gl_profile_tab[PROFILE_LED_APP_ID].char_uuid, \
                                   gl_profile_tab[PROFILE_LED_APP_ID].perm, \
                                   gl_profile_tab[PROFILE_LED_APP_ID].property, \
                                   &gl_profile_tab[PROFILE_LED_APP_ID].char_val, \
                                   NULL);
            break;
		// 特征添加到 gatts 服务后会执行
        case ESP_GATTS_ADD_CHAR_EVT:
			// 启动 gatts 服务
            esp_ble_gatts_start_service(gl_profile_tab[PROFILE_LED_APP_ID].service_handle);
			// 获取特征句柄
			// 因为触发此事件之后,才会分配一个特征句柄,之后我们需要用这个句柄判断数据写入来源
            gl_profile_tab[PROFILE_LED_APP_ID].char_handle = params->add_char.attr_handle;
            break;
		// gatts 服务启动后执行
        case ESP_GATTS_START_EVT:
            break;
		// gatts 服务收到写入操作后执行
        case ESP_GATTS_WRITE_EVT:
            uint8_t value = *params->write.value;
            printf("value = %d\n", value);
			// 判断数据写入来源,避免与其他特征的写入操作混淆
            if (params->write.handle == gl_profile_tab[PROFILE_LED_APP_ID].char_handle) {
                if (value == LED_ON) {
                    ESP_LOGI(GATTS_TAG, "LED is ON");
                    led_on();
                } else if (value == LED_OFF) {
                    ESP_LOGI(GATTS_TAG, "LED is OFF");
                    led_off();
                }
            }
			// 接收写入操作完成后,需要返回给客户端一个回应,表示收到了写入的数据,不然客户端会认为写入失败
            esp_ble_gatts_send_response(gl_profile_tab[PROFILE_LED_APP_ID].gatts_if, \
                                        params->write.conn_id, \
                                        params->write.trans_id, \
                                        ESP_GATT_OK, 
                                        NULL);
            break;
        default:
            break;
    }
}

创建 gatts 事件分发函数

此函数意味将 gatts 事件分发给 不同的 profile。
假设现有另外一 profile:PROFILE_TMC2209_APP_ID,事件分发就显得尤为重要,添加事件分发层大大减少了程序量和程序复杂度

static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *params)
{
    switch (event) {
        /* If event is register event, store the gatts_if for each profile */
        case ESP_GATTS_REG_EVT:
            if (params->reg.status == ESP_GATT_OK) {
                gl_profile_tab[params->reg.app_id].gatts_if = gatts_if;
				/* 这里用 gatts_if 来判断需要配置哪个 profile 的广播事件 */
                switch (params->reg.app_id) {
                    case PROFILE_LED_APP_ID:
                        esp_ble_gap_config_adv_data_raw(raw_adv_data_app_led, sizeof(raw_adv_data_app_led));
                        break;
                    case PROFILE_TMC2209_APP_ID:
                        esp_ble_gap_config_adv_data_raw(raw_adv_data_app_tmc2209, sizeof(raw_adv_data_app_tmc2209));
                        break;
                    default:
                        break;
                }
            } else {
                ESP_LOGE(GATTS_TAG, "Register app failed, app_id: %04x, status: %d", params->reg.app_id, params->reg.status);
                return;
            }
            break;
        default:
            break;
    }

	/* 这里的 ACTIVE_APP 为 两个 profile 中的其中一个,用来判断执行哪个 profile 的回调函数 */
    /* Execute the actived app */
    gl_profile_tab[ACTIVE_APP].gatts_cb(event, gatts_if, params);
}

创建 gap 事件处理函数

static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *params)
{
    switch (event) {
		/* 广播数据配置完成后执行 */
        case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
            esp_ble_gap_set_device_name(DEVICE_NAME);
            esp_ble_gap_start_advertising(&adv_params);
            break;
		/* 广播启动后执行 */
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            ESP_LOGI(GATTS_TAG, "GAP handler: Start advertising complete.");
            break;
        default:
            break;
    }
}

app_main

void app_main(void)
{
    ESP_LOGI(GATTS_TAG, "------------------------------------------------\n");

    configure_led();

    esp_err_t ret;

    /* Initialize nvs flash */
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        nvs_flash_erase();
        if (!nvs_flash_init())
            return;
    } else {
        ESP_LOGI(GATTS_TAG, "Initialized nvs flash");
    }
    led_blink();

    /* Initialize bluetooth controller with default config */
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

    /* Initialize bluetooth controller */
    if (esp_bt_controller_init(&bt_cfg)) {
        ESP_LOGE(GATTS_TAG, "Initialize bluetooth controller failed.");
        return;
    } else {
        ESP_LOGI(GATTS_TAG, "Initialized bluetooth controller.");
    }
    led_blink();

    /* Enable bluetooth controller */
    if (esp_bt_controller_enable(ESP_BT_MODE_BLE)) {
        ESP_LOGE(GATTS_TAG, "Enable bluetooth controller failed.");
        return;
    } else {
        ESP_LOGI(GATTS_TAG, "Enabled bluetooth controller.");
    }
    led_blink();

    /* Initialize bluedroid */
    if (esp_bluedroid_init()) {
        ESP_LOGE(GATTS_TAG, "Initialize bluedroid failed.");
        return;
    } else {
        ESP_LOGI(GATTS_TAG, "Initialized bluedroid.");
    }
    led_blink();

    /* Enable bluedroid */
    if (esp_bluedroid_enable()) {
        ESP_LOGE(GATTS_TAG, "Enable bluedroid failed.");
        return;
    } else {
        ESP_LOGI(GATTS_TAG, "Enabled bluedroid.");
    }
    led_blink();

    /* Register GATTS callback handler */
    if (esp_ble_gatts_register_callback(gatts_event_handler)) {
        ESP_LOGE(GATTS_TAG, "Register gatts event handler error.");
        return;
    } else {
        ESP_LOGI(GATTS_TAG, "Registerd gatts event handler");
    }
    led_blink();

    /* Register GAP callback handler */
    if (esp_ble_gap_register_callback(gap_event_handler)) {
        ESP_LOGE(GATTS_TAG, "Register gap event handler error.");
        return;
    } else {
        ESP_LOGI(GATTS_TAG, "Registerd gap event handler.");
    }
    led_blink();
   
    /* Register APP by APP id */
    esp_ble_gatts_app_register(ACTIVE_APP);
    led_blink();
}

编辑led.h

#include "driver/gpio.h"
#include "freertos/task.h"

#define LED_PIN 2
#define LED_ON  1
#define LED_OFF 0

void configure_led(void)
{
    gpio_reset_pin(LED_PIN);
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
}

void led_on(void)
{
    gpio_set_level(LED_PIN, LED_ON);
}

void led_off(void)
{
    gpio_set_level(LED_PIN, LED_OFF);
}

void led_blink(void)
{
    led_on();
    vTaskDelay(pdMS_TO_TICKS(50));
    led_off();
    vTaskDelay(pdMS_TO_TICKS(50));
}
Comment