前言
最近项目需要,需要学一些关于BLE MESH 的内容,学的比较痛苦,中文互联网里关于BLE MESH的内容很少,而且很多都是从协议栈的角度出发去讲,未免有些枯燥,我又是个英文小白,所以学起来就一脸蒙,现在稍微有些入门了,做个笔记总结一下,也方便后来者学习吧。这个笔记会以一个使用者的角度结合代码来描述。
开发环境
ESP-IDF 版本: v5.1
设备:ESP32S3 开发板 * n
nRF Mesh V3.3.0 APP
Provisioner流程概述
在上一篇文章中我们使用的是Nrf MeshAPP作为Provisioner,配网过程中的截图如下(有兴趣的同学可以比对下图文字和本文流程):
使用ESP32作为Provisioner和使用APP作为配网器其实原理上是一样的。先概括总体流程:
这次我们需要两块开发板完成实现,一块作为Provisioner,一块作为Node,分别下载esp-idf/examples/bluetooth/esp_ble_mesh/provisioner和esp-idf/examples/bluetooth/esp_ble_mesh/onoff_models/onoff_server例程,运行程序后我们就可以得到如下LOG:
我们主要分析配网流程:
从Log中我们可以看到流程调用顺序
- 初始化:
1.设置匹配的UUID
用于筛选期望的配网节点esp_ble_mesh_provisioner_set_dev_uuid_match
2.使能配网
选择配网的承载层 esp_ble_mesh_provisioner_set_dev_uuid_match
3.添加本地的APP KEY esp_ble_mesh_provisioner_add_local_app_key
同时在回调里为本地的Onoff Client Model也绑定了AppKey。 - 配网:
1.接收到节点发出的请求,触发事件:ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT
2.配网完成,触发事件:ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT
3.设置节点名称完成,触发事件:ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT - 配置节点:
1.获取节点参数 example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET);
2.添加节点的APP Key example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD);
3.绑定模型的APP Key example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND); - 发送控制命令:
1.在发送之前先读取了一遍状态example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET);
2.发送控制命令:example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET);
我们再仔细分析一下每个操作的对应代码,关键的步骤标注了中文注释,其余部分如有疑惑欢迎留言。
1.1设置匹配的UUID
uint8_t match[2] = {0xdd, 0xdd}; //节点的uuid前两位必须与该UUID相匹配,
err = esp_ble_mesh_provisioner_set_dev_uuid_match(match, sizeof(match), 0x0, false); //第三个参数表示即使收到了匹配的UUID的节点,也不会立即开始配网。
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set matching device uuid (err %d)", err);
return err;
}
1.2使能配网
err = esp_ble_mesh_provisioner_prov_enable(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;
}
1.3添加本地的APP KEY
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;
}
2.1接收到节点发出的请求
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 = (uint8_t)addr_type;
memcpy(add_dev.uuid, dev_uuid, 16);
add_dev.oob_info = oob_info;
add_dev.bearer = (uint8_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,
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;
}
2.2配网完成
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;
}
2.3设置节点名称完成
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;
}
3.1获取节点参数
这一部分代码在2.2中实现
3.2添加节点的APP Key
接下来的代码懒得抄了,按照上述流程按图索骥即可。
注意事项
1.通过之前的文章我们知道,在OnoffServer中存在三个元素,那么第二个和第三个元素可以被控制吗?
答案是不行的,因为绑定APP Key的操作对应的是某个特定元素的特定模型,例程的APP KEY只绑定了主element(和Node地址一致)的ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV
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;//这里写死了是给主element的ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV绑定APP Key
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;
}
2.本文使用的是provisioner和onoff_server两个例程完成的实验,那么换成onoff_client和provisioner两个例程,能不能完成配网呢?
答案是可以,但是仅能完成配网和添加APP KEY,绑定APP Key是不行的,为什么呢?我们看代码:
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; //这里写死了是给ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV模型绑定APP KEY
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;
}
3.可否用这个例程配网对vendor_models配网呢?
答案是不行,因为那两个例程的UUID不一致。
总结
这篇文章介绍了esp-idf/examples/bluetooth/esp_ble_mesh/provisioner例程的工作流程,阅读本文章后,希望读者已能够了解以下几项内容:
- 总体配网流程
- 修改provisioner匹配的UUID,配网承载层,节点名称。
- 使用config_client获取节点信息,配置节点。
- provisioner本地模型设置,绑定APPkey。