DP 开发注意事项

更新时间:2023-11-01 06:06:33下载pdf

DP(Data Point)模型是涂鸦平台设备模型,用于描述设备的能力。TuyaOS 通过 DP 的组合文件(schema 文件),即可知道设备的能力,然后解析 schema 文件,将设备实例化。本文将汇聚 DP 开发过程中的一些注意事项,帮助您快速了解和掌握 DP 开发过程中的窍门。

关于 DP 的更多详细信息,查看 DP 模型与控制协议

上报频率

为了维护整体系统的稳定性和服务质量,涂鸦对每个设备的上报次数设置了限制策略。您在开发上报功能时需要注意上报频率。

网关设备和下属子设备是分开统计的,即子设备的流量不会累加到网关设备上。

  • 对于非传感设备,建议设备上报总次数不超过300次/天。
  • 对于传感类设备,合理控制数据精度,避免毫秒或秒级的上报,建议上报总次数不超过3500次/天。
  • 如果某个设备每天的 DP 上报总次数超过 3500 次,DP 数据可能会被云端限制,从而导致设备功能异常。设备如果一直在频繁上报,设备系统可能会运行不正常,使得设备无法正常工作。

组合上报

组合上报主要适用于对象类型(Object)DP。

  • 透传类型(Raw)DP 和对象类型 DP 不能组合在一起上报。
  • 多个透传类型 DP 也是不能组合在一起上报的。

每次上报数据,设备都会等待云端返回确认报文。如果在规定时间内没收到确认报文,则会触发重传机制。该流程比较消耗时间和资源。建议您尽量将 DP 数据组合成一包数据进行上报。好处如下:

  • 降低数据上报的 延迟性,如果遇到网络情况不乐观时,一条条上报会非常耗时。
  • 降低内存 资源消耗,当您调用异步上报接口时,框架会把待上报数据放入队列中,等待发送任务以此来获取数据。等待队列越大,消耗的资源越大。
  • 降低上报 失败率,等待队列数量有限,如果频繁调用异步上报接口,将队列占用完毕,后续的上报数据会因无法放入队列中而上报失败。

示例代码

关于完整的示例代码,可参考框架内自带的 快速上手例程 tuyaos_demo_quickstart

    OPERATE_RET rt = OPRT_OK;
    TY_OBJ_DP_S all_report_data[2];
    memset(all_report_data, 0, 2*SIZEOF(TY_OBJ_DP_S));

    /* bool type data */
    all_report_data[0].dpid = DPID_SWITCH;
    all_report_data[0].type = PROP_BOOL;
    all_report_data[0].value.dp_bool = light.switch_status;
    all_report_data[0].time_stamp = 0;

    /* enum type data */
    all_report_data[1].dpid = DPID_MODE;
    all_report_data[1].type = PROP_ENUM;
    all_report_data[1].value.dp_enum = light.mode;
    all_report_data[1].time_stamp = 0;

    TUYA_CALL_ERR_LOG(dev_report_dp_json_async(NULL, all_report_data, 2));

同步/异步上报

TuyaOS 框架提供了两种 DP 上报的方式,分别是同步上报和异步上报。

异步上报

您调用异步上报接口后,会触发上报事件,之后开发框架会缓存上报数据,等到框架内的上报线程被调度时再将数据上报,不会堵塞应用线程。当您不关心实际的上报次数,只需最后一次上报的状态和设备的实际状态保持一致时,就可以选择异步上报方式。例如,上报开关状态,异步上报是比较普遍的上报方式。

  • 异步上报方式是指调用 DP 上报带有 async 接口。
  • 不关心 DP 上报成功或者失败,TuyaOS 框架会处理。
  • 主要用于实时型 OBJ 类型 DP 上报

同步上报

您调用同步上报接口后,框架立即上报数据,会堵塞应用线程,且不会过滤相同值的 DP 数据。有些场景对 DP 的上报次数需要精准控制,应用需要立即知道这次上报的结果,从而做出相应处理。例如,上报一些统计类型数据如电量,如果本次上报不成功,应用需要存储数据,等待下一次上报。

  • 同步上报方式是指调用 DP 上报带有 sync 接口。
  • 关心 DP 上报成功或者失败,如果上报失败,需要您自行保存以及重新上报。
  • 上报数据时会阻塞应用,调用时需要注意应用的栈深度。
  • 主要用于 RAW 类型上报 或者 记录型 OBJ 类型 DP 上报
  • 要特别注意上报频率,不要频繁上报数据,这类接口不会做相同值过滤的操作。
  • DP 上报会通过 MQTT 将数据上报给云端,且 DP 同步上报时会阻塞线程,所以不要在 MQTT 指令回调中直接调用同步上报接口。建议将同步上报放到队列中上报,避免在 MQTT 线程中阻塞,导致 MQTT 等待回复超时而让设备 离线
  • DP 查询处理回调 中不要调用同步上报接口,因为 DP 查询指令是通过 MQTT 下发的,回调函数的执行也是 MQTT 线程中。
  • 针对 OBJ 类型 DP 处理回调RAW 类型 DP 处理回调 ,框架内已经做过处理。这两个回调函数并不是在 MQTT 线程中处理的,而是在队列线程中处理的。所以,在这两个回调中调用同步上报接口并不会引起设备离线。

示例

以一个带开关的电量统计单插为例,来说明这两种上报方式。

单插的总控开关 DP 适合使用实时型 OBJ 类型 DP 上报接口。

void switch_status_upload(UINT status)
{
    OPERATE_RET rt = OPRT_OK;
    TY_OBJ_DP_S obj_dp = {0};

    obj_dp.dpid = 1;
    obj_dp.type = PROP_BOOL;
    obj_dp.value.dp_bool = status;
    obj_dp.time_stamp = 0;
    TUYA_CALL_ERR_LOG(dev_report_dp_json_async(NULL, &obj_dp, 1));

    return;
}

