ESP32入门基础之ble spp client 和 ble spp server 的学习理解

1 工程简介

本次实验使用开发板如下
在这里插入图片描述
参考工程

  • 工程 ble_spp_client :为客户端,作为扫描,SPP 即 Serial Port Profile
  • 工程 ble_spp_server:为服务端,作为广播,SPP 即 Serial Port Profile

基本功能1:客户端串口输入数据会通过蓝牙将数据发送给服务端并由串口打印出来;服务端串口输入数据会通过蓝牙将数据发送给客户端并由串口打印出来;
在这里插入图片描述

2 工程分析

2.1 工程 ble_spp_client 分析

2.1.1 初始化分析

从函数 app_main 开始

  • 初始化内存
  • 初始化bt controller并使能
  • 初始化bluedroid并使能
  • ble_client_appRegister
    在该函数下实现如下功能
    1. GAP下注册扫描回调函数。esp_ble_gap_register_callback(esp_gap_cb))
    2. GATT下注册回调函数。esp_ble_gattc_register_callback(esp_gattc_cb))
    3. GATT下设置MTU(Maximum Transmission Unit)值。
    4. 创建client queue、spp_client_reg_task。
  • 初始化串口 spp_uart_init
    在该函数下实现如下功能
    1. 初始化串口及串口中断、创建串口队列和串口任务。

2.1.2 两BLE扫描连接、配置、参数同步分析

