Zigbee SDK Demo 说明

更新时间:2021-04-22 10:22:02下载pdf

本文介绍了基于涂鸦 Zigbee SDK 开发 Zigbee 设备的基本技能,讲述了目录结构和环境搭建,通过大量的 C 语言示例说明了常规接口的使用方法。

阅读本文前,请确保您已经阅读了 涂鸦 Zigbee SDK 说明

应用目录结构

app 目录包含的文件和文件夹结构如下所示:

    app
     ├── build-all.py
     ├── light
     ├── sensor
     ├── smart_plug
     └── switch
           ├── common
           │   ├── include
           │   └── src
           └── project
                 └── sample_switch1
                          ├── documents
                          ├── EFR32MG13P732F512
                          ├── EFR32MG21A020F768
                          ├── include
                          ├── src
                          ├── pre-build.py
                          └── pre-build-iar.py

下表介绍了 sample_switch1 文件夹中包含的文件说明:

目录 说明
EFR32MG13P732F512 芯片是 EFR32MG13P732F512 的编译的入口,包含 IAR 和 GCC 编译的工程文件
EFR32MG21A020F768 芯片是 EFR32MG21A020F768 的编译的入口,包含 IAR 和 GCC 编译的工程文件
include 存放应用工程的 .h 格式的头文件
src 存放应用工程的 .c 格式的源文件
pre-build.py GCC 编译用到的脚本,应用无需修改
pre-build-iar.py IAR 编译用到的脚本,应用无需修改
switch/common 如果多个功能有公共编译文件可以放到这里面

Demo 编译环境要求

涂鸦 Zigbee SDK 应用工程编译依赖于 Python 脚本, 如果使用 Linux 或者 Windows 开发调试, 需要在对应的环境下安装 Python 2.7.X 解析器。

本小节以 silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512 为例,介绍系统环境的编译要求。

IAR 编译(Windows)

  1. 安装 IAR Embedded Workbench IDE 软件 (使用 ARM 8.40.1 或更高版本)

  2. 打开 IAR 工程

  3. 用 IAR 编译( sample_switch1 为例)

    1. 进入目录 silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512

    2. 双击 tuya_sdk.eww 即可打开工程

    3. 打开工程后,进行开发编译

      注意:不要通过打开 IAR 后,然后 Open workspace 方式来打开工程,否则编译可能出错

GCC 编译(Linux)

由于 SDK 自带 GCC 编译器,无需再安装编译器。

  1. 进入工程 sample_switch1\EFR32MG13P732F512,运行以下命令行。

    #./run.sh clean   // 清除编译输出的问题
    #./run.sh build 0 // 编译 release 版本
    #./run.sh build 1 // 编译 debug 版本(带串口打印信息)
    
  2. 如果第一次编译报错的话需要注意下文件权限的配置,具体修改方法如下:

    1. 进入 silicon_lib_zigbee 主目录下。

    2. 在该目录下运行以下命令行。

      chmod –R 777 *
      
  3. 如果编译报动态库相关的错误(.So),需要在 silicon_labs_zigbee/tools 下,删除 gcc-arm-none-eabi-9-2019-q4-major 编译工具文件夹,重新解压编译工具链文件。

    tar -xvf  gcc-arm-none-eabi-9-2019-q4-major.tar.bz2
    

硬件接口

该部分描述了常见的硬件使用说明,通过 C 语言示例的方式来展示使用方法。

使用GPIO

