• 搞定蓝牙——第四章(GATT协议)


    • 文章下面用的英文表示:
      server和client:服务端和客户端
      char.:characteristic缩写,特征
      Attribute:属性
      ATT:Attribute Protocol缩写

    原理介绍

    GATT是蓝牙协议栈的一种协议,它定义了ble设备数据通讯的方法(设备角色、数据格式、服务发现、安全加密等等),也就是说两个ble设备是通讯的规范就是GATT协议。GAP也是一种协议,但是它是规范通讯之前的广播、连接等。

    层次结构

    GATT的层次结构是这样的。
    在这里插入图片描述在这里插入图片描述
    一个Profile包含多个server(与server和client中的server不是一个意思),一个server包含多个char.,profile只是一个定义,表示某个功能,例如测心率、测血氧等,server是一个服务,也表示某个功能,不传递实际的数据。char.是一个特征,可以理解为数据存放的地方。char.里面包含了属性、值和描述符,属性定义了访问权限,值就是一个要传递的数据,描述符描述了这个数据、例如单位。所以只有server和char.是实际运行的组件。

    server和client端

    Server端和client端,这个也是GATT定义的,一般server端提供服务,要传输的数据也在服务里面,client读取server的服务,也就是获取数据。有的服务可以接收client的数据,例如通知和指示。

    Attribute

    因为GATT是一种通讯规范,那通讯的数据是怎么样的呢?你想哈,好多地方都用到了蓝牙技术,但是应用场景差异有些很大,所以,这种数据格式必须能满足所有这些应用场景。于是,SIG(蓝牙技术联盟)就定义了一个统一的数据格式,称为Attribute(属性),也是ble传输最小单元了,每次最少发一个数据就是一个Attribute。
    Attribute的结构是怎么样的呢。看图。
    在这里插入图片描述
    一个Attribute有四种类型的数据,一个一个展开说明。
    Handle:因为ble发送数据都是一个一个Attribute发送的,所以需要加一个表示符来表示这个Attribute,可以看作为这个Attribute的名字,这个handle就是这个东西。
    Type:表示Attribute的类型,使用16bit的UUID来表示,例如0x2800表示这个一个server声明的Attribute,更多看下图。
    在这里插入图片描述
    Value:表示这个Attribute的值,这个值有两种情况,如果从上面的Type中发现这是一个server声明的Attribute或者是char.声明的Attribute,那么这是一个UUID,这个可以自己定义,也可以用通用的,如果是其他,那么就是一个数据,例如心率、血氧。
    Permissions:权限,也就是运行client对于这个Attribute的Value操作权限,因为server声明和char.声明是UUDI,所以该值为只读。

    Ble是按照Attribute的格式来定义一个一个的数据的,在server端需要配置这一些Attribute,也就是Attribute表,这个表必须包含server声明、char.声明和char.值。
    例如看看ESP32中的Attribute表,对于应用而言,维护这张表非常重要
    在这里插入图片描述

    ESP32部分

    结构

    参考官方手册蓝牙技术说明,链接:
    https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_architecture_cn.pdf
    有一张层次结构图
    在这里插入图片描述

    如下图所示,用户只关心USER APP和USER TASK这两部分,从图中可以看出GATT和GAP两部分是和USER APP有交互的,这就是通过两个回调函数来实现的,我们关系回调函数的实现就行了,至于回调函数什么时候被调用,那就是ESP底层写好的库实现的了。
    在这里插入图片描述

    初始化

    直接看官方demo,打开路径,
    在这里插入图片描述
    拉到最底下找到app_main函数,看到初始化内容

    esp_err_t ret;
        esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    
        // 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_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
    
        ret = esp_bt_controller_init(&bt_cfg);
        if (ret) {
            ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
            return;
        }
    
        ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
        if (ret) {
            ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
            return;
        }
    
        ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__);
        ret = esp_bluedroid_init();
        if (ret) {
            ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
            return;
        }
        ret = esp_bluedroid_enable();
        if (ret) {
            ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
            return;
        }
    
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这里初始化nvs_flash、然后释放经典蓝牙内容(当前学习ble,不使用经典蓝牙)、然后初始蓝牙控制器、使能蓝牙控制器、初始化bluedroid、使能bluedroid。

    esp_ble_gatts_register_callback(gatts_event_handler);
    esp_ble_gap_register_callback(gap_event_handler);
    
    • 1
    • 2

    注册两个回调函数,GAP和GATT服务端回调函数

    esp_ble_gatts_app_register(ESP_SPP_APP_ID);
    
    • 1

    注册APP,这里可以注册多个APP
    最后开启任务

    spp_task_init();
    
    • 1

    所以,ble的控制都是在上面注册的两个回调函数里的。

    两个回调函数

    这个是GAP的回调函数

    static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
    {
        esp_err_t err;
        ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event);
    
        switch (event) {
        case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
            esp_ble_gap_start_advertising(&spp_adv_params);
            break;
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            //advertising start complete event to indicate advertising start successfully or failed
            if((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
                ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed: %s\n", esp_err_to_name(err));
            }
            break;
        default:
            break;
        }
    }
    这个是GATT服务端的回调函数
    
    static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
    {
        ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if);
    
        /* If event is register event, store the gatts_if for each profile */
        if (event == ESP_GATTS_REG_EVT) {
            if (param->reg.status == ESP_GATT_OK) {
                spp_profile_tab[SPP_PROFILE_APP_IDX].gatts_if = gatts_if;
            } else {
                ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",param->reg.app_id, param->reg.status);
                return;
            }
        }
    
        do {
            int idx;
            for (idx = 0; idx < SPP_PROFILE_NUM; idx++) {
                if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
                        gatts_if == spp_profile_tab[idx].gatts_if) {
                    if (spp_profile_tab[idx].gatts_cb) {
                        spp_profile_tab[idx].gatts_cb(event, gatts_if, param);
                    }
                }
            }
        } while (0);
    }
    
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    GATTS回调函数有两部分,if (event == ESP_GATTS_REG_EVT){…} 和do {…}while(0)。
    在main注册会使用了esp_ble_gatts_app_register(ESP_SPP_APP_ID);会进入第一个if语句,修改profile表的gatts_if(这个是一个标识,表示这个是APP,注册不同APP会产生不同gatts_if),然后进入do {…}while(0),进行实际的操作,也就是执行gatts_cb的函数。

    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)
    {
        esp_ble_gatts_cb_param_t *p_data = (esp_ble_gatts_cb_param_t *) param;
        uint8_t res = 0xff;
    
        ESP_LOGI(GATTS_TABLE_TAG, "event = %x\n",event);
        switch (event) {
            case ESP_GATTS_REG_EVT:
                ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
                esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
    
                ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
                esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data));
    
                ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
                esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
            break;
            case ESP_GATTS_READ_EVT:
                res = find_char_and_desr_index(p_data->read.handle);
                if(res == SPP_IDX_SPP_STATUS_VAL){
                    //TODO:client read the status characteristic
                }
             break;
            case ESP_GATTS_WRITE_EVT: {
                res = find_char_and_desr_index(p_data->write.handle);
                if(p_data->write.is_prep == false){
                    ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT : handle = %d\n", res);
                    if(res == SPP_IDX_SPP_COMMAND_VAL){
                        uint8_t * spp_cmd_buff = NULL;
                        spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t));
                        if(spp_cmd_buff == NULL){
                            ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed\n", __func__);
                            break;
                        }
                        memset(spp_cmd_buff,0x0,(spp_mtu_size - 3));
                        memcpy(spp_cmd_buff,p_data->write.value,p_data->write.len);
                        xQueueSend(cmd_cmd_queue,&spp_cmd_buff,10/portTICK_PERIOD_MS);
                    }else if(res == SPP_IDX_SPP_DATA_NTF_CFG){
                        if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){
                            enable_data_ntf = true;
                        }else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){
                            enable_data_ntf = false;
                        }
                    }
    #ifdef SUPPORT_HEARTBEAT
                    else if(res == SPP_IDX_SPP_HEARTBEAT_CFG){
                        if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){
                            enable_heart_ntf = true;
                        }else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){
                            enable_heart_ntf = false;
                        }
                    }else if(res == SPP_IDX_SPP_HEARTBEAT_VAL){
                        if((p_data->write.len == sizeof(heartbeat_s))&&(memcmp(heartbeat_s,p_data->write.value,sizeof(heartbeat_s)) == 0)){
                            heartbeat_count_num = 0;
                        }
                    }
    #endif
                    else if(res == SPP_IDX_SPP_DATA_RECV_VAL){
    #ifdef SPP_DEBUG_MODE
                        esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(p_data->write.value),p_data->write.len);
    #else
                        uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len);
    #endif
                    }else{
                        //TODO:
                    }
                }else if((p_data->write.is_prep == true)&&(res == SPP_IDX_SPP_DATA_RECV_VAL)){
                    ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_PREP_WRITE_EVT : handle = %d\n", res);
                    store_wr_buffer(p_data);
                }
                break;
            }
            case ESP_GATTS_EXEC_WRITE_EVT:{
                ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT\n");
                if(p_data->exec_write.exec_write_flag){
                    print_write_buffer();
                    free_write_buffer();
                }
                break;
            }
            case ESP_GATTS_MTU_EVT:
                spp_mtu_size = p_data->mtu.mtu;
                break;
            case ESP_GATTS_CONF_EVT:
                break;
            case ESP_GATTS_UNREG_EVT:
                break;
            case ESP_GATTS_DELETE_EVT:
                break;
            case ESP_GATTS_START_EVT:
                break;
            case ESP_GATTS_STOP_EVT:
                break;
            case ESP_GATTS_CONNECT_EVT:
                spp_conn_id = p_data->connect.conn_id;
                spp_gatts_if = gatts_if;
                is_connected = true;
                memcpy(&spp_remote_bda,&p_data->connect.remote_bda,sizeof(esp_bd_addr_t));
    #ifdef SUPPORT_HEARTBEAT
                uint16_t cmd = 0;
                xQueueSend(cmd_heartbeat_queue,&cmd,10/portTICK_PERIOD_MS);
    #endif
                break;
            case ESP_GATTS_DISCONNECT_EVT:
                is_connected = false;
                enable_data_ntf = false;
    #ifdef SUPPORT_HEARTBEAT
                enable_heart_ntf = false;
                heartbeat_count_num = 0;
    #endif
                esp_ble_gap_start_advertising(&spp_adv_params);
                break;
            case ESP_GATTS_OPEN_EVT:
                break;
            case ESP_GATTS_CANCEL_OPEN_EVT:
                break;
            case ESP_GATTS_CLOSE_EVT:
                break;
            case ESP_GATTS_LISTEN_EVT:
                break;
            case ESP_GATTS_CONGEST_EVT:
                break;
            case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
                ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle);
                if (param->add_attr_tab.status != ESP_GATT_OK){
                    ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status);
                }
                else if (param->add_attr_tab.num_handle != SPP_IDX_NB){
                    ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, SPP_IDX_NB);
                }
                else {
                    memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table));
                    esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
                }
                break;
            }
            default:
                break;
        }
    }
    
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    注册完成以后,所有的GATTS回调函数都在这个函数执行。首先,注册APP会进入这个函数执行case ESP_GATTS_REG_EVT:… ,在里面设置ble名字、设置广播数据(会进入GAP回调)、创建属性表(很重要,会进入GATTS回调)。创建属性表完成后会进入这个函数的case ESP_GATTS_CREAT_ATTR_TAB_EVT:…,这里打印一些内容后开始启动服务(属性表已经在程序上面定义的时候写好了)。
    设置广播数据会进入GAP回调。在GAP回调函数中进入case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:…,开启广播,开启广播完成会进入这个函数的case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:…。至此,等待客户端ble来扫描、连接,然后客户端会读取属性表,所有的数据通讯就是通过这个属性表来完成的。

    属性表

    属性表在程序中定义。

    static const esp_gatts_attr_db_t spp_gatt_db[SPP_IDX_NB] =
    {
        //SPP -  Service Declaration
        [SPP_IDX_SVC]                       =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
        sizeof(spp_service_uuid), sizeof(spp_service_uuid), (uint8_t *)&spp_service_uuid}},
    
        //SPP -  data receive characteristic Declaration
        [SPP_IDX_SPP_DATA_RECV_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_write}},
    
        //SPP -  data receive characteristic Value
        [SPP_IDX_SPP_DATA_RECV_VAL]                 =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_receive_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        SPP_DATA_MAX_LEN,sizeof(spp_data_receive_val), (uint8_t *)spp_data_receive_val}},
    
        //SPP -  data notify characteristic Declaration
        [SPP_IDX_SPP_DATA_NOTIFY_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_notify}},
    
        //SPP -  data notify characteristic Value
        [SPP_IDX_SPP_DATA_NTY_VAL]   =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_notify_uuid, ESP_GATT_PERM_READ,
        SPP_DATA_MAX_LEN, sizeof(spp_data_notify_val), (uint8_t *)spp_data_notify_val}},
    
        //SPP -  data notify characteristic - Client Characteristic Configuration Descriptor
        [SPP_IDX_SPP_DATA_NTF_CFG]         =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        sizeof(uint16_t),sizeof(spp_data_notify_ccc), (uint8_t *)spp_data_notify_ccc}},
    
        //SPP -  command characteristic Declaration
        [SPP_IDX_SPP_COMMAND_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_write}},
    
        //SPP -  command characteristic Value
        [SPP_IDX_SPP_COMMAND_VAL]                 =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_command_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        SPP_CMD_MAX_LEN,sizeof(spp_command_val), (uint8_t *)spp_command_val}},
    
        //SPP -  status characteristic Declaration
        [SPP_IDX_SPP_STATUS_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_notify}},
    
        //SPP -  status characteristic Value
        [SPP_IDX_SPP_STATUS_VAL]                 =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_status_uuid, ESP_GATT_PERM_READ,
        SPP_STATUS_MAX_LEN,sizeof(spp_status_val), (uint8_t *)spp_status_val}},
    
        //SPP -  status characteristic - Client Characteristic Configuration Descriptor
        [SPP_IDX_SPP_STATUS_CFG]         =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        sizeof(uint16_t),sizeof(spp_status_ccc), (uint8_t *)spp_status_ccc}},
    
    #ifdef SUPPORT_HEARTBEAT
        //SPP -  Heart beat characteristic Declaration
        [SPP_IDX_SPP_HEARTBEAT_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_write_notify}},
    
        //SPP -  Heart beat characteristic Value
        [SPP_IDX_SPP_HEARTBEAT_VAL]   =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_heart_beat_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        sizeof(spp_heart_beat_val), sizeof(spp_heart_beat_val), (uint8_t *)spp_heart_beat_val}},
    
        //SPP -  Heart beat characteristic - Client Characteristic Configuration Descriptor
        [SPP_IDX_SPP_HEARTBEAT_CFG]         =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        sizeof(uint16_t),sizeof(spp_data_notify_ccc), (uint8_t *)spp_heart_beat_ccc}},
    #endif
    };
    
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    查看esp_gatts_attr_db_t的结构,

    typedef struct
    {
        esp_attr_control_t      attr_control;                   /*!< The attribute control type */
        esp_attr_desc_t         att_desc;                       /*!< The attribute type */
    } esp_gatts_attr_db_t;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由两部分组成,第一部分

    typedef struct
    {
    #define ESP_GATT_RSP_BY_APP             0
    #define ESP_GATT_AUTO_RSP               1
        /**
         * @brief if auto_rsp set to ESP_GATT_RSP_BY_APP, means the response of Write/Read operation will by replied by application.
                  if auto_rsp set to ESP_GATT_AUTO_RSP, means the response of Write/Read operation will be replied by GATT stack automatically.
         */
        uint8_t auto_rsp;
    } esp_attr_control_t;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    只有一个数据,前面两个define表示了这个数据可以使用的值,ble服务器与客户端通讯,客户端对于服务器数据的读写是需要服务端回复的,这个回复可以由ESP底层完成、也可以由用户自己写程序完成,为了方便,demo使用了ESP库完成,选择ESP_GATT_AUTO_RSP
    第二部分

    typedef struct
     {
         uint16_t uuid_length;              /*!< UUID length */
         uint8_t  *uuid_p;                  /*!< UUID value */
         uint16_t perm;                     /*!< Attribute permission */
         uint16_t max_length;               /*!< Maximum length of the element*/
         uint16_t length;                   /*!< Current length of the element*/
         uint8_t  *value;                   /*!< Element value array*/
     } esp_attr_desc_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    UUDI长度(demo使用16位)、UUDI数据、属性权限、数据最大长度、数据当前长度、数据。数据最大长度和数据当前长度区分:数据最大长度指示这个Attribute可以写入的数据最大的长度,数据当前长度指示Attribute当前只需要写入这么多个数据,两者可以不同的。
    直接看属性表

    //SPP -  Service Declaration
        [SPP_IDX_SVC]                       =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
        sizeof(spp_service_uuid), sizeof(spp_service_uuid), (uint8_t *)&spp_service_uuid}},
    
        //SPP -  data receive characteristic Declaration
        [SPP_IDX_SPP_DATA_RECV_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_write}},
    
        //SPP -  data receive characteristic Value
        [SPP_IDX_SPP_DATA_RECV_VAL]                 =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_receive_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        SPP_DATA_MAX_LEN,sizeof(spp_data_receive_val), (uint8_t *)spp_data_receive_val}},
    
        //SPP -  data notify characteristic Declaration
        [SPP_IDX_SPP_DATA_NOTIFY_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_notify}},
    
        //SPP -  data notify characteristic Value
        [SPP_IDX_SPP_DATA_NTY_VAL]   =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_notify_uuid, ESP_GATT_PERM_READ,
        SPP_DATA_MAX_LEN, sizeof(spp_data_notify_val), (uint8_t *)spp_data_notify_val}},
    
        //SPP -  data notify characteristic - Client Characteristic Configuration Descriptor
        [SPP_IDX_SPP_DATA_NTF_CFG]         =
        {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
        sizeof(uint16_t),sizeof(spp_data_notify_ccc), (uint8_t *)spp_data_notify_ccc}},
    
    
    • 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
    • 29
    • 30

    根据前面的内容,server和char.是实体的,所以需要声明和定义它们,这个属性表就是这么一个作用,由于第一个参数是选择了ESP库自动回复,所以直接看第二个参数。
    数组第一个元素是server的声明、第二个元素是数据接收char.声明、第三个元素是数据接收char.值定义、第四个是notify类的char.声明、第五个是notify类的char.值定义、第六个是客户端char.配置描述。
    Server的声明是必须有的,它的UUID配置为primary_service_uuid,这个值为0x2800,查看下图,表明这个Attribute是一个服务声明
    在这里插入图片描述
    它的权限是只读,因为UUID都是固定的,所以只读。
    它的值使用了自定义的UUID,只是一个标识。
    Char.声明也是必须有的,其他和server声明类似,其中这里的值是ESP_GATT_CHAR_PROP_BIT_WRITE_NR|ESP_GATT_CHAR_PROP_BIT_READ,表示这个char.值是可以读写的,其中WRITE_NR表示无响应写操作,可以连续高效地接收客户端的数据
    Char.数据定义也是必须有的,这里的值就是真正要传输的数据了。可以通过esp_ble_gatts_set_attr_value()函数来写这个属性表,当客户端来读取Attribute的时候就可以获取到这个值了。
    Notify的Attribute和前面的类似,这个是一个通知,服务器可以通过这个Attribute发送一个通知,客户端就能马上收到通知就能获取数据(和上面修改属性表的方式发送数据相比,这种方式客户端能触发一个事件,不需要客户端主动读取属性表),还有一种类似的方式是指示。通知和指示的Attribute还必须配置一个Client Characteristic Configuration Descriptor(简称CCC)的Attribute,这里配置的值表示客户端是否能主动接收通知和指示,例如,在心率服务中,有一个称为心率测量的特征。 GATT 客户端(例如您的手机)可以使用该特征的 CCCD 来接收有关该特征的更新。因此,它通过在 CCCD 中启用所述特征的指示或通知来订阅心率测量特征。这意味着 GATT 服务器(很可能是心率传感器设备)会将这些测量结果推送到您的手机,而您的手机无需轮询这些测量结果。默认为0x00,0x00。这个Attribute可以通过客户端配置,所以必须可读可写。

  • 相关阅读:
    产品经理面试考查的是什么?
    IDEA断点常用5种方式——条件断点(循环)、回退、表达式执行、中断(不执行后续代码)、指定异常(常用寻找空指针位置)
    Spring注解驱动之@Autowired、@Qualifier、@Primary
    亚商投资顾问 早餐FM/12022023年开展第五次全国经济普查
    神经网络的整个过程包括,神经网络的实现过程
    QTableWidget 设置列宽行高大小的几种方式及其他常用属性设置
    集合框架(二)前置知识
    【Python小项目之Tkinter应用】随机点名/抽奖工具大优化:新增查看历史记录窗口!语音播报功能!修复预览文件按钮等之前版本的bug!
    PriorityQueue 源码解析(JDK1.8)
    [附源码]SSM计算机毕业设计学生宿舍管理系统设计JAVA
  • 原文地址:https://blog.csdn.net/hyh80227/article/details/134066466