CH582 Mesh采用self_provisioner_vendor和adv_vendor例程进行多节点通信的相关问题

实验所用文件:

CH582官方例程Mesh中的self_provisioner_vendor和adv_vendor。

配网者节点:self_provisioner_vendor

普通入网节点:adv_vendor

代码修改调整部分:配网者节点:

1、App_Init函数中注释掉测试任务,利用按键回调函数进行测试(按下按键进行数据发送,从而开始节点间数据传输)

 void App_Init(void)
 {
     // 注册事件处理函数到TMOS任务系统
     App_TaskID = TMOS_ProcessEventRegister(App_ProcessEvent);
 
     // 初始化自定义厂商模型客户端
     vendor_model_cli_init(vnd_models);
     // 同步BLE Mesh功能和参数
     blemesh_on_sync();
 
     // 初始化按键硬件
     HAL_KeyInit();
     // 配置按键回调函数
     HalKeyConfig(keyPress);
 
     // 添加一个测试任务,定时向第一个配网设备发送透传数据,定时时长为4800ms
 //    tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 4800);
 }

对keyPress函数进行修改,通过按键控制数据发送,从而开启节点间数据传输

 void keyPress(uint8_t keys)
 {
     APP_DBG("%d", keys); // 打印按键的值,用于调试。
 
     switch(keys) // 根据按键的值执行不同的操作。
     {
         default: // 默认情况下(未指定特殊键的处理逻辑时),执行以下代码:
         {
             if(3) // 如果按键为3,即评估版的S4-KEY
             {
                 // 向所有入网节点广播数据,从而开始节点间数据传输
                 uint8_t status;
                 uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
                 status = vendor_model_cli_send(0xFFFF, data, sizeof(data));
                 if(status) // 如果发送失败,打印错误状态
                     APP_DBG("trans failed %d", status);
             }
 
 
             if(1) // 如果条件为true(这里只是示例,实际情况中会替换为具体的条件判断。)
             {
                 // 删除节点操作
                 if(app_nodes[1].node_addr) // 如果第二个节点的地址存在。
                 {
                     uint8_t status; // 定义删除状态变量。
                     APP_DBG("node1_addr %x", app_nodes[1].node_addr); // 打印节点地址,用于调试。
                     if(0) // 如果条件为false(这里只是示例,实际情况中会替换为具体的条件判断。)
                     {
                         // 通过蓝牙Mesh协议栈提供的命令来删除节点
                         status = bt_mesh_cfg_node_reset(self_prov_net_idx, app_nodes[1].node_addr);
                         if(status) // 如果删除失败
                         {
                             APP_DBG("reset failed %d", status); // 打印删除失败的状态码。
                         }
                         else
                         {
                             reset_node_addr = app_nodes[1].node_addr; // 保存被删除节点的地址。
                         }
                     }
                     if(1) // 如果条件为true(这里只是示例,实际情况中会替换为具体的条件判断。)
                     {
                         // 通过应用层自定义协议来删除节点。
                         app_mesh_manage.delete_node.cmd = CMD_DELETE_NODE; // 设置删除命令。
                         // 设置节点地址,低字节和高字节。
                         app_mesh_manage.delete_node.addr[0] = app_nodes[1].node_addr & 0xFF;
                         app_mesh_manage.delete_node.addr[1] = (app_nodes[1].node_addr >> 8) & 0xFF;
                         // 使用厂商自定义模型发送删除节点的命令,并保存返回的状态值。
                         status = vendor_model_cli_send(app_nodes[1].node_addr, app_mesh_manage.data.buf, DELETE_NODE_DATA_LEN);
                         if(status) // 如果删除失败。
                         {
                             APP_DBG("delete failed %d", status); // 打印删除失败的状态码。
                         }
                         else
                         {
                             // 启动一个定时任务,3秒后没有收到应答则认为超时。
                             tmos_start_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT, 4800);
                         }
                     }
                 }
             }
             break; // 结束switch语句
         }
     }
 }

2、修改vendor_model_cli_status_t结构体的内容,增加一项mydata数据,用于记录addr、rssi、ttl等信息

 typedef struct
 {
     struct vendor_model_cli_EventHdr vendor_model_cli_Hdr;
     union vendor_model_cli_Event_t   vendor_model_cli_Event;
     void* mydata;
 } vendor_model_cli_status_t;

