Develop the on-device scheduling feature for a Tmall Genie project Wi-Fi product

更新时间:
复制 MD 格式

The Tmall Genie project includes a new DeviceTimer property that combines scheduling features such as local scheduled tasks, recurring tasks, and countdowns. This topic provides a development example of the on-device scheduling feature for an outlet. You can use this example as a reference to develop scheduling features based on the DeviceTimer property.

Configure console parameters

  1. Log on to the IoT Platform console.
  2. Create a project. For more information, see Create a project.
  3. Create a product and define its features. Set the connection method to Wi-Fi. For more information, see Create a product and define its features.
  4. On the Human-Machine Interaction > Scheduling Service page of the product, select the Local Scheduling and Local Countdown features. In the Service Configuration section, set the maximum number of on-device scheduled tasks. This number depends on the storage and performance of the device. The default value is 13.
    Note After you select Local Scheduling, Local Countdown, or Recurring Task, the platform automatically adds the DeviceTimer property to the feature definitions.
  5. On the Human-Machine Interaction > Device Panel page of the product, select or configure a panel for the product. You can use the standard panel or customize a panel.

    If you choose to customize the panel, add the Scheduling widget.

Develop the on-device scheduling feature

  1. Develop the scheduling feature.
    After you define the DeviceTimer property in the console, the device can receive property set messages from the cloud to obtain the details of scheduled tasks. The development steps are as follows.
    1. Download IoT Platform SDK V1.6.6-5 or a later version. For more information, see Get the SDK.
      • The sample code for the smart outlet is in Products/example/smart_outlet/smart_outlet_main.c
      • The configuration code for the scheduling feature is in Products/example/smart_outlet/smart_outlet.mk
    2. Develop the scheduling feature based on the device-side SDK.

      In the Products/example/smart_outlet/smart_outlet.mk file, confirm the status of the following macro switches.

      GLOBAL_CFLAGS += -DAIOT_DEVICE_TIMER_ENABLE    // Macro switch for the new on-device DeviceTimer feature. Enabled by default.
      # GLOBAL_CFLAGS += -DAOS_TIMER_SERVICE         // Macro for the old version of the scheduling service. Disabled by default.
      # GLOBAL_CFLAGS += -DENABLE_COUNTDOWN_LIST     // Macro for the old version of the local countdown feature. Disabled by default.
      # GLOBAL_CFLAGS += -DENABLE_LOCALTIMER         // Macro for the old version of the local scheduling feature. Disabled by default.
      # GLOBAL_CFLAGS += -DENABLE_PERIOD_TIMER       // Macro for the old version of the recurring task feature. Disabled by default.
      # GLOBAL_CFLAGS += -DENABLE_RANDOM_TIMER       // Macro for the old version of the random task feature. Disabled by default.
    3. Check the default variable parameters in build.sh to confirm they meet your project requirements. For more information, see README.md.
      Product type: default_type="example"
      Application name: default_app="smart_outlet"
      Module model: default_board="tg7100cevb"  // Configure based on the actual model.
      Cloud connection region: default_region=MAINLAND     
      Cloud connection environment: default_env=ONLINE
      Debug log: default_debug=1           // 0: release, 1: debug
      Other parameters: default_args=""         // You can configure other compilation parameters.
    4. Run ./build.sh to compile the code and generate the firmware.

      After the compilation is successful, the firmware is generated.

    5. Flash the firmware.
      The flashing method varies by module. For detailed instructions, contact the module manufacturer.
  2. Debug the device.
    Use the Tmall Genie app or a Tmall Genie smart speaker to add the device. Then, schedule a task from the panel.
    When the device receives the properties of a scheduled task, you can view the logs in 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
        ...
     }

    The following example shows the data received by the device.

    "{"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}      
    ]     
    }"

    The preceding example is a JSON array. The DeviceTimer contains 13 scheduled task records. This value is set in the Service Configuration section on the Human-Machine Interaction > Scheduling Service page. Each JSON object in the array is a scheduled task. The parameters are described as follows.

    Abbreviation Full Name Field Name Numeric type Description
    A Targets Scheduled Action String The specific action for the scheduled task. If the string contains a vertical bar (|), the part before the bar is the action to execute during `RunTime`, and the part after the bar is the action to execute during `SleepTime`.
    R RunTime Runtime Integer Unit: seconds.
    S SleepTime Sleep Time Integer Unit: seconds.
    T Timer Start Time String The start time of the scheduled task in cron format.
    E Enable Enable Boolean Specifies whether the scheduled task is enabled.
    Y Type Schedule Type Integer The type of scheduled task.
    • 0: Unconfigured
    • 1: Countdown
    • 2: Local scheduled task
    • 3: Recurring schedule
    Z TimeZone Time Zone Integer The difference between the local time and UTC.
    • Unit: seconds.
    • Value range: -43200 to 50400.
    • Step size: 3600.
    N EndTime End Time String Example format: "18:30".

    The cron format is defined as follows:

    cron format: Minute Hour Day Month DayOfWeek Year
    Weekly recurring: 22 10 * * 1,2,3,4,5,6,7 *     // At 10:22 every day from Monday to Sunday.
    Specific date: 22 10 28 12 * 2021            // At 10:22 on December 28, 2021.
    One-time task: 22 10 * * * *                 // At 10:22.

    In the preceding example, out of the 13 default scheduled tasks, four enabled tasks were sent to the device.

      { // Task type is countdown. At 18:01 on May 22, 2021, execute the action to turn off the switch (set the powerstate property to 0).
        "A":"powerstate:0",
        "R":0,
        "S":0,
        "T":"01 18 22 05 ? 2021",
        "E":1,        // This task is enabled.
        "Y":1,        // The task type is countdown.
        "Z":28800,    // UTC+8
        "N":""
      },
      { // Task type is local scheduling. This is a one-time task at 18:00 on May 22, 2021. Execute the action to turn on the switch (set the powerstate property to 1).
        "A":"powerstate:1",
        "R":0,
        "S":0,
        "T":"00 18 22 05 ? 2021",
        "E":1,        // This task is enabled.
        "Y":2,        // The task type is local scheduled task.
        "Z":28800,    // UTC+8
        "N":""
      },
      { // Task type is local scheduling. Execute at 9:30 from Monday to Friday. Execute the action to turn on the switch (set the powerstate property to 1).
        "A":"powerstate:1",
        "R":0,
        "S":0,
        "T":"30 09 ? * 1,2,3,4,5 *",
        "E":1,        // This task is enabled.
        "Y":2,        // The task type is local scheduled task.
        "Z":28800,    // UTC+8
        "N":""
      },
      { // Task type is local scheduling. Execute at 10:00 from Monday to Friday. Execute the action to turn off the switch (set the powerstate property to 0).
        "A":"powerstate:0",
        "R":0,
        "S":0,
        "T":"00 10 ? * 1,2,3,4,5 *",
        "E":1,        // This task is enabled.
        "Y":2,        // The task type is local scheduled task.
        "Z":28800,    // UTC+8
        "N":""
      },
      { // Unconfigured task
        "E":0,        // Disabled
        "Y":0         // The type is unconfigured.
      },  
