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。
。。。
②评估版按下按键S4(对应keyPress为3),实现配网者节点向0xFFFF地址的所有入网节点广播消息,从而开启各节点信息传输功能。此时可以在配网者节点对应的串口调试助手中看到来自各个普通节点的信息以及彼此之间的RSSI值。同时,在各个普通节点对应的串口调试助手中也可以看到和其他节点间的数据传输过程以及彼此之间的RSSI值。
配网器的串口调试助手:
入网节点2的串口调试助手:
。。。
入网节点5的串口调试助手:
疑问:
问题①:存在一个问题,当按下按键开始节点间通信时,刚开始的一段时间内可以看到所有节点彼此之间都可以通信,但是一段时间后,入网节点2会删除自身节点,其他节点间正常通信,请问该问题是什么原因导致的,怎么解决?现象如下所示:
问题②: