Zigbee 网关开发指南

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

本文档分为两部分,第一部分介绍如何集成涂鸦 Zigbee 业务,实现兼容涂鸦生态所有 Zigbee 子设备。第二部分是在第一部分基础上的扩展,介绍如何实现第三方 Zigbee 子设备接入。

背景信息

综合 SDK 支持搭配涂鸦网关 Zigbee 模组使用,使用涂鸦网关 Zigbee 模组可以直接接入涂鸦生态所有 Zigbee 子设备。同时,SDK 也提供了接口,允许开发者自行把第三方 Zigbee 子设备接入到涂鸦生态,可以开发既支持接入涂鸦生态 Zigbee 子设备,又支持接入第三方 Zigbee 子设备的网关产品,提高产品的差异性。

Zigbee 基础

本小节将会介绍如何集成涂鸦 Zigbee 业务,让网关能直接接入涂鸦生态所有 Zigbee 子设备。

集成涂鸦 Zigbee 业务涉及到硬件和软件两方面,硬件上主控与涂鸦 Zigbee 模组通过串口连接,软件上调用 Zigbee 的初始化和启动接口。

SDK 将通过串口与 Zigbee 模组进行通信,连接和设备管理等操作全部由 SDK 实现,应用无需处理任何逻辑。

Zigbee 初始化接口的入参主要是配置信息,为 JSON 数据,字段说明如下:

字段 描述
storage_path 存储路径,要求有读写权限,用于存储 Zigbee 相关数据,如 Zigbee 协调器的 Channel, PANID, Network Key 等,以及子设备的 endpoint, devid, cluster 等信息
cache_path OTA 升级,存储固件的路径,要求有读写权限
dev_name 串口设备名称
cts 是否支持硬件流控
thread_mode 使用线程还是进程方式运行 Zigbee HOST 应用

我们在 快速入门指南 的示例代码基础上,添加 Zigbee 功能实现:

// ...
#include "tuya_zigbee_api.h"
// ...

int main(int argc, char **argv)
{
    // ... 

    ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
    if (zb_cfg == NULL) {
        return OPRT_CJSON_GET_ERR;
    }
    ty_cJSON_AddStringToObject(zb_cfg, "storage_path", "./");
    ty_cJSON_AddStringToObject(zb_cfg, "cache_path", "/tmp/");
    ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
    ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
    ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);

    /* initiate application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_init(&gw_cbs));

    // ...

    /* start application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_start(NULL));

    // ...

    return 0;
}

编译运行后,看到类似如下打印,则表示 Zigbee 已成功运行。理论上,使用涂鸦生态任意的 Zigbee 子设备,都能成功入网。

// ...
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26760] nodeEuiStr = 086bd7fffed0cb51.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26761] nodeId = 0x0000.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26762] panId = 0xc0a7.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26763] channel = 0x0f.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26764] ver = 1.0.9.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26765] netStatus = 0x03.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26766] manufacturerID = 0x0001.
[01-01 00:00:08:778 TUYA D][tuya_z3.c:26767] netWorkKey: 
dd 47 74 57 a8 10 05 20 bd 99 f0 aa 08 ea 27 57
// ...

Zigbee 扩展

本小节将会介绍如何接入第三方 Zigbee 子设备,开发出既支持接入涂鸦生态 Zigbee 子设备,又支持接入第三方 Zigbee 子设备的网关产品,实现产品差异化。

Zigbee 扩展第三方子设备是使用白名单的机制来实现的,开发者把第三方子设备的厂商号和型号添加到白名单列表,注册到 SDK。设备入网时 SDK 根据白名单进行匹配,厂商号和型号匹配上则触发设备管理回调,开发者在回调中实现相关业务逻辑。

接入第三方 Zigbee 子设备要求开发者具备 Zigbee 相关基础知识。此外,还要了解 SDK 接入子设备的开发流程,详见 接入子设备指南

开始之前,先了解一下相关术语:

术语 解释
SDK 子设备管理 指下行回调,数据流方向:云端 -> SDK -> 应用。与具体子设备协议无关,在回调中实现具体协议逻辑
Zigbee 子设备管理 指上行回调,数据流方向:Zigbee -> SDK -> 云端。第三方 Zigbee 子设备接入特有的,在回调中实现应用逻辑

入网

第三方 Zigbee 子设备入网的主要逻辑:

  1. 注册白名单以及 Zigbee 子设备管理 回调。
  2. 注册 SDK 子设备管理 回调。
  3. 进入 App 的网关面板,单击 添加子设备,SDK 接收添加指令后允许 Zigbee 入网。
  4. Zigbee 子设备按复位键进入配网,子设备入网时 SDK 检查该设备是否在白名单列表,在白名单列表则触发 Zigbee 子设备管理 的入网回调,应用在回调中解析设备信息并把子设备绑定到云端。
  5. Zigbee 子设备成功绑定到云端,触发 SDK 子设备管理 的绑定结果回调,应用在回调中读取子设备的状态。
  6. Zigbee 子设备上报数据,触发 Zigbee 子设备管理 的数据上报回调,应用在回调中把 ZCL 数据转成功能点,上报到云端。

第三方 Zigbee 子设备入网的交互流程图:

Zigbee 网关开发指南

相关代码实现:

// ...
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...

#define TY_TS_ONOF_LIGHT_ONE                    "TS0001"
#define TY_TS_ONOF_LIGHT_TWO                    "TS0002"
#define TY_TS_ONOF_LIGHT_THR                    "TS0003"

#define PROFILE_ID_HA                           0x0104
#define PROFILE_ID_LL                           0xC05E

#define ZCL_BASIC_CLUSTER_ID                    0x0000
#define ZCL_POWER_CONFIG_CLUSTER_ID             0x0001
#define ZCL_DEVICE_TEMP_CLUSTER_ID              0x0002
#define ZCL_IDENTIFY_CLUSTER_ID                 0x0003
#define ZCL_GROUPS_CLUSTER_ID                   0x0004
#define ZCL_SCENES_CLUSTER_ID                   0x0005
#define ZCL_ON_OFF_CLUSTER_ID                   0x0006

#define ZCL_CMD_TYPE_GLOBAL                     0x00
#define ZCL_CMD_TYPE_PRIVATE                    0x01

#define ZCL_FRAME_TYPE_UNICAST                  0x00
#define ZCL_FRAME_TYPE_MULTICAST                0x01
#define ZCL_FRAME_TYPE_BROADCAST                0x02

#define ZCL_OFF_COMMAND_ID                      0x00
#define ZCL_ON_COMMAND_ID                       0x01

#define ZCL_READ_ATTRIBUTES_COMMAND_ID          0x00
#define ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID 0x01
#define ZCL_REPORT_ATTRIBUTES_COMMAND_ID        0x0A
#define ZCL_READ_ATTRIBUTER_RESPONSE_HEADER     3 /* Attributer ID: 2 Bytes, Status: 1 Btye */
#define ZCL_REPORT_ATTRIBUTES_HEADER            2 /* Attributer ID: 2 Bytes */

