ble mesh除了节点,还有用于配置节点的设备,这个设备通常是手机APP或者网关,而网关的开发用到的例程就是provisioner这个

切记,是下面那个单独的provisioner,不是上面的fast provisioning

这个工程主要实现了一个 BLE Mesh 配网者的功能,用于完成以下任务:

  • 初始化 BLE Mesh 协议栈。
  • 扫描并配网未配网的设备。
  • 配置节点的 AppKey 和模型绑定。
  • 控制节点的开关状态(OnOff 模型)。

代码使用 ESP-IDF 提供的 BLE Mesh API,通过配置客户端模型(Configuration Client Model)和通用开关客户端模型(Generic OnOff Client Model)与配网后的节点通信。

工程创建好后,我们来简单看一下这个代码,首先还是从main函数开始

void app_main(void)
{
    esp_err_t err;

    ESP_LOGI(TAG, "Initializing...");

    err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    err = bluetooth_init();
    if (err) {
        ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err);
        return;
    }

    ble_mesh_get_dev_uuid(dev_uuid);

    /* Initialize the Bluetooth Mesh Subsystem */
    err = ble_mesh_init();
    if (err) {
        ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err);
    }
}

首先和节点类似的是初始化了蓝牙协议栈并获取UUID,这块因为调用的函数都是基于ble_mesh_example_init.c和ble_mesh_example_init.h的,所以不在过多赘述了,感兴趣的可以去看我的上一篇文章。

接下来我们来看ble_mesh_init();

static esp_err_t ble_mesh_init(void)
{
    uint8_t match[2] = {0xdd, 0xdd};
    esp_err_t err = ESP_OK;

    prov_key.net_idx = ESP_BLE_MESH_KEY_PRIMARY;
    prov_key.app_idx = APP_KEY_IDX;
    memset(prov_key.app_key, APP_KEY_OCTET, sizeof(prov_key.app_key));

    esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb);
    esp_ble_mesh_register_config_client_callback(example_ble_mesh_config_client_cb);
    esp_ble_mesh_register_generic_client_callback(example_ble_mesh_generic_client_cb);

    err = esp_ble_mesh_init(&provision, &composition);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to initialize mesh stack (err %d)", err);
        return err;
    }

    err = esp_ble_mesh_provisioner_set_dev_uuid_match(match, sizeof(match), 0x0, false);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set matching device uuid (err %d)", err);
        return err;
    }

    err = esp_ble_mesh_provisioner_prov_enable((esp_ble_mesh_prov_bearer_t)(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT));
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable mesh provisioner (err %d)", err);
        return err;
    }

    err = esp_ble_mesh_provisioner_add_local_app_key(prov_key.app_key, prov_key.net_idx, prov_key.app_idx);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to add local AppKey (err %d)", err);
        return err;
    }

    ESP_LOGI(TAG, "BLE Mesh Provisioner initialized");

    return err;
}

这个函数作为整个应用的mesh初始化,作用至关重要,它的主要功能包括:

  1. 初始化配网密钥:设置网络密钥索引(net_idx)、应用密钥索引(app_idx)和应用密钥(app_key)。
  2. 注册回调函数:为配网、配置客户端和通用开关客户端注册事件回调。
  3. 初始化协议栈:调用 ESP-IDF 的 BLE Mesh API 初始化协议栈。
  4. 设置 UUID 匹配规则:配置设备 UUID 匹配规则以筛选待配网设备。
  5. 启用配网功能:启用 PB-ADV 和 PB-GATT 配网承载方式。
  6. 添加本地应用密钥:为配网者添加本地应用密钥以支持后续模型通信。

函数返回 esp_err_t 类型的值,表示初始化是否成功(ESP_OK 表示成功,其他错误码表示失败)。

首先在函数开头,定义了一个UUID的筛选规则,这里把UUID的开头定义为了0xdd 0xdd开头的UUID,和server例程相同,如果你修改了server例程的UUID开头,一定要在这里改为相同的内容,否则会无法配网

接下来是初始化配网密钥结构 prov_key,这里需要补充两个宏定义

#define ESP_BLE_MESH_KEY_PRIMARY                            0x0000
#define ESP_BLE_MESH_KEY_ANY                                0xFFFF

这两个宏定义位置位于esp_ble_mesh_defs.h

以及APP_KEY的宏定义

#define APP_KEY_IDX         0x0000
#define APP_KEY_OCTET       0x12

这两个宏定义就在main.c开头

之后,作为配网者,需要配置相关的通信密钥,配置的结果如下

  • net_idx:设置为 ESP_BLE_MESH_KEY_PRIMARY(通常为 0x0000),表示主网络密钥索引。
  • app_idx:设置为 APP_KEY_IDX(定义为 0x0000),表示应用密钥索引。
  • app_key:使用 memset 将 16 字节的应用密钥数组填充为 APP_KEY_OCTET(定义为 0x12),即每个字节为 0x12。

之后开发自己的应用,可以根据需求修改APP_KEY_IDX以及APP_KEY_OCTET用于安全需求

之后注册了三个回调函数,这块作用和节点作用一样,但内容不同

  • example_ble_mesh_provisioning_cb:处理配网相关事件(如发现未配网设备、配网完成等)。
  • example_ble_mesh_config_client_cb:处理配置客户端模型(Configuration Client Model)的事件(如获取组合数据、设置应用密钥等)。
  • example_ble_mesh_generic_client_cb:处理通用开关客户端模型(Generic OnOff Client Model)的事件(如获取或设置开关状态)。

我们来详细说一下这三个回调函数,也是配网者功能的核心

首先是example_ble_mesh_provisioning_cb

static void example_ble_mesh_provisioning_cb(esp_ble_mesh_prov_cb_event_t event,
                                             esp_ble_mesh_prov_cb_param_t *param)
{
    switch (event) {
    case ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT, err_code %d", param->provisioner_prov_enable_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT, err_code %d", param->provisioner_prov_disable_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT");
        recv_unprov_adv_pkt(param->provisioner_recv_unprov_adv_pkt.dev_uuid, param->provisioner_recv_unprov_adv_pkt.addr,
                            param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info,
                            param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT:
        prov_link_open(param->provisioner_prov_link_open.bearer);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT:
        prov_link_close(param->provisioner_prov_link_close.bearer, param->provisioner_prov_link_close.reason);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT:
        prov_complete(param->provisioner_prov_complete.node_idx, param->provisioner_prov_complete.device_uuid,
                      param->provisioner_prov_complete.unicast_addr, param->provisioner_prov_complete.element_num,
                      param->provisioner_prov_complete.netkey_idx);
        break;
    case ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, err_code %d", param->provisioner_add_unprov_dev_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT, err_code %d", param->provisioner_set_dev_uuid_match_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT: {
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code %d", param->provisioner_set_node_name_comp.err_code);
        if (param->provisioner_set_node_name_comp.err_code == ESP_OK) {
            const char *name = NULL;
            name = esp_ble_mesh_provisioner_get_node_name(param->provisioner_set_node_name_comp.node_index);
            if (!name) {
                ESP_LOGE(TAG, "Get node name failed");
                return;
            }
            ESP_LOGI(TAG, "Node %d name is: %s", param->provisioner_set_node_name_comp.node_index, name);
        }
        break;
    }
    case ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT: {
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT, err_code %d", param->provisioner_add_app_key_comp.err_code);
        if (param->provisioner_add_app_key_comp.err_code == ESP_OK) {
            esp_err_t err = 0;
            prov_key.app_idx = param->provisioner_add_app_key_comp.app_idx;
            err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_key.app_idx,
                    ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, ESP_BLE_MESH_CID_NVAL);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Provisioner bind local model appkey failed");
                return;
            }
        }
        break;
    }
    case ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT, err_code %d", param->provisioner_bind_app_key_to_model_comp.err_code);
        break;
    default:
        break;
    }

    return;
}

