本文以TG7100C芯片為例,介紹基于生活物聯網平臺SDK(V1.6.6)中的smart_outlet應用示例,開發單孔Wi-Fi智能插座設備固件的流程。
背景信息
應用示例smart_outlet的功能介紹如下:
- 支持云智能App(V3.5.5以上)與天貓精靈App(4.13.0以上)藍牙輔助配網。
- 支持通過云端、本地通信(目前僅云智能App支持)對設備進行控制的能力。
- 支持通過生活物聯網平臺進行設備OTA的能力。
- 支持恢復工廠設置。
- 支持斷電用戶設置記憶。
TG7100C概述
TG7100C是天貓精靈推出的Wi-Fi藍牙Combo芯片。TG7100C芯片相關文檔和軟件工具介紹,請參見TG7100C。
說明
關于TG7100B芯片的驅動、產測、硬件設計、射頻等使用問題,以及基于生活物聯網平臺藍牙Mesh SDK的應用開發,例如產品配置、配網、連云、OTA等問題,您可以通過商務聯系技術支持進行反饋。
固件編譯
固件燒錄與運行
- 在TG7100C開發板上燒錄固件。詳細操作,請參見TG7100C開發板用戶手冊。
- 短路接通開發板的第4個引腳與第5個引腳,并按開發板的復位鍵。開發板設置圖如下。
- 打開下載好的燒錄工具目錄中的TGFlashEnv.exe,單擊Finish,進入燒錄界面。
- 將Interface選擇Uart,并單擊Refresh按鍵。
- 設置串口參數,以及選擇好對應的燒錄文件。串口參數的配置如下圖所示。
- Partition Table、Boot 2 Bin與MFG Bin選擇燒錄工具目錄下對應的文件即可,文件名如圖。
- Firmware Bin選擇編譯出的固件。
- Chip Erase可以根據是否要擦除整片Flash選擇True或者False,如調試中要保留之前寫入過的設備證書,可以選擇False。
- 單擊Download按鈕,并同時按下開發板上的復位鍵,開始燒錄固件。
- 燒錄完畢后,查看運行的日志。短路連接第3個引腳與第4個引腳(如下圖所示),將串口工具波特率設置為2000000,并按下開發板復位鍵。
常用的cli指令如下。
- reset:設備重置,清除設備配網信息。
- free:查看內存使用情況。
- linkkey:寫入與查看證書。
- mac:查看開發板Wi-Fi MAC地址。
smart_outlet應用代碼結構介紹
smart_outlet應用示例中的文件結構如下。
├── Products
│ │ ├── example/smart_outlet
│ │ │ ├── app_entry.c
│ │ │ ├── app_entry.h
│ │ │ ├── combo_net.c
│ │ │ ├── device_state_manager.c
│ │ │ ├── device_state_manager.h
│ │ │ ├── factory.c
│ │ │ ├── factory.h
│ │ │ ├── makefile
│ │ │ ├── make.settings
│ │ │ ├── msg_process_center.c
│ │ │ ├── msg_process_center.h
│ │ │ ├── property_report.c
│ │ │ ├── property_report.h
│ │ │ ├── smart_outlet.h
│ │ │ ├── smart_outlet.json
│ │ │ ├── smart_outlet_main.c
│ │ │ ├── smart_outlet.mk
│ │ │ ├── vendor.c
│ │ │ └── vendor.h
詳細的文件說明如下。
- 廠家需要適配的文件(設備初始化等):vendor.c與vendor.h
- 應用程序主入口:app_entry.c與smart_outlet_main.c
- 配網和連云狀態管理:device_state_manager.c
- 設備控制指令處理:msg_process_center.c
- 設備屬性上報:property_report.c
- 廠測模式:factory.c
- 藍牙輔助配網:combo_net.c
固件適配說明
標品固件移植適配對單路智能插座應用,只需要較小的修改,就可以完成產品固件的輸出。根據產品的不同需求,涉及到的調整項介紹如下。
- GPIO適配
單路插座需要兩個GPIO分別控制繼電器開關、LED亮滅和一個GPIO讀取按鍵狀態。那么只需要修改vendor.c中定義,實例如下。
...... #elif (defined (TG7100CEVB)) #define LED_GPIO 1 // 控制LED亮滅 #define RELAY_GPIO 5 // 控制繼電器開關 #define KEY_GPIO 3 // 讀取按鍵狀態 ......
產品開發時,可以根據具體的原理圖設計配置對應的GPIO。
- 狀態LED顯示適配
- 設備狀態定義在文件Products/example/smart_outlet/device_state_manager.h 中。
typedef enum { RECONFIGED = 0, //reconfig with netconfig exist UNCONFIGED, //配網開始 AWSS_NOT_START, //配網超時 GOT_AP_SSID, //連接AP成功 CONNECT_CLOUD_SUCCESS, //連云成功 CONNECT_CLOUD_FAILED, //連云失敗 CONNECT_AP_FAILED, //連接AP失敗 CONNECT_AP_FAILED_TIMEOUT, //連接AP超時 APP_BIND_SUCCESS, //APP綁定成功 ... UNKNOW_STATE } eNetState;
- 狀態顯示的處理代碼在文件Products/example/smart_outlet/device_state_manager.c 中的
indicate_net_state_task
函數中??梢愿鶕a品的不同需求做調整。static void indicate_net_state_task(void *arg) { uint32_t nCount = 0; uint32_t duration = 0; int pre_state = UNKNOW_STATE; int cur_state = UNKNOW_STATE; int switch_stat = 0; while (1) { pre_state = cur_state; cur_state = get_net_state(); switch (cur_state) { case RECONFIGED: ... break; case UNCONFIGED: ... break; case AWSS_NOT_START: ... break; case GOT_AP_SSID: case CONNECT_CLOUD_FAILED: ... break; case CONNECT_AP_FAILED_TIMEOUT: ... break; case CONNECT_AP_FAILED: ... break; case CONNECT_CLOUD_SUCCESS: ... break; case APP_BIND_SUCCESS: ... break; ... default: break; } aos_msleep(100); } ... }
- 當前代碼中實現的默認LED顯示如下。
狀態 默認LED顯示 配網模式 插座LED反復閃爍,亮0.8秒,滅0.8秒。 恢復出廠設置 插座LED反復閃爍,亮0.2秒,滅0.2秒。 連接AP 超時/連接AP 認證失?。ǔ瑫r時間2分鐘) 插座LED反復閃爍的模式更改為,亮0.5秒、滅0.5秒,閃爍兩分鐘之后停止閃爍。停止閃爍之后,如果插座配電使能則LED燈點亮,否則LED燈滅掉。 連接AP成功、嘗試連云 插座LED反復閃爍,亮0.8秒,滅0.8秒,然后開始嘗試連接云端。 連云失敗 連接云端失敗后,需要再次嘗試連接,其間LED的顯示與“連接AP成功、嘗試連云”模式一樣。 連云成功 當設備連接云端成功,則停止LED閃爍,若插座配電打開則LED點亮,若插座配電未打開則LED滅掉。
- 設備狀態定義在文件Products/example/smart_outlet/device_state_manager.h 中。
- 按鍵處理適配標品固件根據用戶按下按鍵的時長,確定用戶的行為,目前按鍵有三種用戶行為處理。代碼Products/example/smart_outlet/device_state_manager.c文件中的
key_detect_event_task
函數負責按鍵處理。如下定義了各種行為的時間,如果需要調整各個行為的按鍵時長,可以自行修改。#define AWSS_REBOOT_TIMEOUT (4 * 1000) //長按4s 進入網絡配置模式,開始重新配網 #define AWSS_RESET_TIMEOUT (6 * 1000) //長按6s 進入恢復出廠設置,(在設備已進入網絡配置模式下) #define KEY_PRESSED_VALID_TIME_MIN 100 #define KEY_PRESSED_VALID_TIME_MAX 500 //按鍵按下超過100ms,小于500ms,表示有按鍵按下 #define KEY_DETECT_INTERVAL 50 //按鍵按下的檢測時間間隔 50ms #define AWSS_REBOOT_CNT AWSS_REBOOT_TIMEOUT /KEY_DETECT_INTERVAL #define AWSS_RESET_CNT AWSS_RESET_TIMEOUT /KEY_DETECT_INTERVAL #define KEY_PRESSED_CNT KEY_PRESSED_VALID_TIME /KEY_DETECT_INTERVAL // 此函數處理插座按鍵檢測 void key_detect_event_task(void *arg) { int nCount = 0, awss_mode = 0; int timeout = (AWSS_REBOOT_CNT < AWSS_RESET_TIMEOUT)? AWSS_REBOOT_CNT : AWSS_RESET_TIMEOUT; while (1) { if (!product_get_key()) { nCount++; LOG("nCount :%d", nCount); } else { if (nCount >= KEY_PRESSED_CNT && nCount < timeout) { // 按鍵控制 if (product_get_switch() == ON) { // 按鍵控制插座關閉繼電器 product_set_switch(OFF); user_post_powerstate(OFF); } else { // 按鍵控制插座打開繼電器 product_set_switch(ON); user_post_powerstate(ON); } } if ((awss_flag == 0) && (nCount >= AWSS_REBOOT_CNT)) { LOG("do awss reboot"); // 長按4s 進入網絡配置模式,開始重新配網 do_awss_reboot(); break; } else if ((awss_flag == 1) && (nCount > AWSS_RESET_CNT)) { LOG("do awss reset"); // 長按6s 進入恢復出廠設置 do_awss_reset(); // 實際執行設備重置 break; } nCount = 0; } if ((awss_flag == 0) && (nCount >= AWSS_REBOOT_CNT && awss_mode == 0)) { set_net_state(RECONFIGED); // 設置相應的設備狀態 awss_mode = 1; } else if ((awss_flag == 1) && (nCount > AWSS_RESET_CNT && awss_mode == 0)) { set_net_state(UNCONFIGED); // 設置相應的設備狀態 awss_mode = 1; } aos_msleep(KEY_DETECT_INTERVAL); // 檢測按鍵間隔為50ms } aos_task_exit(0); }
- 短按:如果按鍵按下時長長于100ms,小于500ms, 認為用戶是進行按鍵開關。
- 長按:如果用戶按下時間超過4s,認為用戶觸發設備進入網絡配置模式。如果用戶確認設備已經進入網絡配置模式,此時繼續按鍵6s,設備會進入恢復出廠模式。
設備端通用功能說明
以下功能在smart_outlet應用示例中已有相關實現,僅對設備端的通用功能做一些補充介紹。
- 事件回調在smart_outlet_main.c文件中定義了系統的各種事件處理函數,在
linkkit_main
函數中注冊了回調函數。int linkkit_main() { ... /* Register Callback */ IOT_RegisterCallback(ITE_CONNECT_SUCC, user_connected_event_handler); IOT_RegisterCallback(ITE_DISCONNECTED, user_disconnected_event_handler); // IOT_RegisterCallback(ITE_RAWDATA_ARRIVED, user_down_raw_data_arrived_event_handler); IOT_RegisterCallback(ITE_SERVICE_REQUEST, user_service_request_event_handler); IOT_RegisterCallback(ITE_PROPERTY_SET, user_property_set_event_handler); #ifdef ALCS_ENABLED /*Only for local communication service(ALCS) */ IOT_RegisterCallback(ITE_PROPERTY_GET, user_property_get_event_handler); #endif IOT_RegisterCallback(ITE_REPORT_REPLY, user_report_reply_event_handler); IOT_RegisterCallback(ITE_TRIGGER_EVENT_REPLY, user_trigger_event_reply_event_handler); IOT_RegisterCallback(ITE_INITIALIZE_COMPLETED, user_initialized); IOT_RegisterCallback(ITE_EVENT_NOTIFY, user_event_notify_handler); ... }
事件 事件觸發條件說明 ITE_CONNECT_SUCC 與云端連接成功時 ITE_DISCONNECTED 與云端連接斷開時 ITE_RAWDATA_ARRIVED SDK收到raw data數據時 ITE_SERVICE_REQUEST SDK收到服務(同步/異步)調用請求時 ITE_PROPERTY_SET SDK收到屬性設置請求時 ITE_PROPERTY_GET SDK收到屬性獲取的請求時 ITE_REPORT_REPLY SDK收到上報消息的應答時 ITE_TRIGGER_EVENT_REPLY SDK收到事件上報消息的應答時 ITE_EVENT_NOTIFY SDK收到事件通知時 ITE_INITIALIZE_COMPLETED 設備初始化完成時 - 屬性上報產品的屬性發生變化時,需要將變化后的數值上報到物聯網平臺??梢愿鶕a品需求增加屬性變化的檢測以及上報邏輯。
void user_post_property(property_report_msg_t * msg) { int res = 0; user_example_ctx_t *user_example_ctx = user_example_get_ctx(); char *property_payload = NULL; cJSON *response_root = NULL, *item_csr = NULL; response_root = cJSON_CreateObject(); if (response_root == NULL) { return; } if (msg->seq != NULL && strcmp(msg->seq, SPEC_SEQ)) { item_csr = cJSON_CreateObject(); if (item_csr == NULL) { cJSON_Delete(response_root); return; } cJSON_AddStringToObject(item_csr, "seq", msg->seq); cJSON_AddItemToObject(response_root, "CommonServiceResponse", item_csr); } #ifdef TSL_FY_SUPPORT //兼容舊版本開關PowerSwitch屬性 cJSON_AddNumberToObject(response_root, "PowerSwitch", msg->powerswitch); #endif //處理新版本物模型開關powerstate屬性 cJSON_AddNumberToObject(response_root, "powerstate", msg->powerswitch); //處理新版物模型allPowerstate屬性 cJSON_AddNumberToObject(response_root, "allPowerstate", msg->all_powerstate); property_payload = cJSON_PrintUnformatted(response_root); cJSON_Delete(response_root); char *property_formated; uint32_t len; res = user_property_format(property_payload,strlen(property_payload),&property_formated,&len); #ifdef EN_COMBO_NET //對于Wi-Fi&BLE Combo設備可以同時通過藍牙控制鏈路上報屬性值。 if (combo_ble_conn_state()) { if (0 == res) { combo_status_report(property_formated, strlen(property_formated)); LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_formated); } else { combo_status_report(property_payload, strlen(property_payload)); LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_payload); } } #endif if (0 == res) { if (msg->seq != NULL && strcmp(msg->seq, SPEC_SEQ)) { res = IOT_Linkkit_Report_Ext(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_formated, strlen(property_formated), msg->flag); } else { res = IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_formated, strlen(property_formated)); } LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_formated); example_free(property_formated); } else { if (msg->seq != NULL && strcmp(msg->seq, SPEC_SEQ)) { res = IOT_Linkkit_Report_Ext(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_payload, strlen(property_payload), msg->flag); } else { res = IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)property_payload, strlen(property_payload)); } LOG_TRACE("Post Property Message ID: %d Payload %s", res, property_payload); } example_free(property_payload); }
- 屬性設置smart_outlet按對ITE_PROPERTY_SET注冊的回調函數,在回調函數
user_property_set_event_handler
中獲取云端設置的屬性值,并原樣將收到的數據發回給云端,這樣可以更新在云端的設備屬性值,用戶可在此處對收到的屬性值進行處理。static int user_property_set_event_handler(const int devid, const char *request, const int request_len) { ... property_setting_handle(request, request_len, &msg); ... } static int property_setting_handle(const char *request, const int request_len, recv_msg_t * msg) { ... if ((item = cJSON_GetObjectItem(root, "setPropsExtends")) != NULL && cJSON_IsObject(item)) { ... } if ((item = cJSON_GetObjectItem(root, "powerstate")) != NULL && cJSON_IsNumber(item)) { //設置powerstate屬性處理 msg->powerswitch = item->valueint; msg->all_powerstate = msg->powerswitch; ret = 0; } #ifdef TSL_FY_SUPPORT /* 支持舊版本開關PowerSwitch屬性 */ else if ((item = cJSON_GetObjectItem(root, "PowerSwitch")) != NULL && cJSON_IsNumber(item)) { msg->powerswitch = item->valueint; ret = 0; } #endif else if ((item = cJSON_GetObjectItem(root, "allPowerstate")) != NULL && cJSON_IsNumber(item)) { //設置allPowerstate屬性處理 msg->powerswitch = item->valueint; msg->all_powerstate = msg->powerswitch; ret = 0; } #ifdef AOS_TIMER_SERVICE else if (((item = cJSON_GetObjectItem(root, "LocalTimer")) != NULL && cJSON_IsArray(item))|| \ ((item = cJSON_GetObjectItem(root, "CountDownList")) != NULL && cJSON_IsObject(item)) || \ ((item = cJSON_GetObjectItem(root, "PeriodTimer")) != NULL && cJSON_IsObject(item)) || \ ((item = cJSON_GetObjectItem(root, "RandomTimer")) != NULL && cJSON_IsObject(item))) { // Timer service 定時、倒計時相關屬性設置的處理 cJSON_Delete(root); // Before LocalTimer Handle, Free Memory timer_service_property_set(request); user_example_ctx_t *user_example_ctx = user_example_get_ctx(); IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)request, request_len); return 0; } #endif else { LOG_TRACE("property set payload is not JSON format"); ret = -1; } cJSON_Delete(root); if (ret != -1) send_msg_to_queue(msg); return ret; }
- 本地通信功能(目前僅云智能App支持)
本地通信功能介紹,請參見本地通信開發實踐。
本地通信功能在文件make.settings中通過宏ALCS_ENABLED來管理。使用IOT_RegisterCallback
函數注冊ITE_PROPERTY_GET事件,對應回調函數實現為user_property_get_event_handler
。此函數中目前已實現的本地通信請求的設備屬性如下所示,如果產品需要增加功能,可以相應的增加新屬性的處理case。#ifdef ALCS_ENABLED static int user_property_get_event_handler(const int devid, const char *request, const int request_len, char **response, int *response_len) { user_example_ctx_t *user_example_ctx = user_example_get_ctx(); device_status_t *device_status = &user_example_ctx->status; cJSON *request_root = NULL, *item_propertyid = NULL; cJSON *response_root = NULL; ... for (int index = 0; index < cJSON_GetArraySize(request_root); index++) { item_propertyid = cJSON_GetArrayItem(request_root, index); ... LOG_TRACE("Property ID, index: %d, Value: %s", index, item_propertyid->valuestring); if (strcmp("powerstate", item_propertyid->valuestring) == 0) { //處理新版物模型開關powerstate屬性 cJSON_AddNumberToObject(response_root, "powerstate", device_status->powerswitch); } else if (strcmp("allPowerstate", item_propertyid->valuestring) == 0) { //處理新版物模型allPowerstate屬性 cJSON_AddNumberToObject(response_root,"allPowerstate", device_status->all_powerstate); } #ifdef TSL_FY_SUPPORT /* support old feiyan TSL */ else if (strcmp("PowerSwitch", item_propertyid->valuestring) == 0) { //兼容舊版本開關PowerSwitch屬性 cJSON_AddNumberToObject(response_root, "PowerSwitch", device_status->powerswitch); } #endif #ifdef AOS_TIMER_SERVICE else if (strcmp("LocalTimer", item_propertyid->valuestring) == 0) { ... //處理本地定時LocalTimer } else if (strcmp("CountDownList", item_propertyid->valuestring) == 0) { ... //處理倒計時 #endif } } ... } #endif
- 云端解綁與恢復出廠默認設置通知設備被解綁后,云端會下發一個解綁事件通知
{"identifier":"awss.BindNotify","value":{"Operation":"Unbind"}}
。設備收到此消息可以做重置配網、清空本地數據等處理。如果通過App將設備恢復出廠默認設置,云端會下發一個Reset事件通知{"identifier":"awss.BindNotify","value":{"Operation":"Reset"}}
。設備收到此消息可以做重置配網、清空本地數據等處理。您可以結合具體產品類型,決定收到解綁和恢復出廠默認設置通知后做哪些清空操作。更多介紹,請可以參見示例代碼example/smart_outlet/smart_outlet_main.c中的notify_msg_handle
函數。static int notify_msg_handle(const char *request, const int request_len) { .... if (!strcmp(item->valuestring, "awss.BindNotify")) { cJSON *value = cJSON_GetObjectItem(request_root, "value"); if (value == NULL || !cJSON_IsObject(value)) { cJSON_Delete(request_root); return -1; } cJSON *op = cJSON_GetObjectItem(value, "Operation"); if (op != NULL && cJSON_IsString(op)) { if (!strcmp(op->valuestring, "Bind")) { //綁定通知 LOG_TRACE("Device Bind"); vendor_device_bind(); //設備綁定時需要完成的操作,設備應用可定義 } else if (!strcmp(op->valuestring, "Unbind")) { //解綁通知 LOG_TRACE("Device unBind"); vendor_device_unbind(); //設備解綁時需要完成的操作,設備應用可定義 } else if (!strcmp(op->valuestring, "Reset")) { //重置通知 LOG_TRACE("Device reset"); vendor_device_reset(); //設備重置時需要完成的操作,設備應用可定義 } } } .... }
- 藍牙輔助配網藍牙輔助配網設備端開發,請參見設備端開發。說明 SDK V1.6.6開始支持新的藍牙輔助配網方案(配合天貓精靈App V4.13.0以上版本與云智能App V3.5.5以上版本使用),新方案要求設備證書的Device Name與Wi-Fi MAC保持一致。更多介紹,請參見開發自有品牌項目插座產品、開發天貓精靈生態項目插座產品。
- 設備端上定時功能
平臺統一使用設備端上定時(DeviceTimer)開發設備端定時功能,詳細操作,請參見開發設備端本地定時功能。