#define DEVID_HA_IAS_ZONE                       0x0402    // 0x0402 -> 0x00,IAS类,pir, 门磁
#define DEVID_HA_TEMPERATURE_SENSOR             0x0302    // 0x0302 -> 0x01,温度类(含温湿度)
#define DEVID_HA_ONOF_LIGHT                     0x0100    // 0x0100 -> 0x02,照明开关类
#define DEVID_HA_SMART_PLUG                     0x0051    // 0x0051 -> 0x03, 智能插座类

/* 举例,自定义 DEVID,保存到 UDDD */
#define TY_DEVID_HA_IAS_ZONE                    0x00      // 0x0402 -> 0x00,IAS类,pir, 门磁
#define TY_DEVID_HA_TEMPERATURE_SENSOR          0x01      // 0x0302 -> 0x01,温度类(含温湿度)
#define TY_DEVID_HA_ONOF_LIGHT                  0x02      // 0x0100 -> 0x02,照明开关类
#define TY_DEVID_HA_SMART_PLUG                  0x03      // 0x0051 -> 0x03, 智能类插座类

/* 举例,自定义 TYPE,保存到 UDDD */
#define TY_TYPE_PIR                             0x00      // DEVID_HA_IAS_ZONE
#define TY_TYPE_DOOR_SENSOR                     0x01      // DEVID_HA_IAS_ZONE
#define TY_TYPE_FIRE_SENSOR                     0x02      // DEVID_HA_IAS_ZONE
#define TY_TYPE_WATER_SENSOR                    0x03      // DEVID_HA_IAS_ZONE

#define TY_TYPE_ONOF_LIGHT_ONE                  0x00      // DEVID_HA_ONOF_LIGHT
#define TY_TYPE_ONOF_LIGHT_TWO                  0x01      // DEVID_HA_ONOF_LIGHT
#define TY_TYPE_ONOF_LIGHT_THREE                0x02      // DEVID_HA_ONOF_LIGHT

TY_Z3_DEV_S my_z3_dev[] = {
    { "TUYATEC-nPGIPl5D", "TS0001" },
    { "TUYATEC-1xAC7DHQ", "TS0002" },
};

TY_Z3_DEVLIST_S my_z3_devlist = {
    .devs = my_z3_dev,
    .dev_num = CNTSOF(my_z3_dev),
};

STATIC VOID __z3_onoff_light_read_attr(CONST CHAR_T *dev_id)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_Z3_APS_FRAME_S frame = {0};
    USHORT_T atrr_buf[5] = { 0x0000, 0x4001, 0x4002, 0x8001, 0x5000 };

    strncpy(frame.id, dev_id, SIZEOF(frame.id));
    frame.profile_id = PROFILE_ID_HA;
    frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
    frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
    frame.src_endpoint = 0x01;
    frame.dst_endpoint = 0xff;
    frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;

    frame.msg_length = SIZEOF(atrr_buf);
    frame.message = (UCHAR_T *)atrr_buf;

    op_ret = tuya_zigbee_send_data(&frame);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
        return;
    }
}

STATIC VOID __z3_onoff_light_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_OBJ_DP_S dp_data = {0};

    if (frame->cluster_id != ZCL_ON_OFF_CLUSTER_ID) {
        return;
    }

    dp_data.dpid = frame->src_endpoint;
    dp_data.type = PROP_BOOL;

    if (frame->cmd_id == ZCL_REPORT_ATTRIBUTES_COMMAND_ID) {
        dp_data.value.dp_bool = frame->message[ZCL_REPORT_ATTRIBUTES_HEADER+1];
    } else if (frame->cmd_id == ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID) {
        dp_data.value.dp_bool = frame->message[ZCL_READ_ATTRIBUTER_RESPONSE_HEADER+1];
    } else {
        return;
    }

    op_ret = dev_report_dp_json_async(frame->id, &dp_data, 1);
    if (op_ret != OPRT_OK) {
        PR_ERR("dev_report_dp_json_async err: %d", op_ret);
        return;
    }
}

STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    if (frame->profile_id != PROFILE_ID_HA) {
        return;
    }

    dev_if = tuya_iot_get_dev_if(frame->id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", frame->id);
        tuya_zigbee_del_dev(frame->id);
        return;
    }

    op_ret = tuya_iot_fresh_dev_hb(frame->id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_report(frame);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC VOID __my_z3_dev_join(TY_Z3_DESC_S *dev)
{
    OPERATE_RET op_ret = OPRT_OK;
    CHAR_T pid[PRODUCT_KEY_LEN+1]={0};
    CHAR_T sw_ver[SW_VER_LEN+1] = {0};
    USER_DEV_DTL_DEF_T uddd = 0;

    /**
     * 以下代码仅作为演示,开发者一般需要实现的逻辑:
     *   a) 解析设备信息,取关键信息,保存到 uddd,用于标识设备类型等;
     *   b) 关联 PID,调用绑定接口把设备绑定到云端。
     */

    if (dev->profile_id[0] != PROFILE_ID_HA) {
        return;
    }

    CHAR_T iver1 = (CHAR_T)(dev->version & 0x0F); 
    CHAR_T iver2 = (CHAR_T)((dev->version >> 4) & 0x03);
    CHAR_T iver3 = (CHAR_T)((dev->version >> 6) & 0x03);
    snprintf(sw_ver, SIZEOF(sw_ver), "%d.%d.%d", iver3, iver2, iver1);

    switch (dev->device_id[0]) {
        case DEVID_HA_ONOF_LIGHT:
            uddd |= (TY_DEVID_HA_ONOF_LIGHT << 8);
            if (0 == strncasecmp(dev->model_id, TY_TS_ONOF_LIGHT_ONE, strlen(TY_TS_ONOF_LIGHT_ONE))) {
                uddd |= (TY_TYPE_ONOF_LIGHT_ONE & 0xFF);
                strncpy(pid, "ckj1pnvy", SIZEOF(pid));
            } else if (0 == strncasecmp(dev->model_id, TY_TS_ONOF_LIGHT_TWO, strlen(TY_TS_ONOF_LIGHT_TWO))) {
                uddd |= (TY_TYPE_ONOF_LIGHT_TWO & 0xFF);
                strncpy(pid, "tr4dshuh", SIZEOF(pid));
            } else {
                PR_WARN("model_id: %s is not supported", dev->model_id);
                return;
            }
            break;
        case DEVID_HA_IAS_ZONE:
        case DEVID_HA_TEMPERATURE_SENSOR:
        case DEVID_HA_SMART_PLUG:
        default:
            PR_WARN("devid: %d is not supported", dev->device_id[0]);
            return;
    }

    op_ret = tuya_iot_gw_bind_dev(GP_DEV_ATH_1, uddd, dev->id, pid, sw_ver);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_iot_gw_bind_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_bind_cb(CONST CHAR_T *dev_id, CONST OPERATE_RET result)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    /**
     * 以下代码仅作为演示,开发者一般需要实现的逻辑:
     *   a) 根据绑定状态,封装读取属性 ZCL 数据发给子设备。
     *   b) 配置心跳超时。
     */

    if (result != OPRT_OK) {
        PR_ERR("dev_id: %s bind err", dev_id);
        tuya_zigbee_del_dev(dev_id);
        return;
    }

    op_ret = tuya_iot_set_dev_hb_cfg(dev_id, 120, 3, FALSE);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
    }

    op_ret = tuya_iot_fresh_dev_hb(dev_id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    dev_if = tuya_iot_get_dev_if(dev_id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", dev_id);
        tuya_zigbee_del_dev(dev_id);
        return;
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_read_attr(dev_id);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .report      = __my_z3_dev_report,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_bind_ifm  = __dev_bind_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));

    return OPRT_OK;
}

