本地倒计时功能开发实践

更新时间:2019-07-31 11:26:46

简介

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

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

多路倒计时

1.概述

与单路倒计时不同的是,多路倒计时可以同时对多个设备上的布尔值设定本地倒计时任务,如多路插座。
多路倒计时:

2.准备工作

控制台-产品-人机交互-自动化和定时:

倒计时一列,勾选需要支持本地倒计时的功能,如主灯开关:

完成这个步骤后,点击保存,平台将在 功能定义 中,自动插入一个标准功能“倒计时”(CountDownList):

参数名称 参数标识 数据类型
操作对象 Target string
倒计时命令 Contents string

数据结构说明

功能名称:倒计时列表
标识符:CountDownList
类型:JSON
CountDownList: {
Target: "PowerSwitch"(string,当次设置操作指定的布尔值的identifier),
Contents: "PowerSwitch-1-1-123-1535644800000,LightSwitch-0-1-456-1535644800000"(string, 该设备的所有倒计时任务字符串,具体格式说明见下方)
XXX1:0(bool,该产品已有的布尔类型标准属性),
XXX2:0(bool,该产品已有的布尔类型标准属性)
}
CountDownList本身是可选的标准属性
Target和Contents为CountDownList的必选属性
二者都是string类型(最大长度为2048字节)
Target和Contents期望后台数据加上,不用开发者自己添加
XXX1和XXX2为当前产品已有的标准布尔属性,可以由开发者随意添加或删除

注意:如果功能定义里有CountDownList,但CountDownList里除了Target和Contents外没有别的布尔属性,那么属于异常情况,应该报错提示

设备端同学注意:前端设置完以后,XX1 XX2 这边的值默认是0,设备端不需要Care这个。仅关注Contents里内容即可

字段说明

Target

类型:string
属性名:操作对象
是否必选:是
长度:2048
当次操作的布尔值类型的标准属性
为什么要有这个字段?假设第一次设置一个倒计时任务:PowerSwitch-1-1-1000-1535644800000,那么1分钟后,要设置第二个倒计时任务:PowerSwitch-1-1-1000-1535644800000,Enable-0-1-2000-1535644800000,如果没有这个Target字段指定的属性,那么第一次设置的倒计时任务的时间将又从1000s开始计时

Contents

类型:string
属性名:倒计时命令
是否必选:是
长度:2048
因为本次的需求是同时设置多个布尔值属性的倒计时任务,且当前没有数组类型的数据,故采用一个string来存放设备的所有倒计时任务
例子:"PowerSwitch-1-1-123-1535644800000,LightSwitch-0-1-456-1535644800000"
不同布尔值的倒计时任务用逗号来分隔
用-来分隔一个属性倒计时任务的不同字段
每一条倒计时任务的格式说明:
以PowerSwitch-0-1-1535644800000为例
第一位:PowerSwitch 设备上某一个布尔值的identifier
第二位:0 倒计时到了之后要执行的布尔值动作,可选值仅有0和1
第三位:1 当前这一条倒计时任务的执行状态,可选值仅有0和1,0代表倒计时时间已到,1代表倒计时时间还未到
第四位:123 代表设置的倒计时时间段,单位为s,最大可以设置为23时59分59秒,最小为1秒,不能设置为0。当设置为0时,插件侧应进行报错提示。
第五位:1535644800000 代表设置该倒计时任务时,App侧的当前时间戳。这个字段是用来重新展示倒计时列表时(例如退出设备面板后过一会儿又再进入面板),展示最新的剩余倒计时时间。

点击倒计时按钮时

1.如果当前产品的功能定义里有CountDownList属性,那么此时走新的倒计时列表逻辑,在当前组件展示倒计时列表相关界面
2.如果当前产品的功能定义里没有CountDownList属性,但是有CountDown属性,那么则走以前的倒计时插件逻辑。

点击某个倒计时任务的撤销

1.App删除Contents字段里对应identifier的任务,同时设置Target为这个identifier,上报云端,下发给设备端。
2.设备端收到新的CountDownList属性,发现Target指定了一个identifier,但是Contents里却没有这个identifier,那么删除正在执行的identifier对应的倒计时任务,不影响其余的倒计时任务。

3.场景举例说明

1.点击倒计时按钮
2.检查tsl,发现CountDownList里还有额外的XXX1,XXX2属性
3.倒计时列表弹层展示XXX1、XXX2的中文名称列表
4.选择其中一个属性,例如PowerSwitch,展示设置时间和动作界面,设定好时间(1000s)和动作(打开)
5.设置Target: "PowerSwitch", 设置Contents: "PowerSwitch-1-1-1000-1535644800000",下发给云端
6.设备端收到CountDown后,解析Contents和Target内容,设置实际的定时任务
7.此时又设置了第二个属性XXX2,Contents为:"PowerSwitch-1-1-1000-1535644800000,XXX2-0-1-2000-1535644800000",Target:"XXX2"
8.1000s到了之后,第一个PowerSwitch倒计时任务结束,设备端删除Contents里PowerSwitch这一条任务,同时设置Target为PowerSwitch,将整个CountDownList属性上报到云端,插件侧查询CountDown属性,发现Contents里没有Target指定的布尔属性,代表PowerSwitch倒计时任务已执行,进而提示PowerSwitch倒计时任务执行完毕
注意:整个插件和设备端上报云端的过程中,CountDownList里的XXX1和XXX2等布尔属性可以设置为任意符合布尔属性的值


历史文档:

单路倒计时(仅用于历史参考,新产品推荐使用上述的多路倒计时)

1. 概述

如果该设备中,只有一个功能需要支持倒计时,可以使用该方案,如单路插座。
单路倒计时:

2. 准备工作

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

  • 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 ""