Note For devices without a Real-Time Clock (RTC), be aware of the following issues. First, clock drift increases if the device is offline for an extended period after a scheduled task is configured. Second, the scheduling feature will not work if the device restarts but fails to connect to the network and update the UTC time.

Develop the scheduling feature for a multi-outlet power strip

If you want to develop a multi-outlet power strip based on the smart outlet example, note the following points when you develop the on-device scheduling feature.

  • Enable the MULTI_ELEMENT_TEST macro, which is disabled by default for a single outlet, to enable the multi-element feature.
  • Use the NUM_OF_TIMER_PROPERTYS macro to define the number of elements that are controlled by the scheduling feature.
  • Add the Thing Specification Language (TSL) model field name for each element to the propertys_list[NUM_OF_TIMER_PROPERTYS] array. In the propertys_type[NUM_OF_TIMER_PROPERTYS] array, specify the data type for each property. Use T_INT for Boolean, enumeration, and integer types. Use T_FLOAT for floating-point types.
  • The following code provides an example:
    #ifdef AIOT_DEVICE_TIMER_ENABLE
        #define MULTI_ELEMENT_TEST      // Enable the multi-element feature here. 
        #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

The following code provides an example of the entry point for processing the property settings of each 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;
}

When a scheduled task is executed and its related operations are complete, a callback function is executed. For more information, see the implementation of the timer_service_cb callback function.

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;
}