重点讲解如何使用 ESP32-C3 作为 BLE Mesh Provisioner,扫描并添加 Mesh 节点,并与已加入网络的节点进行数据通信

下面的方案和示例代码基于 ESP-IDF (建议使用最新版本,如ESP-IDF v5.0或更高) 中的 BLE Mesh 核心组件。本文将重点讲解如何使用 ESP32-C3 作为 BLE Mesh Provisioner,扫描并添加 Mesh 节点,并与已加入网络的节点进行数据通信。此过程主要分为以下步骤:

  1. 准备工作

    • 确保您的开发环境已安装 ESP-IDF,并已配置好编译和烧录环境。
    • 一块 ESP32-C3 开发板作为 Provisioner。
    • 一块或多块其他支持 BLE Mesh 的 ESP32/ESP32-C3 开发板作为待加入的节点(Device)。
    • 蓝牙 Mesh 特性在menuconfig中开启(默认已启用于 BLE Mesh 示例)。
  2. BLE Mesh 核心概念回顾

    • Provisioner:负责建立或管理一个 Mesh 网络。它可以扫描未配网(unprovisioned)的节点,使用OOB数据(如果有)完成配网(Provisioning),为节点分配地址和网络信息。
    • Node:加入网络的设备,它有唯一地址,并实现一个或多个 Mesh 模型,用于发送/接收特定类型的数据。
    • 模型(Model):定义节点可支持的功能,如 Generic OnOff 模型,可实现简单的灯开关功能。
    • 通信:通常使用模型的消息来进行控制和数据交换。
  3. 功能实现流程

    1. Provisioner 启动 BLE Mesh 栈:初始化 BLE 栈、BLE Mesh 栈,加载 Provisioner 相关配置信息(如 UUID、OOB、发号地址范围)。
    2. 扫描并发现未配网设备:Provisioner 广播请求,待配网设备广播不带加密的信标,Provisioner 接收后触发回调。
    3. 执行配网(Provisioning)过程:Provisioner 与目标设备建立会话,交换公钥和身份信息,分配网络密钥(NetKey)、应用密钥(AppKey)、单播地址等信息,最终使节点加入 Mesh 网络。
    4. 为节点配置模型和订阅/发布地址:常见地,Provisioner作为配置客户端(Config Client Model),向新加入的节点(运行 Config Server Model)发送配置消息,为其设置模型发布/订阅地址、绑定AppKey等。
    5. 数据通信:使用通用模型(如Generic OnOff Client/Server)或厂商自定义模型发送控制消息。Provisioner通过Client模型向Node的Server模型发送指令或查询状态。
      在这里插入图片描述
  4. 示例代码结构
    以下代码参考自 ESP-IDF 提供的 BLE Mesh 示例进行适当简化和注释(如 examples/bluetooth/esp_ble_mesh 目录下的 provclient 示例)。代码重点展示初始化、Provisioner流程和发送/接收消息的基本逻辑。实际项目中请根据需要拆分文件和添加错误检查。

    请在 menuconfig 中启用 BLE Mesh 功能:

    Component config → Bluetooth → Bluetooth mesh support
    

    假设使用 Generic OnOff Client 模型作为示例模型,用于演示控制已配网节点的 LED。

示例代码(main.c)

#include <string.h>
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_generic_model_api.h"
#include "esp_ble_mesh_networking_api.h"
#include "esp_ble_mesh_provisioning_api.h"
#include "esp_ble_mesh_config_model_api.h"
#include "nvs_flash.h"

#define TAG "MESH_PROV"

// 使用Generic OnOff Client模型ID
static esp_ble_mesh_client_t onoff_client = ESP_BLE_MESH_CLIENT_INIT;
// 定义OnOff客户端模型
static esp_ble_mesh_model_t root_models[] = {
    ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(&onoff_client),
};

// 元素定义
static esp_ble_mesh_elem_t elements[] = {
    ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE),
};