hal_gpio.h 文件有详细的接口定义供您参考,本小节通过 C 语言示例来说明 GPIO 的使用方法。

  • 基本 GPIO 使用:

    #define LED_1_PORT  PORT_A
    #define LED_1_PIN   PIN_4
    #define LED_1_MODE  GPIO_MODE_OUTPUT_PP ///< 设置成输出模式
    #define LED_1_DOUT  GPIO_DOUT_LOW       ///< 第一次初始化输出默认电平
    #define LED_1_DRIVE GPIO_DOUT_HIGH      ///< 输出高有效,高级用法时有用
    
    #define KEY_1_PORT  PORT_A
    #define KEY_1_PIN   PIN_3
    #define KEY_1_MODE  GPIO_MODE_INPUT_PULL ///< 输入上拉或者下拉,通过KEY_1_DOUT来确定
    #define KEY_1_DOUT  GPIO_DOUT_HIGH       ///< 输入上拉
    #define KEY_1_DRIVE GPIO_LEVEL_LOW       ///< 低有效,高级用法时有用
    
    static void __gpio_int_func_t(GPIO_PORT_T port, GPIO_PIN_T pin)
    {
        uint8_t vol_level = gpio_raw_input_read_status(KEY_1_PORT, KEY_1_PIN); // /< 读取输入电平
    }
    
    static void gpio_demo(void)
    {
        const gpio_config_t gpio_ouput_config = {
            LED_1_PORT, LED_1_PIN, LED_1_MODE, LED_1_DOUT, LED_1_DRIVE,
        };  
    
        const gpio_config_t gpio_input_config = {
            KEY_1_PORT, KEY_1_PIN, KEY_1_MODE, KEY_1_DOUT, KEY_1_DRIVE,
        };
    
        gpio_raw_init(gpio_ouput_config);       ///< 设置PA4为输出模式,默认输出低
        
        gpio_raw_output_write_status(LED_1_PORT, LED_1_PIN, 1); // /< 改变输出电平到高
        
        gpio_raw_init(gpio_inputput_config);    ///< 设置PA3为输入模式,输入上拉,非中断模式
        gpio_int_register(&gpio_inputput_config, __gpio_int_func_t); // /< 设置PA3为输入模式,输入上拉,下降沿中断模式
    }
    
  • 高级 GPIO 使用:

    #define LED_1_PORT  PORT_A
    #define LED_1_PIN   PIN_4
    #define LED_1_MODE  GPIO_MODE_OUTPUT_PP ///< 设置成输出模式
    #define LED_1_DOUT  GPIO_DOUT_LOW       ///< 第一次初始化输出默认电平
    #define LED_1_DRIVE GPIO_DOUT_HIGH      ///< 输出高有效,高级用法时有用
    
    #define LED_2_PORT  PORT_D
    #define LED_2_PIN   PIN_15
    #define LED_2_MODE  GPIO_MODE_OUTPUT_PP ///< 设置成输出模式
    #define LED_2_DOUT  GPIO_DOUT_LOW       ///< 第一次初始化输出默认电平
    #define LED_2_DRIVE GPIO_DOUT_HIGH      ///< 输出高有效,高级用法时有用
    
    #define KEY_1_PORT  PORT_A
    #define KEY_1_PIN   PIN_3
    #define KEY_1_MODE  GPIO_MODE_INPUT_PULL ///< 输入上拉或者下拉,通过KEY_1_DOUT来确定
    #define KEY_1_DOUT  GPIO_DOUT_HIGH       ///< 输入上拉
    #define KEY_1_DRIVE GPIO_LEVEL_LOW       ///< 低有效,高级用法时有用
    
    #define KEY_2_PORT  PORT_A
    #define KEY_2_PIN   PIN_5
    #define KEY_2_MODE  GPIO_MODE_INPUT_PULL ///< 输入上拉或者下拉,通过KEY_1_DOUT来确定
    #define KEY_2_DOUT  GPIO_DOUT_HIGH       ///< 输入上拉
    #define KEY_2_DRIVE GPIO_LEVEL_LOW       ///< 低有效,高级用法时有用
    
    #define KEY_1_INDEX 0   ///< 初始化数组的索引
    #define KEY_2_INDEX 1
    
    #define LED_1_INDEX 0   ///< 初始化数组的索引
    #define LED_2_INDEX 1
    
    static void __dev_key_handle(uint32_t key_id, key_st_t key_st, uint32_t push_time)
    {
        switch(key_id) {
            case KEY_1_INDEX: {
                if(key_st == KEY_ST_PUSH) { ///< 按钮处于按下状态
                    if(push_time == 3000) { ///< 按钮按下持续了3000ms,后让LED2 开始闪烁
                        dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1输出500ms高,300ms低,交替输出,让灯闪烁
                    }
                }
                else {
                    if(push_time < 3000) { ///< 按钮按下持续了不到3000ms,做短按处理
                        //TODO:
                    }
                    else {
                        dev_led_stop_blink(LED_2_INDEX, DEV_IO_OFF); // /< LED2停止闪烁并灭掉
                        //TODO:
                    }
                }
                break;
            }
            
            case KEY_2_INDEX: {
                //TODO:
                break;
            }
            
            default: {
                break;
            }
        }
    }
    
    static void gpio_demo(void)
    {
        gpio_config_t gpio_ouput_config[] = {
            {LED_1_PORT, LED_1_PIN, LED_1_MODE, LED_1_DOUT, LED_1_DRIVE},
            {LED_2_PORT, LED_2_PIN, LED_2_MODE, LED_2_DOUT, LED_2_DRIVE},
        };  
    
        gpio_config_t gpio_input_config[] = {
            {KEY_1_PORT, KEY_1_PIN, KEY_1_MODE, KEY_1_DOUT, KEY_1_DRIVE},
            {KEY_2_PORT, KEY_2_PIN, KEY_2_MODE, KEY_2_DOUT, KEY_2_DRIVE},
        };
    
        gpio_button_init((gpio_config_t *)gpio_input_config, get_array_len(gpio_input_config), 50, __dev_key_handle); // /< 按钮初始化,过滤50ms抖动
        gpio_output_init((gpio_config_t *)gpio_ouput_config, get_array_len(gpio_ouput_config)); // /输出初始化,可以用更高级的函数来控制
    
        dev_led_start_blink(LED_1_INDEX, 500, 300, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1输出500ms高,300ms低,交替输出,让灯闪烁
    }
    

使用串口

hal_uart.h 文件有详细的接口定义供您参考。串口使用有一定的限制,需要在 dev_system_on_init 或以后再使用。本小节通过 C 语言示例来说明串口的使用方法。

static void __uart_rx_callback(uint8_t *data, uint16_t len)
{
    // TODO:  串口接收处理,该回调函数不在中断环境。
}

static void uart_demo(void)
{
    user_uart_config_t uart_cfg = TYZS3_USART_CONFIG_DEFAULT; // /< TYZS3 模块的串口默认值

    uart_cfg.func = __uart_rx_callback; // /< 填充串口接收回调函数
    user_uart_init(&uart_cfg);
    
    uint8_t test_data[2] = {0x01, 0x02};
    user_uart_send(UART_ID_UART0, test_data, sizeof(test_data)); // /< 串口发送
    
}

使用硬件定时器

hal_systick_timer.h 有详细的接口定义供您参考,本小节通过 C 语言示例来说明 systick_timer 的使用方法。

void __hardware_timer_func_t(TIMER_ID_T timer_id)
{
    static uint8_t test_times = 100;
    
    switch(timer_id) {
        case V_TIMER0: { ///< 1000ms 调用一次,周期进来
            if((--test_times) == 0) {
                timer_hardware_stop_100us(timer_id); // /< 执行100次后关闭
            }
            if(timer_hardware_is_active(timer_id)) {
                //TODO: 定时有效
            }
            else {
                //TODO: 定时器无效
            }
            
            //TODO:
            break;
        }
        
        case V_TIMER1: { ///< 500ms 调用一次,一次性
            break;
        }
    }
}

static void systick_timer_demo()
{
    hardware_timer_enable();
    
    timer_hardware_start_with_id(V_TIMER0, 10000, HARDWARE_TIMER_AUTO_RELOAD_ENABLE, __hardware_timer_func_t);
    timer_hardware_start_with_id(V_TIMER1, 5000, HARDWARE_TIMER_AUTO_RELOAD_DISABLE, __hardware_timer_func_t);
}

Zigbee 网络接口

tuya_zigbee_stack.h 文件有详细的接口定义供您参考,本小节通过 C 语言示例来说明 Zigbee 网络接口的使用方法。

创建 Zigbee 设备

Zigbee 设备创建和对应的网络行为是密不可分的,这部分需要您通过 dev_power_on_init() 实现。

  • 以一个继电器开关为例,介绍如何创建 Zigbee 路由设备:

    const attr_t g_group_attr_list[] = {
        GROUP_ATTR_LIST
    };
    
    const attr_t g_scene_attr_list[] = {
        SCENE_ATTR_LIST
    };
    
    #define ON_OFF_PRIVATE_ATTR_LIST \
        { 0x0000, ATTR_BOOLEAN_ATTRIBUTE_TYPE, 1, (ATTR_MASK_TOKEN_FAST),  (uint8_t*)0x00 }, \
    
    const attr_t g_light_attr_list[] = {
        ON_OFF_PRIVATE_ATTR_LIST
    };
    
    const cluster_t g_server_cluster_id[] = {
        DEF_CLUSTER_GROUPS_CLUSTER_ID(g_group_attr_list)
        DEF_CLUSTER_SCENES_CLUSTER_ID(g_scene_attr_list)
        DEF_CLUSTER_ON_OFF_CLUSTER_ID(g_light_attr_list)
    };
    
    
    #define SERVER_CLUSTER_LEN  get_array_len(g_server_cluster_id)
    
    /**
    * @note 代表一个ON_OFF_LIGHT 设备,1个endpoint,
    * 支持 Server端 group、scene、onoff cluster。
    * 分别支持GROUP_ATTR_LIST、SCENE_ATTR_LIST、ON_OFF_PRIVATE_ATTR_LIST属性
    */
    const dev_description_t g_dev_des[] = {
        { 0x01, ZHA_PROFILE_ID, ZG_DEVICE_ID_ON_OFF_LIGHT, SERVER_CLUSTER_LEN, (cluster_t *)&g_server_cluster_id[0], 0, NULL},
    };
    
    void dev_power_on_init(void)
    {
        zg_dev_config_t g_zg_dev_config;
        join_config_t cfg;
    
        /**
        * @note 描述是一个什么设备,多少 endpoint,每个 endpoint 有什么 cluster 和 attributes。
        */
        dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS); 
    
        /**
        * @note 把 Zigbee 设备设置配成一个路由设备。
        */
        memset(&g_zg_dev_config, 0, sizeof(zg_dev_config_t));
        g_zg_dev_config.dev_type = ZG_ROUTER;
        dev_register_zg_dev_config(&g_zg_dev_config);
    
        /**
        * @note 配置上电或者远程删除后立即进入组网状态,组网超时 1 分钟。
        */
        memset(&cfg, 0, sizeof(cfg));
        cfg.auto_join_power_on_flag = TRUE;
        cfg.auto_join_remote_leave_flag = TRUE;
        cfg.join_timeout = 60000;
        dev_zg_join_config(&cfg);
    
        return;
    }
    
  • 以一个单火开关为例,介绍如何创建 Zigbee 低功耗设备:

    #define ON_OFF_PRIVATE_ATTR_LIST \
        { 0x0000, ATTR_BOOLEAN_ATTRIBUTE_TYPE, 1, (ATTR_MASK_TOKEN_FAST),  (uint8_t*)0x00 }, \
    
    const attr_t g_light_attr_list[] = {
        ON_OFF_PRIVATE_ATTR_LIST
    };
    
    const cluster_t g_server_cluster_id[] = {
        DEF_CLUSTER_ON_OFF_CLUSTER_ID(g_light_attr_list)
    };
    
    
    #define SERVER_CLUSTER_LEN  get_array_len(g_server_cluster_id)
    
    /**
    * @note 代表一个ON_OFF_LIGHT 设备,1 个 endpoint,
    * 仅仅支持 Server端 onoff cluster。同时也支持ON_OFF_PRIVATE_ATTR_LIST属性
    */
    const dev_description_t g_dev_des[] = {
        { 0x01, ZHA_PROFILE_ID, ZG_DEVICE_ID_ON_OFF_LIGHT, SERVER_CLUSTER_LEN, (cluster_t *)&g_server_cluster_id[0], 0, NULL},
    };
    
    void dev_power_on_init(void)
    {
        zg_dev_config_t zg_dev_config;
        join_config_t   cfg;
    
        /**
        * @note 描述是一个什么设备,多少 endpoint,每个 endpoint 有什么 cluster 和 attributes。
        */
        dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS); 
    
        /**
        * @note 把 Zigbee 设备设置配成一个路由设备。
        */
        memset(&zg_dev_config, 0, sizeof(zg_dev_config_t));
        zg_dev_config.dev_type = ZG_SLEEPY_END_DEVICE;
        zg_dev_config.config.sleep_dev_cfg.poll_conifg.poll_failed_times = 3;     ///< 3次poll失败后进入NET_LOST状态(父节点失联)
        zg_dev_config.config.sleep_dev_cfg.poll_conifg.poll_forever_flag = TRUE;  ///< 设备保持poll
        zg_dev_config.config.sleep_dev_cfg.poll_conifg.poll_interval = 250;       ///< poll间隔是250ms
        zg_dev_config.config.sleep_dev_cfg.poll_conifg.wait_app_ack_time = 1000;  ///< 发送应用数据后跟(2+wait_app_ack_time/poll_interval)个poll,对poll_forever_flag为FALSE有效
    
        zg_dev_config.config.sleep_dev_cfg.rejoin_config.auto_rejoin_send_data = TRUE;     ///< 当发送数据失败后自动rejoin
        zg_dev_config.config.sleep_dev_cfg.rejoin_config.next_rejoin_time = 5000;          ///< 这次rejoin失败后,过5s后再进行下次rejoin
        zg_dev_config.config.sleep_dev_cfg.rejoin_config.power_on_auto_rejoin_flag = TRUE; // /< 程序启动后自动rejoin
        zg_dev_config.config.sleep_dev_cfg.rejoin_config.rejoin_try_times = 5;             ///< 一次rejoin尝试的beacon request的次数
        zg_dev_config.config.sleep_dev_cfg.rejoin_config.wake_up_time_after_join = 30000;  ///< 组网成功后poll持续的时间,对poll_forever_flag为FALSE有效
        zg_dev_config.config.sleep_dev_cfg.rejoin_config.wake_up_time_after_rejoin = 5000; // /< rejoin成功后poll持续的时间,对poll_forever_flag为FALSE有效
        zg_dev_config.beacon_send_interval_for_join = 300;    ///< 组网发送beacon request的间隔
        zg_dev_config.beacon_send_interval_for_rejoin = 300;  ///< 尝试rejoin 时,发送beacon request的间隔
        zg_dev_config.zb_scan_duration = ZB_SCAN_DURATION_3;  ///< 发送beacon request后持续接收的时间
        dev_register_zg_dev_config(&zg_dev_config);
    
    
        /**
        * @note 配置上电不进行自组网,远程删除后自动组网,组网超时1分钟。
        */
        memset(&cfg, 0, sizeof(cfg));
        cfg.auto_join_power_on_flag = FALSE;
        cfg.auto_join_remote_leave_flag = TRUE;
        cfg.join_timeout = 60000;
        dev_zg_join_config(&cfg);
    
        return;
    }
    