int main(int argc, char **argv)
{
    // ... 

    ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
    if (zb_cfg == NULL) {
        return OPRT_CJSON_GET_ERR;
    }
    ty_cJSON_AddStringToObject(zb_cfg, "storage_path", "./");
    ty_cJSON_AddStringToObject(zb_cfg, "cache_path", "/tmp/");
    ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
    ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
    ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);

    /* initiate application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_init(&gw_cbs));
    TUYA_CALL_ERR_RETURN(__user_init());

    // ...

    /* start application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_start(NULL));

    // ...

    return 0;
}

设备控制

第三方 Zigbee 子设备控制的主要逻辑:

  1. 注册功能点管理回调,在指令接收回调中实现把功能点转成 ZCL 数据,下发给子设备。
  2. 注册 Zigbee 子设备管理 回调,在数据上报回调中实现刷新心跳,并把 ZCL 数据转成功能点,上报到云端。

说明:设备控制是以子设备数据上报作为响应状态,所以子设备收到控制指令完成控制动作之后,需要上报当前状态,应用把它转成功能点上报到云端。如果第三方 Zigbee 子设备实现控制后主动上报的逻辑,则需要应用在下发控制指令后,再下发查询指令。

设备控制的交互流程示意图:

Zigbee 网关开发指南

相关代码实现:

// ...

STATIC VOID __z3_onoff_light_write_attr(CONST TY_RECV_OBJ_DP_S *cmd)
{
    INT_T i= 0;
    OPERATE_RET op_ret = OPRT_OK;
    TY_Z3_APS_FRAME_S frame = {0};

    strncpy(frame.id, cmd->cid, SIZEOF(frame.id));
    frame.profile_id = PROFILE_ID_HA;
    frame.src_endpoint = 0x01;

    for (i = 0; i < cmd->dps_cnt; i++) {
        switch (cmd->dps[i].dpid) {
        case 1 ... 3:
            frame.dst_endpoint = cmd->dps[i].dpid;
            frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
            frame.cmd_type = ZCL_CMD_TYPE_PRIVATE;
            frame.frame_type = ZCL_FRAME_TYPE_UNICAST;
            frame.msg_length = 0;
            if (cmd->dps[i].value.dp_bool) {
                frame.cmd_id = ZCL_ON_COMMAND_ID;
            } else {
                frame.cmd_id = ZCL_OFF_COMMAND_ID;
            }
            break;
        default:
            PR_WARN("dev_id: %s, dpid: %d is not supported", cmd->cid, cmd->dps[i].dpid);
            return;
        }

        op_ret = tuya_zigbee_send_data(&frame);
        if (op_ret != OPRT_OK) {
            PR_WARN("tuya_zigbee_send_data err: %d", op_ret);
        }
    }
}

STATIC VOID __z3_onoff_light_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_OBJ_DP_S dp_data = {0};

    if (frame->cluster_id != ZCL_ON_OFF_CLUSTER_ID) {
        return;
    }

    dp_data.dpid = frame->src_endpoint;
    dp_data.type = PROP_BOOL;

    if (frame->cmd_id == ZCL_REPORT_ATTRIBUTES_COMMAND_ID) {
        dp_data.value.dp_bool = frame->message[ZCL_REPORT_ATTRIBUTES_HEADER+1];
    } else if (frame->cmd_id == ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID) {
        dp_data.value.dp_bool = frame->message[ZCL_READ_ATTRIBUTER_RESPONSE_HEADER+1];
    } else {
        return;
    }

    op_ret = dev_report_dp_json_async(frame->id, &dp_data, 1);
    if (op_ret != OPRT_OK) {
        PR_ERR("dev_report_dp_json_async err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_cmd_obj_cb(CONST TY_RECV_OBJ_DP_S *cmd)
{
    DEV_DESC_IF_S *dev_if = NULL;

    /**
     * 以下代码仅作为演示,开发者一般需要实现的逻辑:
     *   a) 获取设备的 UDDD,根据 UDDD 映射到设备类型。
     *   b) 根据 UDDD,把功能点转成 ZCL 数据,下发给子设备。
     */

    dev_if = tuya_iot_get_dev_if(cmd->cid);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", cmd->cid);
        return;
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_write_attr(cmd);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    if (frame->profile_id != PROFILE_ID_HA) {
        return;
    }

    dev_if = tuya_iot_get_dev_if(frame->id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", frame->id);
        tuya_zigbee_del_dev(frame->id);
        return;
    }

    op_ret = tuya_iot_fresh_dev_hb(frame->id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_report(frame);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .report      = __my_z3_dev_report,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_bind_ifm  = __dev_bind_cb,
    };

    TY_IOT_DP_CBS_S dev_dp_cbs = {
        .obj   = __dev_cmd_obj_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_dp_cb(DP_DEV, GP_DEV_ATH_1, &dev_dp_cbs));

    return OPRT_OK;
}

// ...

心跳管理

子设备在离线状态是通过心跳机制来维持,心跳一般有两种实现:

  1. 主动上报,子设备以一定的时间间隔定期上报数据。
  2. 被动查询,网关以一定的时间间隔定期查询子设备状态(获取版本号,或其他数据)。

主动上报比较简单,当应用收到第三方 Zigbee 数据时刷新子设备心跳即可。

而被动查询是由 SDK 实现的心跳管理,应用只需要配置心跳超时以及心跳超时处理。

被动查询的主要逻辑:

  1. 注册 SDK 子设备管理 回调,在心跳回调中查询子设备状态,并在绑定成功通知回调中设置子设备超时。
  2. 注册 Zigbee 子设备管理 回调,在数据上报回调中刷新子设备的心跳,并在初始化通知回调中设置子设备心跳超时。

相关代码实现:

// ...

