前言
最近项目需要,需要学一些关于BLE MESH 的内容,学的比较痛苦,中文互联网里关于BLE MESH的内容很少,而且很多都是从协议栈的角度出发去讲,未免有些枯燥,我又是个英文小白,所以学起来就一脸蒙,现在稍微有些入门了,做个笔记总结一下,也方便后来者学习吧。这个笔记会以一个使用者的角度结合代码来描述。
开发环境
ESP-IDF 版本: v5.1
设备:ESP32S3 开发板 * n
nRF Mesh V3.3.0 APP
例程概览
ESP-IDF 官方的例程代码位于esp-idf/examples/bluetooth/esp_ble_mesh目录下,名称和功能对应如下:
- aligenie_demo 接入天猫精灵的灯泡例程
- ble_mesh_coex_test TCP Server/Client 和BLE MESH共存测试例程
- ble_mesh_console 控制台程序(可以不看)
- ble_mesh_fast_provision Espressif定制的快速配网例程
- ble_mesh_node 初始节点示例
- ble_mesh_provisioner 配网器示例
- ble_mesh_sensor_model 传感器示例
- ble_mesh_vendor_model 自定义模型示例
- ble_mesh_wifi_coexist WIfi共存示例
在这些例程中,推荐的学习顺序是:
1.ble_mesh_node 初始节点示例
2.ble_mesh_provisioner 配网器示例
3.ble_mesh_vendor_model 自定义模型示例
4.ble_mesh_sensor_model 传感器示例
如果有项目需要,可以参考一下Wi-fi共存的例子,其余的例子可以先不用看。
概念简介
节点(Node) 通常来讲,一个蓝牙芯片对应一个节点。
**元素(Element)**通常用于指定一个节点下多个被控的设备。比方说,一块ESP32芯片控制了三个灯,那我们可以设定三个元素分别对应这三个灯。
**模型(Model)**是描述每个元素所具有的功能。以刚才的灯为例,灯可以被控制,所以它带有一个Onoff Server Model,表明该模型是可以被控制的。
实战
选择一块开发板编译烧录ble_mesh_node/onoff_server程序,烧录成功后,其Log应当如下:
解释LOG的事,统一放到最后。
看到设备的MAC地址58:cf:79:1e:d9:d6在nRF Mesh APP中点击+号即可进入配网流程,配网成功的界面如下:
同时多了如下几条Log:
接下来我们尝试控制一个灯亮灭:
单击Generic Onoff Server,进入Model配置界面,首先绑定APP KEY,然后下滑找到Generic On Off Controls ,点击 OnOff 即可控制开发板(由于使用的开发板不同,灯不亮属于正常情况,看Log即可)多出来的Log如下:
恭喜你!Ble mesh 点灯成功了
流程解释:
上述流程演示了一个节点从未配网状态到变为一个可控设备所经过的流程,要完成BLE Mesh点灯至少包括如下几个步骤:
1.provision(配置),作用于每个节点,包括绑定NET Key。此时手机充当配网者(provisioner)角色,ESP32作为待配网设备。
2.添加和绑定APP key,作用于某个模型(Model)。此时手机实际使用Configuration Client对ESP32作为Configuration Server进行配置。
3.发送控制命令,作用于某个模型(Model)。此时手机实际使用Generic OnOff Client对ESP32作为Generic OnOff Server进行配置。
这些步骤各自对应的代码如下:
1.provision
设备要使能配网功能,首先需要注册配网的回调
esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb);
开启配网功能
err = esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable mesh node (err %d)", err);
return err;
}
回调函数内容
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_PROV_REGISTER_COMP_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, err_code %d", param->prov_register_comp.err_code);
mesh_example_info_restore(); /* Restore proper mesh example info */
break;
case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT, err_code %d", param->node_prov_enable_comp.err_code);
break;
case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT, bearer %s",
param->node_prov_link_open.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT");
break;
case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT, bearer %s",
param->node_prov_link_close.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT");
break;
case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT");
prov_complete(param->node_prov_complete.net_idx, param->node_prov_complete.addr,
param->node_prov_complete.flags, param->node_prov_complete.iv_index);
break;
case ESP_BLE_MESH_NODE_PROV_RESET_EVT:
break;
case ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT, err_code %d", param->node_set_unprov_dev_name_comp.err_code);
break;
default:
break;
}
}
2.添加和绑定APP key
接收绑定APP Key命令是Configuration Server的功能,因此需要注册Configuration Server 回调
esp_ble_mesh_register_config_server_callback(example_ble_mesh_config_server_cb);
回调函数的内容如下:
static void example_ble_mesh_config_server_cb(esp_ble_mesh_cfg_server_cb_event_t event,
esp_ble_mesh_cfg_server_cb_param_t *param)
{
if (event == ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_EVT) {
switch (param->ctx.recv_op) {
case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD:
ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD");
ESP_LOGI(TAG, "net_idx 0x%04x, app_idx 0x%04x",
param->value.state_change.appkey_add.net_idx,
param->value.state_change.appkey_add.app_idx);
ESP_LOG_BUFFER_HEX("AppKey", param->value.state_change.appkey_add.app_key, 16);
break;
case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND:
ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND");
ESP_LOGI(TAG, "elem_addr 0x%04x, app_idx 0x%04x, cid 0x%04x, mod_id 0x%04x",
param->value.state_change.mod_app_bind.element_addr,
param->value.state_change.mod_app_bind.app_idx,
param->value.state_change.mod_app_bind.company_id,
param->value.state_change.mod_app_bind.model_id);
if (param->value.state_change.mod_app_bind.company_id == 0xFFFF &&
param->value.state_change.mod_app_bind.model_id == ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI) {
store.app_idx = param->value.state_change.mod_app_bind.app_idx;
mesh_example_info_store(); /* Store proper mesh example info */
}
break;
default:
break;
}
}
}
接收控制命令是Generic OnOff Client的功能,对应的,我们也能在代码中找到相关内容:
注册Generic OnOff Client回调
esp_ble_mesh_register_generic_server_callback(example_ble_mesh_generic_server_cb);
对应的回调函数如下:
static void example_ble_mesh_generic_server_cb(esp_ble_mesh_generic_server_cb_event_t event,
esp_ble_mesh_generic_server_cb_param_t *param)
{
esp_ble_mesh_gen_onoff_srv_t *srv;
ESP_LOGI(TAG, "event 0x%02x, opcode 0x%04x, src 0x%04x, dst 0x%04x",
event, param->ctx.recv_op, param->ctx.addr, param->ctx.recv_dst);
switch (event) {
case ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT");
if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET ||
param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) {
ESP_LOGI(TAG, "onoff 0x%02x", param->value.state_change.onoff_set.onoff);
example_change_led_state(param->model, ¶m->ctx, param->value.state_change.onoff_set.onoff);
}
break;
case ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT");
if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) {
srv = param->model->user_data;
ESP_LOGI(TAG, "onoff 0x%02x", srv->state.onoff);
example_handle_gen_onoff_msg(param->model, ¶m->ctx, NULL);
}
break;
case ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT:
ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT");
if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET ||
param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) {
ESP_LOGI(TAG, "onoff 0x%02x, tid 0x%02x", param->value.set.onoff.onoff, param->value.set.onoff.tid);
if (param->value.set.onoff.op_en) {
ESP_LOGI(TAG, "trans_time 0x%02x, delay 0x%02x",
param->value.set.onoff.trans_time, param->value.set.onoff.delay);
}
example_handle_gen_onoff_msg(param->model, ¶m->ctx, ¶m->value.set.onoff);
}
break;
default:
ESP_LOGE(TAG, "Unknown Generic Server event 0x%02x", event);
break;
}
}
最后我们看Mesh Init是怎么做的:
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好理解,关于配网的一些消息,composition中包含了该节点的全部定义:
static esp_ble_mesh_comp_t composition = {
.cid = CID_ESP, //设备公司ID
.elements = elements, //元素
.element_count = ARRAY_SIZE(elements), //元素个数
};
元素定义:
static esp_ble_mesh_elem_t elements[] = { //包含三个元素
ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE),
ESP_BLE_MESH_ELEMENT(0, extend_model_0, ESP_BLE_MESH_MODEL_NONE),
ESP_BLE_MESH_ELEMENT(0, extend_model_1, ESP_BLE_MESH_MODEL_NONE),
};
在该例程中有三个元素:
static esp_ble_mesh_elem_t elements[] = {
ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE),
ESP_BLE_MESH_ELEMENT(0, extend_model_0, ESP_BLE_MESH_MODEL_NONE),
ESP_BLE_MESH_ELEMENT(0, extend_model_1, ESP_BLE_MESH_MODEL_NONE),
};
上述代码使用了一个宏,可能降低了代码阅读性,展开它会更加便于理解:
static esp_ble_mesh_elem_t elements[] = {
//节点总共包含了三个元素
{
.location = (0),
.sig_model_count = ARRAY_SIZE(root_models),//元素1包含2个通用模型:Config Server Model和Generic OnOff Server Model
.sig_models = (root_models),
.vnd_model_count = 0,
.vnd_models = NULL,
},
{
.location = (0),
.sig_model_count = ARRAY_SIZE(extend_model_0),//元素2包含1个通用模型:Generic OnOff Server Model
.sig_models = (extend_model_0),
.vnd_model_count = 0,
.vnd_models = NULL,
},
{
.location = (0),
.sig_model_count = ARRAY_SIZE(extend_model_0), //元素3包含1个通用模型:Generic OnOff Server Model
.sig_models = (extend_model_0),
.vnd_model_count = 0,
.vnd_models = NULL,
},
};
每个模型有对应的配置
//关于Config Server Model的配置
static esp_ble_mesh_cfg_srv_t config_server = {
.relay = ESP_BLE_MESH_RELAY_DISABLED,
.beacon = ESP_BLE_MESH_BEACON_ENABLED,
#if defined(CONFIG_BLE_MESH_FRIEND)
.friend_state = ESP_BLE_MESH_FRIEND_ENABLED,
#else
.friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED,
#endif
#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
.default_ttl = 7,
/* 3 transmissions with 20ms interval */
.net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20),
.relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20),
};
//关于Generic OnOff Server Model的配置
static esp_ble_mesh_gen_onoff_srv_t onoff_server_0 = {
.rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
.rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
};
//对于通用模型还需要定义模型的publication context
ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_cli_pub, 2 + 1, ROLE_NODE);
结合Log我们再梳理一遍从初始化到可控的完整流程:
事件 | 成功后对应的回调 |
---|---|
注册配网功能 | ESP_BLE_MESH_PROV_REGISTER_COMP_EVT |
使能配网 | ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT |
配网完成 | ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT |
添加APP KEY | ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD |
绑定APP KEY | ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND |
控制灯 | ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT |
在下一篇文章中,将介绍ESP32作为 provisioner 的功能和工作流程