Zigbee 设备入网和退网

  • 通过调用 dev_zigbee_join_start() 可以实现退出之前的网络,然后尝试组网。

  • 通过调用 dev_zigbee_leave_for_user() 可以实现只退出之前的网络。

比如在按键处理调用,示例如下:

/*
 * @note __dev_key_handle 假定是注册的按键处理函数
*/
static void __dev_key_handle(uint32_t key_id, key_st_t key_st, uint32_t push_time)
{
    switch(key_id) {
        case KEY_1_INDEX: {
            if(key_st == KEY_ST_PUSH) { ///< 按钮处于按下状态
                if(push_time == 3000) { ///< 按钮按下持续了3000ms,后让LED2 开始闪烁
                     dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1输出500ms高,300ms低,交替输出,让灯闪烁
                }
            }
            else {
                if(push_time < 3000) { ///< 按钮按下持续了不到3000ms,做短按处理
                    //TODO:
                }
                else {
                    dev_zigbee_join_start(60000); // /< 按键长按3s以上后,松开组网,组网超时1分钟
                }
            }
            break;
        }
        
        case KEY_2_INDEX: {
            if(key_st == KEY_ST_PUSH) { ///< 按钮处于按下状态
                if(push_time == 3000) { ///< 按钮按下持续了3000ms,后让LED2 开始闪烁
                     dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1输出500ms高,300ms低,交替输出,让灯闪烁
                }
            }
            else {
                if(push_time < 3000) { ///< 按钮按下持续了不到3000ms,做短按处理
                    //TODO:
                }
                else {
                    dev_zigbee_leave_for_user(); // /< 按键长按3s以上后,松开退网
                    dev_led_stop_blink(LED_2_INDEX, DEV_IO_ON); // /< 点亮灯代表处于退网状态
                }
            }
            break;
        }
        
        default: {
            break;
        }
    }
}

