文章目录
一、前言
Bluetooth SIG Mesh Model已经制定得很详细了,详情看:MshMDLv1.0.1.pdf 这份文档。
足以满足我们大部分的应用场景,但是总有一些私人定制类的需求。这时候就需要我们自定义Model。
二、演示
- 首页——>CMD按钮
- 选择Vendor On、Vendor Off、Vendor Get等功能
- Vendor Model的操作码
opcode
都以0211开头,因为这两个字节是Vendor ID。详情见:蓝牙Sig Mesh 概念入门⑤——Mesh通信消息格式详解
三、日志
Vendor On
[2021-04-17 22:53:09.236]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00c2,par_len:2,par:01 09
[USER]:(USER)cb_vd_group_g_set par=01 09
[USER]:(USER)cb_vd_group_g_get par=01 09
[LIB]:(sdk)mesh tx NoAck,op:0xc4,src:0x0136,dst:0x0001,par_len:1 par:01
[INFO]:(mesh)mesh_tx_access_key_get:print index part0,0,0
Vendor Off
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00c2,par_len:2,par:00 0a
[USER]:(USER)cb_vd_group_g_set par=00 0a
[USER]:(USER)cb_vd_group_g_get par=00 0a
[LIB]:(sdk)mesh tx NoAck,op:0xc4,src:0x0136,dst:0x0001,par_len:1 par:00
[INFO]:(mesh)mesh_tx_access_key_get:print index part0,0,0
Vendor On/Off Get
[2021-04-17 22:56:09.756]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00c1,par_len:0,par:
[USER]:(USER)cb_vd_group_g_get par=
[LIB]:(sdk)mesh tx NoAck,op:0xc4,src:0x0136,dst:0x0001,par_len:1 par:00
[INFO]:(mesh)mesh_tx_access_key_get:print index part0,0,0
Vendor On NO-ACK
[2021-04-17 22:56:55.442]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00c3,par_len:2,par:01 0d
[USER]:(USER)cb_vd_group_g_set par=01 0d
四、源码
- 源码主要集中在
vendor\common\vendor_model.c
和vendor_model.h
- 这里面的流程就不再过多的赘述,详情见:泰凌微8258入门教程 基础篇⑥——接收数据流程
去掉多余的#if和不必要的代码
- 定义
opcode
操作码
#define VD_GROUP_G_GET 0xC1
#define VD_GROUP_G_SET 0xC2
#define VD_GROUP_G_SET_NOACK 0xC3
#define VD_GROUP_G_STATUS 0xC4
- 定义
mesh_cmd_sig_func_t
数组 - 数组的主要作用是将操作码
opcode
、resp opcode
和cb回调函数
关联起来。
typedef struct{
u16 op;
u16 status_cmd; // receive status message even though there is not server model // u16 for align
u32 model_id_tx;
u32 model_id_rx;
cb_cmd_sig2_t cb;
u32 op_rsp; // -1 for no rsp and ensure 4BYTE align
}mesh_cmd_sig_func_t;
mesh_cmd_sig_func_t mesh_cmd_vd_func[] = {
{VD_GROUP_G_SET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_set, VD_GROUP_G_STATUS},
{VD_GROUP_G_GET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_get, VD_GROUP_G_STATUS},
{VD_GROUP_G_SET_NOACK, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_set, STATUS_NONE},
{VD_GROUP_G_STATUS, 1, VENDOR_MD_LIGHT_S, VENDOR_MD_LIGHT_C, cb_vd_group_g_status, STATUS_NONE},
};
- 回调函数是我们主要实现业务代码的地方
// --------- vendor sub op code end --------
int cb_vd_group_g_get(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
u8 sub_op = 0;
LOG_USER_MSG_INFO(par, par_len, "cb_vd_group_g_get par=", 0);
if(par_len){ // compatible with legacy version that is no par.
sub_op = par[0];
}
model_g_light_s_t *p_model = (model_g_light_s_t *)cb_par->model;
cb_vd_group_g_sub_tx_st p_cb = (cb_vd_group_g_sub_tx_st)search_vd_group_g_func(sub_op, SEARCH_VD_GROUP_G_FUNC_TYPE_TX_ST);
if(p_cb){
return p_cb(cb_par->model_idx, sub_op, p_model->com.ele_adr, cb_par->adr_src, 0, 0);
}else{
return -1;
}
}
int cb_vd_group_g_set(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
int err = -1;
int pub_flag = 0;
LOG_USER_MSG_INFO(par, par_len, "cb_vd_group_g_set par=", 0);
//model_g_light_s_t *p_model = (model_g_light_s_t *)cb_par->model;
vd_group_g_set_t *p_set = (vd_group_g_set_t *)par;
u8 sub_op = p_set->sub_op;
if(!cb_par->retransaction){
cb_vd_group_g_sub_set p_cb_set = (cb_vd_group_g_sub_set)search_vd_group_g_func(sub_op, SEARCH_VD_GROUP_G_FUNC_TYPE_SET);
if(p_cb_set){
pub_flag = p_cb_set(par, par_len, cb_par);
}else{
return -1;
}
}
if(VD_GROUP_G_SET_NOACK != cb_par->op){
err = cb_vd_group_g_get(par, par_len, cb_par);
}else{
err = 0;
}
if(!err && pub_flag){
if(is_vd_onoff_op(sub_op)){ // only onoff need publish now
model_pub_check_set(ST_G_LEVEL_SET_PUB_NOW, cb_par->model, 1);
}
}
return err;
}
五、自定义Model(呼吸灯)
- 了解了Vendor Model的流程,现在开始我们自定义的模型开发之旅
- 我们选择做一个呼吸灯的模型
- 可以查询当前呼吸灯的状态
- 设置呼吸灯开始呼吸和停止呼吸
5.1 定义操作码opcode
-
vendor\common\vendor_model.h
- 定义4个
opcode
,分别是- 查询呼吸灯
- 设置呼吸灯
- 不需要ACK的设置呼吸灯
- 呼吸灯当前的状态(应答)
#define VD_BREATH_G_GET 0xF0
#define VD_BREATH_G_SET 0xF1
#define VD_BREATH_G_SET_NOACK 0xF2
#define VD_BREATH_G_STATUS 0xF3
5.2 定义操作数组 mesh_cmd_sig_func_t
vendor\common\vendor_model.c
mesh_cmd_sig_func_t mesh_cmd_vd_func[] = {
{VD_BREATH_G_SET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_breath_g_set, VD_BREATH_G_STATUS},
{VD_BREATH_G_GET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_breath_g_get, VD_BREATH_G_STATUS},
{VD_BREATH_G_SET_NOACK, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_breath_g_set, STATUS_NONE},
{VD_BREATH_G_STATUS, 1, VENDOR_MD_LIGHT_S, VENDOR_MD_LIGHT_C, cb_vd_scence_g_status, STATUS_NONE},
}
5.3 定义回调函数cb
vendor\common\vendor_model.c
// --------- vendor sub op code end --------
int cb_vd_breath_g_get(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
LOG_USER_MSG_INFO(par, par_len,"cb_vd_breath_g_get par=", 0);
return 0;
}
int cb_vd_breath_g_set(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
LOG_USER_MSG_INFO(par, par_len, "cb_vd_breath_g_set par=", 0);
if(par[0]==0x01){
LOG_USER_MSG_INFO(0, 0, "kangweijian start breath", 0);
}else{
LOG_USER_MSG_INFO(0, 0, "kangweijian stop breath", 0);
}
return 0;
}
5.4 测试
2021-04-17 23:30:07.385]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:01
[USER]:(USER)cb_vd_breath_g_set par=01
[USER]:(USER)kangweijian start breath
[2021-04-17 23:30:08.651]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:01
[USER]:(USER)cb_vd_breath_g_set par=01
[USER]:(USER)kangweijian start breath
[2021-04-17 23:30:09.970]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:01
[USER]:(USER)cb_vd_breath_g_set par=01
[USER]:(USER)kangweijian start breath
[2021-04-17 23:30:28.429]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:00
[USER]:(USER)cb_vd_breath_g_set par=00
[USER]:(USER)kangweijian stop breath
[2021-04-17 23:30:29.690]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:00
[USER]:(USER)cb_vd_breath_g_set par=00
[USER]:(USER)kangweijian stop breath
[2021-04-17 23:30:31.016]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:00
[USER]:(USER)cb_vd_breath_g_set par=00
[USER]:(USER)kangweijian stop breath
现在出现了一个问题,为什么会连续发三次吗,间隔1s左右。是分包吗,看着不像。最后深究发现,是因为App下发后,设备没有应答!
5.5 增加应答rsp
- 主要是增加了
mesh_tx_cmd_rsp
应答函数 - 最后测试,有了应答,命令就只下发一次
vendor\common\vendor_model.c
int cb_vd_breath_g_get(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
LOG_USER_MSG_INFO(par, par_len,"cb_vd_breath_g_get par=", 0);
model_g_light_s_t *p_model = (model_g_light_s_t *)cb_par->model;
u8 rsp = 00;
return mesh_tx_cmd_rsp(VD_BREATH_G_STATUS, (u8 *)&rsp, sizeof(rsp), p_model->com.ele_adr, cb_par->adr_src, 0, 0);
}
int cb_vd_breath_g_set(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
LOG_USER_MSG_INFO(par, par_len, "cb_vd_breath_g_set par=", 0);
if(par[0]==0x01){
LOG_USER_MSG_INFO(0, 0, "kangweijian start breath", 0);
}else{
LOG_USER_MSG_INFO(0, 0, "kangweijian stop breath", 0);
}
if(VD_GROUP_G_SET_NOACK != cb_par->op){
return cb_vd_breath_g_get(par, par_len, cb_par);
}else{
return 0;
}
}
- 日志Log
[2021-04-17 23:50:06.776]
RX:[LIB]:(sdk)rcv access layer,retransaction:0,ttl:10,src:0x0001,dst:0xffff op:0x00f1,par_len:1,par:01
[USER]:(USER)cb_vd_breath_g_set par=01
[USER]:(USER)kangweijian start breath
[USER]:(USER)cb_vd_breath_g_get par=01
六、呼吸灯的具体实现
呼吸灯的具体实现,详情见下一篇博文:泰凌微8258入门教程 进阶篇⑥——proc_led指示灯处理函数和呼吸灯实现
觉得好,就一键三连呗(点赞+收藏+关注)