接下来针对每一个event说明:

1. ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT 配网启用完成事件

  • 功能:处理配网功能启用完成事件(对应 ble_mesh_init 中的 esp_ble_mesh_provisioner_prov_enable 调用)。
  • 参数:param->provisioner_prov_enable_comp.err_code 表示启用结果(ESP_OK 表示成功)。
  • 作用:记录启用配网功能的成功或失败状态。

2. ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT 配网禁用完成事件

  • 功能:处理配网功能禁用完成事件(对应 esp_ble_mesh_provisioner_prov_disable API,未在 main.c 中调用)。
  • 参数:param->provisioner_prov_disable_comp.err_code 表示禁用结果。
  • 作用:记录禁用配网功能的成功或失败状态。

3. ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT 接收未配网设备广播包事件

  • 功能:处理接收到未配网设备广播包的事件。
  • 参数
    • dev_uuid:未配网设备的 UUID。
    • addr:设备地址(6 字节)。
    • addr_type:地址类型(esp_ble_mesh_addr_type_t)。
    • oob_info:设备支持的 OOB(Out-of-Band)认证信息。
    • adv_type:广播类型。
    • bearer:承载方式(PB-ADV 或 PB-GATT)。
  • 作用:调用 recv_unprov_adv_pkt 函数(定义在 main.c)处理未配网设备信息,将设备添加到配网队列(通过 esp_ble_mesh_provisioner_add_unprov_dev)。

这里我们顺便说明一下recv_unprov_adv_pkt 函数

static void recv_unprov_adv_pkt(uint8_t dev_uuid[16], uint8_t addr[BD_ADDR_LEN],
                                esp_ble_mesh_addr_type_t addr_type, uint16_t oob_info,
                                uint8_t adv_type, esp_ble_mesh_prov_bearer_t bearer)
{
    esp_ble_mesh_unprov_dev_add_t add_dev = {0};
    int err;

    /* Due to the API esp_ble_mesh_provisioner_set_dev_uuid_match, Provisioner will only
     * use this callback to report the devices, whose device UUID starts with 0xdd & 0xdd,
     * to the application layer.
     */

    ESP_LOGI(TAG, "address: %s, address type: %d, adv type: %d", bt_hex(addr, BD_ADDR_LEN), addr_type, adv_type);
    ESP_LOGI(TAG, "device uuid: %s", bt_hex(dev_uuid, 16));
    ESP_LOGI(TAG, "oob info: %d, bearer: %s", oob_info, (bearer & ESP_BLE_MESH_PROV_ADV) ? "PB-ADV" : "PB-GATT");

    memcpy(add_dev.addr, addr, BD_ADDR_LEN);
    add_dev.addr_type = (esp_ble_mesh_addr_type_t)addr_type;
    memcpy(add_dev.uuid, dev_uuid, 16);
    add_dev.oob_info = oob_info;
    add_dev.bearer = (esp_ble_mesh_prov_bearer_t)bearer;
    /* Note: If unprovisioned device adv packets have not been received, we should not add
             device with ADD_DEV_START_PROV_NOW_FLAG set. */
    err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev,
            (esp_ble_mesh_dev_add_flag_t)(ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_START_PROV_NOW_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG));
    if (err) {
        ESP_LOGE(TAG, "%s: Add unprovisioned device into queue failed", __func__);
    }

    return;
}

recv_unprov_adv_pkt 是 BLE Mesh 配网者(Provisioner)用于处理未配网设备广播包的回调函数,其主要作用包括:

  1. 记录未配网设备信息
    • 解析广播包中的设备 UUID、MAC 地址、地址类型、OOB 信息、广播类型和承载方式。
    • 通过日志输出设备信息,便于调试。
  2. 添加设备到配网队列
    • 将未配网设备信息存储到配网者的队列中,准备开始配网流程。
    • 配置添加设备的标志,控制配网行为(如立即配网、配网后移除)。
  3. 触发配网流程
    • 通过调用 esp_ble_mesh_provisioner_add_unprov_dev,将设备加入配网队列并启动配网。
  4. 过滤特定 UUID
    • 根据注释,函数仅处理 UUID 以 0xdd, 0xdd 开头的设备(由 esp_ble_mesh_provisioner_set_dev_uuid_match 设置)。

首先说明一下该函数传入的参数:

参数

  • dev_uuid[16]:未配网设备的 16 字节 UUID,唯一标识设备。
  • addr[BD_ADDR_LEN]:设备的 6 字节 MAC 地址(BD_ADDR_LEN 通常为 6)。
  • addr_type:地址类型(如公共地址或随机地址)。
  • oob_info:带外(OOB)认证信息,指示设备支持的 OOB 方式(如 QR 码、静态 OOB)。
  • adv_type:广播包类型(如 ADV_IND、ADV_NONCONN_IND)。
  • bearer:配网承载方式(PB-ADV 或 PB-GATT)。

函数执行后,首先填充设备信息到add_dev中

  • addr:MAC 地址,复制到 add_dev.addr。
  • addr_type:地址类型,赋值到 add_dev.addr_type。
  • dev_uuid:设备 UUID,复制到 add_dev.uuid。
  • oob_info:OOB 信息,赋值到 add_dev.oob_info。
  • bearer:承载方式,赋值到 add_dev.bearer。

之后调用esp_ble_mesh_provisioner_prov_enable添加设备到配网队列,并设置三个添加标志

设置添加标志(esp_ble_mesh_dev_add_flag_t):

  • ADD_DEV_RM_AFTER_PROV_FLAG:配网完成后从队列中移除设备。
  • ADD_DEV_START_PROV_NOW_FLAG:立即开始配网流程。
  • ADD_DEV_FLUSHABLE_DEV_FLAG:允许设备信息可被刷新(覆盖)。

  

4. ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT 配网链接建立事件

  • 功能:处理配网链接建立事件,表示与未配网设备建立通信链接(PB-ADV 或 PB-GATT)。
  • 参数:param->provisioner_prov_link_open.bearer 表示承载方式(ESP_BLE_MESH_PROV_ADV 或 ESP_BLE_MESH_PROV_GATT)。
  • 作用:调用 prov_link_open(定义在 main.c)记录链接建立的日志。

5. ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT 配网链接关闭事件

  • 功能:处理配网链接关闭事件,表示与设备的配网链接断开。
  • 参数
    • bearer:承载方式。
    • reason:关闭原因(例如,配网完成或失败)。
  • 作用:调用 prov_link_close(定义在 main.c)记录关闭原因的日志。

这里把prov_link_open和prov_link_close的函数原型做简单说明

