此文档以开发一个2调光灯设备为示例,让开发者了解 TuyaOS Zigbee 开发包的基本原理,并且利用 TuyaOS Zigbee 开发包快速进行开发。最后可以在开发板上通过按键进行配网以及在 APP 上控制照明设备的冷暖模式、亮度和亮灭。

你能学到什么

了解 TuyaOS Zigbee 开发包架构以及基础能力; 了解如何开发灯类设备; 了解灯类设备如何于网关和 APP 进行数据交互。

你需要什么

windows 操作系统

USB 转 TTL 工具

带 ZS3L 模组的一块开发板

要做完整个工作,我们需要完成以下几个步骤:

  1. 认识硬件
  2. 运行原理(SDK 启动、文件和接口介绍)
  3. 修改代码
  4. 编译代码
  5. 烧写代码
  6. 观看效果

让我们愉快的开始吧。

认识 Zigbee SoC 主控板(ZS3L)

涂鸦三明治 Zigbee SoC 主控板(ZS3L)是方便开发者快速实现各种智能硬件产品原型的一款开发板。您可通过涂鸦 Zigbee SoC 主控板(ZS3L),搭配其他功能电路模组或电路板,实现对应的产品功能。

Start-up_flowchart

Start-up_flowchart

引脚

实验中用到的引脚介绍如下,点击查看Zigbee SoC 主控板(ZS3L)的详细介绍

丝印名称

芯片引脚编号

备注

PD01

GPIO_NUM_16

按键引脚,初始化高电平,按下为低电平

PB01

GPIO_NUM_8

PWM 通道0 输出引脚

PA04

GPIO_NUM_4

PWM 通道1 输出引脚

PA05

GPIO_NUM_5

串口0 TX 引脚

PA06

GPIO_NUM_6

串口0 RX 引脚

该开发板提供了烧录口(从左往右依次是电源、时钟、数据、复位、地线、地线、TXD、RXD),可以通过 J-Link 和串口使用涂鸦云模组烧录平台或对应的烧录工具进行烧录. 注意:ZS3L 是 efr32mg21a020f768im32 芯片平台,需要在 appconfig.json 中 修改chip_id;

{
  "firmwareInfo": {
        "description": "this is a demon project",
        "dev_role":"sleep_end_dev",
        "image_type":"0x1602",
        "manufacture_id":"0x1002",
        "model_id":"TS0203",
        "pid": "cz8yd6r2",
        "manufacture_name": "_TZ3000_",
        "module_name":"ZSU",
        "chip_id":"efr32mg21a020f768im32"
  }
}

在开始开发前,需要了解 SDK 的初始化流程。启动流程如下图所示。 Tips: 绿底步骤 需要用户关注,进行重写或实现。

Start-up_flowchart

部分头文件

头文件名称

功能

tuya_tools.h

Tuya IoT OS 通用工具函数接口

tkl_system.h

提供统一适配系统基础接口,常用 API 有得到系统重启原因,得到系统运行的 ticket 等

tkl_memory.h

封装了内存管理接口

tuya_error_code.h

涂鸦对一些错误类型的定义

tuya_cloud_types.h

对参数类型的封装。对变量、函数进行类型定义或修饰时应调用这里的函数。

tuya_sdk_callback.c 文件

