天貓精靈項目新增了DeviceTimer屬性,整合了本地定時、循環定時、倒計時等定時相關的功能。本文提供了一個插座設備端上定時功能的開發示例,作為基于DeviceTimer屬性開發定時功能的參考示例。
配置控制臺參數
- 登錄生活物聯網控制臺。
- 創建一個項目。更多操作,請參見創建項目。
- 創建產品,并定義產品功能。更多操作,請參見創建產品并定義產品功能,注意選擇聯網方式為Wi-Fi。
- 在產品的服務配置中設置設備端上定時的最大條數(與設備端的存儲、性能有關,默認為13)。 頁面,勾選本地定時與本地倒計時的功能,并在說明 勾選本地定時、本地倒計時或本地循環定時后,平臺會自動在功能定義中添加設備端上定時(DeviceTimer)屬性。
- 在產品的 頁面,選擇或者配置產品的面板,可以選擇宜控面板,或者自己編輯面板。
如果選擇編輯面板,注意要選上預約組件。
開發設備端上定時功能
- 開發定時功能。在控制臺上定義DeviceTimer的功能屬性后,設備端可以接收從云端下來的
property set
消息,從而獲取定時任務的具體信息。詳細開發步驟如下。 - 調試設備。用天貓精靈App或者天貓精靈音箱找隊友添加設備后,通過面板預約定時。設備收到定時任務的屬性時,在user_property_set_event_handler中查看日志。
static int user_property_set_event_handler(const int devid, const char *request, const int request_len) { int ret = 0; recv_msg_t msg; #ifdef CERTIFICATION_TEST_MODE return ct_main_property_set_event_handler(devid, request, request_len); #endif LOG_TRACE("property set, Devid: %d, payload: \"%s\"", devid, request); msg.from = FROM_PROPERTY_SET; strcpy(msg.seq, SPEC_SEQ); property_setting_handle(request, request_len, &msg); return ret; } static int property_setting_handle(const char *request, const int request_len, recv_msg_t * msg) { cJSON *root = NULL, *item = NULL; int ret = -1; if ((root = cJSON_Parse(request)) == NULL) { LOG_TRACE("property set payload is not JSON format"); return -1; } ... #ifdef AIOT_DEVICE_TIMER_ENABLE else if ((item = cJSON_GetObjectItem(root, DEVICETIMER)) != NULL && cJSON_IsArray(item)) { // Before LocalTimer Handle, Free Memory cJSON_Delete(root); ret = deviceTimerParse(request, 0, 1); user_example_ctx_t *user_example_ctx = user_example_get_ctx(); if (ret == 0) { IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)request, request_len); } else { char *report_fail = "{\"DeviceTimer\":[]}"; IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)report_fail, strlen(report_fail)); ret = -1; } // char *property = device_timer_post(1); // if (property != NULL) // HAL_Free(property); return 0; } #ifdef MULTI_ELEMENT_TEST else if (propertys_handle(root) >= 0) { user_example_ctx_t *user_example_ctx = user_example_get_ctx(); cJSON_Delete(root); IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY, (unsigned char *)request, request_len); return 0; } #endif #endif ... }
設備端接收到的示例如下。
"{"DeviceTimer":[ {"A":"powerstate:0","R":0,"S":0,"T":"01 18 22 05 ? 2021","E":1,"Y":1,"Z":28800,"N":""}, {"A":"powerstate:1","R":0,"S":0,"T":"00 18 22 05 ? 2021","E":1,"Y":2,"Z":28800,"N":""}, {"A":"powerstate:1","R":0,"S":0,"T":"30 09 ? * 1,2,3,4,5 *","E":1,"Y":2,"Z":28800,"N":""}, {"A":"powerstate:0","R":0,"S":0,"T":"00 10 ? * 1,2,3,4,5 *","E":1,"Y":2,"Z":28800,"N":""}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0}, {"E":0,"Y":0} ] }"
以上示例為JSON數組格式結構,DeviceTimer內共有13條定時記錄(在 頁面的服務配置中設置的值)。每條數組中的每個JSON為一個定時任務,參數解釋如下。
縮寫 全名 字段名稱 數值類型 參數描述 A Targets 定時動作 字符串 表示當次設置的定時任務的具體動作,如字符串里包含"|",則"|"前面的是RunTime需執行的action,"|"后面的是SleepTime需執行的action R RunTime 運行時間 整數 單位:秒 S SleepTime 睡眠時間 整數 單位:秒 T Timer 開始時間 字符串 用于表示定時任務開始時間,使用cron格式 E Enable 啟用 布爾 定義該條定時任務是否啟用 Y Type 定時類型 整數 定時類型 - 0:未配置
- 1:倒計時
- 2:本地定時
- 3:循環定時
Z TimeZone 時區 整數 表示本地事件與UTC時間的差值 - 單位:秒
- 取值范圍為:-43200到50400
- 步長:3600
N EndTime 結束時間 字符串 參考格式為"18:30" cron格式定義與示例如下:
cron格式:分 時 日 月 周 年 周重復: 22 10 * * 1,2,3,4,5,6,7 * // 每周一到周日,10點22分 指定日期:22 10 28 12 * 2021 // 2021年12月28日10點22分 單次定時:22 10 * * * * // 10點22分
所以上面示例中,默認13條定時任務,下發了4條啟用的任務。
{ //類型為倒計時,2021年5月22日18時01分,執行開關關閉(powerstate屬性值設為0) "A":"powerstate:0", "R":0, "S":0, "T":"01 18 22 05 ? 2021", "E":1, //本任務啟用 "Y":1, //類型為倒計時 "Z":28800, //東八區 "N":"" }, { //類型為本地計時,2021年5月22日18時00分單次執行,執行開關打開(powerstate屬性值設為1) "A":"powerstate:1", "R":0, "S":0, "T":"00 18 22 05 ? 2021", "E":1, //本任務啟用 "Y":2, //類型為本地定時 "Z":28800, //東八區 "N":"" }, { //類型為本地計時,每周一到周五9:30分執行,執行開關打開(powerstate屬性值設為1) "A":"powerstate:1", "R":0, "S":0, "T":"30 09 ? * 1,2,3,4,5 *", "E":1, //本任務啟用 "Y":2, //類型為本地定時 "Z":28800, //東八區 "N":"" }, { //類型為本地計時,每周一到周五10:00分執行,執行開關關閉(powerstate屬性值設為0)。 "A":"powerstate:0", "R":0, "S":0, "T":"00 10 ? * 1,2,3,4,5 *", "E":1, //本任務啟用 "Y":2, //類型為本地定時 "Z":28800, //東八區 "N":"" }, { //未設置的任務 "E":0, //未啟用 "Y":0 //類型為未配置 },
說明 對于沒有RTC的設備,會有兩個問題需要注意。第一,配置定時后,如果長時間離線,時鐘偏差會逐漸變大;第二,配置定時后,發生設備重啟,如果設備未成功聯網并更新UTC時間,定時功能將無法工作。
開發多孔插座的定時功能
如果要基于智能插座示例開發多孔插座,設備端在開發定時功能時需要注意以下事項。
- 將單孔插座中默認關閉的宏MULTI_ELEMENT_TEST打開,可以使能多element功能。
- 通過宏NUM_OF_TIMER_PROPERTYS定義定時控制的element數量。
- 把各element對應的物模型字段名,填入數組propertys_list[NUM_OF_TIMER_PROPERTYS],并在數組propertys_type[NUM_OF_TIMER_PROPERTYS]填寫對應屬性的數據類型(布爾型,枚舉型,整型統一填T_INT,浮點型填T_FLOAT)
- 示例如下:
#ifdef AIOT_DEVICE_TIMER_ENABLE #define MULTI_ELEMENT_TEST //此處使能多element功能 #ifndef MULTI_ELEMENT_TEST #define NUM_OF_TIMER_PROPERTYS 3 /* */ const char *propertys_list[NUM_OF_TIMER_PROPERTYS] = { "PowerSwitch", "powerstate", "allPowerstate" }; #else // only for test #define NUM_OF_TIMER_PROPERTYS 14 /* */ // const char *propertys_list[NUM_OF_TIMER_PROPERTYS] = { "testEnum", "testFloat", "testInt", "powerstate", "allPowerstate" }; const char *propertys_list[NUM_OF_TIMER_PROPERTYS] = { "powerstate", "allPowerstate", "mode", "powerstate_1", "brightness", "windspeed", "powerstate_2", "powerstate_3", "heaterPower", "windspeed", "angleLR", "testEnum", "testFloat", "testInt" }; typedef enum { T_INT = 1, T_FLOAT, T_STRING, T_STRUCT, T_ARRAY, } data_type_t; const data_type_t propertys_type[NUM_OF_TIMER_PROPERTYS] = { T_INT,T_INT,T_INT,T_INT,T_INT,T_INT,T_INT,T_INT,T_FLOAT,T_INT,T_INT,T_INT,T_FLOAT,T_INT }; static int propertys_handle(cJSON *root) { cJSON *item = NULL; int ret = -1, i = 0; for (i = 0; i < NUM_OF_TIMER_PROPERTYS; i++) { if (propertys_type[i] == T_STRUCT && (item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsObject(item)) { //structs printf(" %s\r\n", propertys_list[i]); ret = 0; } else if (propertys_type[i] == T_FLOAT && (item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsNumber(item)){ // float printf(" %s %f\r\n", propertys_list[i], item->valuedouble); ret = 0; } else if (propertys_type[i] == T_INT &&(item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsNumber(item)){ // int printf(" %s %d\r\n", propertys_list[i], item->valueint); ret = 0; } else if (propertys_type[i] == T_STRING &&(item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsString(item)){ // string printf(" %s %s\r\n", propertys_list[i], item->valuestring); ret = 0; } else if (propertys_type[i] == T_ARRAY &&(item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsArray(item)){ // array printf(" %s \r\n", propertys_list[i]); ret = 0; } } return ret; } #endif
各element屬性設置處理入口示例如下。
static int propertys_handle(cJSON *root) {
cJSON *item = NULL;
int ret = -1, i = 0;
for (i = 0; i < NUM_OF_TIMER_PROPERTYS; i++) {
if (propertys_type[i] == T_STRUCT && (item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsObject(item)) { //structs
printf(" %s\r\n", propertys_list[i]);
ret = 0;
} else if (propertys_type[i] == T_FLOAT && (item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsNumber(item)){ // float
printf(" %s %f\r\n", propertys_list[i], item->valuedouble);
ret = 0;
} else if (propertys_type[i] == T_INT &&(item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsNumber(item)){ // int
printf(" %s %d\r\n", propertys_list[i], item->valueint);
ret = 0;
} else if (propertys_type[i] == T_STRING &&(item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsString(item)){ // string
printf(" %s %s\r\n", propertys_list[i], item->valuestring);
ret = 0;
} else if (propertys_type[i] == T_ARRAY &&(item = cJSON_GetObjectItem(root, propertys_list[i])) != NULL && cJSON_IsArray(item)){ // array
printf(" %s \r\n", propertys_list[i]);
ret = 0;
}
}
return ret;
}
定時執行,完成相關操作,會執行回調函數,參考timer_service_cb
函數回調實現。
static void timer_service_cb(const char *report_data, const char *property_name, const char *data)
{
uint8_t value = 0;
char property_payload[128] = {0};
// if (report_data != NULL) /* post property to cloud */
// user_post_property_json(report_data);
if (property_name != NULL) { /* set value to device */
LOG_TRACE("timer event callback=%s val=%s", property_name, data);
#ifdef MULTI_ELEMENT_TEST
user_example_ctx_t *user_example_ctx = user_example_get_ctx();
if (strcmp(propertys_list[0], property_name) != 0 && strcmp(propertys_list[1], property_name) != 0) {
snprintf(property_payload, sizeof(property_payload), "{\"%s\":%s}", property_name, data);
IOT_Linkkit_Report(user_example_ctx->master_devid, ITM_MSG_POST_PROPERTY,
property_payload, strlen(property_payload));
return;
}
else
#endif
{
// data is int; convert it.
value = (uint8_t)atoi(data);
}
recv_msg_t msg;
msg.powerswitch = value;
msg.all_powerstate = value;
msg.flag = 0x00;
strcpy(msg.seq, SPEC_SEQ);
send_msg_to_queue(&msg);
}
return;
}