天貓精靈項目新增了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;
}