STATIC VOID __dev_hb_cb(CONST CHAR_T *dev_id)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_Z3_APS_FRAME_S frame = {0};
    USHORT_T atrr_buf[1] = { 0x0001 };

    strncpy(frame.id, dev_id, SIZEOF(frame.id));
    frame.profile_id = PROFILE_ID_HA;
    frame.cluster_id = ZCL_BASIC_CLUSTER_ID;
    frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
    frame.src_endpoint = 0x01;
    frame.dst_endpoint = 0xFF;
    frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;

    frame.msg_length = SIZEOF(atrr_buf);
    frame.message = (UCHAR_T *)atrr_buf;

    op_ret = tuya_zigbee_send_data(&frame);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
        return;
    }
}

STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    if (frame->profile_id != PROFILE_ID_HA) {
        return;
    }

    dev_if = tuya_iot_get_dev_if(frame->id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", frame->id);
        tuya_zigbee_del_dev(frame->id);
        return;
    }

    op_ret = tuya_iot_fresh_dev_hb(frame->id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_report(frame);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC VOID __my_z3_dev_notify(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;
    VOID *iter = NULL;

    dev_if = tuya_iot_dev_traversal(&iter);
    while (dev_if) {
        if (dev_if->tp == GP_DEV_ATH_1) {
            op_ret = tuya_iot_set_dev_hb_cfg(dev_if->id, 120, 3, FALSE);
            if (op_ret != OPRT_OK) {
                PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
            }
        }
        dev_if = tuya_iot_dev_traversal(&iter);
    }
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .report      = __my_z3_dev_report,
        .notify      = __my_z3_dev_notify,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_bind_ifm  = __dev_bind_cb,
        .dev_hb        = __dev_hb_cb,
    };

    TY_IOT_DP_CBS_S dev_dp_cbs = {
        .obj   = __dev_cmd_obj_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_dp_cb(DP_DEV, GP_DEV_ATH_1, &dev_dp_cbs));

    return OPRT_OK;
}

// ...

移除重置

移除设备有两种方式:

  • App 上移除,主要逻辑为:注册 SDK 子设备管理 回调,在移除回调和重置回调中移除第三方 Zigbee 子设备。
  • 子设备复位按键重置,主要逻辑为:注册 Zigbee 子设备管理 回调,在移除回调中解绑第三方 Zigbee 子设备。

相关代码实现:

// ...

STATIC VOID __my_z3_dev_leave(CONST CHAR_T *dev_id)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_iot_gw_unbind_dev(dev_id);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_iot_gw_unbind_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_del_cb(CONST CHAR_T *dev_id, CONST GW_DELDEV_TYPE type)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_zigbee_del_dev(dev_id);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_del_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_reset_cb(CONST CHAR_T *dev_id, DEV_RESET_TYPE_E type)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_zigbee_del_dev(dev_id);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_del_dev err: %d", op_ret);
        return;
    }
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .leave       = __my_z3_dev_leave,
        .report      = __my_z3_dev_report,
        .notify      = __my_z3_dev_notify,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_del       = __dev_del_cb,
        .dev_bind_ifm  = __dev_bind_cb,
        .dev_hb        = __dev_hb_cb,
        .dev_reset     = __dev_reset_cb,
    };

    TY_IOT_DP_CBS_S dev_dp_cbs = {
        .obj   = __dev_cmd_obj_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_dp_cb(DP_DEV, GP_DEV_ATH_1, &dev_dp_cbs));

    return OPRT_OK;
}

// ...

设备升级

Zigbee 子设备升级使用的是标准 OTA Cluster,SDK 内部实现了固件下载和存储,以及把固件分块传输给子设备。提供了对应的接口给应用使用,简化应用的开发门槛。

关于如何把子设备固件上传到涂鸦 IoT 开发平台,可以参见 接入子设备指南-设备OTA

设备升级的主要逻辑:

  • 注册 SDK 子设备管理 回调,在升级通知回调中调用子设备升级接口。
  • 注册 Zigbee 子设备管理 回调,在升级结果通知回调中实现业务逻辑。升级成功则更新版本号,升级失败则上报升级状态为失败。

相关代码实现:

// ...

STATIC VOID __dev_upgrade_cb(CONST CHAR_T *dev_id, CONST FW_UG_S *fw)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_zigbee_upgrade_dev(dev_id, fw);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_upgrade_dev err: %d", op_ret);
        return;
    }

    return;
}

STATIC VOID __my_z3_dev_upgrade_end(CONST CHAR_T *dev_id, INT_T rc, UCHAR_T version)
{
    CHAR_T sw_ver[SW_VER_LEN+1] = {0};

    if (rc != OPRT_OK) {
        tuya_iot_dev_upgd_result_report(dev_id, GP_DEV_ATH_1, 4);
        return;
    }

    CHAR_T iver1 = (CHAR_T)(version & 0x0F); 
    CHAR_T iver2 = (CHAR_T)((version >> 4) & 0x03);
    CHAR_T iver3 = (CHAR_T)((version >> 6) & 0x03);
    snprintf(sw_ver, SIZEOF(sw_ver), "%d.%d.%d", iver3, iver2, iver1);

    tuya_iot_gw_subdevice_update(dev_id, sw_ver);

    return;
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .leave       = __my_z3_dev_leave,
        .report      = __my_z3_dev_report,
        .notify      = __my_z3_dev_notify,
        .upgrade_end = __my_z3_dev_upgrade_end,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_del       = __dev_del_cb,
        .dev_bind_ifm  = __dev_bind_cb,
        .dev_hb        = __dev_hb_cb,
        .dev_reset     = __dev_reset_cb,
        .dev_upgrade   = __dev_upgrade_cb,
    };

    TY_IOT_DP_CBS_S dev_dp_cbs = {
        .obj   = __dev_cmd_obj_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_dp_cb(DP_DEV, GP_DEV_ATH_1, &dev_dp_cbs));

    return OPRT_OK;
}

// ...

状态同步

应用启动后,一般需要读取子设备当前的状态,然后上报到云端,实现状态同步,否则 App 面板上设备状态可能与真实状态不一致。

状态同步的主要逻辑:

  • 注册 Zigbee 子设备管理 回调,在初始化通知回调中遍历第三方 Zigbee 子设备,读取子设备当前状态。
  • 在数据上报回调中刷新心跳,并把 ZCL 数据转成功能点,上报到云端。

相关代码实现:

// ...

STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    if (frame->profile_id != PROFILE_ID_HA) {
        return;
    }

    dev_if = tuya_iot_get_dev_if(frame->id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", frame->id);
        tuya_zigbee_del_dev(frame->id);
        return;
    }

    op_ret = tuya_iot_fresh_dev_hb(frame->id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_report(frame);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC VOID __my_z3_dev_notify(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;
    VOID *iter = NULL;

    dev_if = tuya_iot_dev_traversal(&iter);
    while (dev_if) {
        if ((dev_if->tp == GP_DEV_ATH_1) && dev_if->bind) {
            op_ret = tuya_iot_set_dev_hb_cfg(dev_if->id, 120, 3, FALSE);
            if (op_ret != OPRT_OK) {
                PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
            }

            UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
            switch (uc_devid)
            {
                case TY_DEVID_HA_ONOF_LIGHT:
                    __z3_onoff_light_read_attr(dev_if->id);
                    break;
                case TY_DEVID_HA_IAS_ZONE:
                case TY_DEVID_HA_TEMPERATURE_SENSOR:
                case TY_DEVID_HA_SMART_PLUG:   
                default:
                    PR_WARN("devid: %d is not support", uc_devid);
                    break;
            }
        }
        dev_if = tuya_iot_dev_traversal(&iter);
    }
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .leave       = __my_z3_dev_leave,
        .report      = __my_z3_dev_report,
        .notify      = __my_z3_dev_notify,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_del       = __dev_del_cb,
        .dev_bind_ifm  = __dev_bind_cb,
        .dev_hb        = __dev_hb_cb,
        .dev_reset     = __dev_reset_cb,
    };

    TY_IOT_DP_CBS_S dev_dp_cbs = {
        .obj   = __dev_cmd_obj_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_dp_cb(DP_DEV, GP_DEV_ATH_1, &dev_dp_cbs));

    return OPRT_OK;
}

// ...

附录

Zigbee 基础完整代码

#include <unistd.h>

#include "uni_log.h"
#include "base_os_adapter.h"
#include "tuya_iot_base_api.h"
#include "tuya_iot_com_api.h"

#include "tuya_iot_sdk_api.h"
#include "tuya_iot_sdk_defs.h"

#include "tuya_zigbee_api.h"

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
#include "tuya_os_adapt_bt.h"
#endif

#define PID       "fljmamufiym5fktz"                  // 替换成自己的产品 ID
#define UUID      "tuya461dbc63aeeb991f"              // 替换成自己的 UUID
#define AUTHKEY   "c8X4PR4wx1gMFaQlaZu5dfgVvVRwB8Ug"  // 替换成自己的 AUTHKEY

STATIC VOID __gw_reset_cb(GW_RESET_TYPE_E type)
{
    PR_DEBUG("gw reset callback, type: %d", type);

    if (GW_RESET_DATA_FACTORY != type) {
        exit(0);
    }

    return;
}

STATIC VOID __gw_upgrade_cb(CONST FW_UG_S *fw)
{
    PR_DEBUG("gw upgrade callback");

    if (fw == NULL) {
        PR_ERR("invalid param");
        return;
    }

    PR_DEBUG("        tp: %d", fw->tp);
    PR_DEBUG("    fw_url: %s", fw->fw_url);
    PR_DEBUG("    sw_ver: %s", fw->sw_ver);
    PR_DEBUG("   fw_hmac: %s", fw->fw_hmac);
    PR_DEBUG(" file_size: %u", fw->file_size);

    return;
}

STATIC VOID __gw_active_stat_cb(GW_STATUS_E status)
{
    PR_DEBUG("gw active stat callback, status: %d", status);

    return;
}

STATIC VOID __gw_reboot_cb(VOID)
{
    PR_DEBUG("gw reboot callback");

    exit(0);

    return;
}

STATIC VOID __nw_stat_cb(IN CONST SDK_NW_STAT_T stat)
{
    PR_DEBUG("network stat: %d", stat);

    return;
}

STATIC VOID __wired_stat_cb(IN CONST SDK_WIRED_NW_STAT_T stat)
{
    PR_DEBUG("wired stat: %d", stat);

    return;
}

int main(int argc, char **argv)
{
    OPERATE_RET rt = OPRT_OK;
    GW_PROD_INFO_S prod_info = {0};

    /* gw base callback */
    TY_GW_INFRA_CBS_S gw_cbs = {
        .gw_reset_cb       = __gw_reset_cb,
        .gw_upgrade_cb     = __gw_upgrade_cb,
        .gw_active_stat_cb = __gw_active_stat_cb,
        .gw_reboot_cb      = __gw_reboot_cb,
    };

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
    tuya_os_adapt_reg_bt_intf();
#endif

    /* initiate os-layer service*/
    tuya_os_intf_init();

    /* initiate iot-layer service */
    TUYA_CALL_ERR_RETURN(tuya_iot_init("./"));

    /* set the logging level to debug */
    SET_PR_DEBUG_LEVEL(TY_LOG_LEVEL_DEBUG);

    PR_DEBUG("SDK INFO: %s", tuya_iot_get_sdk_info());

    /* set uuid and authkey */
    prod_info.uuid     = UUID;
    prod_info.auth_key = AUTHKEY;
    TUYA_CALL_ERR_RETURN(tuya_iot_set_gw_prod_info(&prod_info));

    /* pre-initiate sdk service */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_pre_init(TRUE));  

    ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
    if (zb_cfg == NULL) {
        return OPRT_CJSON_GET_ERR;
    }
    ty_cJSON_AddStringToObject(zb_cfg, "storage_path", "./");
    ty_cJSON_AddStringToObject(zb_cfg, "cache_path", "/tmp/");
    ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
    ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
    ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);

    /* initiate application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_init(&gw_cbs));

    /* initiate sdk service */
#if defined(GW_SUPPORT_WIRED_WIFI) && (GW_SUPPORT_WIRED_WIFI==1)
    TUYA_CALL_ERR_RETURN(tuya_iot_wired_wf_sdk_init(IOT_GW_NET_WIRED_WIFI, GWCM_OLD, WF_START_AP_ONLY, PID, USER_SW_VER, NULL, 0));
#elif defined(WIFI_GW) && (WIFI_GW==1)
    TUYA_CALL_ERR_RETURN(tuya_iot_wf_sdk_init(GWCM_OLD, WF_START_AP_ONLY, PID, USER_SW_VER, NULL, 0));
