阿里云&天猫精灵实战
经言,望而知之谓之神,闻而知之谓之圣,问而知之谓之工,切脉而知之谓之巧。
以下是我个人总结出来的工程项目框架,重点有三部分:
- 事件响应user_event()
- 元素模型g_elem_state[]
- 组播地址genie_sub_list_init()
举个例子:假如我要开发一个天猫精灵语音控制的小风扇,这时候就要涉及到组播地址 0xC007以及对应设备的三元组。事件响应地方写相关驱动程序。元素模型主要是功能定义(比如开关风扇、调转速等)。这里先暂时介绍这么多,有个大概框架印象。
SDK说明
- genie_init()创建了关于mesh数据的回调线程。
- 追入genie_mesh_init()进行初始化
- 函数bt_enable()使能蓝牙,创建线程收发蓝牙mesh的消息
- 追入函数bt_enable()所带参数_genie_mesh_ready
- genie_event()天猫精灵事件函数入口
- 事件函数genie_event()之后调用事件函数user_event(),进行用户事件处理
工程支持多种板子(在board目录),很多重名文件,为了筛选,创建 .vscode 文件夹,里边放.json文件
创建前
创建后
全局查找所打印的信息sys_init ,从而找到入口位置。
往下查看会发现这个
这个是拿了BLE的协议栈过来用,而以下的是对接天猫精灵的
程序入口
上图的 init genie()改成genie_init(),然后追入genie_init()函数,该函数使能之后会创建一个线程,管理mesh收发的数据,追协议栈也是在此。genie_init()创建了关于mesh数据的回调线程。
宏定义情况可以在对应的.mk 文件中使能控制,比如说上面的CONFIG_BT_MESH,所以在编译的时候该宏会被激活
再进一步追入genie_flash_init(),发现这个是处理三元组的
Alt+左键和Alt+逗号:表示追入和返回
追回到上一次然后追入genie_mesh_init()
1243行那个函数是蓝牙使能函数bt_enable(),bt指bluetooth
由此可见,在genie_mesh_init()里的bt_enable()创建线程收发蓝牙mesh的消息
进一步追入函数bt_enable()所带参数_genie_mesh_ready
先判断是否数据传输成功,然后是一个事件函数入口genie_event(),这个函数很重要很重要非常重要。追入函数里的事件参数
枚举了很多类型事件
继续追入到genie_event()里面,发现有事件函数user_event(),这个一样是非常重要
和genie_event()类似,事件参数也是枚举方式,和genie_event()一起在同个枚举结构里
再说一遍,事件函数user_event()非常重要,因为这个是自定义事件的地方。一般情况下genie_event()都是不怎么需要修改,而user_event()是实现功能事件的入口。
事件响应user_event()
该函数的位置和application_start()在同个c文件中,而且通常在application_start()前面。
下面是light_ctl.c里边的
void user_event(E_GENIE_EVENT event, void *p_arg)
{
E_GENIE_EVENT next_event = event;
//BT_DBG("%s, %s %p\n", __func__, genie_event_str[event], p_arg);
switch(event) {
case GENIE_EVT_SW_RESET: //复位事件标志
case GENIE_EVT_HW_RESET_START://复位开始
_led_flash(5, 1); //闪灯框架,_led_set()里自定义设置灯,下图所示
break;
case GENIE_EVT_HW_RESET_DONE://复位结束
_reset_light_para(); //复位灯的状态
BT_DBG("GENIE_EVT_HW_RESET_DONE\n");
break;
case GENIE_EVT_SDK_MESH_INIT://初始化
_led_init(); //要自定义实现,下图所示
_init_light_para(); //注册相关元素
_user_init(); //必须使能该函数里边的CONFIG_GENIE_OTA,否则整个编译会过不去
if (!genie_reset_get_flag()) {
next_event = GENIE_EVT_SDK_ANALYZE_MSG;
}
break;
case GENIE_EVT_SDK_MESH_PROV_SUCCESS://代理服务成功
_led_flash(3, 0); //闪烁
break;
case GENIE_EVT_SDK_TRANS_CYCLE:
case GENIE_EVT_SDK_ACTION_DONE://动作完成
{
elem_state_t *p_elem = (elem_state_t *)p_arg;//函数传递的第二个参数,天猫精灵下发的指令元素数据
_led_ctrl(p_elem); //给元素赋能,然后又联系到_led_set()
if(event == GENIE_EVT_SDK_ACTION_DONE)
_save_light_state(p_elem);
break;
}
case GENIE_EVT_SDK_INDICATE:
break;
case GENIE_EVT_SDK_VENDOR_MSG:
break;
default:
break;
}
if(next_event != event) {
genie_event(next_event, p_arg);
}
}
元素模型g_elem_state[ ]
每个设备节点对应一个元素,例如led1、led2;每个元素对应有多个模型,所谓模型就是对应的功能,比如开关、调亮度等等;每个模型里包含多个状态,比如开关模型就有开状态和关状态两个状态。
/* element configuration start */
#define MESH_ELEM_COUNT 1
#define MESH_ELEM_STATE_COUNT MESH_ELEM_COUNT
elem_state_t g_elem_state[MESH_ELEM_STATE_COUNT];//声明元素个数
model_powerup_t g_powerup[MESH_ELEM_STATE_COUNT];//最新数据last_data的备份
static struct bt_mesh_model element_models[] = {
BT_MESH_MODEL_CFG_SRV(), //必须要加的模型
BT_MESH_MODEL_HEALTH_SRV(), //必须要加的模型
MESH_MODEL_GEN_ONOFF_SRV(&g_elem_state[0]), //通用开关服务
/*************以下的是其它服务,本例程不需要**************/
// MESH_MODEL_LIGHTNESS_SRV(&g_elem_state[0]), //亮度服务
// MESH_MODEL_CTL_SRV(&g_elem_state[0]),
//#ifndef CONFIG_ALI_SIMPLE_MODLE
// MESH_MODEL_GEN_LEVEL_SRV(&g_elem_state[0]),
// MESH_MODEL_CTL_SETUP_SRV(&g_elem_state[0]),
//#endif
};
元素结构体
typedef struct{
u8_t elem_index; //元素个数
model_state_t state; //状态
model_powerup_t powerup; //保存状态,相当于缓存,避免断电影响
void *user_data; //填充的数据
} elem_state_t;
通用厂商模型
static struct bt_mesh_model g_element_vendor_models[] = {//通用厂商模型(用于拓展)
MESH_MODEL_VENDOR_SRV(&g_elem_state[0]),
};
struct bt_mesh_elem elements[] = {//元素填充
BT_MESH_ELEM(0, element_models, g_element_vendor_models, 0),
};
uint8_t get_vendor_element_num(void)
{
return MESH_ELEM_COUNT;//返回元素个数,方便分配地址
}
编译生成.bin文件的命名
.mk文件修改
编译流程
三元组
调试信息
事件查询
追入_genie_event_handle_indicate
可以发现这个是通用开关内容
厂商模型元素填充
数据上报
vnd_model_msg reply_msg;
seg_count = get_seg_count(i + 4);
reply_msg.opid = VENDOR_OP_ATTR_INDICATE;
if(g_version_tid != 0xFF) {
reply_msg.tid = g_version_tid;
} else {
reply_msg.tid = 0;
}
reply_msg.data = buff;
reply_msg.len = i;
reply_msg.p_elem = &elements[p_elem->elem_index];
//reply_msg.retry_period = GENIE_DEFAULT_DURATION * seg_count + SEG_RETRANSMIT_TIMEOUT;
reply_msg.retry_period = 125 * seg_count + 400;
if(seg_count > 1) {
reply_msg.retry_period *= 2; //(1 + SEG_RETRANSMIT_ATTEMPTS);
}
reply_msg.retry = VENDOR_MODEL_MSG_DFT_RETRY_TIMES;
genie_vendor_model_msg_send(&reply_msg);
上面这段代码引用出来放在厂商模型的函数里
所以有个地方代码调整一下
vnd_model_msg reply_msg;
seg_count = get_seg_count(i + 4);
reply_msg.opid = VENDOR_OP_ATTR_INDICATE;
if (g_version_tid != 0xFF)
{
reply_msg.tid = g_version_tid;
}
else
{
reply_msg.tid = 0;
}
reply_msg.data = buff;
reply_msg.len = i;
reply_msg.p_elem = &elements[p_elem->elem_index];
//reply_msg.retry_period = GENIE_DEFAULT_DURATION * seg_count + SEG_RETRANSMIT_TIMEOUT;
reply_msg.retry_period = 125 * seg_count + 400;
if(seg_count > 1) {
reply_msg.retry_period *= 2; //(1 + SEG_RETRANSMIT_ATTEMPTS);
}
reply_msg.retry = VENDOR_MODEL_MSG_DFT_RETRY_TIMES;
genie_vendor_model_msg_send(&reply_msg);
改成
vnd_model_msg reply_msg;
u8_t seg_count;
seg_count = get_seg_count(my_vnd_msg->len + 4);
reply_msg.opid = VENDOR_OP_ATTR_INDICATE;
reply_msg.tid = vendor_model_msg_gen_tid();
reply_msg.data = my_vnd_msg->data;//指定类型
reply_msg.len = my_vnd_msg->len;
reply_msg.p_elem = &elements[0];//绑定给元素
reply_msg.retry_period = 125 * seg_count + 400;
reply_msg.retry = VENDOR_MODEL_MSG_DFT_RETRY_TIMES;
genie_vendor_model_msg_send(&reply_msg);
以上是数据上报的相关部分代码