本文章使用的例程来自esp-idf的官方例程 vendor_client和vendor_server。
多个esp32设备想组成mesh网络必须先经过组网,按照乐鑫官方的手册说明,组成网络的需要有一个配网的角色(Provisioner),他负责将搜索到mesh设备配置节点。vendor_client例程刚好就有配网的操作,接下来就以下载vendor_client例程的esp32作为配网器,下载vendor_server例程的多个esp32作为节点使用,让多个esp32的mesh互传消息。
配置网络:
上电下载vendor_client例程的esp32和下载vendor_server例程的多个esp32就可以开始复杂的配网操作,实际配网逻辑都是官方写好的,要实现多个节点配网只需要把修改uuid就行(这段存疑,若有误请指出),这里我分别把三个下载vendor_server例程的多个esp32的uuid修改成0x3210,0x3211,0x3212然后进行配网。
static uint8_t dev_uuid[ESP_BLE_MESH_OCTET16_LEN] = { 0x32, 0x12 };
这是官方文档给出的配网过程,可以参照日志来了解配网过程。
下面这张图来自vendor_client的搜索vendor_server设备(uuid32 11 cc 7b 5c 25 c3 36 00 00 00 00 00 00 00 00)的日志:
ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT: 这说明了ESP BLE Mesh Provisioner收到了一个未被配置的设备的广播数据包事件。
Device address: cc 7b 5c 25 c3 36:这是未配置设备的设备地址。
Address type 0x00, adv type 0x03:说明设备的地址类型和广播类型。在这个情况中,地址类型为公共设备地址,广播类型为非连接可扫描无定向广播。
Device UUID: 32 11 cc 7b 5c 25 c3 36 00 00 00 00 00 00 00 00:设备的UUID,是设备在网状网络中的唯一标识符。
oob info 0x0000, bearer PB-ADV:提示连接接入点是使用广播承载(PB-ADV),没有外部OOB(out of band)认证方法。
ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT, bearer PB-ADV:提示配网器已经开启了一个使用广播承载的配置链接。
ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, err_code 0:配网器成功地将未配网的设备添加到其设备列表中,执行操作时未报错。
搜索到日志后vendor_client就开始为vendor_server设备(uuid32 11 cc 7b 5c 25 c3 36 00 00 00 00 00 00 00 00)进行配网:
节点配置和存储信息
node_index 1, primary_addr 0x0006, element_num 1, net_idx 0x0000: 这表示当前节点(设备)的索引为1,主地址(primary address)是 0x0006,包含的元素数量(element number)是1,网络索引(net_idx)为 0x0000。
uuid: 32 11 cc 7b...: 这是该节点的唯一标识符(UUID),用于在Mesh网络中识别不同的节点。
Store, key "vendor_client", length 4: 这表示使用非易失性存储(NVS)保存了关键词“vendor_client”及其对应的数据长度4字节。
Store, data: 06 00 fd 00: 保存到NVS的数据。
节点命名
ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code 0: 指示已成功设置节点名称的事件,没有错误码(err_code 0表示无错误)。
Node 1 name NODE-01: 节点1被命名为 NODE-01。
组成数据(Composition Data)获取
Config client, err_code 0, event 0, addr 0x0006...: 这表示配置客户端成功运行,无错误发生(err_code 0),针对地址 0x0006的节点。
Composition data: e5 02 00 00...: 显示了节点的组成数据,包括公司ID、产品ID、版本ID、功能等信息。这是Mesh网络中节点发布的一种标准数据格式,用于描述节点的能力和功能。
配置客户端回调
Config client, err_code 0, event 1...: 表示另一个配置客户端事件的成功完成。
Provision and config successfully: 提示Provision(配网)和配置流程成功完成。
vendor_client配置设备32 11 cc 7b 5c 25 c3 36 00 00 00 00 00 00 00 00为mesh网络节点成功,网络号为0x0000,地址为0x0005(根据这个地址就可以定向给这个设备发消息了):
这是vendor_server的配网过程的日志:
ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT:
表示节点配网完成事件已发生,节点已成功地完成配网过程。
ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT,bearer PB-ADV:
表示配网链接关闭事件,使用的承载协议是 PB-ADV(广播承载器)。
ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD:
表示应用程序密钥已添加到设备上。AppKey是用于节点之间通信加密的关键元素。
ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND:
表示一个模型与应用程序密钥绑定完成,使模型可以发送和接收经过加密的消息。
net_idx: 网络索引
addr: 节点地址
flags: 显示配置标志(通常用于标识信息、状态等)
index: 初始向量索引,用于网络层的安全加密。
app_idx: 应用程序索引
AppKey:应用程序密钥的值,它是一系列的字节,用于加密Mesh网络内的消息。
elem_addr: 显示元素地址
app_idx: 应用索引
cid: 公司ID
mod_id: 模型ID
看到以上日志消息说明配网成功。
重复以上步骤(给两个下载vendor_server例程的esp32依次上电)为另外两个设备配上网络,配网成功了就可以进行发送消息了。
vendor_client和vendor_server原例程只提供了server按下boot按键就发送cid给client的逻辑,显然这个逻辑是不能作为正常的蓝牙mesh模组使用的。我们用蓝牙mesh组网的方案一般是MCU+蓝牙模组。所以接下来改进一下vendor_server代码,使模块能完成接收来自串口的数据,然后将数据以泛洪传输的方式发送给mesh网络中的各个节点。
这里直接附上修改后的代码:
vendor_server:
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_common_api.h"
#include "esp_ble_mesh_networking_api.h"
#include "esp_ble_mesh_provisioning_api.h"
#include "esp_ble_mesh_config_model_api.h"
#include "esp_ble_mesh_local_data_operation_api.h"
#include "board.h"
#include "ble_mesh_example_init.h"
#define TAG "LBC"
#define EX_UART_NUM UART_NUM_0
#define PATTERN_CHR_NUM (3) /*!< Set the number of consecutive and identical characters received by receiver which defines a UART pattern*/
#define BUF_SIZE (1024)
#define RD_BUF_SIZE (BUF_SIZE)
static QueueHandle_t uart0_queue;
#define CID_ESP 0x02E5
#define MSG_SEND_TTL 3
#define MSG_SEND_REL false
#define MSG_TIMEOUT 0
#define MSG_ROLE ROLE_PROVISIONER
#define ESP_BLE_MESH_VND_MODEL_ID_CLIENT 0x0000
#define ESP_BLE_MESH_VND_MODEL_ID_SERVER 0x0001
#define ESP_BLE_MESH_VND_MODEL_OP_SEND ESP_BLE_MESH_MODEL_OP_3(0x00, CID_ESP)
#define ESP_BLE_MESH_VND_MODEL_OP_STATUS ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP)
static uint8_t dev_uuid[ESP_BLE_MESH_OCTET16_LEN] = { 0x32, 0x12 };
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),
};
static esp_ble_mesh_model_t root_models[] = {
ESP_BLE_MESH_MODEL_CFG_SRV(&config_server),
};
static esp_ble_mesh_model_op_t vnd_op[] = {
ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_SEND, 2),
ESP_BLE_MESH_MODEL_OP_END,
};
static esp_ble_mesh_model_t vnd_models[] = {
ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_SERVER,
vnd_op, NULL, NULL),
};
static esp_ble_mesh_elem_t elements[] = {
ESP_BLE_MESH_ELEMENT(0, root_models, vnd_models),
};
static esp_ble_mesh_comp_t composition = {
.cid = CID_ESP,
.elements = elements,
.element_count = ARRAY_SIZE(elements),
};
static esp_ble_mesh_prov_t provision = {
.uuid = dev_uuid,
};
static void prov_complete(uint16_t net_idx, uint16_t addr, uint8_t flags, uint32_t iv_index)
{
ESP_LOGI(TAG, "net_idx 0x%03x, addr 0x%04x", net_idx, addr);
ESP_LOGI(TAG, "flags 0x%02x, iv_index 0x%08" PRIx32, flags, iv_index);
board_led_operation(LED_G, LED_OFF);
}
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);
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:
ESP_LOGI(TAG, "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;
}
}
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);
break;
default:
break;
}
}
}
void example_ble_mesh_send_vendor_message(bool resend,uint16_t lenght,uint8_t *data)
{
esp_ble_mesh_msg_ctx_t ctx = {0};
uint32_t opcode;
esp_err_t err;
ctx.net_idx = 0x0000;
ctx.app_idx = 0x0000;
ctx.addr = 0xffff;
ctx.send_ttl = MSG_SEND_TTL;
ctx.send_rel = MSG_SEND_REL;
opcode = ESP_BLE_MESH_VND_MODEL_OP_SEND;
if (resend == false) {
data[0]++;
}
//向client上报消息,其他server收不到
//err = esp_ble_mesh_server_model_send_msg(&vnd_models[0],&ctx, ESP_BLE_MESH_VND_MODEL_OP_STATUS,lenght, (uint8_t *)data);
//向其他server发送消息,client收不到
err = esp_ble_mesh_server_model_send_msg(&vnd_models[0],&ctx, ESP_BLE_MESH_VND_MODEL_OP_SEND,lenght, (uint8_t *)data);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to send vendor message 0x%06" PRIx32, opcode);
return;
}
//mesh_example_info_store(); /* Store proper mesh example info */
}
static void example_ble_mesh_custom_model_cb(esp_ble_mesh_model_cb_event_t event,
esp_ble_mesh_model_cb_param_t *param)
{
switch (event) {
case ESP_BLE_MESH_MODEL_OPERATION_EVT:
if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_SEND)
{
//ESP_LOGI(TAG,"Message from: 0x%04x,Message:%s",param->client_recv_publish_msg.ctx->addr,param->client_recv_publish_msg.msg);
printf("Message from: 0x%04x,Message:%s\n",param->client_recv_publish_msg.ctx->addr,param->client_recv_publish_msg.msg);
/*uint16_t tid = *(uint16_t *)param->model_operation.msg;
ESP_LOGI(TAG, "Recv 0x%06" PRIx32 ", tid 0x%04x", param->model_operation.opcode, tid);
esp_err_t err = esp_ble_mesh_server_model_send_msg(&vnd_models[0],
param->model_operation.ctx, ESP_BLE_MESH_VND_MODEL_OP_STATUS,
sizeof(tid), (uint8_t *)&tid);
if (err) {
ESP_LOGE(TAG, "Failed to send message 0x%06x", ESP_BLE_MESH_VND_MODEL_OP_STATUS);
}*/
}
break;
case ESP_BLE_MESH_MODEL_SEND_COMP_EVT:
if (param->model_send_comp.err_code) {
ESP_LOGE(TAG, "Failed to send message 0x%06" PRIx32, param->model_send_comp.opcode);
break;
}
//ESP_LOGI(TAG, "Send 0x%06" PRIx32, param->model_send_comp.opcode);
printf("Message:%s have been sent to the mesh network\n",param->client_recv_publish_msg.ctx->addr,param->client_recv_publish_msg.msg);
break;
default:
break;
}
}
static esp_err_t ble_mesh_init(void)
{
esp_err_t err;
esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb);
esp_ble_mesh_register_config_server_callback(example_ble_mesh_config_server_cb);
esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb);
err = esp_ble_mesh_init(&provision, &composition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize mesh stack");
return err;
}
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");
return err;
}
board_led_operation(LED_G, LED_ON);
ESP_LOGI(TAG, "BLE Mesh Node initialized");
return ESP_OK;
}
static void uart_event_task(void *pvParameters)
{
uart_event_t event;
size_t buffered_size;
uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE);
for(;;) {
//Waiting for UART event.
if(xQueueReceive(uart0_queue, (void * )&event, (TickType_t)portMAX_DELAY)) {
bzero(dtmp, RD_BUF_SIZE);
//ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
switch(event.type)
{
//Event of UART receving data
case UART_DATA:
//ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY);
//ESP_LOGI(TAG, "[DATA EVT]:");
example_ble_mesh_send_vendor_message(true,event.size+1,dtmp);
uart_write_bytes(EX_UART_NUM, (const char*) dtmp, event.size);
break;
//Event of HW FIFO overflow detected
case UART_FIFO_OVF:
ESP_LOGI(TAG, "hw fifo overflow");
// If fifo overflow happened, you should consider adding flow control for your application.
// The ISR has already reset the rx FIFO,
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart0_queue);
break;
//Event of UART ring buffer full
case UART_BUFFER_FULL:
ESP_LOGI(TAG, "ring buffer full");
// If buffer full happened, you should consider increasing your buffer size
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart0_queue);
break;
//Event of UART RX break detected
case UART_BREAK:
ESP_LOGI(TAG, "uart rx break");
break;
//Event of UART parity check error
case UART_PARITY_ERR:
ESP_LOGI(TAG, "uart parity error");
break;
//Event of UART frame error
case UART_FRAME_ERR:
ESP_LOGI(TAG, "uart frame error");
break;
//UART_PATTERN_DET
case UART_PATTERN_DET:
uart_get_buffered_data_len(EX_UART_NUM, &buffered_size);
int pos = uart_pattern_pop_pos(EX_UART_NUM);
ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size);
if (pos == -1) {
// There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not
// record the position. We should set a larger queue size.
// As an example, we directly flush the rx buffer here.
uart_flush_input(EX_UART_NUM);
} else {
uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS);
uint8_t pat[PATTERN_CHR_NUM + 1];
memset(pat, 0, sizeof(pat));
uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "read data: %s", dtmp);
ESP_LOGI(TAG, "read pat : %s", pat);
}
break;
//Others
default:
ESP_LOGI(TAG, "uart event type: %d", event.type);
break;
}
}
}
free(dtmp);
dtmp = NULL;
vTaskDelete(NULL);
}
void app_main(void)
{
esp_err_t err;
ESP_LOGI(TAG, "Initializing...");
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
//Install UART driver, and get the queue.
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0);
uart_param_config(EX_UART_NUM, &uart_config);
//Set UART log level
esp_log_level_set(TAG, ESP_LOG_INFO);
//Set UART pins (using UART0 default pins ie no changes.)
uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//Set uart pattern detect function.
uart_enable_pattern_det_baud_intr(EX_UART_NUM, '+', PATTERN_CHR_NUM, 9, 0, 0);
//Reset the pattern queue length to record at most 20 pattern positions.
uart_pattern_queue_reset(EX_UART_NUM, 20);
//Create a task to handler UART event from ISR
xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, 12, NULL);
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);
board_init();
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);
}
}
这里加了串口的逻辑和修改了发送的逻辑,把从串口收到信息转发到mesh网络中的server设备(这里不是想只发送给server设备,也想发给client设备,但试过发现,不能同时发,他是下图这种情况,实在搞不清如何做到client和server同时都能收到,欢迎评论区讨论。 ps:试过下面两个函数同时运行,但是会报Ignoring old SeqAuth.的异常)
以下是代码运行截图,主要实现了三个设备互相发消息,模拟MCU给模组发数据,然后模组转发。
这个逻辑实现后就可以加上更多好用的逻辑了,比如加上指令解析,实时获取节点消息和节点状态,选择点对点发送还是群发。
以下是遇到并且还没解决的问题,求大佬解答!
1.大框这个参数是配置设备为client模型还是server模型的,是不是也可以把vendor_client改成server模型?改成server之后还能进行配网吗?未尝试;红色小框的操作码是怎么定的?官方文档没找到。
2.在配网的过程中会出现BLE_MESH: No matching TX context for ack.的异常报错,出现这个的原因是不是我部分数据没配置好导致各节点冲突了?
3.server节点多次密集发同一条信息会出现这样的报错,是ESP BLE MESH的机制问题还是哪里没有设置好?