XVR SDK v2.0

更新时间:2022-11-24 09:20:05下载pdf

Tuya XVR SDK 是提供具有类似 NVR、DVR 等功能的设备接入 Tuya 服务的 SDK。主要提供激活设备,APP 预览,Tuya 云存储和本地存储协议等核心功能。当前 Tuya XVR SDK 主要用于 NVR 设备,文档中,若无特殊说明,后面 XVR 和 NVR 表示同一个含义。本文主要讲述涂鸦 XVR SDK 的相关开发。

请开发者,务必抽出两个小时,配合我们提供的 Demo,研读指导文档,弄明白基本的过程。避免后期不断返工,造成人力成本的增加。我们也会根据开发者反馈的,不断优化文档和 Demo 的内容

运行模式

XVR 的运行模式可分为 NVR 模式基站模式。Demo 程序通过宏定义 DEMO_USE_AS_NVR 控制设备运行的模式,具体见基本参数填写部分。

  • NVR 模式:主设备一个 UUID 可以拖 N 个子设备(价格也高),子设备 PID 相同,子设备 UUID 信息包含在主设备的 UUID 中,主设备 UUID 所拖的子设备个数,也就是最大可以绑定子设备的数量。子设备添加、删除由设备端批量地发起。NVR 的 API 操作简单。通常通过通道号或者 node ID 来区分具体的子设备。

  • 基站模式:子设备有独立的 UUID/PID,子设备的添加、删除由移动应用一个个发起。基站的 API 粒度细、灵活,可实现复杂功能。多用子设备 devid 区分子设备。

  • UUID 是接入涂鸦平台的一种凭证。一台设备需要是一个授权的 UUID 来认证。在 XVR SDK 中,通常是主设备和子设备组成,那么理论上需要一个主设备 UUID 和多个子设备的 UUID 来完成认证。为优化这个,提供一种特殊的 UUID。这个 UUID 不仅可以激活主设备,还可以设定可以激活指定的几路子设备。通常我们说一个 XVR 设备的 UUID,都表达成是几路的 UUID。例如,需要一个四路的 UUID,那么这个 XVR 设备,可以绑定最多四路的子设备。
  • NVR 模式和基站模式的最大区别在于设备绑定的过程。NVR 模式是一次性批量的把 UUID 对应下包含的 N 个子设备绑定到主设备上。而基站模式需要客户一个个通多手机 APP 来添加。当前接入 XVR SDK 的客户以 NVR 模式居多。
    在 XVR SDK 中,子设备的设备 ID 通常是指 node ID。node ID 是设备端管理子设备的唯一标识符。和应用面板 设备信息 里面的设备 ID 不是同一个概念,APP 面板中的设备 ID,通常称为虚拟设备 ID。虚拟 ID 和 node ID 唯一的表征了同一个设备。Tuya 云会对两者做一个映射关系。
  • 在 NVR 模式中, 设备的 node ID 是根据主设备的 UUID 生成的,(规则是通过主设备 uuid+a+数字排序(例如 uuida001,uuida002)生成的一个唯一 node ID。本文中,在 NVR 模式中,涉及到的子设备 device id ,如果无特别说明,都是指 node ID。设备端通过 node ID 用于区分不同子设备。在 DP 模块介绍,会进一步说明使用。
  • 在基站模式中,子设备的 node ID,则就是对应子设备 uuid 来生成唯一的 node ID;
    XVR SDK 通过可以通过通道号或者 node ID 来相互获取。即通道号可以获得 node ID,node ID 可以获得通道。两者都是唯一表征一个子设备。
  • 基本流程路下图所示:NVR 初始化(左图)基站初始化(右图)
XVR SDK v2.0 XVR SDK v2.0

快速体验

本章节以 Ubuntu 系统为例,介绍如何快速体验 NVR Demo 程序。

搭建环境

如果您的 Ubuntu 系统是运行在虚拟机中,则可以按以下步骤体验有线配网模式:

  1. 打开虚拟机的 设置

    XVR SDK v2.0

  2. 选择 添加

    XVR SDK v2.0
  3. 选择添加 网络适配器

    XVR SDK v2.0

  4. 添加完成后,选择 桥接模式

    XVR SDK v2.0
  5. 有线连接路由器。

  6. 重启设备后环境配置完成。

运行 Demo

  1. 从 GitHub 下载 Demo 代码。

  2. 修改网卡设置,修改成开发者设备可用网卡,例如:

    \#define WLAN_DEV  "ens38"
    
  3. 设置 UUID、AUTHKEY、PID,具体步骤请参考 参数设置

    UUID 和 AuthKey 需要支持 NVR 功能的授权信息,经过特定的打标处理。一台设备配一个,不可以重复使用。需要重复使用前,需要解绑对应的设备。

  4. ubuntu_Demo 目录下执行指令,生成可执行文件:

    ./build.sh ubuntu_xvr_5_2
    

    当编译完成后,会在ubuntu_Demo/cmake-build/out路径下生成Demo_ipc文件。

  5. ubuntu_Demo/cmake-build/out目录下执行如下指令运行程序:

    ./Demo_ipc -r "../../components/Demo_tuya_ipc"
    
  6. 使用手机下载安装 智能生活 App,打开下载好的 App,点击添加设备。

    XVR SDK v2.0
  7. 选择安防监控品类重的 NVR。

    XVR SDK v2.0
  8. 手机 Wi-Fi 连接设备有线连接的路由器。

  9. 进入 下一步

    XVR SDK v2.0
  10. 等待一段时间即可添加成功。

    点击添加成功的设备即可成功的查询到视频流,如果没有视频显示,则表示之前的 Demo_resource 文件没有设置正确。

    XVR SDK v2.0
  11. 在终端中输入下列指令可以模拟体验网络摄像机功能:

    指令 指令说明
    showSubDevInfo 展示子设备信息
    showSubCloudInfo 展示子设备云存储信息
    move4 模拟前 4 路移动侦测(channel 0/1/2/3)
    move 模拟第一路移动侦测
    bell 模拟门铃事件上报

    例如虚拟设备执行过程中,执行:

    #move
    

    可以在消息中心产看得到一条移动侦测的上报

    XVR SDK v2.0

通过上述步骤可以快速体验涂鸦 NVR SDK 功能。

参数设置

  • 您需要在user_main.c文件中,分别修改 DB 文件、OTA 文件、本地录像文件的保存路径,宏定义如下:

    #define IPC_APP_STORAGE_PATH "/tmp/"
    #define IPC_APP_UPGRADE_FILE "/tmp/upgrade.file"
    #define IPC_APP_SD_BASE_PATH "/tmp/"
    

    DB 文件中保存设备的配网信息,确保路径可读可写等权限,确保掉电后,数据不能丢失。如果数据丢失或者因为损坏,会导致配网过的设备重新进入配网状态。
    DB 文件与本地录像文件保存路径需要检查结尾是否有/

  • tuya_ipc_common_Demo.h文件中,修改 NVR 运行模式、子设备数量、PID、UUID、AUTHKEY 等信息

    • 设备处于 NVR 模式下,其绑定子设备为批量绑定。
    • 基站模式下,设备绑定子设备仅支持单独添加。
    #define DEMO_USE_AS_NVR 1    //1 表示此设备以 NVR 模式进行运行,非 1 表示以基站模式进行运行
    #define DEMO_NVR_SUB_DEV_NUM 4 // according to UUID, must smaller than SUB_DEV_MAX_NUM
    #if defined(DEMO_USE_AS_NVR) && (DEMO_USE_AS_NVR == 1)
    /*nvr dev config*/
    #define IPC_APP_PID "potalyu2wh72hmjq" //NVR 设备的 PID
    #define IPC_APP_UUID "zy008e00e83e6e2e70b1" //NVR 设备的 UUID
    #define IPC_APP_AUTHKEY "EDhPZXTlMj6eNaqJFDOzgSpk2E2gdAa3" //NVR 设备的 AUTHKEY
    
    #define IPC_APP_VERSION "1.0.0" //NVR 版本号
    #define IPC_APP_SUB_DEV_PID "hswzdzhuyplzhjzy"//子设备 PID
    #define IPC_APP_SUB_DEV_VERSION "1.0.0" //子设备版本号
    
    #else
    /*基站配置*/
    #define IPC_APP_PID "b4f2rzqixqfnxsja" //基站的 PID
    #define IPC_APP_UUID "tuya78d607d67b28f58a"//基站的 UUID
    #define IPC_APP_AUTHKEY "ByEKFL9wZTqwW1JO5RNAXuTMqiXSChTB"//基站的 AUTHKEY
    #define IPC_APP_VERSION "1.0.0" //基站的版本号
    #endif
    

至此,SDK 基本参数已完成填写,进入配网模式。

配网

NVR 产品支持 有线配网Wi-Fi 快连配网。Wi-Fi 快连配网开发过程请参考 IPC 部分

有线配网

  • 设备端调用:IPC_APP_Init_SDK 函数进行 SDK 初始化。

  • 以下接口需要您进行实现,具体实现可以参考 tuya_ipc_wired_Demo.c 中的实现。

    //设备查询 IP 地址  设备在检测到已连接路由器时,返回查询到的 IP 地址
    OPERATE_RET tuya_adapter_wired_get_ip(OUT NW_IP_S *ip)
    //查询设备 Mac 地址
    OPERATE_RET tuya_adapter_wired_get_mac(OUT NW_MAC_S *mac)
    //查询端口的连接状态
    BOOL_T tuya_adapter_wired_station_conn(VOID)
    
  • SDK 将查询到的设备 IP 地址与 Mac 地址通过路由器发送广播包,手机连上路由器 Wi-Fi 并接收到广播信息后,向云端查询配网 Token。Token 查询成功后,返回给设备端进行联网,至此,配网操作完成。

    所有 XVR SDK 对外配网的适配层接口,即所有以 tuya_apapter_开头的接口,不允许阻塞。阻塞会导致 XVR SDK 无法配网,出现各种异常配网现象

    XVR SDK 的业务逻辑 都是建立在 MQTT 上线的基础上进行的,如果没有进行 MQTT 上线处理,那么业务逻辑会出现异常逻辑。所以确保 MQTT 上线后,再处理启动业务逻辑。

绑定子设备

在调用 IPC_APP_Init_SDK 进行 SDK 初始化,并且 MQTT 上线(s_mqtt_status= 1)后可进行子设备绑定操作。

函数介绍

  • 子设备绑定函数:tuya_xvr_dev_binds

    typedef struct
    {
    	GW_PERMIT_DEV_TP_T tp;   //设备类型,子设备绑定请配置为 10 或者 0。是 0 的好处是可以和单品 IPC 的属性共用。
    	USER_DEV_DTL_DEF_T uddd; //子设备配网请配置为(0x2 << 24)
    	char product_id[64];     //PID
    	char uuid[64];           //UUID
    	char version[64];        //版本号
    	INT_T bind_dev_cnt;      //绑定数
    }Tuya_XVR_BIND_CTX_T;
    
    /**
    * \fn tuya_xvr_dev_binds
    * \brief 子设备绑定
    * \[in] bind_ctx_ptr:子设备参数
    * \return OPRT_OK is success,other is fail;
    */
    OPERATE_RET tuya_xvr_dev_binds(Tuya_XVR_BIND_CTX_T * bind_ctx_ptr)
    
  • 查询通道对应子设备设备信息函数:tuya_xvr_dev_info_get_by_chan

    typedef struct {
    	INT_T chn; //通道号
    	CHAR_T devId[64];   // 子设备 ID
    	CHAR_T virId[64];   //子设备虚拟 ID
    	INT_T vmChn;        //起始虚拟通道号,排序参照 RINGBUFFER_STREAM_E
    	BOOL_T alive;       //子设备是否在线,GW 模式使用
    	UINT_T mod;
    } Tuya_XVR_DEV_INFO_T;
    
    /**
    * \fn tuya_xvr_dev_info_get_by_chan
    * \brief 查询通道对应子设备设备信息函数
    * \[in]  chanNo:子设备对应的通道号
    * \[out] sub_dev_info:子设备信息
    * \return OPRT_OK is success,other is fail;
    */
    OPERATE_RET tuya_xvr_dev_info_get_by_chan(IN INT_T chanNo, OUT Tuya_XVR_DEV_INFO_T* sub_dev_info);
    
  • 子设备绑定回调函数:ty_gw_subdev_add_dev

    设备收到添加子设备命令时会执行此回调函数:

    • NVR 模式下,您无需实现此函数。
    • 基站模式下,您需要在此函数实现查询子设备信息操作,包括子设备的 UUID,PID,version 等信息(参考Tuya_XVR_BIND_CTX_T)的查询,调用tuya_xvr_dev_binds进行子设备绑定,以及调用tuya_xvr_dev_hearbeat_time_set设备子设备心跳时间。具体设计参考tuya_xvr_dev_Demo.c
    /**
    * \fn ty_gw_subdev_add_dev
    * \brief 基站绑定子设备回调函数
    * \[in] tp:设备类型,子设备绑定请配置为 0 或者大于等于 10 的值
    * \[in] permit:0 代表禁止添加子设备,1 代表允许添加
    * \[in] timeout:子设备添加超时时间
    * \return OPRT_OK is success,other is fail;
    */
    BOOL_T ty_gw_subdev_add_dev(IN CONST GW_PERMIT_DEV_TP_T tp,
    							IN CONST BOOL_T permit, IN CONST UINT_T timeout);
    
  • 子设备绑定参数设置函数:tuya_xvr_dev_bind_ctx_set

    /**
    * \fn tuya_xvr_dev_bind_ctx_set
    * \brief 子设备绑定参数设置函数
    * \[in] devId:设备 ID
    * \return OPERATE_RET
    * \NOTICE: 只在基站模式下有效,NVR 模式无需调用此函数
    */
    OPERATE_RET tuya_xvr_dev_bind_ctx_set(IN CHAR_T* devId);
    

NVR 模式示例

调用 IPC_APP_Init_SDK 进行设备初始化,在 MQTT 上线后,调用 tuya_xvr_dev_binds 进行子设备绑定。具体实现参照 Demo 的Tuya_APP_Add_XVR_Dev函数。

基站模式示例

  1. 参考 Demo,您自行实现 ty_gw_subdev_add_dev 函数。

  2. 调用 IPC_APP_Init_SDK 函数初始化 SDK。

  3. 设备在收到配网信息后,SDK 调用回调函数 ty_gw_subdev_add_dev 进行绑定。

  4. 绑定成功后,通过回调 ty_gw_subdev_inform_dev(您可参考 Demo,自行设计),进行子设备资源初始化。

设备心跳保活

心跳功能是云端识别设备是否在线的依据,设备端定时向云端发送保活数据,告知设备处于在线状态。若云端在一定时间内未收到保活数据,云端将判定设备离线。

函数介绍

  • 心跳初始化函数:tuya_xvr_dev_hb_init

    /***********************************************************
    *  Function: tuya_xvr_dev_hb_init
    *  Desc:     开启网关心跳检查机制。如果启用,网关每 3000ms 检查所有子设备,如果子设备在设定的超时时间内没有向网关发送心跳数据。网关将使子设备离线,通过 hb_send_cb 通知用户最多 3 次。大概每 8 到 10 秒回调一次心跳给应用层
    *  Input:    hb_send_cb: 心跳发送回调函数
    *  Return:   OPRT_OK: success  Other: fail
    ***********************************************************/
    OPERATE_RET tuya_xvr_dev_hb_init(IN CONST DEV_HEARTBEAT_SEND_CB hb_send_cb,IN CONST UINT_T min_query_interval);
    
  • 设置子设备心跳时间函数:tuya_xvr_dev_hearbeat_time_set

    /**
    * \fn tuya_xvr_dev_hearbeat_time_set
    * \brief 与云端交互的子设备心跳时间
    * \[in] dev_id:设备 ID,基站模式下既为子设备 UUID
    * \[in] timeout:心跳时间 单位为秒,因为与云端交互,故心跳时间设置建议时间:60~120。太小会增加网络开销,太大,可能出现心跳不及时情况
    * \return OPERATE_RET
    * \NOTICE: 在子设备绑定成功后调用
    */
    OPERAT
    
    
  • 查询通道号对应设备 ID 函数:tuya_xvr_dev_devId_get_by_chan

    /**
    * \fn tuya_xvr_dev_devId_get_by_chan
    * \brief 查询通道号对应设备 ID
    * \[in]  chn:子设备对应的通道号
    * \[in]  len:设备 ID 长度
    * \[out] devId:通道号对应的设备 ID
    * \return OPRT_OK is success,other is fail;
    */
    OPERATE_RET tuya_xvr_dev_devId_get_by_chan(IN CONST INT_T chn, OUT CHAR_T* devId, IN CONST INT_T len);
    

示例流程

  1. 调用 tuya_xvr_dev_hb_init 进行心跳初始化。

  2. 调用 tuya_xvr_dev_devId_get_by_chan 查询对应通道号下的设备 ID。

  3. 调用 tuya_xvr_dev_hearbeat_time_set 设置心跳时间。

音视频

实时预览

SDK 初始化时,将默认创建 10 秒音视频数据缓存,您将设备采集到的音视频数据输入到环形缓冲区 ringbuffer 即可。SDK 库文件已实现数据的发送,开发者不需要再做对应开发。

函数介绍

  • 确认 IPC_APP_Set_Media_Info 函数中视频参数信息:

    • GOP 推荐 FPS 参数的 2~3 倍,码率上限 2Mbps。
    • 分辨率推荐 16:9,支持 H264、H265 编码格式。
    //主码流信息配置 可根据需要修改参数
    /* 请注意如果设备主码流支持多种视频流配置,这里请把各项设置为允许配置的上限值 */
    s_media_info.channel_enable[E_IPC_STREAM_VIDEO_MAIN] =TRUE;/* Whether to enable local HD video streaming 是否开启本地高清视频流*/
    s_media_info.video_fps[E_IPC_STREAM_VIDEO_MAIN] = 30;  /* FPS */
    s_media_info.video_gop[E_IPC_STREAM_VIDEO_MAIN] = 30;  /* GOP */
    s_media_info.video_bitrate[E_IPC_STREAM_VIDEO_MAIN] = Tuya_VIDEO_BITRATE_1M; /* Rate limit 码率上限*/
    s_media_info.video_width[E_IPC_STREAM_VIDEO_MAIN] = 1920; /* Single frame resolution of width 单帧分辨率 宽*/
    s_media_info.video_height[E_IPC_STREAM_VIDEO_MAIN] = 1080;/* Single frame resolution of height 单帧分辨率 高 */
    s_media_info.video_freq[E_IPC_STREAM_VIDEO_MAIN] = 90000; /* Clock frequency 时钟频率*/
    s_media_info.video_codec[E_IPC_STREAM_VIDEO_MAIN] = Tuya_CODEC_VIDEO_H264; /* Encoding format 编码格式*/
    
    //子码流信息配置 可根据需要修改参数
    /* 请注意如果设备子码流支持多种视频流配置,这里请把各项设置为允许配置的上限值 */
    s_media_info.channel_enable[E_IPC_STREAM_VIDEO_SUB] = TRUE; /* Whether to enable local SD video stream 是否开启本地标清视频流*/
    s_media_info.video_fps[E_IPC_STREAM_VIDEO_SUB] = 30;  /* FPS */
    s_media_info.video_gop[E_IPC_STREAM_VIDEO_SUB] = 30;  /* GOP */
    s_media_info.video_bitrate[E_IPC_STREAM_VIDEO_SUB] = Tuya_VIDEO_BITRATE_512K; /* Rate limit 码率上限*/
    s_media_info.video_width[E_IPC_STREAM_VIDEO_SUB] = 640; /* Single frame resolution of width 单帧分辨率 宽*/
    s_media_info.video_height[E_IPC_STREAM_VIDEO_SUB] = 360;/* Single frame resolution of height 单帧分辨率 高 */
    s_media_info.video_freq[E_IPC_STREAM_VIDEO_SUB] = 90000; /* Clock frequency 时钟频率 */
    s_media_info.video_codec[E_IPC_STREAM_VIDEO_SUB] = Tuya_CODEC_VIDEO_H264; /* Encoding format 编码格式 */
    
  • 确认 IPC_APP_Set_Media_Info 函数中音频参数信息:

    • 音频支持 PCM、G711U、G711A 格式
    • 上传最大长度:1400 字节,支持 8K、16K 音频
    • App 下发音频长度固定为:320 字节
    /* 音频流配置
    Note: SDK 的内部 P2P 预览、云存储、本地存储都使用 E_CHANNEL_AUDIO 数据。*/
    s_media_info.channel_enable[E_IPC_STREAM_AUDIO_MAIN] = TRUE;  /* Whether to enable local sound collection 是否开启本地声音采集 */
    s_media_info.audio_codec[E_IPC_STREAM_AUDIO_MAIN] = Tuya_CODEC_AUDIO_PCM;/* Encoding format 编码格式 */
    s_media_info.audio_sample [E_IPC_STREAM_AUDIO_MAIN]= Tuya_AUDIO_SAMPLE_8K;/* Sampling Rate 采样率 */
    s_media_info.audio_databits [E_IPC_STREAM_AUDIO_MAIN]= Tuya_AUDIO_DATABITS_16;/* Bit width 位宽 */
    s_media_info.audio_channel[E_IPC_STREAM_AUDIO_MAIN]= Tuya_AUDIO_CHANNEL_MONO;/* channel 信道 */
    s_media_info.audio_fps[E_IPC_STREAM_AUDIO_MAIN] = 25;/* Fragments per second 每秒分片数 */
    
  • ringbuffer 初始化函数:tuya_ipc_ring_buffer_init

    /**
    * \fn Tuya_APP_Init_Ring_Buffer
    * \brief ringbuffer 初始化
    * \[in] device:设备编号(NVR 子设备对应通道号)
    * \[in] channel:摄像头编号(一个设备可以有多个摄像头,如只有一个摄像头请填写 0)
    * \[in] stream:码流类型
    * \[in] pparam:码流参数
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_ipc_ring_buffer_init(INT_T device, INT_T channel, IPC_STREAM_E stream, Ring_Buffer_Init_Param_S* pparam);
    

    XVR SDK 是根据子设备 ID,来生成通道号的。通常正常的情况下,APP 上显示的子设备顺序,在 XVR SDK 标记的通道号是对应的,即第一个设备对应 XVR SDK 维护的 0 号设备。但有时候这种顺序可能不是对应的,即 APP 上显示第一个设备,在 XVR 中可能不是 0,而是其他的值。故用户一定需要根据接口 tuya_xvr_dev_chan_get_by_devId 来获取通道号。

  • 音视频传输 Session 开启函数:tuya_ipc_ring_buffer_open

    所有关于 ringbuffer 的接口兼容 NVR 和 IPC,IPC 中 device,和 channel 参数一般为 0。

    /*
    * \fn tuya_ipc_ring_buffer_open
    * \brief 打开一个新的会话进行读/写
    * \[in] device :设备编号(子设备对应通道号)
    * \[in] channel:摄像头编号(一个设备可以有多个摄像头,如只有一个摄像头请填写 0)
    * \[in] stream:码流类型(主码流,子码流)
    * \return   open_type:session 的 ringbuffer 的类型
    */
    Ring_Buffer_User_Handle_S tuya_ipc_ring_buffer_open(INT_T device, INT_T channel, IPC_STREAM_E stream, RBUF_OPEN_TYPE_E open_type);
    
  • UTC 时间同步函数:IPC_APP_Sync_Utc_Time

    OPERATE_RET IPC_APP_Sync_Utc_Time(VOID);
    
  • p2p 使能函数:Tuya_APP_Enable_P2PTransfer

    /*
    * \fn Tuya_APP_Enable_P2PTransfer
    * \brief 使能 P2P 传输
    * \[in] max_users :最大 P2P 数
    * \return  OPERATE_RET
    */
    OPERATE_RET Tuya_APP_Enable_P2PTransfer(IN UINT_T max_users);
    

    这个接口是开启预览的函数。用户所有的预览的入口都是从这个函数开始的。当有预览的时候,XVR SDK 会回调相关的事件给开发者,开发者根据自身需要或者 XVR SDK,完成对应事件功能开发。

  • 音视频数据传输函数:Tuya_APP_Put_Frame

    /* 将音频和视频的原始数据发送到 SDK
    [in] handle:对应 tuya_ipc_ring_buffer_open 返回的 handle
    [in] p_frame:需要传输的音视频帧
    */
    OPERATE_RET Tuya_APP_Put_Frame(Ring_Buffer_User_Handle_S handle,IN CONST MEDIA_FRAME_S *p_frame);
    

    p_frame 中的 pts 如果没有其他值,用户需要写-1。否则 PUT 会失败

  • 您需在 __Tuya_APP_speaker_audio_callback 函数中自行实现喇叭的开关控制,函数如下所示:

    void __Tuya_APP_speaker_audio_callback(IN CONST INT_T chn, IN CHAR_T * devId,IN CONST TRANSFER_AUDIO_FRAME_S *p_audio_frame, IN CONST UINT_T frame_no)
    {
        /* Developers need to implement the operations of voice playback*/
        PR_DEBUG("dev[%s] audio data recv\n",devId);
        return ;
    }
    
  • App 默认下发 G711U 格式音频,设备端如需以 PCM 格式播放,需要调用函数 tuya_g711_decode 对下发的音频做转码处理,函数如下所示:

    函数参数:

    • 1:编码数据类型 Tuya_G711_MU_LAW
    • 2:被解析数据的地址
    • 3:被解析数据的长度
    • 4:数据解析后输出的地址
    • 5:数据解析后的长度
    int tuya_g711_decode(unsigned char type, unsigned short *src, unsigned int srcLen, unsigned char *drc, unsigned int *pOut);
    

示例开发流程

  1. IPC_APP_Set_Media_Info 中配置视频主码流、子码流和音频参数信息。

  2. 调用 Tuya_APP_Init_Ring_Buffer 对每个子设备的 ringbuffer 进行初始化。

  3. 调用 IPC_APP_Sync_Utc_Time 进行 UTC 时间同步。

  4. 调用 Tuya_APP_Enable_P2PTransfer 进行 P2P 传输使能。

  5. 开启音视频线程(两个线程)。

    pthread_t h264_output_thread;
    pthread_create(&h264_output_thread, NULL, thread_live_video, NULL);
    pthread_detach(h264_output_thread);
    
    pthread_t pcm_output_thread;
    pthread_create(&pcm_output_thread, NULL, thread_live_audio, NULL);
    pthread_detach(pcm_output_thread);
    
  6. 分别在音视频线程中调用 tuya_ipc_ring_buffer_open 函数开启对应子设备的 Session。

  7. 在线程函数中调用 Tuya_APP_Put_Frame 将采集到的视频帧传输至对应子设备的 ringbuffer 中。

  8. P2P 线程将会自动将 ringbuffer 中的数据传输给移动应用。

云存储

SDK 库文件已集成云存储功能,在开通云存储服务后,SDK 将 ringbuffer 中的音视频信息发送至云端保存,保存时间根据所购买的云存储服务而定。

SDK 默认对数据采用软件加密的方式,加密接口为 OpensslAES_CBC128_encrypt,使用的是 AES CBC 方式。若软件加密给 MCU 带来较大的负载,可改用硬件加密方式,使用 SoC 硬件加密通道,加密方式:PKCS5/PKCS7,如下表所示:

填充类型 说明
不填充 有些块加密模式不需要填充,或者明文都是块长度的整数倍
ISO10126 例:假设块长度为 64bit
数据:FF FF FF FF FF FF FF FF FF
填充后:FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07
PKCS5/PKCS7 例:假设块长度为 64bit
数据:FF FF FF FF FF FF FF FF FF
填充后:FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
PCKSS 要求块大小为 8 字节,后来扩充到 16 字节,PKCS7 块大小从 1~255 字节
OAEP/PKCS1 为 RSA 算法提供的

函数介绍

  • 云存储查询音视频信息回调函数:__cloud_stor_get_media

    VOID __cloud_stor_get_media(IN CONST INT_T chn,IN OUT IPC_MEDIA_INFO_S *param)
    {
    	//user insert media info for chn
    	//请保证输入参数与初始化 ringbuff 的函数一致
    	//云存储有 2 秒的预录,请确保 GOP 设置 ok
    	IPC_APP_Set_Media_Info();
    	memcpy(param, &s_media_info, sizeof(IPC_MEDIA_INFO_S));
    }
    
  • 云存储初始化函数:tuya_xvr_cloud_storage_init

    typedef struct {
    	Tuya_GET_DEV_MEDIA_CB media_cb; //音视频参数信息
    	AES_HW_CBC_FUNC *aes_func;//加密模式
                XVR_CLOUD_STORAGE_CLARITY_E stream_clarity;//设置云存是主码流录像还是附码流。默认主码流
                XVR_CLOUD_STORAGE_AUDIO_CHANNEL_E audio_channel;//设置是主音频通路还是子音频通路。默认主音频
    }Tuya_XVR_CLOUD_STORAGE_INIT_T;
    /**
    * \fn OPERATE_RET tuya_xvr_cloud_storage_init
    * \brief 初始化云存储,这将在此之后 malloc 所有需要的内存
    * \[in] xvr_cloud_stro_init_ptr 初始化参数
    * \return OPRT_OK:success other:fail
    */
    OPERATE_RET tuya_xvr_cloud_storage_init(Tuya_XVR_CLOUD_STORAGE_INIT_T * xvr_cloud_stro_init_ptr);
    
  • 云存储订单回调函数:tuya_xvr_cloud_order_cb_regist

    此函数将查询子设备云存的事件类型(参考XVR_ClOUD_STORAGE_EVENT_E),和子设备的相关信息(参考XVR_CLOUD_STORAGE_ORDER_T)作为入参传给回调函数 cb。

    /**
    * \fn OPERATE_RET tuya_xvr_cloud_order_cb_regist
    * \brief 查询 NVR 下所有子设备云存状态
    * \param[in] 回调函数
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_cloud_order_cb_regist(Tuya_XVR_Cloud_Stor_ORDER_Cb cb);
    
  • 子设备云存事件回调函数:_sub_cloud_stor_event_Cb

    INT_T _sub_cloud_stor_event_Cb(XVR_ClOUD_STORAGE_EVENT_E type, VOID_T * args)
    {
    	/*客户根据返回的订单类型,若有业务需求,自行实现*/
    }
    
  • 根据子设备 ID 开启对应子设备的云存储:tuya_xvr_cloud_storage_start

    /**
    * \fn OPERATE_RET tuya_xvr_cloud_storage_add
    * \brief start device cloud storage
    * \param[in]xvr devId:设备 ID
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_cloud_storage_start(CHAR_T* devId);
    
  • 根据子设备通道号开启对应子设备的云存储:tuya_xvr_cloud_storage_start_by_chn

    /**
    * \fn OPERATE_RET tuya_xvr_cloud_storage_start_by_chn
    * \brief 开启对应通道号云存储
    * \param[in] chn 通道号
    * \return OPERATE_RET
    * NOTICE:the same with tuya_xvr_cloud_storage_start
    */
    OPERATE_RET tuya_xvr_cloud_storage_start_by_chn(INT_T chn);
    
  • 触发一个云存储事件添加:tuya_xvr_cloud_storage_event_add

    /**
    * \fn OPERATE_RET tuya_xvr_cloud_storage_event_add
    * \brief 报告一个事件,云存储将启动并维护一个事件列表
    * \param[in] devid:设备 ID
    * \param[in] type 事件类型
    * \param[in] max_duration:此次事件云存储最大持续时间,单位为秒
    * \return EVENT_ID 事件 ID
    */
    EVENT_ID tuya_xvr_cloud_storage_event_add(CHAR_T* devId,ClOUD_STORAGE_EVENT_TYPE_E type, UINT_T max_duration);
    
  • 触发一个云储存事件结束:tuya_xvr_cloud_storage_event_delete

    /**
    * \fn OPERATE_RET tuya_xvr_cloud_storage_event_delete
    * \brief 停止一个事件云存储
    * \param[in] devid:设备 ID
    * \param[in] event_id:tuya_xvr_cloud_storage_event_add 接口返回的事件 ID
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_cloud_storage_event_delete(CHAR_T* devId, EVENT_ID event_id)
    
  • 云储存录像静音:tuya_xvr_cloud_storage_audio_stat_set

    /**
    * \fn OPERATE_RET tuya_xvr_cloud_storage_audio_stat_set
    * \brief 给当前设备指定设备云存录像静音。注意该接口只是强制屏蔽了声音,开发者仍然需要往 SDK 里面塞音频数据。当前暂不支持送音频的逻辑
    * \param[in] chan:设备通道索引
    * \param[in] isAudioOpen:是否静音云存视频,FASLE 静音,TRUE 有声音
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_cloud_storage_audio_stat_set(INT_T chan,IN CONST BOOL_T isAudioOpen)
    

开发流程

  • 事件云存储录像:

    1. 调用tuya_xvr_cloud_storage_init进行云存储功能初始化。
    2. 当子设备检测到需要上报的事件时,调用tuya_xvr_dev_devId_get_by_chan函数,查询此子设备的设备 ID。
    3. 调用tuya_xvr_cloud_storage_event_add开启一个事件云存储。
    4. 当事件结束时,调用tuya_xvr_cloud_storage_event_delete终止云存储事件录像## 本地录像回放功能开发
  • 连续云存储:

    1. 调用tuya_xvr_cloud_storage_init进行云存储功能初始化。

    2. 调用tuya_xvr_cloud_storage_start_by_chn或者tuya_xvr_cloud_storage_start(这两个接口传参不同)开始对应通道号下子设备的云存储。

    3. 开通连续云存,设备端自动进行连续录像,无需开发者干预

通常在 XVR 里,基本上没有客户会使用连续云存功能。这样是额外增加成本的。

本地录像回放功能开发

XVR SDK 本身不支持本地回放的功能的实现(即不提供设备如何录像,如何保存的功能)。XVR SDK 只提供协议,支持开发者完成 APP 来查询本地录像以及传送本地录像到 APP 播放的功能。开发者只需要在对应接口处按照提供的协议完成本地回看文件搜寻信息填入和音视频流的传入即可。

开发相关函数介绍

  • APP 事件处理回调函数__Tuya_APP_event_cb:

    说明:此回调函数在初始化 p2p 函数 Tuya_APP_Enable_P2PTransfer 中注册入 SDK,当设备端收到 app 端的操作指令时将会在此函数中进行处理。与回放相关指令如下:

  • 按月查询本地视频信息指令:TRANS_PLAYBACK_QUERY_MONTH_SIMPLIFY_GW

    说明:APP 上点击选择的月份时会下发此事件,根据设备端 tuya_ipc_pb_query_by_month 接口返回的数据,呈现对应月份是否有回放数据。

    /**
     UINT 一共有 32 位,每 1 位表示对应天数是否有数据,最右边一位表示第 0 天。
    比如 day = 26496 = B0110 0111 1000 0000
    那么表示第 7,8,9,10,13,14 天有回放数据。
    */
    // 按月查询有回放数据的天 request, response
    typedef struct tagC2CCmdQueryGWPlaybackInfoByMonth{
        unsigned int channel;//子设备对应通道号,用于区别子设备
        unsigned int idx;  //保留位                                  
        char subdid[64];   //设备 ID
        unsigned int year; // 要查询的年份
        unsigned int month;// 要查询的月份
        unsigned int day;  // 有回放数据的天
    }C2C_TRANS_QUERY_GW_PB_MONTH_REQ, C2C_TRANS_QUERY_GW_PB_MONTH_RESP;
    
    case TRANS_PLAYBACK_QUERY_MONTH_SIMPLIFY_GW:
        {   
            //根据实际查询结果返回日期
            C2C_TRANS_QUERY_GW_PB_MONTH_REQ *p = (C2C_TRANS_QUERY_GW_PB_MONTH_REQ *)args;
            PR_DEBUG("dev[%s] pb query by month: %d-%d\n",p->subdid, p->year, p->month);
            //用户实现参数的填充
    
            break;
        }
    
    • 按天查询本地视频信息指令:TRANS_PLAYBACK_QUERY_DAY_TS_GW

      说明:APP 上点击选择有回放数据的天时会下发此事件,根据设备端 tuya_ipc_pb_query_by_day 接口返回的数据,呈现对应当天是否有回放数据。

      typedef struct
      {
          UINT_T file_count; /*当天有多少个回放文件 */
          SS_FILE_TIME_TS_S file_arr[0]; /*回放文件数组 */
      } SS_QUERY_DAY_TS_ARR_S;
      
      case TRANS_PLAYBACK_QUERY_DAY_TS_GW:
          {
              //根据实际情况返回当天录像文件
              C2C_TRANS_QUERY_GW_PB_DAY_RESP *pquery = (C2C_TRANS_QUERY_GW_PB_DAY_RESP *)args;
              PR_DEBUG("pb_ts query by day: idx[%d]%d-%d-%d\n", pquery->channel,pquery->year, pquery->month, pquery->day);
              SS_QUERY_DAY_TS_ARR_S *p_day_ts = NULL;
              //用户查询数据,填充数据
      
      
    • 回放开始指令:TRANS_PLAYBACK_START_TS_GW

      说明:APP 上选择需要播放的回放事件时会下发此事件,根据结构体 C2C_TRANS_CTRL_GW_PB_START 中的 channel 号判断通道,idx 区分多路回看事件,直接回传回来即可,PLAYBACK_TIME_S 获取查询片段的开始和结束时间,传对应的音视频流信息

      typedef struct
      {
          UINT_T start_timestamp; /* 回放开始的时间戳 start timestamp in second of playback */
          UINT_T end_timestamp;   /* 回放结束的时间戳 end timestamp in second of playback */
      } PLAYBACK_TIME_S;
      
      typedef struct tagC2C_TRANS_CTRL_GW_PB_START{
          unsigned int channel; /* 子设备通道号 */
          unsigned int idx;    /* 内部会话通道,回传回来即可 */
          char subdid[64];    /* 子设备 ID */
          PLAYBACK_TIME_S time_sect;
          UINT_T playTime;  /* 实际回放开始时间戳(以秒为单位) */
      }C2C_TRANS_CTRL_GW_PB_START;
      
      
      case TRANS_PLAYBACK_START_TS_GW:
          {
              /* 开始回放时 client 会带上开始时间点,这里简单起见,只进行了日志打印 */
              C2C_TRANS_CTRL_GW_PB_START *pParam = (C2C_TRANS_CTRL_GW_PB_START *)args;
              PR_DEBUG("PB StartTS idx:%d %u [%u %u]\n", pParam->channel, pParam->playTime, pParam->time_sect.start_timestamp, pParam->time_sect.end_timestamp);
              SS_FILE_TIME_TS_S pb_file_info;
              int ret;
              memset(&pb_file_info, 0x00, sizeof(SS_FILE_TIME_TS_S));
              memcpy(&pb_file_info, &pParam->time_sect, sizeof(SS_FILE_TIME_TS_S));
      
    • 回放视频数据传输接口:tuya_ipc_playback_send_video_frame_with_channel

      typedef struct
      {
          Tuya_CODEC_ID video_codec; //编码格式
          TRANSFER_VIDEO_FRAME_TYPE_E video_frame_type; //帧类型
          BYTE_T *p_video_buf;  //回放帧数据
          UINT_T buf_len;       //回放帧数据长度
          UINT64_T pts;         //时间戳,单位微妙,webrtc,三方拉流使用
          UINT64_T timestamp;   //时间戳,单位毫秒,p2p 拉流和云存功能使用
          VOID *p_reserved;     
      }TRANSFER_VIDEO_FRAME_S;
      /**
      * \fn OPERATE_RET tuya_ipc_playback_send_video_frame_with_channel
      * \brief 回放视频数据传输接口
      * \param[in] chn:子设备通道号
      * \param[in] client:内部会话通道
      * \param[in] p_video_frame:视频帧
      */
      OPERATE_RET tuya_ipc_playback_send_video_frame_with_channel(IN CONST UINT_T chn,IN CONST UINT_T client, IN CONST TRANSFER_VIDEO_FRAME_S *p_video_frame);
      
    • 回放音频数据传输接口:tuya_ipc_playback_send_audio_frame_with_channel

      /**
      * \fn OPERATE_RET tuya_ipc_playback_send_audio_frame_with_channel
      * \brief 回放视频数据传输接口
      * \param[in] chn:子设备通道号
      * \param[in] client:内部会话通道
      * \param[in] p_audio_frame:音频帧
      */
      OPERATE_RET tuya_ipc_playback_send_audio_frame_with_channel(IN CONST UINT_T chn,IN CONST UINT_T client, IN CONST TRANSFER_AUDIO_FRAME_S *p_audio_frame);
      
    • 回放结束接口:tuya_ipc_playback_send_finish_with_channel

      说明:当回放时间结束后,调用此接口通知回放已结束

      /**
      * \fn OPERATE_RET tuya_ipc_playback_send_finish_with_channel
      * \brief 回放结束通知接口
      * \param[in] chn:子设备通道号
      * \param[in] client:内部会话通道
      */
      OPERATE_RET tuya_ipc_playback_send_finish_with_channel(IN CONST UINT_T chn,IN CONST UINT_T client)
      
    • 回放停止指令:TRANS_PLAYBACK_PAUSE_GW

      说明:APP 在回放过程中点击暂停会下发此事件

      typedef struct tagC2C_TRANS_CTRL_GW_PB_STOP{
          unsigned int channel;/* 子设备通道号 */
          unsigned int idx;    /* 暂不使用,默认为 0 */
          char subdid[64];     /* 子设备 ID */
      }C2C_TRANS_CTRL_GW_PB_STOP;
      
      case TRANS_PLAYBACK_PAUSE_GW:
          {
              C2C_TRANS_CTRL_GW_PB_PAUSE *pParam = (C2C_TRANS_CTRL_GW_PB_PAUSE *)args;
              PR_DEBUG("PB Pause idx:%d\n", pParam->channel);
              //用户停止送流
              break;
          }
      
  • 回放继续播放指令:TRANS_PLAYBACK_RESUME_GW

case TRANS_PLAYBACK_RESUME_GW:
    {
        C2C_TRANS_CTRL_GW_PB_RESUME *pParam = (C2C_TRANS_CTRL_GW_PB_RESUME *)args;
        PR_DEBUG("PB Resume idx:%d\n", pParam->channel);
  	//用户恢复送流
        break;
    }
  • 回放静音指令:TRANS_PLAYBACK_MUTE_GW
 case TRANS_PLAYBACK_MUTE_GW:
    {
        C2C_TRANS_CTRL_GW_PB_MUTE *pParam = (C2C_TRANS_CTRL_GW_PB_MUTE *)args;
        PR_DEBUG("PB idx:%d mute\n", pParam->channel);
  	//用户停止送音频流
        break;
    }
  • 回放取消静音指令:TRANS_PLAYBACK_UNMUTE_GW

    case TRANS_PLAYBACK_UNMUTE_GW:
        {
            C2C_TRANS_CTRL_GW_PB_UNMUTE *pParam = (C2C_TRANS_CTRL_GW_PB_UNMUTE *)args;
            PR_DEBUG("devId[%s]PB idx:%d unmute\n",pParam->subdid, pParam->channel);
            //用户送音频流.
            break;
        }
    

示例开发流程

1:设备端收到 TRANS_PLAYBACK_QUERY_MONTH_SIMPLIFY_GW 按月查询回放数据信令,返回有回放数据的天数。

2:设备收到 TRANS_PLAYBACK_QUERY_DAY_TS_GW 按天查询当天回放数据信令,返回回放文件信息。

3:设备收到 TRANS_PLAYBACK_START_TS_GW 回放播放开始信令,读取回放文件数据并调用音视频发送接口发送音视频数据。

4:设备收到 TRANS_PLAYBACK_PAUSE_GW 回放暂停信令,暂停数据流的发送。

5:设备收到 TRANS_PLAYBACK_RESUME_GW 回放继续播放信令,重启数据流的发送。

6:设备收到 TRANS_PLAYBACK_MUTE_GW 回放静音信令,暂停音频的发送。

7:设备收到 TRANS_PLAYBACK_UNMUTE_GW 回放取消静音信令,重启音频的发送。

新增回放事件协议

目前的回放查询,APP 仅下发年月日信息,设备回复当日的录像片段信息。APP 根据此信息绘制进度条。
目前的方案设计无法很好的兼容以下两个需求,故设计新的检索方案来实现。
1:仅进度条上颜色差异区分有事件的录像和无事件的录像。
2:可以通过筛选选项,来筛选对应的事件类型,并仅在进度条上显示本类型事件(筛选条件可能复选)。

  • 新增本地存储能力值来区分新协议,用户使用以下代码来增加能力:

        tuya_ipc_skill_param_u skill_param = {.value = 50397699};
        tuya_ipc_skill_enable(Tuya_IPC_SKILL_LOCALSTG,&skill_param);
        //保证 value 第 26bit 位为二进制 1.
    
  • 新增本地事件查询:

    说明:APP 端下发事件查询,用于查询指定日期的事件,SDK 通过 P2P 回调函数,回调协议 TRANS_PLAYBACK_QUERY_DAY_TS_PAGE_MODE 给客户。

    客户收到的内容数据结构如下:

    typedef struct{
        char subid[64];//对应的设备 ID
        unsigned int channel;//对应的通道
        unsigned int year;//查询年
        unsigned int month;//查询月
        unsigned int day;//查询日
        int page_id;//对应的当前查询的 page 页索引。由 APP 端下发。从 0 开始编号。当前回复报文是第几页
        int total_cnt;//当前天的事件总条数。用户填写。
        int page_size;//每一个 page 里含有的事件条数。用户自行决定大小。
        C2C_PB_EVENT_INFO_ARR_S * event_arr;//事件列表。详细查看 C2C_PB_EVENT_INFO_ARR_S
    }C2C_TRANS_QUERY_EVENT_PB_DAY_RESP;
    
    typedef struct {
        int version;//版本号。当前填写 1
        int event_cnt;/*事件列表的个数。注意和 page_size 的区别。取个说明:如果当前查询到 10 个事件,用户 page_size 定义成 3,那么 APP 会下发 page id = 0,1,2,3 下来。对于 page_id=0,1,2 这三个页,
    	那么 event_cnt 都是等于 3。当下发 page_id=3 时,这个时候 page_size 应该填写 1,对应 event_cnt 也应该是 1
    	*/
        C2C_PB_EVENT_INFO_S event_info_arr[0];//事件的详细信息。详细查看 C2C_PB_EVENT_INFO_S
    }C2C_PB_EVENT_INFO_ARR_S;
    
    typedef struct {
    	unsigned int start_timestamp; /* 事件的开始事件 */
    	unsigned int end_timestamp; /* 事件的接收事件 */
    	int type;/*事件类型*/
    	char pic_id[20];/*图片信息。预留*/
    }C2C_PB_EVENT_INFO_S;
    

回放下载功能开发

在实现了回放功能的基础上,SDK 支持 APP 端下载回放文件的功能。

开发相关函数介绍

  • 回放下载信令接收回调:__Tuya_APP_event_cb

    说明:APP 上点击“下载”时,此回调会收到事件 TRANS_DOWNLOAD_START_GW,收到数据结构体包含 channel(下载事件编号,用于多路下载区分)、下载片段的起止时间 。如设备检索录像片段异常,调用 tuya_xvr_local_video_download_end_report(IN CHAR_T* devId,IN CONST UINT_T client, IN CONST UINT_T percent)接口,client 传 channel 号,percent 上报 100(不支持失败的事件通知,先上报完成结束下载)百分比传-1 是失败。

  • 录像音视频数据上传接口:tuya_xvr_local_video_download

    typedef struct
    {
        UINT_T    type; //帧类型,0 表示 PB 帧,1 表示 I 帧
        UINT_T    size; //帧大小
        UINT64_T  timestamp;//时间戳,单位毫秒,p2p 拉流和云存功能使用
        UINT64_T  pts;//时间戳,单位微妙,webrtc,三方拉流使用
    }STORAGE_FRAME_HEAD_S;
    /*
    * \fn OPERATE_RET tuya_xvr_local_video_download
    * \param[in] devId:子设备通道号
    * \param[in] client:内部会话通道,即 channel 号
    * \param[in] pHead:帧头,具体见 STORAGE_FRAME_HEAD_S
    * \param[in] pData:音视频帧数据
    */
    OPERATE_RET tuya_xvr_local_video_download(IN CHAR_T* devId,IN CONST UINT_T client, IN CONST STORAGE_FRAME_HEAD_S * pHead, IN CONST CHAR_T * pData);
    

    有些客户的回放视频是无声的,导致没有音频数据的上报。当前 APP 下载依赖音频的数据,否则 APP 无卡住。当前 APP 还在修复此问题。后期修复后,可不用塞音频也可以。日期待定

  • 录像下载结束上报接口:tuya_xvr_local_video_download_end_report

    /*
    * \fn OPERATE_RET tuya_xvr_local_video_download
    * \param[in] devId:子设备通道号
    * \param[in] client:内部会话通道,即 channel 号
    * \param[in] percent:下载百分比,-1 表示下载失败,100 表示下载结束
    */
    OPERATE_RET tuya_xvr_local_video_download_end_report(IN CHAR_T* devId,IN CONST UINT_T client, IN CONST UINT_T percent);
    

示例开发流程

1:在 App 上轻按 下载,此时设备 Tuya_APP_event_cb 收到 P2P 事件 TRANS_DOWNLOAD_START,收到数据结构体包含 channel(下载事件编号,用于多路下载区分)、下载片段的起止时间 。如设备检索录像片段异常,调用 tuya_xvr_local_video_download_end_report(不支持失败的事件通知,先上报完成结束下载)百分比传 -1 是失败的。

2:检索录像片段成功后,调用 tuya_xvr_local_video_download 接口上报录像文件,一帧一调用。pHead 结构体:type 参考 MEDIA_FRAME_TYPE_E type;size 为传输数据大小,最大不超 MAX_MEDIA_FRAME_SIZE;Timestamp 精确到 ms,不精确会导致下载视频卡顿(App 根据 Timestamp 判断)。

3:数据传输完成后,调用 tuya_xvr_local_video_download_end_report 上报 percent 100 通知 App 下载完成。

DP 功能点

DP 主要用以控制设备的各项功能,一个 DP 通常就是代表一个功能点。开发者需要根据云端下发的 DP(根据 DP 的值区分),完成对应功能的处理(例如,下发视频翻转 DP,用户收到这个 DP 后,则翻转对应设备视频画面,然后上报翻转成功的消息给云端即可)。在 tuya_ipc_dp_utils.h 文件中对通用 DP 进行了具体说明,开发者可以根据已有 Demo 代码来进行开发与设置。若有定制化 DP 加入,则仿照已有的相同数据类型的 DP 代码进行添加与设置即可。

DP 跟设备的产品 ID 相关联。每一种产品配置的 DP 不尽相同。开发者根据自身需要,向对应的 Tuya 接口人来配置所需要的 DP。如果 DP 中途有新增或者变更,设备需要重新配网,否则无法识别新的 DP 点。

DP 参数类型现有的为 5 种:

  • 布尔型(BOOL)
  • 值型(VALUE)
  • 字符串型(STR)
  • 枚举型(ENUM)
  • BITMAP 型(作为预留暂不使用)

分别适应不同 DP 参数的设置,具体参考结构体 TY_OBJ_DP_VALUE_U

NVR 产品主设备和子设备 DP 可能相同,如 104DP 主设备上代表存储状态,子设备上代表 OSD。开发者根据 dp 回调函数中 cid 来确定是主设备 DP,还是子设备 DP.具体参考 tuya_ipc_dp_utils.h

主设备需要区分收到的 DP 消息为主设备的还是子设备的,并且分别调用主设备处理函数 tuya_xvr_main_dev_dp_process 和子设备处理函数 tuya_xvr_sub_dev_dp_process 进行相关处理。开发者需要根据回调函数中 CID 字段来判定是主设备的 DP 还是子设备的 DP。当 cid==NULL,则表示是主设备的 DP,否则就是对应的子设备的设备 ID.

子设备的 ID,通常指的 node ID,是通过主设备 uuid+a+数字排序生成的一个(例如 uuida001,uuida002),生成的唯一 node ID。

函数介绍

您可根据实际需求决定是否需要对 DP 数据保存与读取。

  • 需要在 __tuya_app_write_INT__tuya_app_write_STR 中实现 DP 数据保存的具体操作,函数如下所示:

    /* 设置保存 DP 本地配置数据。
    这里的参考代码直接写到 tmp 中,如需要您可自行替换路径。
    注意:替换的路径请与 DP 数据的读取路径保持一致*/
    STATIC VOID __tuya_app_write_INT(CHAR_T *key, INT_T value)
    {
     //TODO
      CHAR_T tmp_cmd[128] = {0};
      snprintf(tmp_cmd, 128, "mkdir -p /tmp/tuya.cfgs/;echo %d > /tmp/tuya.cfgs/%s", value, key);
      printf("write int exc: %s \r\n", tmp_cmd);
      system(tmp_cmd);
    }
    
    STATIC VOID __tuya_app_write_STR(CHAR_T *key, CHAR_T *value)
    {
     //TODO
     CHAR_T tmp_cmd[256] = {0};
     snprintf(tmp_cmd, 256, "echo %s > /tmp/tuya.cfgs/%s", value, key);
     printf("write STR exc: %s \r\n", tmp_cmd);
     system(tmp_cmd);
    }
    
  • __tuya_app_read_INT__tuya_app_read_STR 中实现 DP 数据读取的具体操作,具体函数请参考 Demo 程序,部分函数如下所示:

    /* 读取 DP 本地配置数据,如需要开发人员可自行替换路径。
    注意:替换的路径请与 DP 数据的保存路径保持一致*/
    STATIC INT_T __tuya_app_read_INT(CHAR_T *key)
    {
     //TODO
     CHAR_T tmp_file[64] = {0};
     snprintf(tmp_file, 64, "cat /tmp/tuya.cfgs/%s", key);
     printf("read int exc: %s \r\n", tmp_file);
     FILE *p_file = popen(tmp_file, "r");
    
    STATIC INT_T __tuya_app_read_STR(CHAR_T *key, CHAR_T *value, INT_T value_size)
    {
     //TODOmemset(value, 0, value_size);
     CHAR_T tmp_file[64] = {0};
     snprintf(tmp_file, 64, "cat /tmp/tuya.cfgs/%s", key);
     printf("read str exc: %s \r\n", tmp_file);
     FILE *p_file = popen(tmp_file, "r");
    }
    
  • 查询对应设备 ID 的通道号函数:tuya_xvr_dev_chan_get_by_devId

    /**
    * \fn tuya_xvr_dev_info_get_by_chan
    * \brief 根据子设备设备 ID 查询对应通道号
    * \[in]  devId:子设备设备 ID
    * \[out] pChn:对应听通道号
    * \return OPRT_OK is success,other is fail;
    */
    OPERATE_RET tuya_xvr_dev_chan_get_by_devId(IN CONST CHAR_T* devId, OUT INT_T* pChn);
    
  • NVR 主设备 DP 处理函数:tuya_xvr_main_dev_dp_process

    /**
    * \fn ty_gw_deal_gateway_dp_cmd
    * \brief NVR 主设备 DP 处理
    * \[in]  dp:接收到的云端消息
    */
    void tuya_xvr_main_dev_dp_process(CONST TY_RECV_OBJ_DP_S *dp)
    
  • 子设备 DP 处理函数:tuya_xvr_sub_dev_dp_process

    您可以在此函数中实现对不同子设备的 DP 处理操作,实现 NVR 与子设备的交互。

    void tuya_xvr_sub_dev_dp_process(CONST TY_RECV_OBJ_DP_S *dp)
    {
    	printf("SOC Rev DP Obj Cmd t1: %d t2: %d CNT: %u\n",
    				dp->cmd_tp, dp->dtt_tp, dp->dps_cnt);
    
    	UINT_T index = 0;
    
    	for(index = 0; index < dp->dps_cnt; index++) {
    		CONST TY_OBJ_DP_S *p_dp_obj = dp->dps + index;
    		printf("idx: %d dPID: %d type: %d ts: %u cid = %s\n",
    
  • DP 处理回调函数:IPC_APP_handle_dp_cmd_objs

    typedef struct {
    	DP_CMD_TYPE_E cmd_tp;//信令类型
    	DP_TRANS_TYPE_T dtt_tp;//发送类型,参考 DP_TRANS_TYPE_
    	CHAR_T *cid;//此 DP 信令对象的设备 ID,cid=NULL 时代表对象为 NVR 主设备信号
    	BYTE_T dPID;//DPID
    	CHAR_T *mb_id;
    	UINT_T len;//数据长度
    	BYTE_T data[0];//数据
    }TY_RECV_RAW_DP_S;
    
    VOID IPC_APP_handle_dp_cmd_objs(IN CONST TY_RECV_OBJ_DP_S *dp)
    {
    	if(dp == NULL) {
    		printf("soc not have cid\n");
    		return ;
    	}
    	if (NULL == dp->cid){
    		tuya_xvr_main_dev_dp_process(dp);
    	} else {
    		INT_T chn = 0;
    		tuya_xvr_dev_chan_get_by_devId(dp->cid,&chn);
    		//客户根据 chn 来感知是哪个通道的 dp 信令,下面的处理接口可做下修改适配
    		tuya_xvr_sub_dev_dp_process(dp);
    	}
    	return;
    }
    

OSD 水印

OSD 水印功能可以根据云端下发的 DP 值(即 104),给视频加上或取消时间水印,水印位置与内容由设备端控制。

示例流程

  1. tuya_ipc_dp_utils.h中加入:

    #define Tuya_DP_WATERMARK         104
    
  2. tuya_ipc_dp_utils.cs_dp_table[]中加入:

    #ifdef Tuya_DP_WATERMARK
    	{Tuya_DP_WATERMARK,             handle_DP_WATERMARK},
    #endif
    

    handle_DP_WATERMARK 为此功能 DP 数据处理函数,您可自行开发,其 Demo 函数为:

    #ifdef Tuya_DP_WATERMARK
    STATIC VOID handle_DP_WATERMARK(IN TY_OBJ_DP_S *p_obj_dp)
    {
    BOOL_T watermark_on_off = check_dp_bool_invalid(p_obj_dp);
    IPC_APP_set_watermark_onoff(watermark_on_off);
    watermark_on_off = IPC_APP_get_watermark_onoff();
    respone_dp_bool(Tuya_DP_WATERMARK, watermark_on_off);
    }
    #endif
    

    若需要子设备处理此 DP,请在tuya_xvr_sub_dev_dp_process函数中自行实现。

  3. 在主函数中调用IPC_APP_handle_dp_cmd_objs函数对接收到云端下发的 DP 数据进行处理,判断是否触发此 DP 对应的功能函数(IPC_APP_Init_SDK函数中已注册此回调)。

移动侦测

SDK 提供了控制移动侦测功能开关控制、灵敏度控制、定时控制、间歇控制以及数据上报的函数,您可以调用 SDK API 完成功能移动侦测以及移动追踪功能的开发。

函数介绍

除消息上报外,其他相关函数参考 IPC 部分

  • 根据设备 ID 消息上报函数:tuya_xvr_notify_with_event

    /**
    * \fn OPERATE_RET tuya_xvr_notify_with_event
    * \将可编辑的警报发送到 tuya cloud 和 APP
    * \param[in] dev_id: 设备 ID
    * \param[in] snap_buffer: 当前快照的地址
    * \param[in] snap_size: 快照的大小,以字节为单位
    * \param[in] type: 上传数据的类型,NOTIFICATION_CONTENT_TYPE_E
    * \param[in] name: 上报的事件类型, NOTIFICATION_NAME_E
    * \param[in] isNotifyMsgCenter: 是否上报消息中心标志位,0 为不上报,1 为上报
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_notify_with_event(IN CONST CHAR_T * dev_id, IN CONST CHAR_T *snap_buffer, IN CONST UINT_T snap_size, IN CONST NOTIFICATION_CONTENT_TYPE_E type, IN CONST NOTIFICATION_NAME_E name,BOOL_T isNotifyMsgCenter);
    
  • 根据信道号消息上报函数:tuya_xvr_notify_with_event_by_channel

    /**
    * \fn OPERATE_RET tuya_xvr_notify_with_event_by_channel
    * \将可编辑的警报发送到 tuya cloud 和 APP
    * \param[in] chn: 子设备对应通道号
    * \param[in] snap_buffer: 当前快照的地址
    * \param[in] snap_size: 快照的大小,以字节为单位
    * \param[in] type: 上传数据的类型,NOTIFICATION_CONTENT_TYPE_E
    * \param[in] name: 上报的事件类型, NOTIFICATION_NAME_E
    * \param[in] isNotifyMsgCenter: 是否上报消息中心标志位,0 为不上报,1 为上报
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_notify_with_event_by_channel(IN CONST INT_T chn, IN CONST CHAR_T *snap_buffer, IN CONST UINT_T snap_size, IN CONST NOTIFICATION_CONTENT_TYPE_E type, IN CONST NOTIFICATION_NAME_E name,BOOL_T isNotifyMsgCenter);
    

示例开发流程

具体操作可参考 Demo 代码中 thread_md_proc 函数。

  1. 开启移动侦测功能(定义 DP)。

  2. 开启移动侦测线程如下所示:

    pthread_create(&motion_detect_thread, NULL, thread_md_proc, NULL);
    
  3. 在移动侦测线程函数中,调用移动判断函数 get_motion_status 判断对象是否发生移动:

    您可根据不同的芯片平台在此函数自行实现对移动判断的功能,可调用上述 SDK 提供的 Tuya_Ipc_Motion 等相关函数进行开发。

    int fake_md_status = 0;
    int get_motion_status()
    {
    	//if motion detected ,return 1
    	//else return 0 返回值存储在 fake_md_status 参数中
    	return fake_md_status;
    }
    
  4. 如果发生移动,调用 tuya_xvr_dev_devId_get_by_chan 查询对应通道号下子设备的设备 ID。

  5. 根据设备 ID,调用tuya_xvr_cloud_storage_event_add函数开启云存储功能,之后调用get_motion_snapshot函数抓取当前图像,相关函数如下:

    //您可根据不同的芯片平台自行开发抓图函数
    void get_motion_snapshot(char *snap_addr, int *snap_size)
    {
    	//we use file to simulate
    	char snapfile[128];
    	*snap_size = 0;
    	extern char s_raw_path[];
    	printf("get one motion snapshot\n");
    	snprintf(snapfile,64,"%s/resource/media/Demo_snapshot.jpg",s_raw_path);
    	FILE*fp = fopen(snapfile,"r+");
    	//...
    }
    
  6. 根据设备 ID,调用 tuya_xvr_notify_with_event 函数发送报警信号至云端。

  7. 若检测对象不发生移动,且距离上次移动侦测事件时间超过 10 秒则调用tuya_xvr_cloud_storage_event_delete停止本地存储和云存储。

图像缩放

图像缩放相关说明请参考 IPC 部分

PTZ 摇头机功能

摇头机开始转动 DP 为 119,停止转动 DP 为 116。

#define Tuya_DP_PTZ_CONTROL                119
/* PTZ 旋转控制,枚举类型,0 向上,1 右上,2 右,3 右下,4 向下,5 左下,6 左,7 左上
但在 4.0 SDK 中为 0-右上,1-右,2-下右,3-下,4-左下,5-左,6-左上,7-上
*/
#define Tuya_DP_PTZ_STOP                   116         /* PTZ 旋转停止,BOOL 型*/

设备接收到云端 DP 的控制指令后,控制 PTZ 摇头机转动。119 DP 下发的常见格式为:

{
	"range":["1","2","3","4","5","6","7","0"],
	"type":"enum"
}

PTZ 摇头机通用控制

相关说明请参考 IPC 部分

PTZ 摇头机预设位控制

  • 预设位控制功能 DP:178

    #define Tuya_DP_PRESET_SET                   178  /* 收藏点操作,string 型,type:1 添加,type:2 删除,不同类型的数据串不同 */
    
  • 当前最大支持 6 个预设点

一般设备端接收到收藏点相关 cJSON 消息如下:

开发者通过解析此消息,在设备端调用相关接口自行进行相关开发,详情可参考tuya_ipc_dp_handler.cIPC_APP_set_preset函数。

/*
*cid :设备 ID 用于判断此 178DP 消息为哪个子设备
*type:1 表示添加收藏点;2 表示删除收藏点;3 表示设备转到收藏点位置
*mpId:收藏点编号
*t:UTC 时间
*/
{"data":{"cid":"zy006136663551327feca001","ctype":0,"dps":{"178":"{\"type\":3,\"data\":{\"mpId\":\"1\"}}"}},"protocol":5,"t":1627612487}

函数介绍

  • 预设点添加收藏点前,需要上报 179 DP,eum:2 非巡航模式(不在巡航运动模式下)

  • 增加预设点函数:tuya_xvr_preset_add

    /**
    * \fn OPERATE_RET tuya_xvr_preset_add(S_PRESET_POSITION* preset_pos)
    * \brief 添加一个预设点
    * \param[in] devid:设备 ID
    * \param[in] preset_pos:预设点位置
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_preset_add(char *devid, S_PRESET_POSITION* preset_pos);
    
  • S_PRESET_POSITION 结构体:

    typedef struct
    {
       CHAR_T id[32]; 		//服务器的 ID
       CHAR_T name[32]; 	//收藏点名称
       INT_T mpId; 			//索引 ID
       S_PRESET_PTZ ptz; 	//ptz 的预设位置
    } S_PRESET_POSITION;
    
    typedef struct
    {
     	INT_T pan;  	//横向
     	INT_T tilt;  	//竖直
     	INT_T zoom;  	//定焦设备该值为 0
    }S_PRESET_PTZ;
    
    • id[32]:表示云端分配的 ID 值,可以不做处理。
    • name[32]:增加收藏点时,name[32] 用于指定收藏点名字,可以做对应存储。
    • mpId:设备增加对应收藏点后,需要填入收藏点的序号,序号从 1 开始递增。
    • ptz:表示填入设备收藏点的具体 P(pan)与 T(tilt)坐标,若是定焦设备,Z(zoom)坐标值为固定为 0
  • 预设点添加图片函数:tuya_xvr_preset_pic_add

    /*
    \fn OPERATE_RET tuya_xvr_preset_pic_add(CHAR_T *addr, UINT_T size)
    \brief 上传当前预设位置的快照
    \param[in] devid: 设备 ID
    \param[in] addr/size: 要上传图片的地址和大小
    \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_preset_pic_add(char* devid,CHAR_T *addr, UINT_T size);
    
  • 删除预设点函数:tuya_xvr_preset_delete

    /**
    * \fn OPERATE_RET tuya_xvr_preset_delete
    * \brief 删除一个预设点
    * \param[in] preset_id:预设点 ID
    * \param[in] nodeId: devid(设备 ID)
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_preset_delete(char* nodeId,char * preset_id);
    

示例开发流程

  1. 开启 PTZ 预设点功能(在tuya_ipc_dp_utils.h定义相关 DP),如:

    #define Tuya_DP_PRESET_SET                 178
    /*收藏点操作,string 型,type:1 添加,type:2 删除,不同类型的数据串不同*/
    
  2. tuya_ipc_dp_utils.cs_xvr_dp_table[]中加入:

    #ifdef Tuya_DP_PRESET_SET
    	{Tuya_DP_PRESET_SET,              handle_DP_SET_PRESET},
    #endif
    
  3. handle_DP_SET_PRESET 函数使用过程,通过 IPC_APP_set_preset 函数中,判断对预设点的操作类型,根据判断的操作类型分别调用以下函数实现预设点的添加、删除、调用功能:

    • 预设点添加函数:tuya_ipc_preset_add

      调用预设点添加函数tuya_ipc_preset_add之前需先查询 PTZ 当前位置信息。

    • 预设点图像上传函数:tuya_ipc_preset_add_pic
    • 预设点删除函数:tuya_ipc_preset_del
    • 预设点调用函数:tuya_ipc_preset_go(此功能您可自行实现)
    /*1:增加预置点 2:删除预置点 3:预置点调用*/
    if(type->valueint == 1)
    {
    	tuya_xvr_preset_pic_add(char* devid,CHAR_T *addr, UINT_T size);
    }
    else if(type->valueint == 2)
    {
    	tuya_xvr_preset_delete(char* nodeId,char * preset_id);
    }
    else if(type->valueint == 3)
    {
    	tuya_ipc_preset_go(data);
    }
    

    若需要子设备处理此 DP,请在ty_gw_deal_subdev_dp_cmd函数中自行实现。

  4. 在主函数中调用IPC_APP_handle_dp_cmd_objs函数对接收到云端下发的 DP 数据进行处理,判断是否触发此 DP 对应的功能函数(Tuya_IPC_SDK_START函数中已调用此函数)。

门铃功能

本章节主要介绍门铃产品特性功能的开发,可以实现当门铃被按下时向移动应用推送提醒、进行视频通话、留言等功能。

函数介绍

  • 查询当前图像帧数据函数:get_motion_snapshot

    //此示例函数中用预设的图片作为抓取的图像,用户需要根据不同的芯片平台自行实现对当前图像的抓取
    //According to different chip platforms, users need to implement the interface of capture.
    void get_motion_snapshot(char *snap_addr, int *snap_size)
    {
    	//we use file to simulate
    	char snapfile[256];
    	*snap_size = 0;
    	extern char s_raw_path[];
    	printf("get one motion snapshot\n");
    	snprintf(snapfile,256,"%s/rawfiles/tuya_logo.jpg",s_raw_path);
    	FILE*fp = fopen(snapfile,"r+"
    
  • 抓取到图像后,调用tuya_xvr_notify_door_bell_press将图像信息推送给涂鸦云和 APP,函数如下:

    /**
    * \fn OPERATE_RET tuya_ipc_door_bell_press
    * \brief 发送对应通道号下门铃消息给涂鸦云和 APP
    * \param[in] chn: 通道号
    * \param[in] snap_buffer: snapshot 抓取的图像地址
    * \param[in] snap_size: 抓取图像的大小
    * \param[in] type: 抓取图像文件类型 jpeg 或者 png
    * \param[in] name: 上报事件类型,门铃选择 NOTIFICATION_NAME_DOORBELL
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_notify_door_bell_press(IN CONST INT_T chn, IN CONST CHAR_T *snap_buffer, IN CONST UINT_T snap_size, IN CONST NOTIFICATION_CONTENT_TYPE_E type, IN CONST NOTIFICATION_NAME_E name)
    

示例开发流程

  1. 调用IPC_APP_Init_SDK开始 SDK。

  2. 如果门铃被按下调用get_motion_snapshot函数抓取当前图像。

  3. 调用tuya_xvr_notify_door_bell_press上报对应通道号子设备的门铃事件。

OTA 功能

固件 OTA 升级作为设备升级与维护的最重要途径之一,将在本章节进行具体介绍。当有固件需要升级时,SDK 将通过回调的形式告知设备,进行 OTA 操作。

函数介绍

  • 主设备 OTA 请求回调函数:IPC_APP_Upgrade_Inform_cb()

    • 调用tuya_iot_upgrade_gw函数前可进行进行内存资源优化,如关闭本地存储,ringbuffer 反初始化等操作。
    • tuya_iot_upgrade_gw中第一个回调函数__IPC_APP_get_file_data_cb是用于查询文件分片数据(下载)。
    typedef struct {
    	DEV_TYPE_T tp;//设备类型
    	UPGRADE_TYPE_T type;//升级类型
    	CHAR_T fw_url[FW_URL_LEN+1];//固件 url
    	CHAR_T sw_ver[SW_VER_LEN+1];//固件版本
    	UINT_T file_size;//固件大小
    	CHAR_T fw_hmac[FW_HMAC_LEN+1];//固件 hmac
    	BOOL_T diff_ota;//是否是不同的 OTA(暂不使用)
    }FW_UG_S;
    
    VOID IPC_APP_Upgrade_Inform_cb(IN CONST FW_UG_S *fw)
    	{
    	PR_DEBUG("Rev Upgrade Info");
    	PR_DEBUG("fw->fw_url:%s", fw->fw_url);
    	PR_DEBUG("fw->sw_ver:%s", fw->sw_ver);
    	PR_DEBUG("fw->file_size:%u", fw->file_size);
    	//此处进行内存释放
    	OPERATE_RET op_ret = tuya_iot_upgrade_gw(fw, __IPC_APP_get_file_data_cb, __IPC_APP_upgrade_notify_cb, NULL);
    	if(OPRT_OK != op_ret) {
    		PR_ERR("tuya_iot_upgrade_gw err: %d\n", op_ret);
    	}
    	return ;
    }
    
  • OTA 升级函数:tuya_iot_upgrade_gw

    /* @param[in] fw: 固件信息
    * @param[in] get_file_cb: 下载固件回调
    * @param[in] upgrd_nofity_cb: 固件下载完成回调
    * @param[in] pri_data: get_file_cb && upgrd_nofity_cb 的私有参数 (若不使用设为 NULL)
    */
    tuya_iot_upgrade_gw(fw, get_file_cb, upgrd_nofity_cb, pri_data)
    
  • OTA 升级结果提醒回调函数:__IPC_APP_upgrade_notify_cb

    • 此回调中,您可根据download_result来确定 OTA 相关操作:
      • download_result 为 0 时:表示固件下载成功,您需要自行进行固件擦写操作。
      • 其他情况:为下载固件失败情况,反初始化的操作不可逆,若 OTA 失败,设备必须进行重启。
    • OTA 升级前可根据需要,备份 DB 文件。若不备份,升级成功后相关配置将恢复默认值。
    • OTA 升级成功后,对比 DB 文件替换固件前后的 MD5 值,若相同,则可以直接重启。若不相同,则将备份的 DB 文件替换现有的 DB 文件再重启设备。
    VOID __IPC_APP_upgrade_notify_cb(IN CONST FW_UG_S *fw, IN CONST INT_T download_result, IN PVOID_T pri_data)
    {
    	PR_DEBUG("Upgrade Finish");
    	PR_DEBUG("download_result:%d fw_url:%s", download_result, fw->fw_url);
    
    	if(download_result == 0)
    	{
    		// 已成功下载 OTA 文件到指定路径(p_mgr_info->upgrade_file_path),开发者需要实施 OTA 升级的操作
    	}
    	//TODO
    	//重启系统
    }
    
  • 子设备 OTA 请求回调函数:ty_gw_dev_ug_inform

    您需要根据pdevId判断为哪个子设备的 OTA 升级操作。回调函数信息包括 devIDl(用于区分子设备)、固件下载的 url(下载链接)、 md5(用来做文件校验)以及 size(文件的大小)等。 子设备升级与主设备原理一致,OTA 升级可参考主设备。

    //pdevId:子设备设备 ID
    VOID ty_gw_dev_ug_inform(IN CONST CHAR_T *pdevId, IN CONST FW_UG_S* pfw)
    
  • 子设备升级完成版本上报:tuya_xvr_dev_version_update

    //devId:子设备设备 ID,version 升级后的版本号
    OPERATE_RET  tuya_xvr_dev_version_update(char * devId,char *version)
    

OTA 自定义进度上报

  • 设备端通过 URL 开始下载固件,调用tuya_iot_dev_upgd_progress_rept函数,上报 OTA 下载的进度,函数如下所示:

    /**
    * @brief tuya_iot_dev_upgd_progress_rept
    * @param[in] percent :上报进度
    * @param[in] devid :设备 ID
    * @param[in] tp :设备类型,NVR 中请设置 10
    * @return OPERATE_RET
    */
    OPERATE_RET tuya_iot_dev_upgd_progress_rept(IN CONST UINT_T percent, IN CONST CHAR_T *devid, IN CONST DEV_TYPE_T  tp);
    

    上报的进度值建议小于 99%,此接口调用间隔推荐大于 2 秒,频繁调用此接口可能导致上报阻塞。

  • 固件升级时,手机移动应用进度显示会暂停在 98%,此时,固件已完成下载,App 正在等待设备重启并上报新固件版本号。版本号上报成功后,App 将显示 升级成功

  • App 等待设备替换固件与上报新版本号的时间为 1 分钟,若设备流程超过此时间,请联系您的涂鸦客户经理或者提交工单,提供设备 PID 进行后台配置或排查。

设备解绑与复位

本章节主要简述设备通过 App 解绑与按复位键,这两种复位方式,开发中的流程与区别。

NVR 模式下不支持子设备单独解绑。

设备解绑函数

  • 主设备解绑回调函数:IPC_APP_Reset_System_CB

    App 上删除设备后会触发此回调。您需要在函数内,实现设备的重启与对 DP 数据文件进行重置操作,不需要删除 DB 文件。

    VOID IPC_APP_Reset_System_CB(GW_RESET_TYPE_E type)
    {
      printf("reset ipc success. please restart the ipc %d\n", type);
      IPC_APP_Notify_LED_Sound_Status_CB(IPC_RESET_SUCCESS);
      //TODO
      /* Developers need to restart IPC operations */
    }
    
  • 子设备解绑回调函数:ty_gw_subdev_del_dev

    App 上移除子设备时会触发此回调。您需要在此回调中根据 pdevId,进行对应子设备云存储等功能的停止,根据需要删除或者保存 DB 文件,而后调用tuya_iot_gw_unbind_dev进行子设备的解绑。

    VOID ty_gw_subdev_del_dev(IN CONST CHAR_T *pdevId)
    {
    	//tuya_iot_gw_unbind_dev(pdevId);
    }
    

设备复位开发

  • 设备检测到复位键被按下,存在复位操作,需要删除三个 DB 文件:tuya_user.db_baktuya_user.dbtuya_enckey.db
  • 设备对 DP 数据文件进行重置操作并重启。

NVR 子设备面板自适应

此功能在 App 上暂时不支持。

子设备面板自适应 DP 暂定为 231,NVR 设备主要通过此 DP 上报对应子设备所支持的 DP 功能,实现不同子设备面板的个性化呈现。

函数介绍

  • 子设备 DP 上报接口:tuya_xvr_sub_dp_report

    /**
    * \fn OPERATE_RET tuya_xvr_sub_dp_report(IN CONST INT_T chn, IN BYTE_T dp_id, IN DP_PROP_TP_E type, IN VOID * pVal, IN CONST UINT_T cnt)
    * \brief 子设备 DP 上报接口
    * \chn   对应子设备的通道号
    * \dp_id 上报的 DP
    * \type  上报类型,详情看参照 DP_PROP_TP_E
    * \pVal  上报的数据
    * \cnt   上报次数
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_sub_dp_report(IN CONST INT_T chn, IN BYTE_T dp_id, IN DP_PROP_TP_E type, IN VOID * pVal, IN CONST UINT_T cnt);
    
  • 查询对应通道号的设备 ID 接口:tuya_xvr_dev_devId_get_by_chan

    /***********************************************************
    *  Function: tuya_xvr_dev_devId_get_by_chan
    *  Desc:     查询对应通道号的 devId
    *  Input:    chn:通道号
    *  Output:   devId:设备 ID
    *  Input:    len:长度,比 devId 长即可
    *  Return:   OPRT_OK: success  Other: fail
    *  Warning:  deprecated !
    ***********************************************************/
    //OPERATE_RET tuya_xvr_dev_devId_get_by_chan(IN CONST INT_T chn, OUT CHAR_T* devId, IN CONST INT_T len);
    
    OPERATE_RET tuya_xvr_dev_devId_get_by_chan(IN CONST INT_T chn, OUT CHAR_T* devId, IN CONST INT_T len);
    
  • DP 上报接口:tuya_ipc_dp_report

    /**
    * \fn OOPERATE_RET tuya_ipc_dp_report(IN CONST CHAR_T *dev_id, IN BYTE_T dp_id, IN DP_PROP_TP_E type, IN VOID * pVal, IN CONST UINT_T cnt)
    * \brief  子设备 DP 上报接口
    * \dev_id 对应子设备 ID
    * \dp_id  上报的 DP
    * \type   上报类型,详情看参照 DP_PROP_TP_E
    * \pVal   上报的数据
    * \cnt    上报次数
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_ipc_dp_report(IN CONST CHAR_T *dev_id, IN BYTE_T dp_id, IN DP_PROP_TP_E type, IN VOID * pVal, IN CONST UINT_T cnt);
    
  • 查询子设备支持 DP 集字符串接口:tuya_xvr_get_subdev_skill

    此接口需要开发者自行实现。您需要通过chn查询对应子设备需要上报的 DP 能力集,其返回值为字符串类型,如:“[0,524288,554164608,33554464,1373056,0,0,0]”,此字符串由 8 个 32 位的无符号整型组成,其转化成二进制是 8*32 = 256 位,从右往左数,序号从 1 开始,每一位的序号代表 DP ID,每一位上 1 代表此 DP ID 支持,0 代表不支持。8 个无符号整数最多可以标识 DP ID 1~256 的支持情况。

    对应子设备支持的 DP 可以联系您的涂鸦客户经理。

    /**
    * \fn OPERATE_RET tuya_xvr_get_subdev_skill(IN CONST INT_T chn,OUT CHAR_T *dp_report)
    * \brief    查询子设备支持能力接口
    * \chn      对应子设备 ID
    * \dp_list  对应子设备支持的 DP 集字符串
    * \return OPERATE_RET
    */
    OPERATE_RET tuya_xvr_get_subdev_skill(IN CONST INT_T chn,OUT CHAR_T *dp_list)
    {
    	switch(chn)
    	{
    		case 0 :
    		snprintf(dp_report,128, "[0,524288,554164608,33554464,1373056,0,0,0]");
    		break;
    }
    

示例开发流程

  • 示例一

    1. 调用tuya_xvr_get_subdev_skill查询需要上报的数据。

    2. 调用子设备 DP 上报接口 tuya_xvr_sub_dp_report 上报数据。

      示例代码:

      VOID handle_sub_dp_skill_report()
      {
      	CHAR_T dp_report[128] = {0};
      	int chn=0;
      	for (chn = 0; chn < DEMO_NVR_SUB_DEV_NUM; chn++){
      		memset(dp_report,0, sizeof(dp_report));
      		if (OPRT_OK == tuya_xvr_get_subdev_skill(chn,dp_report)){
      			tuya_xvr_sub_dp_report(chn,231,PROP_STR,dp_report,1);
      		}
      	}
      }
      
  • 示例二:若无上例接口可参考如下流程

    1. 调用 tuya_xvr_get_subdev_skill 查询需要上报的数据。

    2. 调用 tuya_xvr_dev_devId_get_by_chan 查询对应通道号的 devid。

    3. 调用 tuya_ipc_dp_report 上报数据。

      示例代码:

      VOID handle_sub_dp_skill_report()
      {
      
      	CHAR_T dp_list[256] = {0};
      	int chn=0;
      	CHAR_T devId[64]={0};
      	for (chn = 0; chn < DEMO_NVR_SUB_DEV_NUM; chn++){
      		memset(devId,0, sizeof(devId));
      		memset(dp_list,0, sizeof(dp_list));
      		if (OPRT_OK == tuya_xvr_get_subdev_skill(chn,dp_list) && OPRT_OK == tuya_xvr_dev_devId_get_by_chan(chn, devId,64)){
      			tuya_xvr_sub_dp_report(devId,231,PROP_STR,dp_list,1);
      		}
      	}
      }
      

动态更换设备处理

越来越多 XVR 客户,需要动态更换一个设备。XVR SDK 提供了这样的功能来完成这个场景。

这个功能需要 APP 配网。当前 APP 在更换设备后,云存是无法播放的。当前 APP 也在更新这个功能。APP 具体上线日期待定

动态更新设备接口:tuya_xvr_media_update

	/**
	* \fn OPERATE_RET tuya_xvr_media_update(INT_T device,XVR_MEDIA_UPDATE_CB cb)
	* \brief  动态更新指定设备接口
	* \device 对应子设备通道
	* \cb  :更新设备媒体信息的回调函数。用户需要在这个回调函数完成对应设备的 ring buffer 去初始化,然后重新初始化对应的 ringbuffer 等处理。详细见 Demo 的过程
	* \return OPERATE_RET
	*/

其他功能补充

  • 预览码流与声道对应接口:tuya_xvr_p2p_video_audio_chan_sync
    此接口可以让用户在预览时,设置当预览是主码流是,从主音频通道获取音频,当时子码流时,从子音频通道获取音频。

  • 删除本地录像后,上报接口:tuya_xvr_local_video_delete_end_report
    此接口用于,当 APP 点击删除对应的本地录像后,设备上报结果给 APP。

  • 强制让子设备下线接口:tuya_iot_dev_online_update
    正常的情况下,主设备激活成功后,绑定的子设备也是显示在线的。无论是否有真实的子设备。如果开发者想在没有真实子设备的时候,在 APP 端显示对应的子设备,可以调用此接口强制刷成离线的状态

        /**
        * \fn OPERATE_RET tuya_iot_dev_online_update(IN CONST CHAR_T *dev_id,IN CONST BOOL_T online, IN CONST BOOL_T is_force);
        * \brief  强制刷子设备上下线。
        * \dev_id 对应子设备
        * \online :TRUE,在线,FALSE,离线
        *\is_force:TRUE,强制更新状态。FALSE,SDK 根据上次状态更新状态,不会强制和云端交互。
        * \return OPERATE_RET
        */
        ```