static void prov_link_open(esp_ble_mesh_prov_bearer_t bearer)
{
    ESP_LOGI(TAG, "%s link open", bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT");
}

static void prov_link_close(esp_ble_mesh_prov_bearer_t bearer, uint8_t reason)
{
    ESP_LOGI(TAG, "%s link close, reason 0x%02x",
             bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT", reason);
}

这两个函数用于打印日志

6. ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT 配网完成事件

  • 功能:处理配网完成事件,表示一个设备成功加入 Mesh 网络。
  • 参数
    • node_idx:节点索引。
    • device_uuid:设备 UUID。
    • unicast_addr:分配的单播地址。
    • element_num:设备元素数量。
    • netkey_idx:网络密钥索引。
  • 作用:调用 prov_complete(定义在 main.c)执行以下操作:
    • 设置节点名称(格式为 NODE-<node_idx>)。
    • 存储节点信息(UUID、单播地址、元素数量、开关状态)到 nodes 数组。
    • 获取节点组合数据(Composition Data)以进一步配置节点。

这里介绍一下prov_complete配网完成的回调函数

static esp_err_t prov_complete(int node_idx, const esp_ble_mesh_octet16_t uuid,
                               uint16_t unicast, uint8_t elem_num, uint16_t net_idx)
{
    esp_ble_mesh_client_common_param_t common = {0};
    esp_ble_mesh_cfg_client_get_state_t get_state = {0};
    esp_ble_mesh_node_info_t *node = NULL;
    char name[11] = {0};
    int err;

    ESP_LOGI(TAG, "node index: 0x%x, unicast address: 0x%02x, element num: %d, netkey index: 0x%02x",
             node_idx, unicast, elem_num, net_idx);
    ESP_LOGI(TAG, "device uuid: %s", bt_hex(uuid, 16));

    sprintf(name, "%s%d", "NODE-", node_idx);
    err = esp_ble_mesh_provisioner_set_node_name(node_idx, name);
    if (err) {
        ESP_LOGE(TAG, "%s: Set node name failed", __func__);
        return ESP_FAIL;
    }

    err = example_ble_mesh_store_node_info(uuid, unicast, elem_num, LED_OFF);
    if (err) {
        ESP_LOGE(TAG, "%s: Store node info failed", __func__);
        return ESP_FAIL;
    }

    node = example_ble_mesh_get_node_info(unicast);
    if (!node) {
        ESP_LOGE(TAG, "%s: Get node info failed", __func__);
        return ESP_FAIL;
    }

    example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET);
    get_state.comp_data_get.page = COMP_DATA_PAGE_0;
    err = esp_ble_mesh_config_client_get_state(&common, &get_state);
    if (err) {
        ESP_LOGE(TAG, "%s: Send config comp data get failed", __func__);
        return ESP_FAIL;
    }

    return ESP_OK;
}

prov_complete 是 BLE Mesh 配网者(Provisioner)处理配网完成事件的回调函数,在 example_ble_mesh_provisioning_cb 中被调用,用于处理配网成功后的节点信息存储和配置流程的初始化。其主要作用包括:

  1. 记录配网完成信息
    • 记录节点的索引、UUID、单播地址、元素数量和网络密钥索引。
    • 为节点设置名称并存储到配网者的节点信息数组(nodes)。
  2. 触发配置流程
    • 获取节点的组合数据(Composition Data),启动后续配置(如添加应用密钥、绑定模型)。
  3. 日志输出
    • 输出配网完成的节点信息,便于调试。
  4. 错误处理
    • 检查每个操作的返回值,确保配网和配置流程顺利进行。

它的传入参数如下:

  • 参数
    • node_idx:配网节点的索引,用于在 nodes 数组中定位节点。
    • uuid:节点的 16 字节设备 UUID,唯一标识节点。
    • unicast:节点的单播地址,用于 Mesh 网络通信。
    • elem_num:节点的元素数量,表示节点的功能单元数。
    • net_idx:网络密钥索引,标识节点所属的 Mesh 网络。
  • 返回值:esp_err_t,成功返回 ESP_OK,失败返回 ESP_FAIL。

在函数里,首先调用 esp_ble_mesh_provisioner_set_node_name 将名称存储到协议栈的节点管理列表,随后调用 example_ble_mesh_store_node_info 将节点信息(UUID、单播地址、元素数量、初始开关状态)存储到 nodes 数组。之后调用 example_ble_mesh_get_node_info 根据单播地址(unicast)查找 nodes 数组中的节点,并调用 example_ble_mesh_set_msg_common 设置通用消息参数(common),包括目标地址(node->unicast)、模型(config_client.model)和操作码(ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET),设置 get_state.comp_data_get.page 为 COMP_DATA_PAGE_0

在该函数中,同时调用了三个函数,example_ble_mesh_store_node_info example_ble_mesh_get_node_info以及example_ble_mesh_set_msg_common ,接下来说明一下这三个函数。

static esp_err_t example_ble_mesh_store_node_info(const uint8_t uuid[16], uint16_t unicast,
                                                  uint8_t elem_num, uint8_t onoff_state)
{
    int i;

    if (!uuid || !ESP_BLE_MESH_ADDR_IS_UNICAST(unicast)) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Judge if the device has been provisioned before */
    for (i = 0; i < ARRAY_SIZE(nodes); i++) {
        if (!memcmp(nodes[i].uuid, uuid, 16)) {
            ESP_LOGW(TAG, "%s: reprovisioned device 0x%04x", __func__, unicast);
            nodes[i].unicast = unicast;
            nodes[i].elem_num = elem_num;
            nodes[i].onoff = onoff_state;
            return ESP_OK;
        }
    }

    for (i = 0; i < ARRAY_SIZE(nodes); i++) {
        if (nodes[i].unicast == ESP_BLE_MESH_ADDR_UNASSIGNED) {
            memcpy(nodes[i].uuid, uuid, 16);
            nodes[i].unicast = unicast;
            nodes[i].elem_num = elem_num;
            nodes[i].onoff = onoff_state;
            return ESP_OK;
        }
    }

    return ESP_FAIL;
}

static esp_ble_mesh_node_info_t *example_ble_mesh_get_node_info(uint16_t unicast)
{
    int i;

    if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast)) {
        return NULL;
    }

    for (i = 0; i < ARRAY_SIZE(nodes); i++) {
        if (nodes[i].unicast <= unicast &&
                nodes[i].unicast + nodes[i].elem_num > unicast) {
            return &nodes[i];
        }
    }

    return NULL;
}

static esp_err_t example_ble_mesh_set_msg_common(esp_ble_mesh_client_common_param_t *common,
                                                 esp_ble_mesh_node_info_t *node,
                                                 esp_ble_mesh_model_t *model, uint32_t opcode)
{
    if (!common || !node || !model) {
        return ESP_ERR_INVALID_ARG;
    }

    common->opcode = opcode;
    common->model = model;
    common->ctx.net_idx = prov_key.net_idx;
    common->ctx.app_idx = prov_key.app_idx;
    common->ctx.addr = node->unicast;
    common->ctx.send_ttl = MSG_SEND_TTL;
    common->msg_timeout = MSG_TIMEOUT;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
    common->msg_role = MSG_ROLE;
#endif

    return ESP_OK;
}