管理 Zigbee 网关心跳

为了维护和网关之间的链路有效性,刷新路由信息。设备和网关之间会维护一个心跳信息。心跳的启动会依赖系统的软定时服务,所以需要在 dev_system_on_init() 或更晚的地方调用。

SDK 内部默认会根据设备类型产生一个周期心跳消息,您也可以显性调用更改默认行为。下面是一个在 dev_system_on_init() 实现的例子:

void dev_system_on_init(void)
{
    dev_heartbeat_set(APP_VERSION, 180000); // /< 180s+30s随机值发送一次心跳
}

Zigbee 网络状态

SDK 提供一个回调函数来报告当前设备的 Zigbee 网关网络状态,应用开发无需维护网络状态。应用要实现的回调函数例子如下:

void nwk_state_changed_callback(NET_EVT_T state)
{
    switch(state) {
        case NET_POWER_ON_LEAVE: {
            //TODO: 上电无网络状态
            break;
        }
        case NET_JOIN_START: {
            //TODO: 组网开始
            break;
        }
        case NET_JOIN_TIMEOUT: {
            //TODO: 组网失败
            break;
        }
        case NET_POWER_ON_ONLINE: {
            //TODO: 上电有网络状态
            break;
        }
        case NET_JOIN_OK: {
            //TODO: 组网成功
            break;
        }
        case NET_REJOIN_OK: {
            //TODO: 重连成功
            break;
        }
        case NET_LOST: {
            //TODO: 和父节点丢失
            break;
        }
        case NET_REMOTE_LEAVE: {
            //TODO: 远程离网通知
            break;
        }
        case NET_LOCAL_LEAVE: {
            //TODO: 本地离网通知
            break;
        }
        case NET_MF_TEST_LEAVE: {
            //TODO: 产测离网通知
            break;
        }
        default: {
            break;
        }
    }
}

接收数据

您可以通过通过 dev_msg_recv_callback() 来接收 Zigbee 网关的相关数据。以下为一个开关的数据接收示例:

ZCL_CMD_RET_T dev_msg_recv_callback(dev_msg_t *dev_msg)
{
    ZCL_CMD_RET_T result = ZCL_CMD_RET_SUCCESS;

    switch (dev_msg->cluster) {
        case CLUSTER_PRIVATE_TUYA_CLUSTER_ID: { // 私有数据处理
            uint8_t len = dev_msg->data.bare_data.len;
            uint8_t *data = dev_msg->data.bare_data.data;
            //TODO: 私有协议处理
            break;
        }

        // 标准数据处理
        case CLUSTER_ON_OFF_CLUSTER_ID: {
            attr_value_t *attr_list = dev_msg->data.attr_data.attr_value;
            uint8_t attr_sums = dev_msg->data.attr_data.attr_value_sums;
            uint8_t i;

            for(i=0; i<attr_sums; i++) {
                switch(attr_list[i].cmd) {
                    case CMD_OFF_COMMAND_ID: {
                        //TODO:关命令
                        break;
                    }
                    case CMD_ON_COMMAND_ID: {
                        //TODO:开命令
                        break;
                    }
                    case CMD_TOGGLE_COMMAND_ID: {
                        //TODO:取反命令
                        break;
                    }
                    default: {
                        break;
                    }
                }
                break;
            }
            
        }
        default:
            // Unrecognized cluster ID, error status will apply.
            break;
    }

    return result;
}

Zigbee 全局类的写命令通过 dev_msg_write_attr_callback_ext() 或者 dev_msg_write_attr_callback() 来通知应用层

void dev_msg_write_attr_callback(uint8_t endpoint, CLUSTER_ID_T cluster, uint16_t attr_id)
{
    //TODO: 远程写属性成功后调用该函数
    
    return;
}

void dev_msg_write_attr_callback_ext(
    uint8_t endpoint,
    CLUSTER_ID_T cluster,
    uint16_t attr_id,
    uint8_t mask,
    uint16_t manufacturer_code,
    uint8_t type,
    uint8_t size,
    uint8_t* value)
{
    //TODO: 远程写属性成功后调用该函数,附带了原始数据参数

    return;
}

数据发送

以下示例通过 send_data 上报了一个属性值给 Zigbee 网关:

static void __send_result_cb(SEND_ST_T st, dev_send_data_t *msg)
{
    switch(st) {
        case SEND_ST_OK: {
            //TODO: 发送成功
            break;
        }
        default: {
            //TODO: 发送失败
            break;
        }
    }
}