利用上述修改后的结构体中增加的mydata变量,在vendor_message_cli_trans函数中修改厂商模型客户端状态参数

 static void vendor_message_cli_trans(struct bt_mesh_model   *model,
                                      struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
 {
     vendor_model_cli_status_t vendor_model_cli_status; // 定义厂商模型客户端状态结构体
     uint8_t                  *pData = buf->data; // 数据指针指向缓存的数据部分
     uint16_t                  len = buf->len; // 数据长度
 
     // 检查收到的TID和地址是否与期望的一致
     if((pData[0] != vendor_model_cli->cli_tid.trans_tid) ||
        (ctx->addr != vendor_model_cli->cli_tid.trans_addr))
     {
         // 更新期望的TID和地址
         vendor_model_cli->cli_tid.trans_tid = pData[0];
         vendor_model_cli->cli_tid.trans_addr = ctx->addr;
         // pData指针指向真正的数据,跳过TID
         pData++;
         len--;
 
         // 设置厂商模型客户端状态参数
         vendor_model_cli_status.vendor_model_cli_Hdr.opcode =
             OP_VENDOR_MESSAGE_TRANSPARENT_MSG;
         vendor_model_cli_status.vendor_model_cli_Hdr.status = 0;
         vendor_model_cli_status.vendor_model_cli_Event.trans.pdata = pData;
         vendor_model_cli_status.vendor_model_cli_Event.trans.len = len;
         vendor_model_cli_status.vendor_model_cli_Event.trans.addr = ctx->addr;
         vendor_model_cli_status.mydata = (void*)ctx; // 新增的mydata变量记录ctx
 
         // 如果设置了处理函数,调用之
         if(vendor_model_cli->handler)
         {
             vendor_model_cli->handler(&vendor_model_cli_status);
         }
     }
 }

修改vendor_model_cli_rsp_handler函数,在接收到消息后,打印上述获得的ctx的具体内容,即addr、rssi、ttl等,然后定义一个数据data广播给0xFFFF,即所有节点,这样在配网器节点接收到数据后就会向所有入网节点广播信息,从而获得配网器节点和各个入网节点间的rssi等信息

 static void vendor_model_cli_rsp_handler(const vendor_model_cli_status_t *val)
 {
     // 如果状态码非0,说明存在错误或者超时未收到应答
     if(val->vendor_model_cli_Hdr.status)
     {
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_cli_Hdr.opcode); // 打印超时信息及操作码
         return; // 函数返回,不处理后续内容
     }
 
     // 根据收到的操作码执行对应的处理
     if(val->vendor_model_cli_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收数据
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
         if(ctx->addr==0x0001)
             return;
         APP_DBG("MYDATA_CLI---src:0x%04x dst:0x%04x rssi:%d app_idx:0x%x recv_ttl:%d send_ttl:%d", ctx->addr, ctx->recv_dst, ctx->recv_rssi, ctx->app_idx, ctx->recv_ttl, ctx->send_ttl);
         // 发送数据
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status=vendor_model_cli_send(0xFFFF,data,sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         // 将透传数据复制到app_mesh_manage结构中
         tmos_memcpy(&app_mesh_manage, val->vendor_model_cli_Event.trans.pdata, val->vendor_model_cli_Event.trans.len);
 
         // 根据透传数据的首个字节判断处理逻辑
         switch(app_mesh_manage.data.buf[0])
         {
             case CMD_DELETE_NODE_ACK: // 应用层自定义删除命令应答
             {
                 // 如果接收到的数据长度不正确,打印错误信息后返回
                 if(val->vendor_model_cli_Event.trans.len != DELETE_NODE_ACK_DATA_LEN)
                 {
                     APP_DBG("Delete node ack data err!");
                     return;
                 }
 
                 // 定义节点指针,处理删除节点的应答逻辑
                 node_t *node;
                 // 停止删除节点的超时任务
                 tmos_stop_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT);
                 // 按地址删除节点
                 bt_mesh_node_del_by_addr(val->vendor_model_cli_Event.trans.addr);
                 // 获取相应地址的节点
                 node = node_get(val->vendor_model_cli_Event.trans.addr);
                 // 设置节点为初始化状态
                 node->stage.node = NODE_INIT;
                 // 设置节点地址为未分配
                 node->node_addr = BLE_MESH_ADDR_UNASSIGNED;
                 // 设置false,表示节点不固定(未配网状态)
                 APP_DBG("Delete node complete"); // 打印节点删除成功的信息
                 break;
             }
             // 可以添加更多的case处理其他消息类型
         }
     }
     else if(val->vendor_model_cli_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
 
     }
     else if(val->vendor_model_cli_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
         // 如果是write类型的响应,这里没有做特殊处理
     }
     else
     {
         // 未知的操作码类型,打印操作码信息
         APP_DBG("Unknow opcode 0x%02x", val->vendor_model_cli_Hdr.opcode);
     }
 }

普通入网节点:

