阿裡雲&天貓精靈實戰
- SDK說明
- 程式入口
- 事件響應user_event()
- 元素模型g_elem_state[ ]
- 編譯生成.bin檔案的命名
-
- .mk檔案修改
- 編譯流程
- 調試資訊
- 事件查詢
- 廠商模型元素填充
-
- 資料上報
經言,望而知之謂之神,聞而知之謂之聖,問而知之謂之工,切脈而知之謂之巧。
以下是我個人總結出來的工程項目架構,重點有三部分:
- 事件響應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);
以上是資料上報的相關部分代碼