static void send_data_demo(void)
{
    dev_send_data_t send_data;

    memset(&send_data, 0, sizeof(dev_send_data_t));

    send_data.zcl_id = 0;  ///< 用户自定义 id,发送成功失败回调会传回该参数
    send_data.qos = QOS_1; // /< 如果没有收到ACK,会重传
    send_data.direction = ZCL_DATA_DIRECTION_SERVER_TO_CLIENT;
    send_data.command_id = CMD_REPORT_ATTRIBUTES_COMMAND_ID;       ///< 通用上报属性的命令
    send_data.addr.mode = SEND_MODE_GW;                            ///< 发送给网关
    send_data.addr.type.gw.cluster_id = CLUSTER_ON_OFF_CLUSTER_ID; // /< 开关属性
    send_data.addr.type.gw.src_ep = 1; // /< 设备 endpoint
    send_data.delay_time = 0;   ///< 延时发送时间
    send_data.random_time = 0;  ///< 随机发送时间范围
    send_data.data.zg.attr_sum = 1;  ///< 上报属性的个数
    send_data.data.zg.attr[0].attr_id = ATTR_ON_OFF_ATTRIBUTE_ID; // /< 开光状态属性
    send_data.data.zg.attr[0].type = ATTR_BOOLEAN_ATTRIBUTE_TYPE; // /< 属性数据类型
    send_data.data.zg.attr[0].value_size = 1;                     ///< 属性数据长度
    send_data.data.zg.attr[0].value[0] = 1; // /< 1代表开,0代表关状态
    dev_zigbee_send_data(&send_data, __send_result_cb, 1000); // /< 发送,1000代表报文最大重传持续时间是1秒
}

场景管理

当增加情景时,保存用户自定义数据,调用情景时读取该数据给用户,网络收到增加场景命令时调用 dev_scene_add_callback() 接口。

static uint8_t g_onoff_status = 0; // 假定代表开关状态
void dev_scene_add_callback(uint8_t endpoint, uint8_t *out_data, uint8_t *in_out_len)
{
    out_data[0] = g_onoff_status;
    *in_out_len = 1;
    
    //TODO:保存设备状态,增加场景时调用该函数
}

void dev_scene_recall_callback(uint8_t endpoint, const scene_save_data_t *in_data)
{
    //TODO:执行场景
    switch(in_data->type) {
        case SCENE_DATA_TYPE_USER_DEFINE: {
            if(in_data->data[0] == 1) {
                g_onoff_status = 1;
                //TODO:执行开
            }
            else if(in_data->data[0] == 0) {
                g_onoff_status = 1;
                //TODO:执行关
            }
            break;
        }
        default: {
            break;
        }
    }
    return;
}

对于设备是一个场景面板,而不是是执行设备时,需要如下特殊操作:

  • 添加场景之前是否移除 endpoint 下所有场景, 用于场景开关添加场景时使用,返回 FALSE 代表不移除所有, 返回 TRUE 代表移除所有,默认返回 FALSE。

    bool_t zigbee_sdk_scene_remove_before_add(uint8_t endpoint)
    {
    return TRUE; // 场景面板需要实现固定返回 TRUE
    }
    
  • 场景开关需要 recall scene 时使用 dev_scene_recall_send_command 接口。

    bool_t devGetSceneCallback(uint16_t endpoint, uint16_t* groupId, uint8_t* sceneId);
    bool_t dev_scene_recall_send_command(uint8_t endpoint, uint16_t groupId, uint8_t sceneId);
    
    static void __scene_panel_demo(void)
    {
        bool_t result = FALSE;
        uint16_t group_id = 0;
        uint8_t  scene_id = 0;
        
        result = devGetSceneCallback(1, &group_id, &scene_id);  ///< 获取 endpoint=1 的场景 id 和组 id
        if(result) {
            dev_scene_recall_send_command(1, group_id, scene_id); // /< 发送 endpoint=1 的场景
        }
    }
    

系统接口

管理内存

目前 SDK 提供的内存分配使用通用的 malloc 和 free 函数,SDK 内部和应用共享 10 KB RAM。

管理事件

SDK 整个系统是基于事件驱动,所有的事件都是一次性事件。相关使用示例如下:

#define SDK_TEST_EVT1    DEV_EVT_1
#define SDK_TEST_EVT2    DEV_EVT_2
#define SDK_TEST_EVT3    DEV_EVT_3
#define SDK_TEST_EVT4    DEV_EVT_4

static void __dev_evt_callback(uint8_t evt)
{
    switch(evt) {
        case SDK_TEST_EVT1: {
            //TODO: 该事件在 dev_system_on_init 延时 1s 后执行
            dev_timer_start_with_callback(SDK_TEST_EVT2, 0, __dev_evt_callback);
            break;
        }
        
        case SDK_TEST_EVT2: {
            //TODO: 该事件在 SDK_TEST_EVT1 后延时 0ms 后执行
            break;
        }
        
        case SDK_TEST_EVT3: {
            //TODO: 该事件在 dev_system_on_init 延时 2s 后执行,并再次注册延时2s后执行,所以会周期 2s 一直执行
            dev_timer_start_with_callback(SDK_TEST_EVT3, 2000, __dev_evt_callback);
            break;
        }
        
        case SDK_TEST_EVT4: {
            //TODO: 该事件在 SDK_TEST_EVT1 后延时10s后执行,取消 SDK_TEST_EVT3 的周期执行。
            dev_timer_stop(SDK_TEST_EVT3);
            break;
        }
        
        default: {
            break;
        }
    }
}

void dev_system_on_init(void)
{
    dev_timer_start_with_callback(SDK_TEST_EVT1, 1000, __dev_evt_callback);
    dev_timer_start_with_callback(SDK_TEST_EVT3, 2000, __dev_evt_callback);
    dev_timer_start_with_callback(SDK_TEST_EVT4, 10000, __dev_evt_callback);
}

应用层开发

接口目录

include 目录下的文件为每个应用工程都需要使用到的头文件,文件结构如下所示:

include/
  ├── hal_adc.h
  ├── hal_battery.h
  ├── hal_flash.h
  ├── hal_gpio.h
  ├── hal_i2c.h
  ├── hal_pwm.h
  ├── hal_spi.h
  ├── hal_systick_timer.h
  ├── hal_timer.h
  ├── hal_uart.h
  ├── tuya_app_timer.h
  ├── tuya_mcu_os.h
  ├── tuya_mf_test.h
  ├── tuya_oem_kit.h
  ├── tuya_tools.h
  ├── tuya_zigbee_modules.h
  ├── tuya_zigbee_sdk.h
  ├── tuya_zigbee_stack.h
  ├── type_def.h
  ├── zigbee_attr.h
  ├── zigbee_cmd.h
  ├── zigbee_dev_template.h
  ├── zigbee_modules.h
  └── zigbee_raw_cmd_api.h

