本地倒计时功能开发实践

更新时间:2018-09-21 15:40:15

1. 概述

本文提供了一个插座本地倒计时功能的开发案例,开发者可以参考本文,实现任意设备的本地倒计时功能。
“本地倒计时”是指,由App页面向设备端下发“开始倒计时”的任务后,设备端开始根据本地时钟,执行倒计时任务。
要实现本地倒计时的效果,设备端和APP端需要按照本文推荐的方式进行实现。

设备端:按照平台标准数据格式,实现倒计时任务的增删改。
APP端:在公版App中,或平台给自定义App提供的设备界面中,如“灯”“开关”,可以直接使用倒计时功能,如下图:

image.png | left | 175x308

点击倒计时:

image.png | left | 827x311

2. 准备工作

控制台中注册您的产品,详细参考 产品注册,本文以插座为例。在 功能定义 中,需要选择标准功能“倒计时”(CountDown),倒计时属性的数据结构:JSON对象
例子:
CountDown: {
IsRunning: 0(bool,0:没有倒计时,1:正在倒计时,),
TimeLeft: 200(int,秒数,最大值为23h59m59s对应的秒数),
PowerSwitch: 0(bool,代表倒计时时间到了后,开关要执行的动作,0为关闭,1为打开)
Timestamp: 12312312321(date,调用设置接口时当时的时间戳,用于同步时间)
}
功能名称:本地倒计时
标识符:CountDown
类型:JSON

参数名称 参数标识 数据类型
执行状态 IsRunning bool 0 - 已停止1 - 执行中
剩余时间 TimeLeft int min: 1max: 86399step: 1unit: 秒/s
开关动作 PowerSwitch bool 0 - 关闭1 - 打开
当前时间戳 Timestamp date string类型的UTC时间戳

3. 设计原理

如果实现本地倒计时:

  • APP上设定时间,点击开始倒计时,会将上面所述的CountDown通过云端下发到设备端;

  • 设备端收到CountDown后,根据TimeLeft和PowerSwitch的内容,开始执行倒计时,并且在倒计时时间到了之后,执行PowerSwitch中的开关动作;

  • 倒计时的动作执行后,将下发时的CountDown中的IsRunning设置为0,再将CountDown上报到云端(不用修改CountDown中除IsRunning外的其他字段);

  • 若设备中途断电,在上电后,需要将CountDown中的IsRunning设置为0,然后上报CountDown到云端。

如何取消本地倒计时功能:

  • 将之前收到的CountDown中的IsRunning设置为0下行到设备端

  • 设备端会回复CountDown(IsRunning=0)至云端

一些说明:

  • APP设置了一个200s后关闭电源的动作,set时候传的数据应为:
    CountDown: {
    IsRunning: 1(bool,0:没有倒计时,1:正在倒计时,),
    TimeLeft: 200(int,秒数,最大值为23h59m59s对应的秒数),
    PowerSwitch: 0(bool,代表倒计时时间到了后,开关要执行的动作,0为关闭,1为打开)
    Timestamp: 1537496418274(date,调用设置接口时当时的本地时间戳,用于同步时间)
    }

  • APP收到set成功的返回响应后,跳转到开始倒计时的页面,开始正常倒计时

  • 此时若退出APP,50s后再打开APP进入设备控制界面或本地倒计时插件,那么此时显示的剩余时间应为:
    200 - (当前本地时间戳 - set时候传递的本地时间戳)= 200 - 50 = 150s

4. 设备端开发

以下时间案例中,设备每次收到云端下行的指令会立即回复CountDown做出响应,同时创建一个绑定至定时器服务函数app_timer_expired_handle()的定时器,等到本地倒计时时间耗尽时,该定时器服务函数会自动调用,CountDown属性中的IsRunning修改成0上行至云端,同时上报设备PowerSwitch属性,即宣告本地倒计时完成。

/*
 * the handler of property changed
 * alink method: thing.service.property.set
 */