—> 表示前面的函数直接调用后面的函数
- - - > 表示前面的函数执行后触发后面的函数

  • app_main() —> ble_client_appRegister() —> esp_ble_gattc_app_register(PROFILE_APP_ID) (产生ESP_GATTC_REG_EVT事件)- - - >esp_gattc_cb() —> gattc_profile_event_handler()。完成扫描参数的配置。

    static void gattc_profile_event_handler(...)
    {
        switch (event) {
        case ESP_GATTC_REG_EVT:
            esp_ble_gap_set_scan_params(&ble_scan_params);
            break;
    
  • esp_ble_gap_set_scan_params(&ble_scan_params) (产生ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT事件)- - -> esp_gap_cb()。开始扫描

    static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
    {
        switch(event){
        case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
            esp_ble_gap_start_scanning(duration);
            break;
        }
    
  • esp_ble_gap_start_scanning()(产生ESP_GAP_BLE_SCAN_START_COMPLETE_EVT事件) - - - > esp_gap_cb()。这里没有执行任何函数,在实际工程中可根据需求添加相关函数

    static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
    {
    
        switch(event){
        case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
            //scan start complete event to indicate scan start successfully or failed
            if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
                ESP_LOGE(GATTC_TAG, "Scan start failed: %s", esp_err_to_name(err));
                break;
            }
            ESP_LOGI(GATTC_TAG, "Scan start successed");
            break;
    
  • esp_ble_gap_start_scanning()(产生ESP_GAP_BLE_SCAN_RESULT_EVT事件) - - - > esp_gap_cb()。扫描开始后会搜索空中的BLE蓝牙广播数据,

    static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
    {
        switch(event){
        case ESP_GAP_BLE_SCAN_RESULT_EVT: {
            esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
            switch (scan_result->scan_rst.search_evt) {
            case ESP_GAP_SEARCH_INQ_RES_EVT:
                adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);//解析广播数据
                if (adv_name != NULL) {
                    // 判断名字是否为指定的名字
                    if ( strncmp((char *)adv_name, device_name, adv_name_len) == 0) {
                        memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t));
                        esp_ble_gap_stop_scanning();
                    }
                }
            }
            break;
        }
    

    如果搜索不到指定的蓝牙,会一直搜索,串口数据打印如下(上诉代码删除了串口输出函数,详细请参考工程)
    在这里插入图片描述
    如果搜索到指定的蓝牙 static const char device_name[] = "ESP_SPP_SERVER";,会停止广播
    在这里插入图片描述

  • esp_ble_gap_stop_scanning()(产生ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT事件) - - - > esp_gap_cb()。停止扫描并根据搜索到的蓝牙广播进行连接。

    static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
    {
        switch(event){
        case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
            ESP_LOGI(GATTC_TAG, "Scan stop successed");
            if (is_connect == false) {
                ESP_LOGI(GATTC_TAG, "Connect to the remote device.");
                //Open a direct connection or add a background auto connection
                esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda, scan_rst.scan_rst.ble_addr_type, true);
            }
            break;
    
  • esp_ble_gattc_open() (产生ESP_GATTC_CONNECT_EVT事件)- - - > esp_gattc_cb() —> gattc_profile_event_handler()。完成连接

    static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    {
        esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
        switch (event) {
        case ESP_GATTC_CONNECT_EVT:
            spp_gattc_if = gattc_if;
            is_connect = true;
            spp_conn_id = p_data->connect.conn_id;
            memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
            // This function is called to get service from local cache.This function report service search result by a callback event, and followed by a service search complete event.
            esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid);
            break;
    
  • esp_ble_gattc_search_service() (产生ESP_GATTC_SEARCH_RES_EVT事件)- - - > esp_gattc_cb() —> gattc_profile_event_handler()

    static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    {
        esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
        switch (event) {
        case ESP_GATTC_SEARCH_RES_EVT:
            spp_srv_start_handle = p_data->search_res.start_handle;
            spp_srv_end_handle = p_data->search_res.end_handle;
            break;
    
  • esp_ble_gattc_search_service() (产生ESP_GATTC_SEARCH_CMPL_EVT事件)- - - > esp_gattc_cb() —> gattc_profile_event_handler()。同步mtu的配置,需要提前调用函数esp_ble_gatt_set_local_mtu();

    static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    {
        esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
        switch (event) {
        case ESP_GATTC_SEARCH_CMPL_EVT:
        	//Configure the MTU size in the GATT channel. This can be done,only once per connection. Before using, use esp_ble_gatt_set_local_mtu() to configure the local MTU size.
            esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id);
            break;
    
  • esp_ble_gattc_send_mtu_req() (产生ESP_GATTC_CFG_MTU_EVT事件)- - - > esp_gattc_cb() —> gattc_profile_event_handler()

    	static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    	{
    	    esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
    	    switch (event) {
    	        case ESP_GATTC_CFG_MTU_EVT:
           		if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){
    	            ESP_LOGE(GATTC_TAG,"%s:get db falied\n",__func__);
    	            break;
           		}
            	cmd = SPP_IDX_SPP_DATA_NTY_VAL;
           		xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
    	        break;
    

    这里将队列发送后,有函数 spp_client_reg_task(); 接收,队列参数为 SPP_IDX_SPP_DATA_NTY_VAL

    void spp_client_reg_task(void* arg)
    {
        uint16_t cmd_id;
        for(;;) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
            if(xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) {
                if(db != NULL) {
                    if(cmd_id == SPP_IDX_SPP_DATA_NTY_VAL){
                        ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_DATA_NTY_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
                        esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
    
  • 函数 esp_ble_gattc_register_for_notify(...)SPP_IDX_SPP_DATA_NTY_VAL 属性注册完成后,就可以接收蓝牙信息了

    static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    {
        esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
        switch (event) {
        case ESP_GATTC_NOTIFY_EVT:
            ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n");
            notify_event_handler(p_data);
            break;
    

    notify_event_handler(p_data); 函数中可根据用户需求来处理数据。

2.1.3 蓝牙数据发送流程分析

  • 在电脑串口助手客户端发送数据后,数据在uart_task函数被接收并通过蓝牙将数据发送给服务端

    void uart_task(void *pvParameters)
    {
        for (;;) {
            //Waiting for UART event.
            if (xQueueReceive(spp_uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
                switch (event.type) {
                //Event of UART receving data
                case UART_DATA:
                        uart_read_bytes(...); //接收串口数据
                        esp_ble_gattc_write_char(...);//蓝牙发送数据
    

    esp_ble_gattc_write_char() (产生ESP_GATTC_WRITE_CHAR_EVT事件) - - - > esp_gattc_cb() —> gattc_profile_event_handler()

    		static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    		{
    		    esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
    		    switch (event) {
    		    case ESP_GATTC_WRITE_CHAR_EVT:
    		        ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_CHAR_EVT:status = %d,handle = %d", param->write.status, param->write.handle);
    

    在这里插入图片描述

  • 在电脑串口助手服务端发送数据后,客户端收到数据后会触发ESP_GATTC_NOTIFY_EVT事件。 esp_gattc_cb() —> gattc_profile_event_handler()

    static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
    {
        esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
        switch (event) {
        case ESP_GATTC_NOTIFY_EVT:
            ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n");
            notify_event_handler(p_data);
            break;
    

    notify_event_handler(p_data); 函数中可根据用户需求来处理数据。
    在这里插入图片描述

2.2 工程 ble_spp_server 分析

与 《2.1 工程 ble_spp_client 分析》类似,这里省略。

2.3 属性(Attribute)分析

参考文章《esp32_bluetooth_architecture_cn》
我们把存有数据(即属性)的设备叫做服务器器 (Server),而将获取别人设备数据的设备叫做客户端 (Client)。下面是服务器器和客户端间的常用操作:

  • 客户端给服务端发数据: 通过对服务器器的数据进行写操作 (Write),来完成数据发送工作。写操作分两种,一种是写入请求(Write Request),一种是写入命令 (Write Command),两者的主要区别是前者需要对方回复响应 (Write Response),而后者不需要对方回复响应。

  • 服务端给客户端发数据: 主要通过服务端指示 (Indication) 或者通知 (Notification) 的形式,实现将服务端更新的数据发给客户端。写操作类似,指示和通知的主要区别是前者需要对方设备在收到数据指示后,进行回复(Confirmation)。

  • 客户端也可以主动通过读操作读取服务端的数据:
    在这里插入图片描述

2.3.1 服务特征属性表

在这里插入图片描述
在程序定义的属性表只有一个数组,但在该数组里分从属关系,如下表所示。
在这里插入图片描述
主要服务UUID、特征声明UUID使用官方定义的UUID,特征声明下的值UUID可使用官方定义的UUID,如电量值,也可以使用自定义UUID。

2.3.2 服务端(server)属性分析

  1. app_main ---> esp_ble_gatts_app_register(ESP_SPP_APP_ID); 注册app之后,会产生 ESP_GATTS_REG_EVT 事件,在该事件下创建属性表。

    static void gatts_profile_event_handler(...)
    {
        switch (event) {
        	case ESP_GATTS_REG_EVT:
            	esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
    
  2. 执行 esp_ble_gatts_create_attr_tab 函数后,会产生 ESP_GATTS_CREAT_ATTR_TAB_EVT 事件,在该事件下开始服务

    	static void gatts_profile_event_handler(...)
    	{
    	    switch (event) {
    	    	case ESP_GATTS_CREAT_ATTR_TAB_EVT:
            	esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
    

开始服务并连接成功后就可以使用指示 (Indication) 或者通知 (Notification) 的形式,实现将服务端更新的数据发给客户端。例如

2.3.3 客户端(client)属性分析

  1. 客户端连接成功后,会搜索服务

    static void gattc_profile_event_handler(...)
    {
        switch (event) {
        case ESP_GATTC_CONNECT_EVT:
            esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid);
    
  2. 执行搜索服务函数 esp_ble_gattc_search_service 后,会产生 ESP_GATTC_SEARCH_RES_EVTESP_GATTC_SEARCH_CMPL_EVT 事件。

    	static void gattc_profile_event_handler(...)
    	{
    	    switch (event) {
    		   case ESP_GATTC_SEARCH_RES_EVT:
    		       spp_srv_start_handle = p_data->search_res.start_handle;
    		       spp_srv_end_handle = p_data->search_res.end_handle;
    		       break;
    		   case ESP_GATTC_SEARCH_CMPL_EVT:
    		       esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id);
    		       break;
    
  3. 执行函数 esp_ble_gattc_send_mtu_req 后,会产生 ESP_GATTC_CFG_MTU_EVT 事件。在该事件下获取服务端属性数据,客户端是如何知道服务端属性数据呢?是由步骤1 esp_ble_gattc_search_service函数获取的,从该函数第三个参数描述(filter_uuid: a UUID of the service application is interested in.If Null, discover for all services.)中可知。

    static void gattc_profile_event_handler(...)
    {
        switch (event) {
        case ESP_GATTC_CFG_MTU_EVT:
            esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count);
            cmd = SPP_IDX_SPP_DATA_NTY_VAL;
     		xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
    

    在获取服务端属性数据后,对服务特定属性功能的通知进行注册,之后就可以实现与服务端通信了。

    void spp_client_reg_task(void* arg)
    {
        uint16_t cmd_id;
        for(;;) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
            if(xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) {
                if(db != NULL) {
                    if(cmd_id == SPP_IDX_SPP_DATA_NTY_VAL){
                        esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
                    }
    

2.4 具体函数详细分析

要在Android上实现BLE(蓝牙低功耗)客户端主动连接BLE服务器,可以按照以下步骤进行操作: 1. 确保你的Android设备支持BLE功能,并在AndroidManifest.xml文件中添加必要的权限特性声明(例如`BLUETOOTH``BLUETOOTH_ADMIN`权限)。 2. 创建一个BLE客户端类,该类将负责处理与BLE设备的连接通信。你可以使用`BluetoothAdapter`类来获取本地蓝牙适配器,并使用`BluetoothDevice`类来表示操作BLE设备。 3. 初始化BLE适配器: ```java BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); ``` 4. 扫描并获取要连接的BLE设备: ```java bluetoothAdapter.startLeScan(mLeScanCallback); ... private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { // 根据设备名称或其他标识符筛选要连接的BLE设备 if (device.getName().equals("YourDeviceName")) { // 发现目标设备后,停止扫描 bluetoothAdapter.stopLeScan(mLeScanCallback); // 连接到BLE设备 connectToDevice(device); } } }; ``` 5. 连接到BLE设备: ```java private BluetoothGatt mBluetoothGatt; private void connectToDevice(BluetoothDevice device) { mBluetoothGatt = device.connectGatt(this, false, mGattCallback); } private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功 gatt.discoverServices(); // 发现BLE设备支持的服务 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 连接断开 } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { // 发现BLE设备的服务后,可以进行进一步操作,如读取或写入特征值等 } // 其他回调方法,如读取/写入特征值的结果等 }; ``` 6. 在`onServicesDiscovered()`方法中,你可以获取到BLE设备支持的服务特征值,并根据需要进行读取、写入或订阅操作。 这些步骤只是一个基本示例,实际情况可能会因应用程序的需求BLE设备的特性而有所不同。你可以根据自己的需求来扩展优化代码。同时,请注意处理异常情况适当的错误处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值