具体文件具体描述如下表所示:

文件 说明
hal_xxx.h 硬件抽象层接口
tuya_app_timer.h 设备日期时间相关接口
tuya_mcu_os.h 事件使用相关接口
tuya_mf_test.h 产测相关接口
tuya_oem_kit.h 涂鸦 OEM 配置相关接口
tuya_zigbee_modules.h 涂鸦模组相关接口
tuya_zigbee_sdk.h 应用层需要实现的接口
tuya_zigbee_stack.h 涂鸦 SDK 和 Zigbee 协议栈有关的接口
zigbee_attr.h Zigbee 标准属性定义
zigbee_cmd.h Zigbee 标准命令定义
zigbee_dev_template.h 常见 Zigbee 设备定义模板
zigbee_raw_cmd_api.h Silicon Lab 协议栈原生命令发送封装

实现对应函数

在新建工程时会自动生成 project_name_callback.c 文件,文件里的接口为应用需要实现的接口函数。相关使用示例如下:

#include "zigbee_sdk.h"
dev_power_on_init(); // 上电初始化
dev_system_on_init(); // 协议栈和系统基本组件启动后初始化
dev_recovery_factory(); // 恢复出厂设置用户回调
dev_mf_test_callback(); // 生产测试用户回调
dev_msg_recv_callback(); // 收数据回调
dev_scene_add_callback(); // 增加情景回调
dev_scene_recall_callback(); // 调用情景回调
nwk_state_changed_callback(); // 网络状态改变回调
user_evt1_handle(); // 用户自定义事件1处理函数
user_evt2_handle(); // 用户自定义事件2处理函数
user_evt3_handle(); // 用户自定义事件3处理函数
.....

配置固件信息

固件信息配置在对应工程下的 package.json 文件里进行配置,重要片段如下所示。

package.json
{
    "fimwareInfo": {
        "name": "oem_si32_zg_plug_10A_USB_dlx", // 固件名称
        "description": "Zigbee switch common", // 固件描述
        "version": "1.0.9", // 软件版本号
        "bv_version": "1.0", // 目前固定
        "ic": "efr32mg13p732gm48", // Zigbee 芯片型号
        "ota_image_type":"0x1602", // OTA升级识别使用,目前固定
        "manufacture_id":"0x1002", // OTA升级识别使用,目前固定
        "model_id":"TS0105", // 设备组网快速识别设备类型使用
        "pid": "ZHahfZRP", // 设备默认产品ID,产品经理分配
        "manufacture_name": "TUYATEC-" // 产品ID前缀,网关识别用什么协议来交互
    }
}

app 框架介绍

app/switch 目录包含的文件和文件夹如下所示:

app/switch/
├── common
│    ├── include
│    └── src
└── project
    └── sample_switch1
            ├── documents
            ├── EFR32MG13P732F512
            ├── EFR32MG21A020F768
            ├── include
            ├── src
            ├── iar_clear.bat
            ├── pre-build.py
            └── pre-build-iar.py

不同的品类文件下有 commonproject 两个文件夹:

  • common 文件夹下包含 includesrc 两个文件,这两个文件夹做为该品类公共资源池文件夹,分别可以存放公共的 C 文件和 H 文件。
  • project 文件下为对应的应用工程文件,同时 project 下有一个 demo 空工程,例如开关下的 demo 是 sample_switch1,可用于新建工程使用。

新建工程

以创建一个开关工程为例,进入 switch/project 路径,复制 project 文件夹下的 sample_switch1 文件夹,并且重命名为 switch。进入 switch 文件夹下的 EFR32MG13P732F512 可以看到整个工程概况。

  • sample_switch1 的 EFR32MG13P732F512 平台下的目录结构如下所示:

    app/switch/project/sample_switch1/EFR32MG13P732F512
    │── build
    │    ├── exe
    │    ├── lst
    │    └── obj
    ├── iar_make.bat
    ├── makefile
    ├── package.json
    ├── run.sh
    └── tuya_sdk.eww
    
  • 文件和目录说明:

    目录/文件 说明
    run.sh Linux环境下编译运行脚本
    tuya_sdk.eww IAR工程文件
    package.json 设备基础信息配置文件
    exe 目录中生成最终的生产固件和OTA固件,分别是 xx_QIO_1.0.x.s37xx_QIO_1.0.x.bin

设备基础信息

package.json 里的 fimwareInfo 记录设备的重要信息(pidmode_idmanufacture_name 等信息需要涂鸦提供)

"fimwareInfo": {
    "name": "sample_switch1",
    "description": "this is a project demo",
    "version": "1.0.0",
    "bv_version": "1.0",
    "ic": "EFR32MG13P732F512",
    "ota_image_type":"0x1602",
    "manufacture_id":"0x1002",
    "model_id":"TS0001",
    "pid": "nPGIPl5D",
    "manufacture_name": "_TZ3000_",
    "module_name":"TYZS3"
},

硬件配置信息

相关使用示例如下:

// 按键输入IO配置
const gpio_config_t gpio_input_config[] =
{
    {KEY_1_PORT, KEY_1_PIN, KEY_1_MODE, KEY_1_OUT, KEY_1_DRIVER},
};

// LED指示灯,继电器 等输出IO口配置  
const gpio_config_t gpio_ouput_config[] =
{
    {LED_1_PORT, LED_1_PIN, LED_1_MODE, LED_1_DOUT, LED_1_DRIVE},
    {NET_LED_2_PORT, NET_LED_2_PIN, NET_LED_2_MODE, NET_LED_2_DOUT, NET_LED_2_DRIVE},
    {RELAY_1_PORT, RELAY_1_PIN, RELAY_1_MODE, RELAY_1_DOUT, RELAY_1_DRIVE},
}; 

设备的 Cluster 和 Attribute 配置信息

相关使用示例如下:

// 设备包含的Cluster 和 属性配置,可以在 Zigbee_dev_template.h 中找到相应的模板,也可以按照模板的格式用户自己定义

const attr_t g_group_attr_list[] =
{
    GROUP_ATTR_LIST
};