1、和配网者节点类似,修改vendor_model_srv_status_t结构体的内容,增加一项mydata数据,用于记录addr、rssi、ttl等信息

 typedef struct
 {
     struct vendor_model_srv_EventHdr vendor_model_srv_Hdr;
     union vendor_model_srv_Event_t   vendor_model_srv_Event;
     void* mydata;
 } vendor_model_srv_status_t;

2、利用上述修改后的结构体中增加的mydata变量,在vendor_message_srv_trans函数中修改厂商模型服务端状态参数

 static void vendor_message_srv_trans(struct bt_mesh_model   *model,
                                      struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
 {
     // 定义Vendor Model服务状态结构体
     vendor_model_srv_status_t vendor_model_srv_status;
     uint8_t                  *pData = buf->data;  // 指向接收数据内容的指针
     uint16_t                  len = buf->len;     // 接收数据的长度
 
     // 如果接收到的消息的事务ID或地址与期望的不同,则记录并处理该消息
     if((pData[0] != vendor_model_srv->srv_tid.trans_tid) ||
        (ctx->addr != vendor_model_srv->srv_tid.trans_addr))
     {
         // 更新期望的事务ID和来源地址
         vendor_model_srv->srv_tid.trans_tid = pData[0];
         vendor_model_srv->srv_tid.trans_addr = ctx->addr;
         // 跳过事务ID
         pData++;
         len--;
         // 设置状态结构体的操作码和状态
         vendor_model_srv_status.vendor_model_srv_Hdr.opcode =
             OP_VENDOR_MESSAGE_TRANSPARENT_MSG;
         vendor_model_srv_status.vendor_model_srv_Hdr.status = 0;
         // 设置状态结构体的事件参数,包括数据内容、长度和来源地址
         vendor_model_srv_status.vendor_model_srv_Event.trans.pdata = pData;
         vendor_model_srv_status.vendor_model_srv_Event.trans.len = len;
         vendor_model_srv_status.vendor_model_srv_Event.trans.addr = ctx->addr;
         vendor_model_srv_status.mydata=ctx; // 新增的mydata变量记录ctx
         // 如果存在处理函数,调用该函数
         if(vendor_model_srv->handler)
         {
             vendor_model_srv->handler(&vendor_model_srv_status);
         }
     }
 }

3、修改vendor_model_srv_rsp_handler函数,在接收到消息后,打印上述获得的ctx的具体内容,即addr、rssi、ttl等,然后定义一个数据data广播给0xFFFF,即所有节点,这样在入网节点接收到数据后就会向所有节点(包括配网器节点和其他入网节点)广播信息,从而获得当前入网节点和其他节点间的rssi等信息

 static void vendor_model_srv_rsp_handler(const vendor_model_srv_status_t *val)
 {
     // 如果收到的消息表明有错误或状态不正确
     if(val->vendor_model_srv_Hdr.status)
     {
         // 如果是因为应答回复超时未收到
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
         return;
     }
     // 如果操作码表示是透传消息
     if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
         if(ctx->addr==my_addr)
             return;
         APP_DBG("MYDATA_SRV---src:0x%04x dst:0x%04x rssi:%d app_idx:0x%x recv_ttl:%d send_ttl:%d", ctx->addr, ctx->recv_dst, ctx->recv_rssi, ctx->app_idx, ctx->recv_ttl, ctx->send_ttl);
 
         // 发送
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status=vendor_model_srv_send(0xFFFF,data,sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         // 将收到的数据复制到app_mesh_manage中
         tmos_memcpy(&app_mesh_manage, val->vendor_model_srv_Event.trans.pdata, val->vendor_model_srv_Event.trans.len);
         // 根据消息内容的第一个字节判断消息类型
         switch(app_mesh_manage.data.buf[0])
         {
             // 如果是删除节点的命令
             case CMD_DELETE_NODE:
             {
                 // 检查接收到的数据长度是否合理
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_DATA_LEN)
                 {
                     APP_DBG("Delete node data err!");
                     return;
                 }
                 uint8_t status;
                 // 收到删除命令,打印信息并发送ack回信,然后启动延时以删除本节点
                 APP_DBG("receive delete cmd, send ack and start delete node delay");
                 app_mesh_manage.delete_node_ack.cmd = CMD_DELETE_NODE_ACK;
                 status = vendor_model_srv_send(val->vendor_model_srv_Event.trans.addr,
                                                 app_mesh_manage.data.buf, DELETE_NODE_ACK_DATA_LEN);
                 // 如果发送ack失败,打印错误信息
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 向所有节点发送命令,告知它们删除存储的相关信息
                 APP_DBG("send to all node to let them delete stored info ");
                 app_mesh_manage.delete_node_info.cmd = CMD_DELETE_NODE_INFO;
                 status = vendor_model_srv_send(BLE_MESH_ADDR_ALL_NODES,
                                                 app_mesh_manage.data.buf, DELETE_NODE_INFO_DATA_LEN);
                 // 如果发送失败,则打印错误信息
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 启动一个任务,延时删除本节点
                 tmos_start_task(App_TaskID, APP_DELETE_LOCAL_NODE_EVT, APP_DELETE_LOCAL_NODE_DELAY);
                 break;
             }
             // 如果是有节点被删除的命令,需要删除存储的该节点信息
             case CMD_DELETE_NODE_INFO:
             {
                 // 检查接收到的数据长度是否合理
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_INFO_DATA_LEN)
                 {
                     APP_DBG("Delete node info data err!");
                     return;
                 }
                 // 记录要删除信息的地址,并启动一个任务,延时执行删除操作
                 delete_node_info_address = val->vendor_model_srv_Event.trans.addr;
                 tmos_start_task(App_TaskID, APP_DELETE_NODE_INFO_EVT, APP_DELETE_NODE_INFO_DELAY);
                 break;
             }
             // 其他情况,不是已知的命令类型
             default:
                 APP_DBG("Unknow opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
                 break;
         }
     }
     // 如果操作码表示是写入消息
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
 
         // 收到write数据,打印长度和来源地址
         APP_DBG("len %d, from 0x%04x", val->vendor_model_srv_Event.write.len, val->vendor_model_srv_Event.write.addr);
 
         // 新增:循环打印完整的数据内容
         for(int i = 0; i < val->vendor_model_srv_Event.write.len; i++) {
             APP_DBG("data[%d] = 0x%02x", i, val->vendor_model_srv_Event.write.pdata[i]);
         }
 
     }
     // 如果操作码表示是确认消息
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
         // 发送的indicate已收到应答
     }
     // 如果操作码未知
     else
     {
         // 打印未知的操作码信息
         APP_DBG("Unknow opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
     }
 }


