天猫精灵项目新增了DeviceTimer属性,整合了本地定时、循环定时、倒计时等定时相关的功能。本文提供了一个插座设备端上定时功能的开发示例,作为基于DeviceTimer属性开发定时功能的参考示例。

配置控制台参数

  1. 登录生活物联网控制台
  2. 创建一个项目。更多操作,请参见创建项目
  3. 创建产品,并定义产品功能。更多操作,请参见创建产品并定义产品功能,注意选择联网方式为Wi-Fi。
    创建天猫精灵Wi-Fi插座
  4. 在产品的人机交互 > 定时服务页面,勾选本地定时与本地倒计时的功能,并在服务配置中设置设备端上定时的最大条数(与设备端的存储、性能有关,默认为13)。
    说明 勾选本地定时、本地倒计时或本地循环定时后,平台会自动在功能定义中添加设备端上定时(DeviceTimer)属性。
    定时服务勾选
  5. 在产品的人机交互 > 设备面板页面,选择或者配置产品的面板,可以选择宜控面板,或者自己编辑面板。
    猫精插座-选择面板

    如果选择编辑面板,注意要选上预约组件。

    猫精插座-配置面板

开发设备端上定时功能

  1. 开发定时功能。
    在控制台上定义DeviceTimer的功能属性后,设备端可以接收从云端下来的property set消息,从而获取定时任务的具体信息。详细开发步骤如下。
    1. 必须下载生活物联网平台SDKV1.6.6-5以上的版本。更多操作,请参见获取SDK
      • 智能插座示例代码,位于Products/example/smart_outlet/smart_outlet_main.c
      • 定时功能的配置代码,位于Products/example/smart_outlet/smart_outlet.mk
    2. 基于设备端SDK开发定时功能。

      确认Products/example/smart_outlet/smart_outlet.mk文件中以下宏开关的打开与关闭状态。

      GLOBAL_CFLAGS += -DAIOT_DEVICE_TIMER_ENABLE    //新版设备端DeviceTimer支持的宏开关,默认为打开状态
      # GLOBAL_CFLAGS += -DAOS_TIMER_SERVICE         //老版本定时服务的宏,默认为关闭状态
      # GLOBAL_CFLAGS += -DENABLE_COUNTDOWN_LIST     //老版本本地倒计时的宏,默认为关闭状态
      # GLOBAL_CFLAGS += -DENABLE_LOCALTIMER         //老版本本地定时的宏,默认为关闭状态
      # GLOBAL_CFLAGS += -DENABLE_PERIOD_TIMER       //老版本周期定时的宏,默认为关闭状态
      # GLOBAL_CFLAGS += -DENABLE_RANDOM_TIMER       //老版本随机定时的宏,默认为关闭状态
    3. 查看确认build.sh中的默认变量参数,是否符合项目实际需求(如下所示,更多介绍请参见README.md)。
      产品类型:default_type="example"
      应用名称:default_app="smart_outlet"
      模组型号:default_board="tg7100cevb"  //根据实际型号配置
      连云区域:default_region=MAINLAND     
      连云环境:default_env=ONLINE
      Debug log:default_debug=1           //0:release   1:debug
      其他参数:default_args=""             //可配置其他编译参数
    4. 执行./build.sh编译生成固件。

      编译成功后,即可获得烧录所需的固件。

    5. 烧录固件。
      各模组的烧录方式略有差异,请向模组厂商获取详细烧录方法。
  2. 调试设备。
    用天猫精灵App或者天猫精灵音箱找队友添加设备后,通过面板预约定时。猫精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为一个定时任务,参数解释如下。

    缩写全名字段名称数值类型参数描述
    ATargets定时动作字符串表示当次设置的定时任务的具体动作,如字符串里包含"|",则"|"前面的是RunTime需执行的action,"|"后面的是SleepTime需执行的action
    RRunTime运行时间整数单位:秒
    SSleepTime睡眠时间整数单位:秒
    TTimer开始时间字符串用于表示定时任务开始时间,使用cron格式
    EEnable启用布尔定义该条定时任务是否启用
    YType定时类型整数定时类型
    • 0:未配置
    • 1:倒计时
    • 2:本地定时
    • 3:循环定时
    ZTimeZone时区整数表示本地事件与UTC时间的差值
    • 单位:秒
    • 取值范围为:-43200到50400
    • 步长:3600
    NEndTime结束时间字符串参考格式为"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;
}