首先是example_ble_mesh_store_node_info,它的作用用于将配网完成后的节点信息(UUID、单播地址、元素数量、开关状态)存储到 nodes 数组中。它检查节点是否已存在(通过 UUID 匹配),更新或添加节点信息。

函数参数说明:

  • 参数
    • uuid[16]:节点的 16 字节 UUID。
    • unicast:节点的单播地址。
    • elem_num:节点元素数量。
    • onoff_state:初始开关状态(通常为 LED_OFF,即 0x00)。
  • 返回值:esp_err_t,成功返回 ESP_OK,失败返回 ESP_ERR_INVALID_ARG 或 ESP_FAIL。

逻辑上流程如下:

  • 参数检查
    • 检查 uuid 是否为 NULL 或 unicast 是否为有效单播地址(通过 ESP_BLE_MESH_ADDR_IS_UNICAST,验证范围 0x0001-0x7FFF)。
    • 若无效,返回 ESP_ERR_INVALID_ARG。
  • 检查重复配网
    • 遍历 nodes 数组,比较 UUID 是否匹配(memcmp(nodes[i].uuid, uuid, 16))。
    • 若找到匹配 UUID,说明节点已配网(重新配网),更新 unicast、elem_num 和 onoff_state,记录警告日志,返回 ESP_OK。
  • 添加新节点
    • 遍历 nodes 数组,查找未分配的槽位(nodes[i].unicast == ESP_BLE_MESH_ADDR_UNASSIGNED,通常为 0x0000)。
    • 将 uuid、unicast、elem_num 和 onoff_state 复制到空槽位,返回 ESP_OK。
  • 失败处理
    • 若无空槽位(nodes 数组满),返回 ESP_FAIL。

接下来是example_ble_mesh_get_node_info,它根据单播地址查找 nodes 数组中的节点信息,返回对应的节点指针。它用于在配置或控制流程中获取节点信息。

函数参数说明:

  • 参数:unicast(节点的单播地址)。
  • 返回值:esp_ble_mesh_node_info_t *,成功返回节点指针,失败返回 NULL。

它的逻辑流程是:

  • 参数检查
    • 使用 ESP_BLE_MESH_ADDR_IS_UNICAST 验证 unicast 是否为有效单播地址(0x0001-0x7FFF)。
    • 若无效,返回 NULL。
  • 查找节点
    • 遍历 nodes 数组,检查 unicast 是否落在节点地址范围内(nodes[i].unicast 到 nodes[i].unicast + nodes[i].elem_num - 1)。
    • 节点可能有多个元素,单播地址范围覆盖所有元素地址。
    • 若找到匹配节点,返回 nodes[i] 的指针。
  • 失败处理
    • 若无匹配节点,返回 NULL。

这里说明一下nodes数组的定义,它是例程内部定义的结构体数组

typedef struct {
    uint8_t  uuid[16];
    uint16_t unicast;
    uint8_t  elem_num;
    uint8_t  onoff;
} esp_ble_mesh_node_info_t;

static esp_ble_mesh_node_info_t nodes[CONFIG_BLE_MESH_MAX_PROV_NODES] = {0};

这个结构体数组中为每一个node设置了uuid,单播地址,elements数量,还有开关状态。

最后是example_ble_mesh_set_msg_common,用于设置 BLE Mesh 客户端消息的通用参数(esp_ble_mesh_client_common_param_t),包括操作码、模型、目标地址、网络密钥索引等,为发送配置或控制消息做准备。

它的函数参数说明:

  • 参数
    • common:指向消息参数结构体的指针(esp_ble_mesh_client_common_param_t)。
    • node:目标节点信息(esp_ble_mesh_node_info_t)。
    • model:发送消息的客户端模型(如 config_client.model)。
    • opcode:消息操作码(如 ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET)。
  • 返回值:esp_err_t,成功返回 ESP_OK,失败返回 ESP_ERR_INVALID_ARG。

它的逻辑流程如下:

    1. 参数检查
      • 验证 common、node 和 model 是否为 NULL。
      • 若任一参数无效,返回 ESP_ERR_INVALID_ARG。
    2. 设置消息参数
      • common->opcode:设置操作码(如获取组合数据、设置开关状态)。
      • common->model:设置客户端模型(如 config_client.model)。
      • common->ctx.net_idx:设置网络密钥索引,从 prov_key.net_idx 获取。
      • common->ctx.app_idx:设置应用密钥索引,从 prov_key.app_idx 获取。
      • common->ctx.addr:设置目标单播地址,从 node->unicast 获取。
      • common->ctx.send_ttl:设置消息 TTL(MSG_SEND_TTL,通常为 7)。
      • common->msg_timeout:设置消息超时时间(MSG_TIMEOUT,通常为 0,表示默认超时)。
      • common->msg_role:设置消息角色(已废弃,仅在 ESP-IDF 5.2.0 前使用)。
    3. 返回
      • 成功设置参数,返回 ESP_OK。

整体来说,这三个函数作用如下

  • example_ble_mesh_store_node_info:存储节点信息到 nodes 数组。
  • example_ble_mesh_get_node_info:验证节点信息并获取节点指针。
  • example_ble_mesh_set_msg_common:设置组合数据获取消息的参数。

他们统一为后续配置及控制节点做准备

7. ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT 添加未配网设备完成事件

  • 功能:处理将未配网设备添加到配网队列完成的事件(对应 recv_unprov_adv_pkt 中的 esp_ble_mesh_provisioner_add_unprov_dev)。
  • 参数:param->provisioner_add_unprov_dev_comp.err_code 表示添加结果。

8. ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT 设置 UUID 匹配完成事件

  • 功能:处理设置 UUID 匹配规则完成的事件(对应 ble_mesh_init 中的 esp_ble_mesh_provisioner_set_dev_uuid_match)。
  • 参数:param->provisioner_set_dev_uuid_match_comp.err_code 表示设置结果。
  • 作用:记录设置结果,未触发进一步操作。

9. ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT  设置节点名称完成事件

  • 功能:处理设置节点名称完成的事件(对应 prov_complete 中的 esp_ble_mesh_provisioner_set_node_name)。
  • 参数
    • param->provisioner_set_node_name_comp.err_code:设置结果。
    • param->provisioner_set_node_name_comp.node_index:节点索引。
  • 作用
    • 如果设置成功(err_code == ESP_OK),调用 esp_ble_mesh_provisioner_get_node_name 获取节点名称并记录日志。
    • 如果获取名称失败,记录错误日志。

10. ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT 添加本地应用密钥完成事件

  • 功能:处理添加本地应用密钥完成的事件(对应 ble_mesh_init 中的 esp_ble_mesh_provisioner_add_local_app_key)。
  • 参数
    • param->provisioner_add_app_key_comp.err_code:添加结果。
    • param->provisioner_add_app_key_comp.app_idx:分配的应用密钥索引。
  • 作用
    • 如果添加成功,更新 prov_key.app_idx 为协议栈返回的索引。
    • 调用 esp_ble_mesh_provisioner_bind_app_key_to_local_model 将应用密钥绑定到配网者的通用开关客户端模型(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI)。

11. ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT 绑定应用密钥到模型完成事件

  • 功能:处理绑定应用密钥到模型完成的事件(对应上述 esp_ble_mesh_provisioner_bind_app_key_to_local_model)。
  • 参数:param->provisioner_bind_app_key_to_model_comp.err_code 表示绑定结果。
  • 作用:记录绑定结果,未触发进一步操作。

这里其实就包含了配网者和节点通信的一系列流程:

  • 配网启用/禁用:记录状态。
  • 发现未配网设备:调用 recv_unprov_adv_pkt 添加到配网队列。
  • 链接建立/关闭:调用 prov_link_open 或 prov_link_close 记录日志。
  • 配网完成:调用 prov_complete 设置节点名称、存储信息并获取组合数据。
  • 添加密钥/绑定模型:更新 prov_key 并绑定密钥到模型。

接下来说明第二个回调函数

static void example_ble_mesh_config_client_cb(esp_ble_mesh_cfg_client_cb_event_t event,
                                              esp_ble_mesh_cfg_client_cb_param_t *param)
{
    esp_ble_mesh_client_common_param_t common = {0};
    esp_ble_mesh_node_info_t *node = NULL;
    uint32_t opcode;
    uint16_t addr;
    int err;

    opcode = param->params->opcode;
    addr = param->params->ctx.addr;

    ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x, opcode: 0x%04" PRIx32,
             __func__, param->error_code, event, param->params->ctx.addr, opcode);

    if (param->error_code) {
        ESP_LOGE(TAG, "Send config client message failed, opcode 0x%04" PRIx32, opcode);
        return;
    }

    node = example_ble_mesh_get_node_info(addr);
    if (!node) {
        ESP_LOGE(TAG, "%s: Get node info failed", __func__);
        return;
    }

    switch (event) {
    case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT:
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: {
            ESP_LOGI(TAG, "composition data %s", bt_hex(param->status_cb.comp_data_status.composition_data->data,
                     param->status_cb.comp_data_status.composition_data->len));
            esp_ble_mesh_cfg_client_set_state_t set_state = {0};
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD);
            set_state.app_key_add.net_idx = prov_key.net_idx;
            set_state.app_key_add.app_idx = prov_key.app_idx;
            memcpy(set_state.app_key_add.app_key, prov_key.app_key, 16);
            err = esp_ble_mesh_config_client_set_state(&common, &set_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Config AppKey Add failed", __func__);
                return;
            }
            break;
        }
        default:
            break;
        }
        break;
    case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT:
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: {
            esp_ble_mesh_cfg_client_set_state_t set_state = {0};
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND);
            set_state.model_app_bind.element_addr = node->unicast;
            set_state.model_app_bind.model_app_idx = prov_key.app_idx;
            set_state.model_app_bind.model_id = ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV;
            set_state.model_app_bind.company_id = ESP_BLE_MESH_CID_NVAL;
            err = esp_ble_mesh_config_client_set_state(&common, &set_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Config Model App Bind failed", __func__);
                return;
            }
            break;
        }
        case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: {
            esp_ble_mesh_generic_client_get_state_t get_state = {0};
            example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET);
            err = esp_ble_mesh_generic_client_get_state(&common, &get_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Generic OnOff Get failed", __func__);
                return;
            }
            break;
        }
        default:
            break;
        }
        break;
    case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT:
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS:
            ESP_LOG_BUFFER_HEX("composition data %s", param->status_cb.comp_data_status.composition_data->data,
                               param->status_cb.comp_data_status.composition_data->len);
            break;
        case ESP_BLE_MESH_MODEL_OP_APP_KEY_STATUS:
            break;
        default:
            break;
        }
        break;
    case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT:
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: {
            esp_ble_mesh_cfg_client_get_state_t get_state = {0};
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET);
            get_state.comp_data_get.page = COMP_DATA_PAGE_0;
            err = esp_ble_mesh_config_client_get_state(&common, &get_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Config Composition Data Get failed", __func__);
                return;
            }
            break;
        }
        case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: {
            esp_ble_mesh_cfg_client_set_state_t set_state = {0};
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD);
            set_state.app_key_add.net_idx = prov_key.net_idx;
            set_state.app_key_add.app_idx = prov_key.app_idx;
            memcpy(set_state.app_key_add.app_key, prov_key.app_key, 16);
            err = esp_ble_mesh_config_client_set_state(&common, &set_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Config AppKey Add failed", __func__);
                return;
            }
            break;
        }
        case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: {
            esp_ble_mesh_cfg_client_set_state_t set_state = {0};
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND);
            set_state.model_app_bind.element_addr = node->unicast;
            set_state.model_app_bind.model_app_idx = prov_key.app_idx;
            set_state.model_app_bind.model_id = ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV;
            set_state.model_app_bind.company_id = ESP_BLE_MESH_CID_NVAL;
            err = esp_ble_mesh_config_client_set_state(&common, &set_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Config Model App Bind failed", __func__);
                return;
            }
            break;
        }
        default:
            break;
        }
        break;
    default:
        ESP_LOGE(TAG, "Not a config client status message event");
        break;
    }
}

example_ble_mesh_config_client_cb 是 BLE Mesh 配置客户端模型(Configuration Client Model)的回调函数,用于处理配置客户端相关的事件。它通过 esp_ble_mesh_cfg_client_cb_event_t 类型的事件参数和 esp_ble_mesh_cfg_client_cb_param_t 参数结构,响应协议栈触发的配置事件。主要功能包括:

  1. 事件分发:根据事件类型(event)和操作码(opcode)处理配置客户端的操作,如获取组合数据(Composition Data)、添加应用密钥(AppKey)、绑定模型等。
  2. 日志记录:记录事件状态、错误码和操作结果,便于调试。
  3. 后续操作:在接收到特定事件(如获取组合数据成功)后,触发下一步配置操作(如添加应用密钥、绑定模型或获取开关状态)。

接下来详细说明其中的每一个事件:

1. ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT 获取状态事件

  • 功能:处理配置客户端获取状态的事件,具体处理 ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET(获取组合数据)。
  • 操作
    • 记录收到的组合数据(param->status_cb.comp_data_status.composition_data)的十六进制表示。
    • 调用 example_ble_mesh_set_msg_common 设置消息参数,准备添加应用密钥(ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD)。
    • 配置 set_state.app_key_add 参数,包括网络密钥索引(prov_key.net_idx)、应用密钥索引(prov_key.app_idx)和应用密钥(prov_key.app_key)。
    • 调用 esp_ble_mesh_config_client_set_state 发送添加应用密钥的消息。
  • 作用:在获取节点组合数据后,向节点添加应用密钥,为后续模型通信做准备。

2. ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT 设置状态事件

        这个事件中,判断两个opcode

  • 功能:处理配置客户端设置状态的事件,处理以下操作码:
    • ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD:添加应用密钥成功。
    • ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND:绑定应用密钥到模型成功。
  • 操作
    • 添加应用密钥成功
      • 调用 example_ble_mesh_set_msg_common 准备绑定模型(ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND)。
      • 配置 set_state.model_app_bind 参数,包括节点单播地址(node->unicast)、应用密钥索引(prov_key.app_idx)、通用开关服务器模型 ID(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV)和公司 ID(ESP_BLE_MESH_CID_NVAL)。
      • 调用 esp_ble_mesh_config_client_set_state 发送绑定模型消息。
    • 绑定模型成功
      • 调用 example_ble_mesh_set_msg_common 准备获取通用开关状态(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET)。
      • 使用 onoff_client 模型发送获取开关状态的消息。
  • 作用:完成应用密钥添加后,绑定密钥到节点的通用开关服务器模型(Generic OnOff Server),然后获取节点的开关状态,完成配置流程。

3. ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT 发布事件

  • 功能:处理配置客户端接收到发布消息的事件,处理 ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS(组合数据状态)。
  • 操作:记录收到的组合数据(十六进制格式),不触发进一步操作。
  • 作用:处理节点通过发布(Publish)方式发送的组合数据状态,通常用于异步响应。

4. ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT 超时事件

  • 功能:处理配置客户端消息超时的重试逻辑,针对以下操作码:
    • ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET:重试获取组合数据。
    • ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD:重试添加应用密钥。
    • ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND:重试绑定模型。
  • 操作:对于每种超时情况,重新构造消息并调用 esp_ble_mesh_config_client_set_state 或 esp_ble_mesh_config_client_get_state 重试操作。
  • 作用:确保配置流程在超时后能够自动重试,提高鲁棒性。

这个回调函数主要处理配置节点的流程:

  • 初始化和日志
    • 初始化消息参数和节点指针,提取操作码和目标地址。
    • 记录事件日志(事件类型、错误码、地址、操作码)。
  • 错误检查
    • 如果 param->error_code 非零,记录错误并退出。
  • 获取节点信息
    • 调用 example_ble_mesh_get_node_info 获取节点信息,若失败则退出。
  • 事件分发
    • 获取状态(ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT)
      • 处理组合数据获取(ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET),记录数据并触发添加应用密钥。
    • 设置状态(ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT)
      • 添加应用密钥成功后,绑定密钥到通用开关服务器模型。
      • 绑定模型成功后,获取节点的开关状态。
    • 发布事件(ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT)
      • 记录发布的组合数据状态。
    • 超时事件(ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT)
      • 重试获取组合数据、添加应用密钥或绑定模型。
  • 返回:无返回值(void 函数),通过日志和后续操作反馈状态。

最后是example_ble_mesh_generic_client_cb

static void example_ble_mesh_generic_client_cb(esp_ble_mesh_generic_client_cb_event_t event,
                                               esp_ble_mesh_generic_client_cb_param_t *param)
{
    esp_ble_mesh_client_common_param_t common = {0};
    esp_ble_mesh_node_info_t *node = NULL;
    uint32_t opcode;
    uint16_t addr;
    int err;

    opcode = param->params->opcode;
    addr = param->params->ctx.addr;

    ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x, opcode: 0x%04" PRIx32,
             __func__, param->error_code, event, param->params->ctx.addr, opcode);

    if (param->error_code) {
        ESP_LOGE(TAG, "Send generic client message failed, opcode 0x%04" PRIx32, opcode);
        return;
    }

    node = example_ble_mesh_get_node_info(addr);
    if (!node) {
        ESP_LOGE(TAG, "%s: Get node info failed", __func__);
        return;
    }

    switch (event) {
    case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT:
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: {
            esp_ble_mesh_generic_client_set_state_t set_state = {0};
            node->onoff = param->status_cb.onoff_status.present_onoff;
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET onoff: 0x%02x", node->onoff);
            /* After Generic OnOff Status for Generic OnOff Get is received, Generic OnOff Set will be sent */
            example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET);
            set_state.onoff_set.op_en = false;
            set_state.onoff_set.onoff = !node->onoff;
            set_state.onoff_set.tid = 0;
            err = esp_ble_mesh_generic_client_set_state(&common, &set_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__);
                return;
            }
            break;
        }
        default:
            break;
        }
        break;
    case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT:
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET:
            node->onoff = param->status_cb.onoff_status.present_onoff;
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET onoff: 0x%02x", node->onoff);
            break;
        default:
            break;
        }
        break;
    case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT:
        break;
    case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT:
        /* If failed to receive the responses, these messages will be resend */
        switch (opcode) {
        case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: {
            esp_ble_mesh_generic_client_get_state_t get_state = {0};
            example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET);
            err = esp_ble_mesh_generic_client_get_state(&common, &get_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Generic OnOff Get failed", __func__);
                return;
            }
            break;
        }
        case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: {
            esp_ble_mesh_generic_client_set_state_t set_state = {0};
            node->onoff = param->status_cb.onoff_status.present_onoff;
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET onoff: 0x%02x", node->onoff);
            example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET);
            set_state.onoff_set.op_en = false;
            set_state.onoff_set.onoff = !node->onoff;
            set_state.onoff_set.tid = 0;
            err = esp_ble_mesh_generic_client_set_state(&common, &set_state);
            if (err) {
                ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__);
                return;
            }
            break;
        }
        default:
            break;
        }
        break;
    default:
        ESP_LOGE(TAG, "Not a generic client status message event");
        break;
    }
}

example_ble_mesh_generic_client_cb 是 BLE Mesh 通用开关客户端模型(Generic OnOff Client Model)的回调函数,用于处理与通用开关模型相关的消息事件。它通过 esp_ble_mesh_generic_client_cb_event_t 类型的事件参数和 esp_ble_mesh_generic_client_cb_param_t 参数结构,响应协议栈触发的通用开关客户端事件。主要功能包括:

  1. 事件分发:根据事件类型(event)和操作码(opcode)处理通用开关客户端的操作,如获取开关状态(OnOff Get)或设置开关状态(OnOff Set)。
  2. 日志记录:记录事件状态、错误码和开关状态,便于调试。
  3. 状态管理:更新节点的开关状态(node->onoff)并触发后续操作(如在获取状态后设置相反状态)。
  4. 超时重试:处理消息超时情况,重新发送获取或设置开关状态的请求。

接下来详细说明每个事件:

1. ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT 获取状态事件

  • 更新节点开关状态(node->onoff)为收到的状态(param->status_cb.onoff_status.present_onoff)。
  • 记录当前开关状态(0x00 表示关,0x01 表示开)。
  • 调用 example_ble_mesh_set_msg_common 设置消息参数,准备设置开关状态(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET)。
  • 配置 set_state.onoff_set 参数:
    • op_en:设为 false,表示不启用可选参数。
    • onoff:设为当前状态的相反值(!node->onoff),实现开关翻转。
    • tid:事务标识符,设为 0(固定值,可能用于区分消息)。
  • 调用 esp_ble_mesh_generic_client_set_state 发送设置开关状态的消息。

2. ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT 设置状态事件

  • 功能:处理通用开关客户端设置状态的事件,处理 ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET(设置开关状态)。
  • 操作
    • 更新节点开关状态(node->onoff)为收到的状态。
    • 记录新的开关状态。
  • 作用:确认节点开关状态已更新,保持 nodes 数组中的状态同步。

3. ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT 发布事件

  • 功能:处理通用开关客户端接收到发布消息的事件。
  • 操作:空操作,未处理发布消息。
  • 作用:允许协议栈通过发布(Publish)方式发送开关状态消息,当前代码未处理。

4. ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT 超时事件

  • 功能:处理通用开关客户端消息超时的重试逻辑,针对以下操作码:
    • ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET:重试获取开关状态。
    • ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET:重试设置开关状态。
  • 操作
    • 获取状态超时
      • 调用 example_ble_mesh_set_msg_common 重新构造获取状态消息。
      • 调用 esp_ble_mesh_generic_client_get_state 重试。
    • 设置状态超时
      • 更新节点开关状态(node->onoff)。
      • 记录当前开关状态。
      • 重新构造设置状态消息(翻转开关状态)。
      • 调用 esp_ble_mesh_generic_client_set_state 重试。
  • 作用:确保开关消息在超时后能够自动重试,提高鲁棒性。

这个函数的流程主要是处理操作节点的onoff,具体流程如下:

  • 初始化和日志
    • 初始化消息参数和节点指针,提取操作码和目标地址。
    • 记录事件日志(事件类型、错误码、地址、操作码)。
  • 错误检查
    • 如果 param->error_code 非零,记录错误并退出。
  • 获取节点信息
    • 调用 example_ble_mesh_get_node_info 获取节点信息,若失败则退出。
  • 事件分发
    • 获取状态(ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT
      • 处理开关状态获取(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET),更新节点状态并发送设置状态消息(翻转开关)。
    • 设置状态(ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT
      • 处理开关状态设置(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET),更新节点状态。
    • 发布事件(ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT
      • 空操作,未处理发布消息。
    • 超时事件(ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT
      • 重试获取或设置开关状态。
  • 返回:无返回值(void 函数),通过日志和后续操作反馈状态。

说完了三个回调函数,接下来是初始化ble mesh协议栈

    err = esp_ble_mesh_init(&provision, &composition);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to initialize mesh stack (err %d)", err);
        return err;
    }

这块我们着重说一下这两个参数

首先是provision

static esp_ble_mesh_prov_t provision = {
    .prov_uuid           = dev_uuid,
    .prov_unicast_addr   = PROV_OWN_ADDR,
    .prov_start_address  = 0x0005,
    .prov_attention      = 0x00,
    .prov_algorithm      = 0x00,
    .prov_pub_key_oob    = 0x00,
    .prov_static_oob_val = NULL,
    .prov_static_oob_len = 0x00,
    .flags               = 0x00,
    .iv_index            = 0x00,
};

其中的每个成员介绍如下:

1. prov_uuid

指定配网者的设备 UUID,用于唯一标识配网者设备。

2. prov_unicast_addr

指定配网者的单播地址,用于 Mesh 网络中的通信。单播地址是 BLE Mesh 网络中节点的唯一标识,配网者使用此地址与其他节点交互。

3. prov_start_address

配网者在配网过程中为未配网设备分配单播地址,从 prov_start_address 开始递增。

4. prov_attention

注意力时间用于控制新配网设备在配网过程中的提示行为(如 LED 闪烁或蜂鸣器响)。0x00 表示禁用此功能,设备不会执行提示动作。

5. prov_algorithm

指定配网时的密钥交换算法,0x00 表示使用 FIPS P-256 椭圆曲线算法(BLE Mesh 标准默认算法)。

6. prov_pub_key_oob

指定是否通过带外方式(如 QR 码或 NFC)提供公钥。0x00 表示使用设备内部生成的公钥(基于椭圆曲线算法)。

7. prov_static_oob_valprov_static_oob_len

指定配网过程中是否使用静态 OOB 认证数据(如固定的 PIN 码)。NULL 和 0x00 表示禁用静态 OOB,配网依赖无 OOB 认证(基于设备交互)

8. flags

用于指定配网过程中的特殊标志(如 IV 更新或密钥刷新)。0x00 表示无特殊标志,使用默认配网行为。

9. iv_index

IV 索引用于 Mesh 网络的消息加密和重放保护。0x00 表示初始 IV 索引,通常用于新网络。

ble mesh初始化的第二个参数定义如下

static esp_ble_mesh_comp_t composition = {
    .cid = CID_ESP,
    .element_count = ARRAY_SIZE(elements),
    .elements = elements,
};

composition 是一个 esp_ble_mesh_comp_t 类型的结构体,定义了配网者(Provisioner)的模型组成(Composition Data),包括公司 ID、元素和模型信息。

在这里它的三个成员介绍如下:

  • .cid = CID_ESP
    • 定义:CID_ESP 是 Espressif 的公司 ID(0x02E5)。
    • 作用:标识设备制造商,符合 BLE Mesh 规范,用于组合数据中的公司标识。
  • .element_count = ARRAY_SIZE(elements)
    • 定义:使用 ARRAY_SIZE 宏计算 elements 数组的大小(当前为 1)。
    • 作用:指定节点包含的元素数量。
  • .elements = elements
    • 定义:指向 elements 数组。
    • 作用:提供节点的元素列表,每个元素包含模型信息。

这块同时还需要引出model的定义,在这个例程中,数组仅定义了一种模型

static esp_ble_mesh_elem_t elements[] = {
    ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE),
};

elements 数组用于描述 BLE Mesh 节点的元素,在这里调用ESP_BLE_MESH_ELEMENT来定义

  • 第一个参数 0:元素索引,表示主元素(Primary Element)。
  • 第二个参数 root_models:该元素支持的模型数组(即上述 root_models)。
  • 第三个参数 ESP_BLE_MESH_MODEL_NONE:表示无 Vendor 模型(自定义模型)。

一个元素包含多个模型(配置服务器、配置客户端、通用开关客户端)。
元素是 BLE Mesh 节点的功能单元,配网者在此只有一个主元素,包含所有模型。

至于root_models,用于描述 BLE Mesh 模型。

static esp_ble_mesh_model_t root_models[] = {
    ESP_BLE_MESH_MODEL_CFG_SRV(&config_server),
    ESP_BLE_MESH_MODEL_CFG_CLI(&config_client),
    ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(NULL, &onoff_client),
};
  • ESP_BLE_MESH_MODEL_CFG_SRV(&config_server):配置服务器模型(Configuration Server Model),用于处理节点的配置请求(如设置应用密钥、绑定模型)。
  • ESP_BLE_MESH_MODEL_CFG_CLI(&config_client):配置客户端模型(Configuration Client Model),用于发送配置请求(如获取组合数据、添加应用密钥)。
  • ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(NULL, &onoff_client):通用开关客户端模型(Generic OnOff Client Model),用于发送开关控制消息(如获取或设置开关状态)。

这里我们说一下root_models里面传入的三个全局变量的定义

首先是config_server

static esp_ble_mesh_cfg_srv_t config_server = {
    /* 3 transmissions with 20ms interval */
    .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20),
    .relay = ESP_BLE_MESH_RELAY_DISABLED,
    .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20),
    .beacon = ESP_BLE_MESH_BEACON_ENABLED,
#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER)
    .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED,
#else
    .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED,
#endif
#if defined(CONFIG_BLE_MESH_FRIEND)
    .friend_state = ESP_BLE_MESH_FRIEND_ENABLED,
#else
    .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED,
#endif
    .default_ttl = 7,
};

config_server 是一个静态的 esp_ble_mesh_cfg_srv_t 类型结构体,定义在 main.c 中,用于配置 BLE Mesh 的配置服务器模型(Configuration Server Model)。该模型是所有 BLE Mesh 节点必须支持的模型,用于处理配网后节点的配置请求(如设置应用密钥、绑定模型、配置发布参数等)。