实验流程及现象:

①烧录程序到配网器节点和多个入网节点(实验采用4个普通节点)中,并将所有节点连接串口,在串口调试助手中可以看到入网节点依次分配地址为2、3、4、5。

image-20240327164520717.png

image-20240327164622909.png

。。。

image-20240327164747904.png

②评估版按下按键S4(对应keyPress为3),实现配网者节点向0xFFFF地址的所有入网节点广播消息,从而开启各节点信息传输功能。此时可以在配网者节点对应的串口调试助手中看到来自各个普通节点的信息以及彼此之间的RSSI值。同时,在各个普通节点对应的串口调试助手中也可以看到和其他节点间的数据传输过程以及彼此之间的RSSI值。

配网器的串口调试助手:

image-20240327165403690.png

入网节点2的串口调试助手:

image-20240327165615974.png

。。。

入网节点5的串口调试助手:

image-20240327165916320.png



疑问:

问题①:存在一个问题,当按下按键开始节点间通信时,刚开始的一段时间内可以看到所有节点彼此之间都可以通信,但是一段时间后,入网节点2会删除自身节点,其他节点间正常通信,请问该问题是什么原因导致的,怎么解决?现象如下所示:

image-20240327170406838.png

image-20240327193247443.png

问题②:目前是通过串口调试助手观察信息接收情况和RSSI值,且针对配网者和所有入网节点,每一个节点的串口调试助手中都能看到当前节点和其余节点间通信的RSSI值,我需要将所有节点的串口调试助手中的数据传输给电脑,在电脑中进行后续处理,已知的方法是通过串口调试助手的日志打印成txt文本,然后在电脑中就可以读取。但是如果不用串口调试助手,如何把串口中的这些打印数据传给电脑,可以利用wifi模块么?如果可以,怎么实现?


您好,问题①中,测试收发的数据包的内容是怎样的。看串口打印,是收到了删除节点指令后删除节点本身的,可能是数据包内容恰好是删除指令。正常运行不会自动触发删除节点命令。

image.png

问题②中,是希望无线传输日志到电脑上吗?

用wifi是可以的,不过电脑端的上位机需要自行编写,PCB板子上需要增加wifi模块的成本。如果不用wifi,可以利用2.4G RF射频信号来实现。mesh节点是支持2.4G RF射频+mesh组网在同一套代码里运行的,需要上传给电脑的信息可以通过2.4G射频信号发出去;在电脑端插接一个接收头,2.4G收到的信号再通过USB模拟串口来输出给上位机软件。

如果有线模式也能接受,是不希望使用USB转串口模块,MCU上的USB接口是支持模拟COM口,直接给串口助手上位机发包的。


只有登录才能回复,可以选择微信账号登录