// Provisioner节点组件定义
static esp_ble_mesh_comp_t comp = {
    .cid = ESP_BLE_MESH_CID_NVAL, // 使用本地CID或者0xFFFF
    .elements = elements,
    .element_count = ARRAY_SIZE(elements),
};

// Provisioning信息(Provisioner自身信息)
static esp_ble_mesh_prov_t prov = {
    .uuid = {0x32, 0xC3, 0x00, 0x00}, // 用于区分本Provisioner的UUID
    .prov_name = "ESP32-C3_Prov",
    .prov_uuid = {0x32, 0xC3, 0x00, 0x00},
    .prov_unicast_addr = 0x0001,    // Provisioner自有的单播地址
    .prov_start_address = 0x0005,   // 为分配给新节点的地址起始位置
};

// 全局变量存储配网完成的节点信息
typedef struct {
    uint16_t unicast_addr;
    uint8_t  dev_uuid[16];
} node_info_t;

static node_info_t nodes[10];
static int node_count = 0;

// 回调函数处理Prov和配置事件
static void ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event,
                             esp_ble_mesh_prov_cb_param_t *param)
{
    switch (event) {
    case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT:
        ESP_LOGI(TAG, "Provisioner register complete, err_code %d", param->prov_register_comp.err_code);
        break;
    case ESP_BLE_MESH_PROV_LINK_OPEN_EVT:
        ESP_LOGI(TAG, "Provisioning link opened with device");
        break;
    case ESP_BLE_MESH_PROV_LINK_CLOSE_EVT:
        ESP_LOGI(TAG, "Provisioning link closed");
        break;
    case ESP_BLE_MESH_PROV_COMPLETE_EVT:
        ESP_LOGI(TAG, "Provisioning completed: addr 0x%04x", param->prov_complete.addr);
        if (node_count < 10) {
            nodes[node_count].unicast_addr = param->prov_complete.addr;
            memcpy(nodes[node_count].dev_uuid, param->prov_complete.dev_uuid, 16);
            node_count++;
        }
        break;
    default:
        break;
    }
}

// 配置模型回调,用于配置节点(如绑定AppKey,设置订阅地址等)
static void ble_mesh_config_client_cb(esp_ble_mesh_cfg_client_cb_event_t event,
                                      esp_ble_mesh_cfg_client_cb_param_t *param)
{
    if (event == ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT ||
        event == ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT) {
        ESP_LOGI(TAG, "Config Client event, opcode:0x%04x, status:0x%02x",
                 param->params->opcode, param->status_cb.data->status);
    }
}

// 通用模型消息回调
static void ble_mesh_generic_client_cb(esp_ble_mesh_generic_client_cb_event_t event,
                                       esp_ble_mesh_generic_client_cb_param_t *param)
{
    if (event == ESP_BLE_MESH_GENERIC_CLIENT_STATE_CHANGE_EVT) {
        // 收到状态响应
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS) {
            ESP_LOGI(TAG, "OnOff Status from 0x%04X: %d",
                     param->params->ctx.addr, param->status_cb.onoff_status.present_onoff);
        }
    }
}

// 初始化BLE,Mesh
static void ble_mesh_init(void)
{
    esp_ble_mesh_register_prov_callback(ble_mesh_prov_cb);
    esp_ble_mesh_register_config_client_callback(ble_mesh_config_client_cb);
    esp_ble_mesh_register_generic_client_callback(ble_mesh_generic_client_cb);

    esp_ble_mesh_init(&prov, &comp);

    esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV); // 启用Provisioner模式
    esp_ble_mesh_provisioner_set_dev_uuid_match((uint8_t *)"\x32\xC3", 2, 0, false);  
    // 匹配UUID前2字节,扫描时将识别并尝试配网此类设备

    esp_ble_mesh_enable();  
    ESP_LOGI(TAG, "BLE Mesh provisioner initialized");
}