电量 DP 非常适合使用记录型 OBJ 类型 DP 上报。

在累计电量和计量事件满足上报条件后,会上报这段时间消耗的电量值。为了避免重复或漏掉上报电量,使用同步上报的接口。如果上报失败后,会将当前的时间戳和累计电量值存到 flash 中,等待可以成功上报后再对 flash 中存储的数据进行上报。上报成功后会删除 flash 中的数据,如果不成功则继续保留。

void electric_quantity_upload(UINT add_value)
{
    OPERATE_RET rt = OPRT_OK;
    TIME_T time_stamp = 0;
    TY_OBJ_DP_S obj_dp = {0};

    // 获取当前时间戳
    time_stamp = tal_time_get_posix();

    obj_dp.dpid = xx;
    obj_dp.type = PROP_VALUE;
    obj_dp.value.dp_value = add_value;
    obj_dp.time_stamp = time_stamp;
    rt = dev_report_dp_stat_sync_extend(NULL, &obj_dp, 1, 5, TRUE); // DP 同步上报
    if (rt != OPRT_OK) {
        // 上报失败,保存时间戳和数据到 flash
    } else {
        // 上报成功,查看 flash 中是否还有数据,如果有,对 flash 中的数据进行上报
        // flash 中数据上报成功,清除 flash 中数据
        // flash 中数据上报失败,保留 flash 中数据
    }

    return;
}

按需强制上报

  • 强制上报主要针对 OBJ DP 异步上报而言。RAW DP 上报时和 OBJ DP 同步上报时均不会进行过滤。
  • 开启强制上报时,要特别注意上报的频率,不要超出限制。

当应用调用 DP 上报接口时,开发框架会对 DP 进行合法性校验。当需要上报的值和当前的值是相同时,框架会进行过滤,不会再次上报。如果某个 DP 需要上报相同的值,有两种办法可以支持该 DP 的强制上报:

  • 定义 DP 时,选择特殊配置为 重复上报

DP 开发注意事项

带时间戳上报

云端默认以收到数据的时间作为 DP 的发生时间。但是实际上由于设备可能存在离线的情况,云端收到的时间可能并不是实际发生并上报的时间。因此,当 DP 发生时间和实际上报时间有很大差别时,需要在调用接口时将发生时间戳通过参数传入。

  • 实时型 OBJ 类型的 DP,可以认为不支持带时间戳上报。

  • RAW 类型 DP 通过接口参数 time_str 提供字符串形式的时间信息。

    // 同步上报 RAW DP,带时间戳,使用 dev_report_dp_raw_sync_with_time 上报更加简洁。
    OPERATE_RET dev_report_dp_raw_sync_extend_with_time(IN CONST CHAR_T *dev_id, IN CONST BYTE_T dpid,
                                                        IN CONST BYTE_T *data, IN CONST UINT_T len,
                                                        IN CONST UINT_T timeout, IN CONST BOOL_T enable_auto_retrans,
                                                        IN CONST CHAR_T *time_str);
    #define dev_report_dp_raw_sync_with_time(dev_id, dpid, data, len, timeout, time_str) \
        dev_report_dp_raw_sync_extend_with_time(dev_id, dpid, data, len, timeout, TRUE, time_str)
    
  • 记录型 OBJ 类型 DP 通过设置 TY_OBJ_DP_S 结构体中的 time_stamp 成员,提供发生 DP 的时间戳信息。

    // 同步上报记录型 OBJ DP,是否带时间戳由 dp_data 的 time_stamp 成员确定,使用 dev_report_dp_stat_sync 上报更加简洁。
    typedef struct {
        /** dp ID */
        BYTE_T dpid;
        /** dp type, see DP_PROP_TP_E */
        DP_PROP_TP_E type;
        /** dp value, see TY_OBJ_DP_VALUE_U */
        TY_OBJ_DP_VALUE_U value;
        /** dp happen time. if 0, mean now */
        UINT_T time_stamp;
    } TY_OBJ_DP_S;
    
    OPERATE_RET dev_report_dp_stat_sync_extend(IN CONST CHAR_T *dev_id, IN CONST TY_OBJ_DP_S *dp_data,
                                                IN CONST UINT_T cnt, IN CONST UINT_T timeout, IN CONST BOOL_T enable_auto_retrans);
    #define dev_report_dp_stat_sync(dev_id, dp_data, cnt, timeout) \
        dev_report_dp_stat_sync_extend(dev_id, dp_data, cnt, timeout, TRUE)
    

DP 数量限制

设备功能点越多,设备功能描述文件(schema 文件)就会越大。当设备功能描述文件大于 8 KB 时,一些 RTOS 系统的产品就会无法下载,导致 设备激活时会失败。云端在 https 交互超过 8 KB 时会启用 chunked 模式,而设备不支持 chunked 模式。本地报错关键日志如下:

chunked err

因此,涂鸦 IoT 开发平台早期提供一个提示,建议设备的 DP 数量不超过 40 个。实际限制的不是 DP 数量,而是描述文件的大小。

TuyaOS 3.5.0 及以上版本没有功能描述文件不大于 8 KB 的限制,您可以按需创建 DP。但是 DP 越多,功能描述文件越大,所需的内存资源也会越大,所以 DP 数量也并不是无限制的。

常见问题

打开面板的那瞬间设备经常出现短暂离线,可能是什么原因导致的?

检查设备固件是否在 DP 查询处理回调 中调用了同步上报接口,阻塞了 MQTT 线程,导致 MQTT 因收不到回复而关闭。具体信息,请查看上文提到的 同步上报