README.md

    一、项目简介

    蓝牙网关 又叫蓝牙探针,是采集蓝牙设备的蓝牙数据,通过 WIFI 等方式传至服务器的一款中继设备。如果类比的话,就如同 WIFI 网络中的无线 AP 的作用。WIFI 网络中无线 AP 是将 WIFI 设备接入网络,而蓝牙网络中的蓝牙网关,是将蓝牙设备接入网络。

    项目特性:

    • 采用 240MHz Xtensa 32-bit LX6 双核处理器
    • 支持 STA 工作模式
    • 支持 Smart Config/AirKiss 一键配网
    • 内嵌 Lwip 和 FreeRTOS
    • 支持 BLE 广播/扫描
    • 支持 Beacons 设备蓝牙广播上报功能

    二、基本工作流程

    1. Smart Config/AirKiss 一键配网,WIFI 连接路由器;
    2. 通过 TCP 方式连接到指定 IP 地址服务器;
    3. 扫描周围蓝牙设备广播数据;
    4. 将采集蓝牙数据通过 TCP 方式发送到指定服务器;
    5. 上电或按下网关按键发送蓝牙广播数据,时长5秒(默认)。

    三、API接口

    3.1 外设

    • LED灯
    函数 功能
    void LED_Init(void) LED模块初始化
    void SetWarningLedStatus(uint8_t status) 设置告警灯状态
    void SetNetworkLedStatus(uint8_t status) 设置网络灯状态
    void LED_GPIO_Init(void) LED灯GPIO初始化
    void LED_GPIO_Write(uint8_t ledNum, uint8_t ledMode) 设置LED灯GPIO状态
    uint8_t LED_GPIO_Read(uint8_t ledNum) 获取LED灯GPIO状态
    • 按键
    函数 功能
    void Key_Init(void) 按键模块初始化
    void Key_GPIO_Init(void) 按键GPIO初始化
    uint8_t Key_GPIO_Read(uint8_t keyNum) 获取按键GPIO状态
    void Key_GPIO_IrqCallback(void *arg) 按键中断触发回调函数
    • 蜂鸣器
    函数 功能
    void Buzzer_Init(void) 蜂鸣器模块初始化
    void Buzzer_Beep(uint8_t beepMode) 设置蜂鸣器状态(短鸣/长鸣)
    void Buzzer_GPIO_Init(void) 蜂鸣器GPIO初始化
    void Buzzer_GPIO_Write(uint8_t buzzerMode) 设置蜂鸣器GPIO状态

    3.2 Socket

    函数 功能
    uint8_t Socket_Init(void) socket初始化,并开启连接
    void Socket_Close(void) 关闭socket
    void Socket_Send(char *pString) 发送数据
    uint32_t Socket_Receive(char *pRecvDataBuf) 接收数据

    3.3 WIFI

    函数 功能
    void WIFI_Init(void) WIFI模块初始化,并等待配网
    uint8_t WIFI_Status(void) 获取WIFI连接状态

    3.4 BLE

    函数 功能
    void BLE_Init(void) 蓝牙BLE模块初始化
    void BLE_Advertise(void) 开启BLE广播
    void BLE_StopAdvertise(void) 停止BLE广播
    void BLE_AdvertisingDataInit(void) BLE广播数据包内容初始化
    void BLE_SetManufacturerData(uint8_t *pData, uint32_t dataLen) 设置用户自定义内容
    void BLE_Scan(void) 开启BLE扫描
    void BLE_StopScan(void) 停止BLE扫描
    bool BLE_GetAdTypeData(uint8_t adType, uint8_array_t *pAdvData, uint8_array_t *pTypeData) 从广播包中提取AdType格式数据
    bool BLE_GetUuidFromAdv(uint8_array_t *pAdvData, uint8_array_t *pTypeData) 从广播包中提取UUID
    bool BLE_GetNameFromAdv(uint8_array_t *pAdvData, uint8_array_t *pTypeData) 从广播包中提取设备名称
    bool BLE_GetUserDataFromAdv(uint8_array_t *pAdvData, uint8_array_t *pTypeData) 从广播包中提取用户自定义内容

    四、工程代码

    GitCode:https://gitcode.net/qq_36347513/esp32-wifi_ble_gateway

    将文件解压到 esp-idf/examples 目录下:

    4.1 工程结构

    4.2 一键配网

    首先在 main.capp_main() 中初始化 WIFI 模块,然后创建一个 network_task 处理网络通信业务。

    void app_main(void)
    {
        ESP_ERROR_CHECK(nvs_flash_init());
    
        /*-------------------------- 外设驱初始化 ---------------------------*/
        ···
        WIFI_Init();                                                            // WIFI模块初始化
        ···
        ···
    
        /*-------------------------- 创建线程 ---------------------------*/
        ···
        xTaskCreate(network_task, "network_task", 4096, NULL, 5, NULL);
    
        ···
    }
    
    /**
     @brief 通信业务
     @param 无
     @return 无
    */
    static void network_task(void *arg)
    {
        while(1)                                                                // 任务都是一个无限循环,不能返回
        { 
            HandleNetworkService();
            Delay(1000);                                                        // 1s
        }
    }

    扫描二维码进行 AirKiss 一键配网

    4.3 连接TCP服务器

    user_socket.h 中修改指定服务器的 IP 地址和端口:

    network_task 获取到 WIFI 状态已连接路由器成功后,调用 Socket_Init() 初始化socket通信,连接到指定 TCP 服务器。

    成功连接 TCP 服务器后,发送一条内容为 TEST 的消息。

    void HandleNetworkService(void)
    {
        if(CONNECT_WIFI_FAIL == WIFI_Status())
        {
            return;
        }
    
        if(INIT_FAIL == g_isSocketInit)
        {
            SetNetworkLedStatus(NO_NETWORK);
            s_isConnectServer = CONNECT_FAIL;
            g_isSocketInit = Socket_Init();                                 // 初始化socket通信
        }
        else if(INIT_SUCCESS == g_isSocketInit)
        {
            if(CONNECT_FAIL == s_isConnectServer)
            {
                SetNetworkLedStatus(IDLE);
                s_isConnectServer = CONNECT_SUCCESS;
                BLE_Scan();
                Socket_Send("TEST");
                ESP_LOGI(TAG, "socket send TEST\r\n");
            }
    
            // 接收服务器数据
            char recvDataBuf[MAX_RECV_BUF_SIZE] = {0};
            int recvDataLen = Socket_Receive(recvDataBuf);                  // 接收服务器数据
            if(recvDataLen > 0)
            {
                handleSocketRecvData(recvDataBuf, recvDataLen);
            }
        }
    }

    4.4 发送BLE扫描数据

    • 首先在 main.capp_main() 中初始化 BLE 模块,然后创建一个 monitor_task 处理事件业务。

    • 创建一个蓝牙消息队列 MsgQueue_Init(g_pMsgQueue); 和一个蓝牙 MAC 地址队列 MacQueue_Init(g_pMacQueue);

    • 创建蓝牙 MAC 地址过滤定时器 CreateFilterMacTimer(),过滤短时间内同一 MAC 地址重复的蓝牙广播包。

    void app_main(void)
    {
        ESP_ERROR_CHECK(nvs_flash_init());
    
        /*-------------------------- 外设驱初始化 ---------------------------*/
        timers_init();                                                          // 定时器驱动初始化(在此加入自定义定时器)
        ···
        BLE_Init();                                                             // 蓝牙模块初始化
        BLE_AdvertisingDataInit();
        BLE_Advertise();
    
        /*---------------------------- 队列初始化 -----------------------------*/
        g_pMsgQueue = (MsgQueue_t *)malloc(sizeof(MsgQueue_t));
        g_pMacQueue = (MacQueue_t *)malloc(sizeof(MacQueue_t));
        MsgQueue_Init(g_pMsgQueue);
        MacQueue_Init(g_pMacQueue);
    
        /*-------------------------- 创建线程 ---------------------------*/
        ···
        xTaskCreate(monitor_task, "monitor_task", 4096, NULL, 4, NULL);
        ···
    
        application_timers_start();
    }
    
    static void timers_init(void)
    {
        esp_timer_init();                                                       // 使用定时器API函数,先调用接口初始化
    
        ···
        CreateFilterMacTimer();
    }
    
    static void application_timers_start(void)
    {
        ···
        StartFilterMacTimer();
    }
    
    static void monitor_task(void *arg)
    {
        while(1)                                                                // 任务都是一个无限循环,不能返回
        {
            HandleEventService();
            Delay(100);                                                         // 100ms
        }
    }

    network_task 获取到连接 TCP 服务器成功后,开启扫描 BLE_Scan()

    void HandleNetworkService(void)
    {
        if(CONNECT_WIFI_FAIL == WIFI_Status())
        {
            return;
        }
    
        if(INIT_FAIL == g_isSocketInit)
        {
            SetNetworkLedStatus(NO_NETWORK);
            s_isConnectServer = CONNECT_FAIL;
            g_isSocketInit = Socket_Init();                                 // 初始化socket通信
        }
        else if(INIT_SUCCESS == g_isSocketInit)
        {
            if(CONNECT_FAIL == s_isConnectServer)
            {
                ···
                s_isConnectServer = CONNECT_SUCCESS;
                BLE_Scan();
                ···
            }
    
            ···
        }
    }

    当扫描到蓝牙广播时,进去蓝牙事件处理函数 bleEventHandler() 处理扫描结果,将蓝牙广播设备的 MAC 地址、设备名称、UUID、用户数据和RSSI 等依次封装成消息,加入消息队列。

    static void bleEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
    {
        esp_err_t err;
    
        switch(event) 
        {
            ···
            /*--------------------------- 扫描 ---------------------------*/
            case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: 
            {
                //the unit of the duration is second, 0 means scan permanently
                // uint32_t duration = 0;
                // esp_ble_gap_start_scanning(duration);
                break;
            }
            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(TAG, "Scan start failed: %s", esp_err_to_name(err));
                }
                break;
            case ESP_GAP_BLE_SCAN_RESULT_EVT: 
                scanResultHandler(param);
                break;
    
            case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
                if((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS)
                {
                    ESP_LOGE(TAG, "Scan stop failed: %s", esp_err_to_name(err));
                }
                else 
                {
                    ESP_LOGI(TAG, "Stop scan successfully");
                }
                break;
    
            default:
                break;
        }
    }
    
    /**
     @brief 处理扫描结果
     @param 无
     @return 无
    */
    static void scanResultHandler(esp_ble_gap_cb_param_t *pScanResult)
    {
        bool result;
        uint8_array_t advData;
        uint8_array_t name;
        uint16_t uuid = 0;
        uint8_array_t uuidArray;
        uint8_array_t userData;
        uint8_t macAddr[ESP_BD_ADDR_LEN] = {0};
        int8_t rssi = 0;
                
        switch(pScanResult->scan_rst.search_evt) 
        {
            case ESP_GAP_SEARCH_INQ_RES_EVT:
                // ESP_LOGI(TAG, "----------Device Found----------");
                advData.p_data = pScanResult->scan_rst.ble_adv;
                advData.size = pScanResult->scan_rst.adv_data_len + pScanResult->scan_rst.scan_rsp_len;
    
                // MAC地址
                memcpy(macAddr, pScanResult->scan_rst.bda, ESP_BD_ADDR_LEN);
                // esp_log_buffer_hex("Device address:", pScanResult->scan_rst.bda, ESP_BD_ADDR_LEN);
                // 设备名称
                result = BLE_GetNameFromAdv(&advData, &name);
                if(result == true)
                {
                    // ESP_LOGI(TAG, "searched Device Name Len %d", name.size);
                    // esp_log_buffer_char(TAG, (char *)name.p_data, name.size);
                }
                // UUID
                result = BLE_GetUuidFromAdv(&advData, &uuidArray);
                if(result == true)
                {
                    uuid = U8ToU16LittleEndian(uuidArray.p_data);
                    // ESP_LOGI(TAG, "searched uuid:%04x", uuid);
                }
                // 用户数据
                result = BLE_GetUserDataFromAdv(&advData, &userData);
                if(result == true)
                {
                    // esp_log_buffer_hex("searched user data:", userData.p_data, userData.size);
                }
                // RSSI
                rssi = pScanResult->scan_rst.rssi;
                // ESP_LOGI(TAG, "RSSI of packet:%d dbm", pScanResult->scan_rst.rssi);
    
                // 检查MAC地址不在待发送消息队列中
                if(!CheckMac(g_pMacQueue, macAddr) && ESP_BT_DEVICE_TYPE_BLE == pScanResult->scan_rst.dev_type)
                {
                    MacFilter_t *pMac;
                    pMac =(MacFilter_t *)malloc(sizeof(MacFilter_t));
                    memcpy(pMac->addr, pScanResult->scan_rst.bda, ESP_BD_ADDR_LEN);   // MAC地址
                    pMac->tick = esp_timer_get_time();              // 当前系统tick,用于过滤重复消息
    
                    if(MacQueue_GetLength(g_pMacQueue) < MAX_MAC_NUM)
                    {
                        MacQueue_PushElement(g_pMacQueue, *pMac);   // 加入MAC队列
                    }
                    free(pMac);
                    pMac = NULL;
    
                    DataType_t *pMsg;
                    pMsg =(DataType_t *)malloc(sizeof(DataType_t));
                    // UUID
                    pMsg->uuid = uuid;
                    // 广播内容
                    pMsg->adv_data.size = advData.size;
                    pMsg->adv_data.p_data = (uint8_t *)malloc(advData.size * sizeof(uint8_t));
                    memcpy(pMsg->adv_data.p_data, advData.p_data, advData.size);
                    // 信号强度
                    pMsg->rssi = rssi;
                    // MAC地址
                    memcpy(pMsg->addr, pScanResult->scan_rst.bda, ESP_BD_ADDR_LEN);
    
                    if(MsgQueue_GetLength(g_pMsgQueue) < MAX_MESSAGE_NUM)
                    {
                        MsgQueue_PushElement(g_pMsgQueue, *pMsg);   // 加入消息队列
                    }
                    else
                    {
                        free(pMsg->adv_data.p_data);
                        pMsg->adv_data.p_data = NULL;
                    }
                    free(pMsg);
                    pMsg = NULL;
                }
                break;
            default:
                break;
        }
    }

    monitor_task 中,从消息队列中提取消息发送到 TCP 服务端。

    4.5 按键发送BLE广播

    • 首先在 main.capp_main() 中初始化 BLE 模块,然后创建一个 monitor_task 处理事件业务。
    • 创建蓝牙广播停止的定时器 CreateKeepAdvertisingTimer(),设备上电广播5秒。
    void app_main(void)
    {
        ESP_ERROR_CHECK(nvs_flash_init());
    
        /*-------------------------- 外设驱初始化 ---------------------------*/
        timers_init();                                                          // 定时器驱动初始化(在此加入自定义定时器)
        ···
        BLE_Init();                                                             // 蓝牙模块初始化
        BLE_AdvertisingDataInit();
        BLE_Advertise();
    
        ···
    
        /*-------------------------- 创建线程 ---------------------------*/
        ···
        xTaskCreate(monitor_task, "monitor_task", 4096, NULL, 4, NULL);
        ···
    
        application_timers_start();
    }
    
    static void timers_init(void)
    {
        esp_timer_init();                                                       // 使用定时器API函数,先调用接口初始化
    
        CreateKeepAdvertisingTimer();
        ···
    }
    
    static void application_timers_start(void)
    {
        StartKeepAdvertisingTimer();
        ···
    }
    
    static void monitor_task(void *arg)
    {
        while(1)                                                                // 任务都是一个无限循环,不能返回
        {
            HandleEventService();
            Delay(100);                                                         // 100ms
        }
    }

    当设备连接上 TCP 服务器时,按下按键通过 socket 发送消息。

    void HandleEventService(void)
    {
        // 网络连接成功
        if(CONNECT_SUCCESS == GetConnectServerStatus())
        {
            // 发送呼叫
            if(KEY_CALL_EVENT == GetKeyTriggerEvent())
            {
                Buzzer_Beep(SHORT);
                Socket_Send("CALL");
                ESP_LOGI(TAG, "socket send CALL\r\n");
                SetKeyTriggerEvent(0);
                return;
            }
           ···
        }
        // 网络故障
        else
        {
            ···
        }
    }

    当设备没有连接上 TCP 服务器时,按下按键通过 BLE 广播发送消息。

    void HandleEventService(void)
    {
        // 网络连接成功
        if(CONNECT_SUCCESS == GetConnectServerStatus())
        {
            ···
        }
        // 网络故障
        else
        {
            if(KEY_CALL_EVENT == GetKeyTriggerEvent())
            {
                Buzzer_Beep(SHORT);
                SetWarningLedStatus(CALL_WARNING);
                uint8_t data[6] = {0};
                data[0] = APP_COMPANY_IDENTIFIER_L;
                data[1] = APP_COMPANY_IDENTIFIER_H;
                data[2] = 0x11;
                data[3] = 0x22;
                data[4] = 0x33;
                data[5] = 0x44;
                BLE_SetManufacturerData(data, sizeof(data));
                BLE_Advertise();                                        // 开启广播
                StartKeepAdvertisingTimer();
                SetKeyTriggerEvent(0);
            }
        }
    }


    • 由 Leung 写于 2022 年 5 月 17 日

    项目简介

    蓝牙网关又叫蓝牙探针,是采集蓝牙设备的蓝牙数据,通过 WIFI 等方式传至服务器的一款中继设备。如果类比的话,就如同 WIFI 网络中的无线 AP 的作用。WIFI 网络中无线 AP 是将 WIFI 设备接入网络,而蓝牙网络中的蓝牙网关,是将蓝牙设备接入网络。

    发行版本

    当前项目没有发行版本

    贡献者 1

    Leung_ManWah @qq_36347513

    开发语言

    • Assembly 47.2 %
    • CMake 30.5 %
    • C 16.5 %
    • C++ 5.1 %
    • Shell 0.7 %