// 向节点发送OnOff命令(示例函数)
static void send_onoff_set(uint16_t addr, bool on)
{
    esp_ble_mesh_generic_client_set_state_t set = {0};
    esp_ble_mesh_msg_ctx_t ctx = {
        .addr = addr,           // 目标节点地址
        .app_idx = 0,
        .net_idx = 0,
        .send_ttl = 3,
        .send_rel = false,
    };

    set.onoff_set.op_en = false;
    set.onoff_set.onoff = on ? 1 : 0;

    esp_err_t err = esp_ble_mesh_generic_client_set_state(&onoff_client.model, &ctx, &set);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to send OnOff set message (err 0x%x)", err);
    } else {
        ESP_LOGI(TAG, "OnOff set to %d sent to 0x%04X", on, addr);
    }
}

void app_main(void)
{
    esp_err_t err = nvs_flash_init();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "NVS init failed");
        return;
    }

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    err = esp_bt_controller_init(&bt_cfg);
    if (err) {
        ESP_LOGE(TAG, "BT controller init failed (0x%x)", err);
        return;
    }
    err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (err) {
        ESP_LOGE(TAG, "BT controller enable failed (0x%x)", err);
        return;
    }

    err = esp_bluedroid_init();
    if (err) {
        ESP_LOGE(TAG, "Bluedroid init failed (0x%x)", err);
        return;
    }
    err = esp_bluedroid_enable();
    if (err) {
        ESP_LOGE(TAG, "Bluedroid enable failed (0x%x)", err);
        return;
    }

    ble_mesh_init();

    // 稍后通过一些机制,比如等待一段时间,让Provisioner扫描并自动配网节点
    // 实际中可以加入按键触发扫描/配网流程

    // 示例:延迟30秒后,如果成功配网了某节点,为该节点发送OnOff指令
    vTaskDelay(30000 / portTICK_PERIOD_MS);
    if (node_count > 0) {
        // 对第一个配网的节点发送OnOff=ON
        send_onoff_set(nodes[0].unicast_addr, true);
    }
}

代码说明

  • prov结构和comp结构定义了Provisioner自身的基本信息和其模型组成。这里包含了一个Generic OnOff Client模型用来发送指令。
  • ble_mesh_prov_cb回调处理配网事件。当 ESP_BLE_MESH_PROV_COMPLETE_EVT 触发时,表示有新节点已成功加入网络。我们在这里记录下节点的单播地址。
  • ble_mesh_config_client_cb可用于后续配置节点的AppKey绑定、Publication地址等高级操作。
  • ble_mesh_generic_client_cb在收到节点状态回复时会回调,这里简化为打印状态。
  • send_onoff_set()函数示范了如何向已配网的节点发送Generic OnOff指令,以实现数据通信。
  1. 实际部署与测试

    • 烧录代码到ESP32-C3。
    • 另准备一个ESP32/ESP32-C3作为节点,运行一个未配网的 BLE Mesh Node 示例(如 ble_mesh_node 示例),使用相同的AppKey。
    • 当Provisioner运行后,会扫描周边未配网设备并自动为其配网。
    • 配网成功后30秒后会自动给第一个节点发送OnOff=ON指令(仅作为演示)。
    • 可观察串口日志,确认配网和消息发送接收情况。
  2. 扩展和补充

    • 可在menuconfig中配置更多特性,如更改发号地址范围,启用OOB验证等。
    • 实际项目中需要实现更多事件回调中的逻辑,如对多个节点进行管理、在PROV_COMPLETE_EVT后对节点进行AppKey绑定、Publish地址设置,确保节点有正确的通信路径。
    • 增加自定义的Vendor Model可以实现更复杂的数据交互。

以上示例演示了一个基本的BLE Mesh Provisioner角色的实现步骤和参考代码,帮助您在ESP32-C3上构建一个可发现、配网并与其他BLE Mesh节点通信的系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值