#else
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_init(PID, USER_SW_VER, NULL, 0));
#endif

    /* register net stat notification callback */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_reg_netstat_cb(__nw_stat_cb, __wired_stat_cb, NULL));

    /* start application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_start(NULL));

    while (1) {
        sleep(10);
    }

    return 0;
}

Zigbee 扩展完整代码

#include <stdio.h>

#include "uni_log.h"
#include "base_os_adapter.h"
#include "tuya_hal_system.h"
#include "tuya_iot_base_api.h"
#include "tuya_iot_com_api.h"

#include "tuya_iot_sdk_api.h"
#include "tuya_iot_sdk_defs.h"

#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
#include "tuya_os_adapt_bt.h"
#endif

#define PID       "fljmamufiym5fktz"                  // 替换成自己的产品 ID
#define UUID      "tuya461dbc63aeeb991f"              // 替换成自己的 UUID
#define AUTHKEY   "c8X4PR4wx1gMFaQlaZu5dfgVvVRwB8Ug"  // 替换成自己的 AUTHKEY

STATIC VOID __gw_reset_cb(GW_RESET_TYPE_E type)
{
    PR_DEBUG("gw reset callback, type: %d", type);

    if (GW_RESET_DATA_FACTORY != type) {
        exit(0);
    }

    return;
}

STATIC VOID __gw_upgrade_cb(CONST FW_UG_S *fw)
{
    PR_DEBUG("gw upgrade callback");

    if (fw == NULL) {
        PR_ERR("invalid param");
        return;
    }

    PR_DEBUG("        tp: %d", fw->tp);
    PR_DEBUG("    fw_url: %s", fw->fw_url);
    PR_DEBUG("    sw_ver: %s", fw->sw_ver);
    PR_DEBUG("   fw_hmac: %s", fw->fw_hmac);
    PR_DEBUG(" file_size: %u", fw->file_size);

    return;
}

STATIC VOID __gw_active_stat_cb(GW_STATUS_E status)
{
    PR_DEBUG("gw active stat callback, status: %d", status);

    return;
}

STATIC VOID __gw_reboot_cb(VOID)
{
    PR_DEBUG("gw reboot callback");

    exit(0);

    return;
}

STATIC VOID __nw_stat_cb(IN CONST SDK_NW_STAT_T stat)
{
    PR_DEBUG("network stat: %d", stat);

    return;
}

STATIC VOID __wired_stat_cb(IN CONST SDK_WIRED_NW_STAT_T stat)
{
    PR_DEBUG("wired stat: %d", stat);

    return;
}

#define TY_TS_ONOF_LIGHT_ONE                    "TS0001"
#define TY_TS_ONOF_LIGHT_TWO                    "TS0002"
#define TY_TS_ONOF_LIGHT_THR                    "TS0003"

#define PROFILE_ID_HA                           0x0104
#define PROFILE_ID_LL                           0xC05E

#define ZCL_BASIC_CLUSTER_ID                    0x0000
#define ZCL_POWER_CONFIG_CLUSTER_ID             0x0001
#define ZCL_DEVICE_TEMP_CLUSTER_ID              0x0002
#define ZCL_IDENTIFY_CLUSTER_ID                 0x0003
#define ZCL_GROUPS_CLUSTER_ID                   0x0004
#define ZCL_SCENES_CLUSTER_ID                   0x0005
#define ZCL_ON_OFF_CLUSTER_ID                   0x0006

#define ZCL_CMD_TYPE_GLOBAL                     0x00
#define ZCL_CMD_TYPE_PRIVATE                    0x01

#define ZCL_FRAME_TYPE_UNICAST                  0x00
#define ZCL_FRAME_TYPE_MULTICAST                0x01
#define ZCL_FRAME_TYPE_BROADCAST                0x02

#define ZCL_OFF_COMMAND_ID                      0x00
#define ZCL_ON_COMMAND_ID                       0x01

#define ZCL_READ_ATTRIBUTES_COMMAND_ID          0x00
#define ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID 0x01
#define ZCL_REPORT_ATTRIBUTES_COMMAND_ID        0x0A
#define ZCL_READ_ATTRIBUTER_RESPONSE_HEADER     3 /* Attributer ID: 2 Bytes, Status: 1 Btye */
#define ZCL_REPORT_ATTRIBUTES_HEADER            2 /* Attributer ID: 2 Bytes */

#define DEVID_HA_IAS_ZONE                       0x0402    // 0x0402 -> 0x00,IAS类,pir, 门磁
#define DEVID_HA_TEMPERATURE_SENSOR             0x0302    // 0x0302 -> 0x01,温度类(含温湿度)
#define DEVID_HA_ONOF_LIGHT                     0x0100    // 0x0100 -> 0x02,照明开关类
#define DEVID_HA_SMART_PLUG                     0x0051    // 0x0051 -> 0x03, 智能插座类

/* 举例,自定义 DEVID,保存到 UDDD */
#define TY_DEVID_HA_IAS_ZONE                    0x00      // 0x0402 -> 0x00,IAS类,pir, 门磁
#define TY_DEVID_HA_TEMPERATURE_SENSOR          0x01      // 0x0302 -> 0x01,温度类(含温湿度)
#define TY_DEVID_HA_ONOF_LIGHT                  0x02      // 0x0100 -> 0x02,照明开关类
#define TY_DEVID_HA_SMART_PLUG                  0x03      // 0x0051 -> 0x03, 智能类插座类

/* 举例,自定义 TYPE,保存到 UDDD */
#define TY_TYPE_PIR                             0x00      // DEVID_HA_IAS_ZONE
#define TY_TYPE_DOOR_SENSOR                     0x01      // DEVID_HA_IAS_ZONE
#define TY_TYPE_FIRE_SENSOR                     0x02      // DEVID_HA_IAS_ZONE
#define TY_TYPE_WATER_SENSOR                    0x03      // DEVID_HA_IAS_ZONE

#define TY_TYPE_ONOF_LIGHT_ONE                  0x00      // DEVID_HA_ONOF_LIGHT
#define TY_TYPE_ONOF_LIGHT_TWO                  0x01      // DEVID_HA_ONOF_LIGHT
#define TY_TYPE_ONOF_LIGHT_THREE                0x02      // DEVID_HA_ONOF_LIGHT

TY_Z3_DEV_S my_z3_dev[] = {
    { "TUYATEC-nPGIPl5D", "TS0001" },
    { "TUYATEC-1xAC7DHQ", "TS0002" },
};

TY_Z3_DEVLIST_S my_z3_devlist = {
    .devs = my_z3_dev,
    .dev_num = CNTSOF(my_z3_dev),
};

STATIC VOID __z3_onoff_light_write_attr(CONST TY_RECV_OBJ_DP_S *cmd)
{
    INT_T i= 0;
    OPERATE_RET op_ret = OPRT_OK;
    TY_Z3_APS_FRAME_S frame = {0};

    strncpy(frame.id, cmd->cid, SIZEOF(frame.id));
    frame.profile_id = PROFILE_ID_HA;
    frame.src_endpoint = 0x01;

    for (i = 0; i < cmd->dps_cnt; i++) {
        switch (cmd->dps[i].dpid) {
        case 1 ... 3:
            frame.dst_endpoint = cmd->dps[i].dpid;
            frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
            frame.cmd_type = ZCL_CMD_TYPE_PRIVATE;
            frame.frame_type = ZCL_FRAME_TYPE_UNICAST;
            frame.msg_length = 0;
            if (cmd->dps[i].value.dp_bool) {
                frame.cmd_id = ZCL_ON_COMMAND_ID;
            } else {
                frame.cmd_id = ZCL_OFF_COMMAND_ID;
            }
            break;
        default:
            PR_WARN("dev_id: %s, dpid: %d is not supported", cmd->cid, cmd->dps[i].dpid);
            return;
        }

        op_ret = tuya_zigbee_send_data(&frame);
        if (op_ret != OPRT_OK) {
            PR_WARN("tuya_zigbee_send_data err: %d", op_ret);
        }
    }
}

