ESP32 ble mesh -- provisioner
它通过 esp_ble_mesh_cfg_client_cb_event_t 类型的事件参数和 esp_ble_mesh_cfg_client_cb_param_t 参数结构,响应协议栈触发的配置事件。首先和节点类似的是初始化了蓝牙协议栈并获取UUID,这块因为调用的函数都是基于ble_mesh_example_init.c和ble_mesh_example_init.h的,所以不在过多赘述了,感
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初始化,作用至关重要,它的主要功能包括:
- 初始化配网密钥:设置网络密钥索引(net_idx)、应用密钥索引(app_idx)和应用密钥(app_key)。
- 注册回调函数:为配网、配置客户端和通用开关客户端注册事件回调。
- 初始化协议栈:调用 ESP-IDF 的 BLE Mesh API 初始化协议栈。
- 设置 UUID 匹配规则:配置设备 UUID 匹配规则以筛选待配网设备。
- 启用配网功能:启用 PB-ADV 和 PB-GATT 配网承载方式。
- 添加本地应用密钥:为配网者添加本地应用密钥以支持后续模型通信。
函数返回 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)用于处理未配网设备广播包的回调函数,其主要作用包括:
- 记录未配网设备信息:
- 解析广播包中的设备 UUID、MAC 地址、地址类型、OOB 信息、广播类型和承载方式。
- 通过日志输出设备信息,便于调试。
- 添加设备到配网队列:
- 将未配网设备信息存储到配网者的队列中,准备开始配网流程。
- 配置添加设备的标志,控制配网行为(如立即配网、配网后移除)。
- 触发配网流程:
- 通过调用 esp_ble_mesh_provisioner_add_unprov_dev,将设备加入配网队列并启动配网。
- 过滤特定 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 中被调用,用于处理配网成功后的节点信息存储和配置流程的初始化。其主要作用包括:
- 记录配网完成信息:
- 记录节点的索引、UUID、单播地址、元素数量和网络密钥索引。
- 为节点设置名称并存储到配网者的节点信息数组(nodes)。
- 触发配置流程:
- 获取节点的组合数据(Composition Data),启动后续配置(如添加应用密钥、绑定模型)。
- 日志输出:
- 输出配网完成的节点信息,便于调试。
- 错误处理:
- 检查每个操作的返回值,确保配网和配置流程顺利进行。
它的传入参数如下:
- 参数:
- 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。
它的逻辑流程如下:
-
- 参数检查:
- 验证 common、node 和 model 是否为 NULL。
- 若任一参数无效,返回 ESP_ERR_INVALID_ARG。
- 设置消息参数:
- 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 前使用)。
- 返回:
- 成功设置参数,返回 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 参数结构,响应协议栈触发的配置事件。主要功能包括:
- 事件分发:根据事件类型(event)和操作码(opcode)处理配置客户端的操作,如获取组合数据(Composition Data)、添加应用密钥(AppKey)、绑定模型等。
- 日志记录:记录事件状态、错误码和操作结果,便于调试。
- 后续操作:在接收到特定事件(如获取组合数据成功)后,触发下一步配置操作(如添加应用密钥、绑定模型或获取开关状态)。
接下来详细说明其中的每一个事件:
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):
- 重试获取组合数据、添加应用密钥或绑定模型。
- 获取状态(ESP_BLE_MESH_CFG_CLIENT_GET_STATE_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 参数结构,响应协议栈触发的通用开关客户端事件。主要功能包括:
- 事件分发:根据事件类型(event)和操作码(opcode)处理通用开关客户端的操作,如获取开关状态(OnOff Get)或设置开关状态(OnOff Set)。
- 日志记录:记录事件状态、错误码和开关状态,便于调试。
- 状态管理:更新节点的开关状态(node->onoff)并触发后续操作(如在获取状态后设置相反状态)。
- 超时重试:处理消息超时情况,重新发送获取或设置开关状态的请求。
接下来详细说明每个事件:
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):
- 重试获取或设置开关状态。
- 获取状态(ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_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_val及prov_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;
}
至此,配网者的例程说明结束。
更多推荐



所有评论(0)