1. net_transmit

  • 定义:使用 ESP_BLE_MESH_TRANSMIT 宏配置网络消息传输参数,2 表示传输次数(3 次,包括初次发送和 2 次重传),20 表示每次传输的间隔(20 毫秒)。
  • 作用:控制配网者发送网络层消息(如配置消息)的重传次数和间隔,提高消息可靠性。总传输次数为 2 + 1 = 3,间隔为 20 毫秒。

2. relay

  • 定义:设置为 ESP_BLE_MESH_RELAY_DISABLED,表示禁用消息转发功能。
  • 作用:控制配网者是否作为中继节点转发其他节点的消息。禁用转发意味着配网者只处理直接发送给自己的消息,减少网络负载和功耗。

3. relay_retransmit

  • 定义:使用 ESP_BLE_MESH_TRANSMIT 宏配置中继消息的重传参数,2 表示 2 次重传(总计 3 次传输),20 表示 20 毫秒间隔。
  • 作用:如果启用中继功能(.relay = ESP_BLE_MESH_RELAY_ENABLED),该参数控制转发消息的重传次数和间隔。由于当前中继禁用(.relay = ESP_BLE_MESH_RELAY_DISABLED),此字段无效。

4. beacon

  • 定义:设置为 ESP_BLE_MESH_BEACON_ENABLED,表示启用安全网络信标(Secure Network Beacon)。
  • 作用:配网者定期发送网络信标,广播网络状态(如 IV 索引),帮助其他节点同步网络信息,增强网络安全性。

5. gatt_proxy

  • 定义
    • 条件编译:若定义了 CONFIG_BLE_MESH_GATT_PROXY_SERVER,则设置为 ESP_BLE_MESH_GATT_PROXY_ENABLED;否则设置为 ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED
    • ESP_BLE_MESH_GATT_PROXY_ENABLED 表示启用 GATT 代理功能,允许通过 GATT 承载(PB-GATT)转发 Mesh 消息。
  • 作用
    • GATT 代理功能允许配网者将 Mesh 消息通过 BLE GATT 连接转发给不支持 Mesh 的设备(如手机)。
    • 如果未启用(NOT_SUPPORTED),配网者不提供 GATT 代理功能。

6. friend_state

  • 定义
    • 条件编译:若定义了 CONFIG_BLE_MESH_FRIEND,则设置为 ESP_BLE_MESH_FRIEND_ENABLED;否则设置为 ESP_BLE_MESH_FRIEND_NOT_SUPPORTED
    • ESP_BLE_MESH_FRIEND_ENABLED 表示启用朋友节点功能,支持低功耗节点(LPN)的消息缓存和转发。
  • 作用
    • 朋友节点功能允许配网者为低功耗节点存储和转发消息,延长 LPN 的电池寿命。
    • 如果未启用(NOT_SUPPORTED),配网者不提供朋友节点功能。

7. default_ttl

  • 定义:设置为 7,表示默认的生存时间(Time To Live, TTL)。
  • 作用:控制消息在 Mesh 网络中的最大跳数。TTL=7 允许消息最多转发 7 次,适合中小型网络。

config_client用于定义 BLE Mesh 的配置客户端模型(Configuration Client Model)。该模型用于配网者(Provisioner)向节点发送配置消息,在这里我们结合它的格式以及刚才回调中他作为参数来分析

/** Client Model user data context. */
typedef struct {
    esp_ble_mesh_model_t *model;                    /*!< Pointer to the client model. Initialized by the stack. */
    uint32_t op_pair_size;                          /*!< Size of the op_pair */
    const esp_ble_mesh_client_op_pair_t *op_pair;   /*!< Table containing get/set message opcode and corresponding status message opcode */
    uint32_t publish_status;                        /*!< Callback used to handle the received unsolicited message. Initialized by the stack. */
    void *internal_data;                            /*!< Pointer to the internal data of client model */
    void *vendor_data;                              /*!< Pointer to the vendor data of client model */
    uint8_t msg_role __attribute__((deprecated));   /*!< Role of the device (Node/Provisioner) that is going to send messages */
} esp_ble_mesh_client_t;

它的字段分析如下:

1. esp_ble_mesh_model_t *model

  • 定义:指向客户端模型的指针,由协议栈初始化。
  • 作用
    • 标识配置客户端模型的实例,与 root_models 数组中的 ESP_BLE_MESH_MODEL_CFG_CLI(&config_client) 关联。
    • 协议栈使用此指针管理模型的状态、消息队列和回调函数。
  • 在 main.c 中的作用
    • config_client.model 在 ble_mesh_init 中由 esp_ble_mesh_init 初始化。
    • 在 example_ble_mesh_config_client_cb 中,通过 param->params->model 访问模型,用于发送配置消息(如 esp_ble_mesh_config_client_set_state)。

2. op_pair_size

  • 定义:op_pair 数组的大小,表示支持的操作码对数量。
  • 作用
    • 指定配置客户端支持的操作码对数量(如 ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET 和其对应的状态操作码 ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS)。
    • 协议栈根据 op_pair_size 解析 op_pair 数组,确保消息与状态匹配。

3. esp_ble_mesh_client_op_pair_t *op_pair

  • 定义:指向操作码对表的指针,包含客户端发送的消息操作码及其对应的状态消息操作码。
  • 作用
    • 定义配置客户端支持的请求-响应操作码对,例如:
      • 请求:ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET
      • 响应:ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS
    • 协议栈使用此表验证接收到的状态消息是否与发送的请求匹配。

4. publish_status

  • 定义:处理接收到的未请求消息(Unsolicited Messages)的回调函数,由协议栈初始化。
  • 作用
    • 当节点通过发布(Publish)方式发送配置状态消息(如 ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS)时,协议栈调用此回调处理消息。
    • 在配置客户端中,用于处理节点的异步状态更新。

5. internal_data

  • 定义:指向客户端模型内部数据的指针,供协议栈使用。
  • 作用
    • 存储协议栈管理的模型特定数据(如消息队列、状态缓存)。
    • 应用程序通常不直接访问此字段

6. vendor_data

  • 定义:指向厂商特定数据的指针,供应用程序使用。
  • 作用
    • 允许开发者为客户端模型添加自定义数据(如特定于应用的配置参数)。
    • 配置客户端模型通常不使用此字段,除非实现 Vendor 模型。

之后的这段函数,用于设置UUID的筛选规则

err = esp_ble_mesh_provisioner_set_dev_uuid_match(match, sizeof(match), 0x0, false);
if (err != ESP_OK) {
    ESP_LOGE(TAG, "Failed to set matching device uuid (err %d)", err);
    return err;
}

之后调用esp_ble_mesh_provisioner_prov_enable启动配网流程

之后添加本地密钥,密钥在上面已经配置过了

err = esp_ble_mesh_provisioner_add_local_app_key(prov_key.app_key, prov_key.net_idx, prov_key.app_idx);
if (err != ESP_OK) {
    ESP_LOGE(TAG, "Failed to add local AppKey (err %d)", err);
    return err;
}

至此,配网者的例程说明结束。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