STATIC VOID __z3_onoff_light_read_attr(CONST CHAR_T *dev_id)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_Z3_APS_FRAME_S frame = {0};
    USHORT_T atrr_buf[5] = { 0x0000, 0x4001, 0x4002, 0x8001, 0x5000 };

    strncpy(frame.id, dev_id, SIZEOF(frame.id));
    frame.profile_id = PROFILE_ID_HA;
    frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
    frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
    frame.src_endpoint = 0x01;
    frame.dst_endpoint = 0xFF;
    frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;

    frame.msg_length = SIZEOF(atrr_buf);
    frame.message = (UCHAR_T *)atrr_buf;

    op_ret = tuya_zigbee_send_data(&frame);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
        return;
    }
}

STATIC VOID __z3_onoff_light_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_OBJ_DP_S dp_data = {0};

    if (frame->cluster_id != ZCL_ON_OFF_CLUSTER_ID) {
        return;
    }

    dp_data.dpid = frame->src_endpoint;
    dp_data.type = PROP_BOOL;

    if (frame->cmd_id == ZCL_REPORT_ATTRIBUTES_COMMAND_ID) {
        dp_data.value.dp_bool = frame->message[ZCL_REPORT_ATTRIBUTES_HEADER+1];
    } else if (frame->cmd_id == ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID) {
        dp_data.value.dp_bool = frame->message[ZCL_READ_ATTRIBUTER_RESPONSE_HEADER+1];
    } else {
        return;
    }

    op_ret = dev_report_dp_json_async(frame->id, &dp_data, 1);
    if (op_ret != OPRT_OK) {
        PR_ERR("dev_report_dp_json_async err: %d", op_ret);
        return;
    }
}

STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    if (frame->profile_id != PROFILE_ID_HA) {
        return;
    }

    dev_if = tuya_iot_get_dev_if(frame->id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", frame->id);
        tuya_zigbee_del_dev(frame->id);
        return;
    }

    op_ret = tuya_iot_fresh_dev_hb(frame->id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_report(frame);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC VOID __my_z3_dev_notify(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;
    VOID *iter = NULL;

    dev_if = tuya_iot_dev_traversal(&iter);
    while (dev_if) {
        if ((dev_if->tp == GP_DEV_ATH_1) && dev_if->bind) {
            op_ret = tuya_iot_set_dev_hb_cfg(dev_if->id, 120, 3, FALSE);
            if (op_ret != OPRT_OK) {
                PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
            }

            UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
            switch (uc_devid)
            {
                case TY_DEVID_HA_ONOF_LIGHT:
                    __z3_onoff_light_read_attr(dev_if->id);
                    break;
                case TY_DEVID_HA_IAS_ZONE:
                case TY_DEVID_HA_TEMPERATURE_SENSOR:
                case TY_DEVID_HA_SMART_PLUG:   
                default:
                    PR_WARN("devid: %d is not support", uc_devid);
                    break;
            }
        }
        dev_if = tuya_iot_dev_traversal(&iter);
    }
}

STATIC VOID __my_z3_dev_upgrade_end(CONST CHAR_T *dev_id, INT_T rc, UCHAR_T version)
{
    CHAR_T sw_ver[SW_VER_LEN+1] = {0};

    if (rc != OPRT_OK) {
        tuya_iot_dev_upgd_result_report(dev_id, GP_DEV_ATH_1, 4);
        return;
    }

    CHAR_T iver1 = (CHAR_T)(version & 0x0F); 
    CHAR_T iver2 = (CHAR_T)((version >> 4) & 0x03);
    CHAR_T iver3 = (CHAR_T)((version >> 6) & 0x03);
    snprintf(sw_ver, SIZEOF(sw_ver), "%d.%d.%d", iver3, iver2, iver1);

    tuya_iot_gw_subdevice_update(dev_id, sw_ver);

    return;
}

STATIC VOID __my_z3_dev_join(TY_Z3_DESC_S *dev)
{
    OPERATE_RET op_ret = OPRT_OK;
    CHAR_T pid[PRODUCT_KEY_LEN+1]={0};
    CHAR_T sw_ver[SW_VER_LEN+1] = {0};
    USER_DEV_DTL_DEF_T uddd = 0;

    /**
     * 以下代码仅作为演示,开发者一般需要实现的逻辑:
     *   a) 解析设备信息,取关键信息,保存到 uddd,用于标识设备类型等;
     *   b) 关联 PID,调用绑定接口把设备绑定到云端。
     */

    if (dev->profile_id[0] != PROFILE_ID_HA) {
        return;
    }

    CHAR_T iver1 = (CHAR_T)(dev->version & 0x0F); 
    CHAR_T iver2 = (CHAR_T)((dev->version >> 4) & 0x03);
    CHAR_T iver3 = (CHAR_T)((dev->version >> 6) & 0x03);
    snprintf(sw_ver, SIZEOF(sw_ver), "%d.%d.%d", iver3, iver2, iver1);

    switch (dev->device_id[0]) {
        case DEVID_HA_ONOF_LIGHT:
            uddd |= (TY_DEVID_HA_ONOF_LIGHT << 8);
            if (0 == strncasecmp(dev->model_id, TY_TS_ONOF_LIGHT_ONE, strlen(TY_TS_ONOF_LIGHT_ONE))) {
                uddd |= (TY_TYPE_ONOF_LIGHT_ONE & 0xFF);
                strncpy(pid, "ckj1pnvy", SIZEOF(pid));
            } else if (0 == strncasecmp(dev->model_id, TY_TS_ONOF_LIGHT_TWO, strlen(TY_TS_ONOF_LIGHT_TWO))) {
                uddd |= (TY_TYPE_ONOF_LIGHT_TWO & 0xFF);
                strncpy(pid, "tr4dshuh", SIZEOF(pid));
            } else {
                PR_WARN("model_id: %s is not supported", dev->model_id);
                return;
            }
            break;
        case DEVID_HA_IAS_ZONE:
        case DEVID_HA_TEMPERATURE_SENSOR:
        case DEVID_HA_SMART_PLUG:
        default:
            PR_WARN("devid: %d is not supported", dev->device_id[0]);
            return;
    }

    op_ret = tuya_iot_gw_bind_dev(GP_DEV_ATH_1, uddd, dev->id, pid, sw_ver);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_iot_gw_bind_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __my_z3_dev_leave(CONST CHAR_T *dev_id)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_iot_gw_unbind_dev(dev_id);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_iot_gw_unbind_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_del_cb(CONST CHAR_T *dev_id, CONST GW_DELDEV_TYPE type)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_zigbee_del_dev(dev_id);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_del_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_reset_cb(CONST CHAR_T *dev_id, DEV_RESET_TYPE_E type)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_zigbee_del_dev(dev_id);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_del_dev err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_upgrade_cb(CONST CHAR_T *dev_id, CONST FW_UG_S *fw)
{
    OPERATE_RET op_ret = OPRT_OK;

    op_ret = tuya_zigbee_upgrade_dev(dev_id, fw);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_upgrade_dev err: %d", op_ret);
        return;
    }

    return;
}

STATIC VOID __dev_bind_cb(CONST CHAR_T *dev_id, CONST OPERATE_RET result)
{
    OPERATE_RET op_ret = OPRT_OK;
    DEV_DESC_IF_S *dev_if = NULL;

    /**
     * 以下代码仅作为演示,开发者一般需要实现的逻辑:
     *   a) 根据绑定状态,封装读取属性 ZCL 数据发给子设备。
     *   b) 配置心跳超时。
     */

    if (result != OPRT_OK) {
        PR_ERR("dev_id: %s bind err", dev_id);
        tuya_zigbee_del_dev(dev_id);
        return;
    }

    op_ret = tuya_iot_set_dev_hb_cfg(dev_id, 120, 3, FALSE);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
    }

    op_ret = tuya_iot_fresh_dev_hb(dev_id);
    if (op_ret != OPRT_OK) {
        PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
    }

    dev_if = tuya_iot_get_dev_if(dev_id);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", dev_id);
        tuya_zigbee_del_dev(dev_id);
        return;
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_read_attr(dev_id);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC VOID __dev_hb_cb(CONST CHAR_T *dev_id)
{
    OPERATE_RET op_ret = OPRT_OK;
    TY_Z3_APS_FRAME_S frame = {0};
    USHORT_T atrr_buf[1] = { 0x0001 };

    strncpy(frame.id, dev_id, SIZEOF(frame.id));
    frame.profile_id = PROFILE_ID_HA;
    frame.cluster_id = ZCL_BASIC_CLUSTER_ID;
    frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
    frame.src_endpoint = 0x01;
    frame.dst_endpoint = 0xFF;
    frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;

    frame.msg_length = SIZEOF(atrr_buf);
    frame.message = (UCHAR_T *)atrr_buf;

    op_ret = tuya_zigbee_send_data(&frame);
    if (op_ret != OPRT_OK) {
        PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
        return;
    }
}

STATIC VOID __dev_cmd_obj_cb(CONST TY_RECV_OBJ_DP_S *cmd)
{
    DEV_DESC_IF_S *dev_if = NULL;

    /**
     * 以下代码仅作为演示,开发者一般需要实现的逻辑:
     *   a) 获取设备的 UDDD,根据 UDDD 映射到设备类型。
     *   b) 根据 UDDD,把功能点转成 ZCL 数据,下发给子设备。
     */

    dev_if = tuya_iot_get_dev_if(cmd->cid);
    if (!dev_if || !dev_if->bind) {
        PR_ERR("dev_id: %s is not bind", cmd->cid);
        return;
    }

    UCHAR_T uc_devid = (((dev_if->uddd) >> 8) & 0xFF);
    switch (uc_devid)
    {
        case TY_DEVID_HA_ONOF_LIGHT:
            __z3_onoff_light_write_attr(cmd);
            break;
        case TY_DEVID_HA_IAS_ZONE:
        case TY_DEVID_HA_TEMPERATURE_SENSOR:
        case TY_DEVID_HA_SMART_PLUG:   
        default:
            PR_WARN("devid: %d is not support", uc_devid);
            return;
    }
}

STATIC OPERATE_RET __user_init(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    /* Zigbee 子设备管理回调 */
    TY_Z3_DEV_CBS_S z3_dev_cbs = {
        .join        = __my_z3_dev_join,
        .leave       = __my_z3_dev_leave,
        .report      = __my_z3_dev_report,
        .notify      = __my_z3_dev_notify,
        .upgrade_end = __my_z3_dev_upgrade_end,
    };

    /* SDK 子设备管理回调 */
    TY_IOT_DEV_CBS_S dev_mgr_cbs = {
        .dev_del       = __dev_del_cb,
        .dev_bind_ifm  = __dev_bind_cb,
        .dev_hb        = __dev_hb_cb,
        .dev_reset     = __dev_reset_cb,
        .dev_upgrade   = __dev_upgrade_cb,
    };

    TY_IOT_DP_CBS_S dev_dp_cbs = {
        .obj   = __dev_cmd_obj_cb,
    };

    TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_gw_mgr_cb(GP_DEV_ATH_1, &dev_mgr_cbs));
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_dp_cb(DP_DEV, GP_DEV_ATH_1, &dev_dp_cbs));

    return OPRT_OK;
}