static int thing_prop_changed(const void *thing_id, const char *property, void *ctx)
{
    int ret = -1;
    app_context_t *app_ctx = (app_context_t *)ctx;
    APP_TRACE("property \"%s\" changed", property);

    if (strstr(property, app_ctx->prop_countdown) != 0) {
        /* post property
        * result is response_id; if response_id = -1, it is fail, else it is success.
        * response_id by be compare in post_property_cb.
        */
        int powerSwitch[1] = {0};
        int timeLeft[1] = {0};
        int isRunning[1] = {0};

        /* Get property value of "CountDown" */
        linkkit_get_value(linkkit_method_get_property_value, thing_id, app_ctx->prop_countdown_pwrsw, powerSwitch, NULL);
        APP_TRACE("app get property value, PowerSwitch = %d", powerSwitch[0]);

        linkkit_get_value(linkkit_method_get_property_value, thing_id, app_ctx->prop_countdown_timelf, timeLeft, NULL);
        APP_TRACE("app get property value, TimeLeft = %d", timeLeft[0]);

        linkkit_get_value(linkkit_method_get_property_value, thing_id, app_ctx->prop_countdown_isrun, isRunning, NULL);
        APP_TRACE("app get property value, IsRunning = %d", isRunning[0]);

        /* Start or stop timer according to "IsRunning" */
        if (isRunning[0] == 1) {
            app_timer_start(app_ctx->timerHandle, timeLeft[0]);
            /* temp powerswitch value to app context */
            app_ctx->powerSwitch_Target = powerSwitch[0];
        } else if (isRunning[0] == 0) {
            app_timer_stop(app_ctx->timerHandle);
        }

        /* Post property "CountDown" to response request from cloud*/
        ret = linkkit_post_property(thing_id, property, post_property_cb);
        if (ret != SUCCESS_RETURN) {
            APP_TRACE("app post property \"%s\" failed", property);
            return ret;
        }
        APP_TRACE("app post property \"%s\" succeed", property);

        /* Set property "PowerSwitch" then post */
        ret = linkkit_set_value(linkkit_method_set_property_value, app_ctx->thing, app_ctx->prop_powerswitch,
                                &app_ctx->powerSwitch_Actual, NULL);
        if (ret != SUCCESS_RETURN) {
            APP_TRACE("app set property \"%s\" failed", app_ctx->prop_powerswitch);
            return ret;
        }

        ret = linkkit_post_property(app_ctx->thing, app_ctx->prop_powerswitch, post_property_cb);
        if (ret != SUCCESS_RETURN) {
            APP_TRACE("app post property \"%s\" failed", app_ctx->prop_powerswitch);
            return ret;
        }
        APP_TRACE("app post property \"%s\" succeed", app_ctx->prop_powerswitch);
    } else if (strstr(property, app_ctx->prop_powerswitch) != 0) {
        int powerSwitch[1] = {0};

        /* Get property value of "PowerSwitch" */
        linkkit_get_value(linkkit_method_get_property_value, thing_id, app_ctx->prop_powerswitch, powerSwitch, NULL);
        APP_TRACE("app get property value, PowerSwitch = %d", powerSwitch[0]);

        /* Trigger powerswitch action, just set value here */
        app_ctx->powerSwitch_Actual = powerSwitch[0];

        /* Set property "PowerSwitch" then post */
        ret = linkkit_set_value(linkkit_method_set_property_value, app_ctx->thing, app_ctx->prop_powerswitch,
                                &app_ctx->powerSwitch_Actual, NULL);
        if (ret != SUCCESS_RETURN) {
            APP_TRACE("app set property \"%s\" failed", app_ctx->prop_powerswitch);
            return ret;
        }

        ret = linkkit_post_property(app_ctx->thing, app_ctx->prop_powerswitch, post_property_cb);
        if (ret != SUCCESS_RETURN) {
            APP_TRACE("app post property \"%s\" failed", app_ctx->prop_powerswitch);
            return ret;
        }
        APP_TRACE("app post property \"%s\" succeed", app_ctx->prop_powerswitch);
    }

    return 0;
}

results matching ""

    No results matching ""