const attr_t g_scene_attr_list[] =
{
    SCENE_ATTR_LIST
};

const attr_t g_light_attr_list[] =
{
    ON_OFF_LIGHT_ATTR_LIST
};

const cluster_t g_server_cluster_id[] =
{
    DEF_CLUSTER_GROUPS_CLUSTER_ID(g_group_attr_list)
    DEF_CLUSTER_SCENES_CLUSTER_ID(g_scene_attr_list)
    DEF_CLUSTER_ON_OFF_CLUSTER_ID(g_light_attr_list)
};

#define SERVER_CLUSTER_LEN  get_array_len(g_server_cluster_id)

const dev_description_t g_dev_des[] =
{
    { 1, ZHA_PROFILE_ID, ZG_DEVICE_ID_ON_OFF_LIGHT, SERVER_CLUSTER_LEN, (cluster_t *)&g_server_cluster_id[0], 0, NULL },
};



设备上电初始化函数

相关使用示例如下:

//设备上电后,配置设备基础信息,配置硬件信息

void dev_power_on_init(void)
{
    dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS);
    zg_dev_config_t g_zg_dev_config;
    g_zg_dev_config.dev_type = ZG_ROUTER;
    g_zg_dev_config.config.router_cfg.reserved = 0;
    dev_register_zg_dev_config(&g_zg_dev_config);
    join_config_t cfg;
    cfg.auto_join_power_on_flag = 1;
    cfg.auto_join_remote_leave_flag = 1;
    cfg.join_timeout = ZIGBEE_JOIN_MAX_TIMEOUT;
    dev_zg_join_config(&cfg);
    dev_zigbee_recovery_network_set(TRUE); // nwk recover open
    gpio_button_init((gpio_config_t *)gpio_input_config, get_array_len(gpio_input_config), 50, __dev_key_handle);
    gpio_output_init((gpio_config_t *)gpio_ouput_config, get_array_len(gpio_ouput_config));
}

系统初始化函数

相关使用示例如下:

//在串口产测超时后进入该函数

void dev_system_on_init(void)
{
    __dev_status_load();
}

Zigbee 网络状态变动回调函数

相关使用示例如下:

//可以在Zigbee网络发生变动时,做出相应的动作

void nwk_state_changed_callback(NET_EVT_T state)
{
    switch(state)
    {
        case NET_POWER_ON_LEAVE:
        {
            break;
        }
        case NET_JOIN_START:
        {
            dev_led_start_blink(NET_LED_1_IO_INDEX, 250, 250, DEV_LED_BLINK_FOREVER, DEV_IO_OFF);
            break;
        }
        case NET_JOIN_TIMEOUT:
        {
            dev_led_stop_blink(NET_LED_1_IO_INDEX, DEV_IO_ON);
            break;
        }
        case NET_POWER_ON_ONLINE:
        case NET_JOIN_OK:
        {
            dev_led_stop_blink(NET_LED_1_IO_INDEX, DEV_IO_OFF);
            break;
        }
        case NET_LOST:
        {
            break;
        }
        case NET_REMOTE_LEAVE:
        {
            break;
        }
        case NET_LOCAL_LEAVE:
        {
            break;
        }
        default:
        {
            break;
        }
    }
}

设备产测处理回调函数

相关使用示例如下:

// 设备收到产测指令时,进入该函数 

MF_TEST_RET_T dev_mf_test_callback(MF_TEST_CMD_T cmd, uint8_t *args, uint16_t arg_len)
{
    switch(cmd)
    {
        case MF_TEST_LED_ON_ALL:
        {
            dev_led_stop_blink(NET_LED_1_IO_INDEX, DEV_IO_ON);
            dev_led_stop_blink(LED_1_IO_INDEX, DEV_IO_ON);
            break;
        }
        case MF_TEST_LED_OFF_ALL:
        {
            dev_led_stop_blink(NET_LED_1_IO_INDEX, DEV_IO_OFF);
            dev_led_stop_blink(LED_1_IO_INDEX, DEV_IO_OFF);
            break;
        }
        case MF_TEST_LED_BLINK_ALL:
        {
            dev_led_start_blink(NET_LED_1_IO_INDEX, 500, 500, 4, DEV_IO_OFF);
            dev_led_start_blink(LED_1_IO_INDEX, 500, 500, 4, DEV_IO_OFF);
            break;
        }
        case MF_TEST_RELAY_ON_ALL:
        {
            dev_led_stop_blink(RELAY_1_IO_INDEX, DEV_IO_ON);
            break;
        }
        case MF_TEST_RELAY_OFF_ALL:
        {
            dev_led_stop_blink(RELAY_1_IO_INDEX, DEV_IO_OFF);
            break;
        }
        case MF_TEST_RELAY_BLINK_ALL:
        {
            dev_led_start_blink(RELAY_1_IO_INDEX, 500, 500, 4, DEV_IO_OFF);
            break;
        }
        case MF_TEST_BUTTON:
        {
            g_work_st = DEV_WORK_ST_TEST;
            return MF_TEST_DOING;
        }
        case MF_TRANSFER:
        {
        }
        default :
        {
            break;
        }
    }
    return MF_TEST_SUCCESS;
}

Zigbee 信息接收回调函数

相关使用示例如下:

// 设备收到ZCL层信息后,会进入该回调函数