tuya_sdk_callback.c和`tuya_sdk_callback.h 这两个文件是用户基于 tuyaOS Zigbee 开发包实现应用业务开发的回调接口集合。需要用户根据实际业务场景的需要实现相关回调、虚函数的重写,应用逻辑功能代码的填充等。一些重要的回调函数/虚函数列表如下:

函数名称

函数功能

tuya_init_first()

用户侧进行硬件初始化

tuya_init_second()

用于 Zigbee 相关初始化、如 Zigbee 设备注册等

tuya_init_third()

进入产测前回调接口

tuya_init_last()

退出产测后回调接口

tuya_main_loop()

主循环回调接口,谨慎使用,不可阻塞!

tal_zcl_general_msg_recv_callback()

ZCL general command 接收回调接口

tal_zcl_specific_msg_recv_callback()

ZCL specific command 接收回调接口

tal_zg_nwk_status_changed_callback()

Zigbee 网络状态改变回调接口

tal_zg_scene_save_callback()

add scene/store scene 命令回调接口

tal_zg_scene_recall_callback()

recall scene 命令回调接口

tal_zg_add_group_callback()

添加群组命令回调接口

tal_zg_remove_group_callback()

移除群组命令回调接口

tal_zg_reset_factory_default_callback()

恢复出厂设置命令回调接口

tuya_init_first()函数介绍

执行到tuya_init_first()的时候,tuyaOS Zigbee SDK 还没有开始初始化,所有和 Zigbee 有关的资源都不能调用。

一般在tuya_init_first()中进行硬件相关的初始化动作,例如初始化打印串口,业务相关硬件资源等。

/**
 * @brief Generally used for peripheral initialization
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_init_first(VOID_T)
{
    TAL_UART_CFG_S cfg = {
        .baudrate = 115200,
        .parity = TAL_UART_PARITY_NONE,
        .databits = TAL_UART_DATA_BIT8,
        .stopbits = TAL_UART_STOP_BIT1,
    };
    tal_uart_init(TKL_UART_PORT_ID(TKL_UART_SYS, 0), &cfg);

    tal_main_debug("/*********first init*********/\r\n");
    return OPRT_OK;
}

tuya_init_second()函数介绍

tuya_init_second()接口需要用户实现和 Zigbee 相关的初始化,包括固件信息、ep 注册、Zigbee 节点配置、入网配置、心跳设置等;

/**
 * @brief Generally used for register Zigbee device
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_init_second(VOID_T)
{
    //initialize firmware infomation
    __zg_firmware_config();

    //creat software timer
    tkl_sw_timer_create(light_ctrl_reset_cnt_clear_timer_callback, NULL, &etimer_clear_rst);
    tkl_sw_timer_create(light_ctrl_blink_timer_callback, NULL, &etimer_blink_sw);
    tkl_sw_timer_create(light_ctrl_count_down_timer_callback, NULL, &etimer_countdown);
    tkl_sw_timer_create(light_control_shade_param, NULL, &etimer_shande_param);
    tkl_sw_timer_create(light_control_shade_rgbcw, NULL, &etimer_shande_rgbcw);
    tkl_sw_timer_create(__network_join_start_delay_timer_callback, NULL, &etimer_join_start_delay);

    //register Zigbee endpoint
    tal_zg_endpoint_register(dev_endpoint_desc, GET_ARRAY_LEN(dev_endpoint_desc));

    //Zigbee node configuration
    __router_node_init();

    //Zigbee joining network configuration
    TAL_ZG_JOIN_CFG_T join_config = {
        .auto_join_power_on_flag = TRUE,
        .auto_join_remote_leave_flag = 0,
        .join_timeout = 20000,
    };
    tal_zg_join_config(&join_config);

    //time synchronization period setting
    tal_time_sync_period_set(10 * 1000);

    //set period of heartbeat
    tal_heartbeat_period_set(15 * 1000);
    tal_main_debug("/*********second init*********/\r\n");
    return OPRT_OK;
}

tuya_init_third()函数介绍

tuya_init_third()接口在进入产测前会回调,用户自定义实现。

OPERATE_RET tuya_init_third(VOID_T)
{
   // TODO:
    return OPRT_OK;
}

tuya_init_last()函数介绍

tuya_init_last()接口在产测结束后会回调,用户自定义实现,此 API 也是进入 while(1) 前最后的回调函数。

OPERATE_RET tuya_init_last(VOID_T)
{
    // TODO:
    return OPRT_OK;
}

tuya_main_loop 函数介绍

TuyaOS Zigbee 开发包基于前后台软件框架,提供了主循环内的回调接口tuya_main_loop(),由用户依据需求自定义实现相关操作注入系统主循环中。 注意:tuya_main_loop()主要用于用户侧添加调试、验证性的操作,需谨慎使用。此回调接口占用过多时间片会影响整个系统框架的稳定性!!!

/**
 * @brief user-defined callback interface in main loop.do not block!!!
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_main_loop(VOID_T)
{
    // TODO:
    return OPRT_OK;
}

当 Zigbee 网络状态发生改变后会回调tal_zg_nwk_status_changed_callback()接口,由应用侧自行定义相应的处理。

/**
 * @brief Zigbee network network change callback(user can rewrite this API)
 *
 * @param[in]   status: network status
 * @return VOID_T
 */
VOID_T tal_zg_nwk_status_changed_callback(TAL_ZG_NWK_STATUS_E status)
{

    if (status == TAL_ZG_NWK_JOIN_OK || status == TAL_ZG_NWK_POWER_ON_ONLINE)
    {
        TKL_NWK_BASIC_INFO_T v_info;
        char v_cache[256] = "";
        tkl_zg_nwk_base_info_get(&v_info);
        snprintf(v_cache, sizeof(v_cache), ">>>power=%d\r\n>>>addr=0x%04X\r\n>>>pan_id=0x%04X\r\n>>>channel=%d\r\n",
                 v_info.radio_power, v_info.nwk_addr, v_info.panid,
                 v_info.radio_channel);
        tal_main_debug("[NWK Info]\n%s\r\n", v_cache);
    }

    switch (status)
    {
    case TAL_ZG_NWK_POWER_ON_LEAVE:
    {
        tal_main_debug("power_on_leave---\r\n");
        break;
    }
    case TAL_ZG_NWK_POWER_ON_ONLINE:
    {
        tal_main_debug("power_on_online---\r\n");
        break;
    }
    case TAL_ZG_NWK_JOIN_START:
    {
        tal_main_debug("nwk_join_start---\r\n");
        break;
    }
    case TAL_ZG_NWK_JOIN_OK:
    {
        tal_main_debug("nwk_join_ok---\r\n");

        break;
    }
    case TAL_ZG_NWK_REJOIN_OK:
    {
        tal_main_debug("nwk_rejoin_ok---\r\n");
        break;
    }
    case TAL_ZG_NWK_JOIN_TIMEOUT:
    {
        tal_main_debug("nwk_join_timeout---\r\n");
        op_light_ctrl_blink_stop();
        break;
    }
    case TAL_ZG_NWK_LOST:
    {
        tal_main_debug("nwk_lost---\r\n");
        break;
    }
    case TAL_ZG_NWK_REMOTE_LEAVE:
    {
        tal_main_debug("nwk_remote_leave---\r\n");
        break;
    }
    case TAL_ZG_NWK_LOCAL_LEAVE:
    {
        tal_main_debug("nwk_local_leave---\r\n");
        break;
    }
    case TAL_ZG_NWK_MF_TEST_LEAVE:
    {
        tal_main_debug("nwk_mf_test_leave---\r\n");
        break;
    }
    default:
    {
        break;
    }
    }
}

Zigbee 的网络状态定义如下:

/*
 * Zigbee network status
*/
typedef enum {
    TAL_ZG_NWK_IDLE = 0,            ///< inner using
    TAL_ZG_NWK_POWER_ON_LEAVE,      ///< power on and device is not joined network
    TAL_ZG_NWK_POWER_ON_ONLINE,     ///< power on and device is already joined network
    TAL_ZG_NWK_JOIN_START,          ///< start joining network
    TAL_ZG_NWK_JOIN_TIMEOUT,        ///< network joining timeout
    TAL_ZG_NWK_JOIN_OK,             ///< network joined success
    TAL_ZG_NWK_LOST,                ///< network lost, lost parent
    TAL_ZG_NWK_REJOIN_OK,           ///< network rejoin ok
    TAL_ZG_NWK_REMOTE_LEAVE,        ///< remove device by remote device
    TAL_ZG_NWK_LOCAL_LEAVE,         ///< remove device by local
    TAL_ZG_NWK_MF_TEST_LEAVE,       ///< remove device by PC test tools
    TAL_ZG_NWK_ZLL_JOINED,          ///< network joined zll network
    TAL_ZG_NWK_ZLL_LEAVE,           ///< remove device Zll Reset To Factory New
} TAL_ZG_NWK_STATUS_E;

数据接收

ZCL general command 接收回调接口

tal_zcl_general_msg_recv_callback()接口处理收到 ZCL 标准定义的 general command 帧。接口定义如下:

/**
 * @brief general message receive callback
 *
 * @param msg
 * @return TAL_MSG_RET_E
 */
TAL_MSG_RET_E tal_zcl_general_msg_recv_callback(TAL_ZCL_MSG_T *msg)
{
    tal_main_debug("app gen msg cb: cluster 0x%02x, cmd %d\r\n", msg->cluster, msg->command);

    return ZCL_MSG_RET_SUCCESS;
}

ZCL 标准 general command 帧定义如下: Alt text

ZCL specific command 接收回调接口

tal_zcl_general_msg_recv_callback()接口处理收到 ZCL 标准定义的 specific command 帧。接口定义如下(cw 两路灯为例):

/**
 * @brief specific message receive callback
 *
 * @param msg
 * @return TAL_MSG_RET_E
 */
TAL_MSG_RET_E tal_zcl_specific_msg_recv_callback(TAL_ZCL_MSG_T *msg)
{
    tal_main_debug("app spec msg cb: cluster 0x%02x, cmd 0x%02x\r\n", msg->cluster, msg->command);

    Zigbee_CMD_T app_cmd_type = Zigbee_CMD_SINGLE;
    if (msg->mode == ZG_UNICAST_MODE)
    {
        app_cmd_type = Zigbee_CMD_SINGLE;
        tal_main_debug("receive single message");
    }
    else
    {
        app_cmd_type = Zigbee_CMD_GROUP;
        tal_main_debug("receive group message");
    }

    switch (msg->cluster)
    {
    case CLUSTER_ON_OFF_CLUSTER_ID:
    {
        //handle on/off cluster command
        on_off_cluster_handler(msg->command, msg->payload, msg->length, app_cmd_type);
    }
    break;
    case CLUSTER_LEVEL_CONTROL_CLUSTER_ID:
    {
        //handle level control cluster command
        level_cluster_handler(msg->command, msg->payload, msg->length, app_cmd_type);
    }
    break;

    case CLUSTER_COLOR_CONTROL_CLUSTER_ID:
    {
        //handle color control cluster command
        color_cluster_handler(msg->command, msg->payload, msg->length, app_cmd_type);
    }
    break;

    case CLUSTER_PRIVATE_TUYA_CLUSTER_ID:
    {
        break;
    }
    default:
        break;
    }

    return ZCL_MSG_RET_SUCCESS;
}

数据发送

tal_zg_send_data()接口用于用户侧发送 Zigbee 网络数据,接口原型如下:

/**
 * @enum Zigbee data for sending
 */
typedef struct {
    UINT16_T delay_time;                ///< send delay time with ms
    UINT16_T random_time;               ///< send random times with ms
    UINT16_T manu_code;                 ///< manufacturer code
    UINT8_T  manu_spec;                 ///< 1: for enable manufacturer code
    UINT8_T  zcl_id;                    ///< applicaiton sequence number(0x00~0xF0 for user, 0xF0~0xFF for SDK)
    UINT8_T  command_id;                ///< zcl command id, the detail in tuya_Zigbee_commmand.h
    ZG_ZCL_FRAME_TYPE_E frame_type;     ///< zcl frame type
    ZG_ZCL_DATA_DIRECTION_E direction;  ///< data transmission direction
    TAL_SEND_QOS_E qos;                 ///< data service quality
    TAL_ZG_ADDR_T  addr;                ///< Zigbee data address
    union {
        TAL_ZG_DATA_T      zg;
        TAL_PRIVATE_DATA_T private;
    } data;
} TAL_ZG_SEND_DATA_T;

typedef VOID_T (*TAL_SEND_RESULT_CB)(TAL_SEND_ST_E, TAL_ZG_SEND_DATA_T *);

/**
 * @brief receive ack handle
 *
 * @param[in] pdata:    point data to be send
 * @param[in] callback: send result callback
 * @param[in] timeout:  data send timeout
 * @return TRUE: none
 */
VOID_T tal_zg_send_data(TAL_ZG_SEND_DATA_T *pdata, TAL_SEND_RESULT_CB callback, UINT_T timeout);

其中,callback参数用于注册发送回调,用户可以在此回调接口中获取发送状态信息。

attribute 操作

tal_attribute_rw.h头文件中提供了 Zigbee 属性的读写接口。接口定义如下:

/**
 * @brief write Zigbee attribute
 * NOTE: only for server clusters attributes
 *
 * @param[in]   endpoint: endpoint id
 * @param[in]   cluster:  cluster id
 * @param[in]   attr_id:  attribute id
 * @param[in]   data: point to attribute data to be write
 * @param[in]   type: type of attribute
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tal_zg_write_attribute(UINT8_T endpoint,
                                UINT16_T cluster,
                                UINT16_T attr_id,
                                VOID_T* data,
                                ZG_ATTR_TYPE_E type);

/**
 * @brief read Zigbee attribute
 * NOTE: only for server clusters attributes
 *
 * @param[in]   endpoint: endpoint id
 * @param[in]   cluster:  cluster id
 * @param[in]   attr_id:  attribute id
 * @param[in]   data:     point to attribute data to be read
 * @param[in]   length:   length of attribute data
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tal_zg_read_attribute(UINT8_T endpoint,
                                UINT16_T cluster,
                                UINT16_T attr_id,
                                VOID_T *data,
                                UINT8_T length);

工程目录树

sdk_package
├── apps
│   ├── tal_Zigbee_sample # 涂鸦Zigbee两路灯demo
│   │   ├── include
│   │   │   ├── tuya_sdk_callback.h
│   │   │   ├── app_cluster_color_control.h
│   │   │   ├── app_cluster_level.h
│   │   │   ├── app_cluster_on_off.h
│   │   │   ├── app_common.h
│   │   │   ├── app_light_control.h
│   │   │   ├── app_light_tools.h
│   │   │   └── tuya_device.h
│   │   └── src
│   │       └── tuya_sdk_callback.c # 应用回调接口集文件
│   └── tuya_demo_xx # 用户新建的demo
├── adapter         # 适配层接口文件
├── build           # 适配层接口文件
├── docs            # 相关文档
├── include         # 上层头文件
├── libs            # 静态库文件
├── samples         # 用例集文件
├── scripts         # 脚本集
├── tools           # 工具文件集
├── build_app.bat   # 编译入口脚本
└── findiar.py      # 查找编译器(IAR)路径脚本

可以根据下面的提示将 PID 信息改为你创建的产品的 PID 信息,也可以不对代码进行任何更改,跳过该步骤直接进入下一步编译生成固件继续操作。

/apps/tuyaos_demo_zg_light2目录下的 appconfig.json 中 修改固件信息,注意 dev_role 和 chip_id 需填写正确,否则可能出现运行异常。

{
  "firmwareInfo": {
        "description": "this is a demon project",
        "dev_role":"sleep_end_dev",
        "image_type":"0x1602",
        "manufacture_id":"0x1002",
        "model_id":"TS0203",
        "pid": "cz8yd6r2",
        "manufacture_name": "_TZ3000_",
        "module_name":"ZSU",
        "chip_id":"efr32mg21a020f768im32"
  }
}

配网按键引脚可在 /apps/tuyaos_demo_zg_light2/src 中的 tuya_device.c 中,通过修改第 39 行的 WIFI_KEY_PIN 进行修改配网按键的引脚。

控制的 PWM 引脚,可以通过修改 ****** 中的 app_common.c 文件中的 OPERATE_RET app_pwm_init(VOID_T)函数内tal_pwm_init()注册的TKL_PWM1_CH1,选择要输出 PWM 的引脚。

使用 云模组烧录授权平台 进行烧录授权或者用 [原厂烧录工具]工具待添加对编译生成的固件进行烧录。

Start-up_flowchart

使用云模组烧录工具烧录

首先设置波特率,点击左上角文件中选中设置,选中波特率为 115200 Start-up_flowchart

点击右侧输入生产凭证,选择生产凭证,点击固件下载,并输入对应的固件 Token,选择烧录授权,点击确定 Start-up_flowchart

左侧选择串口端口号,信道选择 11 信道,选择对应的 J-Link 设备,点击运行即可进行烧录授权,假如提示烧录失败,请检查串口及 J-Link 接线是否正确 Start-up_flowchart

使用原厂烧录工具烧录

烧录器使用的是 J-Link,烧录工具为芯科下载器,在与开发板连接完成后,从 J-Link Device 下拉框中,选择要使用的烧录器,并点击左上角 adapter connect 按钮,选择完毕后点击 Target connect按钮

Start-up_flowchart

识别芯片,如提示连接失败,尝试插拔 J-Link 并检查接线; 成功识别芯片后,可以从 device info 界面查看芯片的相关信息

Start-up_flowchart

点击左侧 Flash 进入烧录界面,再每次烧录前可以点击 Erase chip 来擦除 flash 中的用户数据;需注意,擦除 flash 后,因为设备的配网信息保存在 flash 中,因此擦除后设备会失去之前的网络配置信息需重新配网。擦除 flash 后点击上方 browse 来选择要烧录的固件,并点击 flash 进行烧录。

Start-up_flowchart

烧录完成后通过涂鸦 APP 与设备配网即可实现对设备的控制。

可以通过本地按键 5s 进行配网,配网开始时,灯会闪烁提示开始配网,配网结束后停止闪烁。设备成功配网后,可以通过手机 APP 进行控制,包括开关设备,亮度控制以及色温控制,也可以通过群组方式同时控制多个设备.