int main(int argc, char **argv)
{
    OPERATE_RET rt = OPRT_OK;
    GW_PROD_INFO_S prod_info = {0};

    /* gw base callback */
    TY_GW_INFRA_CBS_S gw_cbs = {
        .gw_reset_cb       = __gw_reset_cb,
        .gw_upgrade_cb     = __gw_upgrade_cb,
        .gw_active_stat_cb = __gw_active_stat_cb,
        .gw_reboot_cb      = __gw_reboot_cb,
    };

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
    tuya_os_adapt_reg_bt_intf();
#endif

    /* initiate os-layer service*/
    tuya_os_intf_init();

    /* initiate iot-layer service */
    TUYA_CALL_ERR_RETURN(tuya_iot_init("./"));

    /* set the logging level to debug */
    SET_PR_DEBUG_LEVEL(TY_LOG_LEVEL_DEBUG);

    PR_DEBUG("SDK INFO: %s", tuya_iot_get_sdk_info());

    /* set uuid and authkey */
    prod_info.uuid     = UUID;
    prod_info.auth_key = AUTHKEY;
    TUYA_CALL_ERR_RETURN(tuya_iot_set_gw_prod_info(&prod_info));

    /* pre-initiate sdk service */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_pre_init(TRUE));  

    ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
    if (zb_cfg == NULL) {
        return OPRT_CJSON_GET_ERR;
    }
    ty_cJSON_AddStringToObject(zb_cfg, "storage_path", "./");
    ty_cJSON_AddStringToObject(zb_cfg, "cache_path", "/tmp/");
    ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
    ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
    ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);

    /* initiate application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_init(&gw_cbs));
    TUYA_CALL_ERR_RETURN(__user_init());

    /* initiate sdk service */
#if defined(GW_SUPPORT_WIRED_WIFI) && (GW_SUPPORT_WIRED_WIFI==1)
    TUYA_CALL_ERR_RETURN(tuya_iot_wired_wf_sdk_init(IOT_GW_NET_WIRED_WIFI, GWCM_OLD, WF_START_AP_ONLY, PID, USER_SW_VER, NULL, 0));
#elif defined(WIFI_GW) && (WIFI_GW==1)
    TUYA_CALL_ERR_RETURN(tuya_iot_wf_sdk_init(GWCM_OLD, WF_START_AP_ONLY, PID, USER_SW_VER, NULL, 0));
#else
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_init(PID, USER_SW_VER, NULL, 0));
#endif

    /* register net stat notification callback */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_reg_netstat_cb(__nw_stat_cb, __wired_stat_cb, NULL));

    /* start application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
    TUYA_CALL_ERR_RETURN(tuya_user_svc_start(NULL));

    tuya_user_svc_start(NULL);

    while (1) {
        tuya_hal_system_sleep(10*1000);
    }

    return 0;
}