ZCL_CMD_RET_T dev_msg_recv_callback(dev_msg_t *dev_msg)
{
    ZCL_CMD_RET_T result = ZCL_CMD_RET_SUCCESS;
    switch (dev_msg->cluster)
    {
        case CLUSTER_PRIVATE_TUYA_CLUSTER_ID: //private data processing
        {
            ////uint8_t len = dev_msg->data.bare_data.len;
            ////uint8_t *data = dev_msg->data.bare_data.data;
            //todo:
            break;
        }
        case CLUSTER_ON_OFF_CLUSTER_ID: //standard data processin
        {
            attr_value_t *attr_list = dev_msg->data.attr_data.attr_value;
            uint8_t attr_sums = dev_msg->data.attr_data.attr_value_sums;
            uint8_t i;
            for(i = 0; i < attr_sums; i++)
            {
                switch(attr_list[i].cmd)
                {
                    case CMD_OFF_COMMAND_ID:
                    {
                        __dev_switch_op(dev_msg->endpoint, DEV_IO_OFF);
                        __dev_report_onoff_msg(dev_msg->endpoint, QOS_0);
                        break;
                    }
                    case CMD_ON_COMMAND_ID:
                    {
                        __dev_switch_op(dev_msg->endpoint, DEV_IO_ON);
                        __dev_report_onoff_msg(dev_msg->endpoint, QOS_0);
                        break;
                    }
                    case CMD_TOGGLE_COMMAND_ID:
                    {
                        __dev_switch_op(dev_msg->endpoint, (DEV_IO_ST_T)!g_relay_onoff_status[dev_msg->endpoint - 1]);
                        __dev_report_onoff_msg(dev_msg->endpoint,QOS_0);
                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
                break;
            }
        }
        default:
            // Unrecognized cluster ID, error status will apply.
            break;
    }
    return result;
}

串口配置信息接口

相关使用示例如下:

// 串口产测时,会用到的配置串口,产测超时后,串口被禁用,如需重新配置串口,可在 dev_system_on_init() 函数中重新初始化串口信息

user_uart_config_t* mf_test_uart_config(void)
{
    static user_uart_config_t config;
    memset(&config, 0, sizeof(user_uart_config_t));
    if (MODULE_NAME == TYZS2R)
    {
        user_uart_config_t default_config = TYZS2R_USART_CONFIG_DEFAULT;
        memcpy(&config, &default_config, sizeof(user_uart_config_t));
    }
    else if(MODULE_NAME == TYZS5)
    {
        user_uart_config_t default_config = TYZS5_USART_CONFIG_DEFAULT;
        memcpy(&config, &default_config, sizeof(user_uart_config_t));
    }
    else
    {
        user_uart_config_t default_config = TYZS3_USART_CONFIG_DEFAULT;
        memcpy(&config, &default_config, sizeof(user_uart_config_t));
    }
    return &config;
}

附录一:重要函数和数据接口说明

该函数的参数描述 Zigbee 的整个网络生命周期的所有行为,应用层只需配置即可,SDK 会根据配置的参数来执行网络行为。您必须在 dev_power_on_init() 里面调用该函数:

extern void dev_register_zg_dev_config(zg_dev_config_t *config);

重要数据描述:

  • 要创建的设备类型:路由设备,非休眠类终端设备,休眠类终端设备

    typedef enum {
        ZG_ROUTER = 0,
        ZG_END_DEVICE,
        ZG_SLEEPY_END_DEVICE,
    }ZG_DEV_T;
    
  • Join 和 rejoin 的相关参数,时间的单位都是 ms:

    typedef struct {
        uint32_t next_rejoin_time;  当1轮rejoin失败后,下一轮rejoin的间隔
        uint32_t wake_up_time_after_join;  join成功后一直poll的时间
        uint32_t wake_up_time_after_rejoin; rejoin成功后一直poll的时间
        uint8_t rejoin_try_times; 丢失父节点后尝试发送几次beacon request
        bool_t  power_on_auto_rejoin_flag;  上电后是否重新rejoin
        bool_t  auto_rejoin_send_data; 发送zcl数据时,如果丢失父节点,是否自动rejoin
    }zg_rejoin_config_t;
    

休眠设备轮训的配置,时间的单位都是 ms:

typedef struct {
    uint16_t poll_interval;   //poll的间隔发送zcl数据后接着发送wait_App_ack_time/poll_interval+2个poll
    uint16_t wait_App_ack_time; 
    bool_t  poll_forever_flag; // 一直poll是否打开 
    uint8_t poll_failed_times; // 几次poll失败后,网络切换到丢失父节点的状态
}zg_poll_config_t;

休眠设备需要填写的配置:

typedef struct {
    zg_poll_config_t  poll_conifg;
    zg_rejoin_config_t rejoin_config;
}zg_sleep_end_device_config;

非休眠的终端设备需要填写的配置:

typedef struct {
    zg_rejoin_config_t rejoin_config;
}zg_end_device_config;

路由设备需要填写的配置,目前仅仅保留字段,无需填写:

typedef struct {
    uint8_t reserved; // 目前不关心该值
}zg_router_config;

Rejoin 扫描信道的方式配置:

typedef enum {
    ZG_SCAN_POLICY_CURR_CHANNEL_FOREVER = 0,  // 只扫描当前信道 
    ZG_SCAN_POLICY_ALL_CHANNEL_ONCE // 先扫描当前信道,最后一次扫描所有信道
}ZG_SCAN_POLICY_T;

发送 beacon request 等待 beacon 的时间,在该时间内射频的接收是一直开着,时间越长约耗电,也能一次收集更多的 beacon,更容易找到合适的设备,目前该选项只适用于非路由设备,路由设备是固定 ZB_SCAN_DURATION_3 。

typedef enum {
    ZB_SCAN_DURATION_0 = 0,  // 19.2 ms
    ZB_SCAN_DURATION_1,   // 38.4 ms
    ZB_SCAN_DURATION_2,   // 76.8 ms
    ZB_SCAN_DURATION_3,   // 153.6 ms
    ZB_SCAN_DURATION_4,   // 307.2 ms
    ZB_SCAN_DURATION_5,   // 614.4 ms
    ZB_SCAN_DURATION_6,   // 1.23 sec
    ZB_SCAN_DURATION_7,   // 2.46 sec
    ZB_SCAN_DURATION_8,   // 4.92 sec
    ZB_SCAN_DURATION_9,   // 9.83 sec
    ZB_SCAN_DURATION_10,  // 19.66 sec
    ZB_SCAN_DURATION_11,  // 39.32 sec
    ZB_SCAN_DURATION_12,  // 78.64 sec
    ZB_SCAN_DURATION_13,  // 157.28 sec
    ZB_SCAN_DURATION_14,  // 314.57 sec
}ZB_SCAN_DURATION_T;

typedef struct {
    ZG_DEV_T dev_type;
    union{
        zg_sleep_end_device_config sleep_dev_cfg;
        zg_end_device_config       end_dev_cfg;
        zg_router_config           router_cfg;
    }config; //  三选一进行填充,根据 dev_type 来对号入座
    uint16_t beacon_send_interval_for_join;   // join时发送beacon的间隔
    uint16_t beacon_send_interval_for_rejoin; //  rejoin时发送beacon的间隔,非路由设备有效
    ZB_SCAN_DURATION_T zb_scan_duration;
}zg_dev_config_t;

文档版本

版本 编写/修订说明 修订日期
1.0.1 创建文档 2020.03.30