天猫精灵项目新增了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;
}