此文档以开发一个低功耗门磁设备为示例,让开发者了解 TuyaOS Zigbee 开发包的基本原理,并且利用 TuyaOS Zigbee 开发包快速进行开发。最后可以在开发板上实现按键配网、门磁状态上报、电量上报等功能。

你能学到什么

了解 TuyaOS Zigbee 开发包架构以及基础能力;

了解如何开发低功耗子设备;

了解安防设备如何进行注册和数据交互。

你需要什么

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)的详细介绍

丝印名称

芯片引脚编号

备注

PB00

GPIO_NUM_7

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

PA03

GPIO_NUM_3

门磁状态检测引脚

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.ctuya_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()中进行硬件相关的初始化动作,例如初始化打印串口,业务相关硬件资源,关闭双Dongle测试功能等。

/**
 * @brief Generally used for peripheral initialization
 * 
 * @return OPERATE_RET 
 */
OPERATE_RET tuya_init_first(VOID_T)
{
    app_debug_uart_init();
    tal_log_create_manage_and_init(TAL_LOG_LEVEL_TRACE, 128, app_uart_output);

    tal_mf_test_disable_beacon_test();
    TAL_PR_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)
{
    dev_Zigbee_init();
}

/**
 * @brief register Zigbee parameter
 * @return none
 */
VOID_T dev_Zigbee_init(VOID_T)
{
    //register Zigbee endpoint
    tal_zg_endpoint_register(dev_endpoint_desc, GET_ARRAY_LEN(dev_endpoint_desc));

    //Zigbee node configuration
    app_end_device_node_init();

    //Zigbee joining network configuration
    dev_joined_config();

    tal_zg_nwk_recovery_disable(TRUE);
}

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)
{
    switch (status)
    {
        case TAL_ZG_NWK_POWER_ON_LEAVE:
        {
            TAL_PR_TRACE("power_on_leave---\r\n");
            break;
        }
        case TAL_ZG_NWK_POWER_ON_ONLINE:
        {
            TAL_PR_TRACE("power_on_online---\r\n");
            tal_sw_timer_start(etimer_network_up_delay_event_id, 5000, TAL_TIMER_ONCE);
            break;
        }
        case TAL_ZG_NWK_JOIN_START:
        {
            TAL_PR_TRACE("nwk_join_start---\r\n");
#if APP_LED_SUPPORT
            dev_start_join_indicate();
#endif
            break;
        }
        case TAL_ZG_NWK_JOIN_OK:
        {
            TAL_PR_TRACE("nwk_join_ok---\r\n");
#if APP_LED_SUPPORT
            dev_join_success_indicate();
#endif
            tal_sw_timer_start(etimer_network_up_delay_event_id, 5000, TAL_TIMER_ONCE);
            app_battery_start();
            break;
        }
        case TAL_ZG_NWK_REJOIN_OK:
        {
            TAL_PR_TRACE("nwk_rejoin_ok---\r\n");
            app_battery_start();
            break;
        }
        case TAL_ZG_NWK_JOIN_TIMEOUT:
        {
            TAL_PR_TRACE("nwk_join_timeout---\r\n");
            break;
        }
        case TAL_ZG_NWK_LOST:
        {
            TAL_PR_TRACE("nwk_lost---\r\n");
            break;
        }
        case TAL_ZG_NWK_REMOTE_LEAVE:
        {
            TAL_PR_TRACE("nwk_remote_leave---\r\n");
            tal_zg_join_start(Zigbee_JOIN_TIMEOUT_MS);
            TAL_PR_TRACE("Zigbee join start %d.\r\n", Zigbee_JOIN_TIMEOUT_MS);
            break;
        }
        case TAL_ZG_NWK_LOCAL_LEAVE:
        {
            TAL_PR_TRACE("nwk_local_leave---\r\n");
            break;
        }
        case TAL_ZG_NWK_MF_TEST_LEAVE:
        {
            TAL_PR_TRACE("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 帧。接口定义如下:

/**
 * @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_PR_TRACE("app spec msg cb: cluster 0x%02x, cmd 0x%02x\r\n", msg->cluster, msg->command);

    Zigbee_CMD_E app_cmd_type = Zigbee_CMD_SINGLE;
    if (msg->mode == ZG_UNICAST_MODE)
    {
        app_cmd_type = Zigbee_CMD_SINGLE;
        TAL_PR_TRACE("receive single message");
    }
    else
    {
        app_cmd_type = Zigbee_CMD_GROUP;
        TAL_PR_TRACE("receive group message");
    }
    TAL_PR_TRACE("app command type %d", app_cmd_type);
    switch (msg->cluster)
    {
#if IAS_ZONE_SUPPORT
        case CLUSTER_IAS_ZONE_CLUSTER_ID: {
            ias_zone_cluster_server_command_handler(msg->command, msg->payload, msg->length, app_cmd_type);
            break;
        }
#endif
        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
│   │   ├── include
│   │   │   ├── app_battery.h
│   │   │   ├── app_config.h
│   │   │   ├── app_dev_register.h
│   │   │   ├── app_gpio.h
│   │   │   ├── app_ias_zone_attribute.h
│   │   │   ├── app_ias_zone.h
│   │   │   ├── app_key.h
│   │   │   ├── app_led.h
│   │   │   ├── app_mfg.h
│   │   │   ├── attribute-size.h
│   │   │   ├── tuya_hal_gpio.h
│   │   │   ├── tuya_sdk_callback.h
│   │   │   ├── app_common.h
│   │   └── src
│   │   │   ├── tuya_sdk_callback.c # 应用回调接口集文件
│   │   │   ├── app_battery.c
│   │   │   ├── app_config.c
│   │   │   ├── app_dev_register.c
│   │   │   ├── app_gpio.c
│   │   │   ├── app_gpio_callback.c
│   │   │   ├── app_ias_zone_attribute.c
│   │   │   ├── app_ias_zone.c
│   │   │   ├── app_key_callback.c
│   │   │   ├── app_key.c
│   │   │   ├── app_led.c
│   │   │   ├── app_mfg.c
│   │   │   ├── tuya_hal_gpio.c
│   │   │   ├── tuya_sdk_callback.c
│   │   │   ├── app_common.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_door_sensor目录下的 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"
  }
}

配网按键引脚可在 tuya_sdk_callback.h 中的,通过修改 KEY_PIN 进行修改配网按键的引脚,如要使能按键功能,需将 APP_KEY_SUPPORT 宏置 1。

门磁检测引脚可在 tuya_sdk_callback.h 中的,通过修改 GPIO_SENSOR_ALARM_1_PIN 进行修改检测的引脚,如要使能门磁检功能,需将宏 IAS_ZONE_SUPPORT 及 GPIO_SENSOR_ALARM_1_ENABLE 置 1。

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

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 进行配网,配网开始时,设备网络指示灯会闪烁提示开始配网,配网结束后网络指示灯长亮3s后停止闪烁。设备成功配网后,会定时上报电量状态,当触发门磁开关时,会上报门磁状态,可于APP上实时显示。