Zigbee SDK Demo Guide

Last Updated on : 2022-02-23 06:44:20

This topic describes the basic skills to develop Zigbee device based on Tuya Zigbee SDK. It explains the directory structure, environment construction, and the use of common interfaces through many C programming examples.

Before reading this documentation, make sure you have read Zigbee SDK Guide.

Application directory structure

The file and folder structure in the app directory is as follows:

    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

The following table describes the files in sample_switch1 folder:

Directory Description
EFR32MG13P732F512 The entry to compiling EFR32MG13P732F512, including the project files used for IAR and GCC compilation
EFR32MG21A020F768 The entry to compiling EFR32MG21A020F768, including the project files used for IAR and GCC compilation
include Store the header file of the application project in .h format
src Store the source file of the application project in .c format
pre-build.py The script used for GCC compilation. The application is not required to be modified
pre-build-iar.py The script used for IAR compilation. The application is not required to be modified
switch/common If multiple functions have common compiled files, you can put them here

Environment requirements of demo compilation

Tuya Zigbee SDK application project compilation is based on Python scripts. If you use Linux or Windows for development debugging, you need to install Python 2.7.X parser in the corresponding environment.

This section takes silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512 as an example to describe the compilation requirements for system environment.

IAR compilation (Windows)

  1. Install IAR Embedded Workbench IDE software (use ARM 8.40.1 or higher)

  2. Open the IAR project

  3. Compile with IAR (sample_switch1 as an example)

    1. Go to silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512

    2. Double click tuya_sdk.eww to open the project

    3. Then, start compilation

      Note: Do not open the project by opening IAR and then “Open workspace”. Otherwise, compilation errors might occur.

GCC compilation (Linux)

The SDK provides a GCC compiler. There is no need to install other compilers.

  1. Enter the project sample_switch1\EFR32MG13P732F512 and run the following command lines.

    #./run.sh clean // Clean compiler error output 
    #./run.sh build 0 // Compile the release version 
    #./run.sh build 1 // Compile the debug version (with serial port print information)
    
  2. If an error is reported during the first compilation, check the configuration of file permissions. The modification methods are as follows:

    1. Enter the home directory of silicon_lib_zigbee.

    2. Run the following command line in this directory.

      chmod –R 777 *
      
  3. If the compiler reports dynamic library (.So) errors, you need to delete gcc-arm-none-eabi-9-2019-q4-major compilation tool folder under silicon_labs_zigbee/tools, and extract the compilation toolchain file.

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

Hardware interface

This part describes the common hardware interfaces and demonstrates how to use them through C programming examples.

GPIO usage

The hal_gpio.h file introduces the detailed interface definition for your reference. This section explains how to use GPIO through C programming examples.

  • Basic use of GPIO:

     #define LED_1_PORT PORT_A 
     #define LED_1_PIN PIN_4 
     #define LED_1_MODE  GPIO_MODE_OUTPUT_PP ///< Set to output mode
     #define LED_1_DOUT  GPIO_DOUT_LOW       ///< On its first initialization, output the default electrical level
     #define LED_1_DRIVE GPIO_DOUT_HIGH      ///< Active high. It is useful for advanced usage
    
     #define KEY_1_PORT  PORT_A
     #define KEY_1_PIN   PIN_3
     #define KEY_1_MODE  GPIO_MODE_INPUT_PULL ///< Input pull-up or pull-down, determined by KEY_1_DOUT
     #define KEY_1_DOUT  GPIO_DOUT_HIGH       ///< Input pull-up 
     #define KEY_1_DRIVE GPIO_LEVEL_LOW       ///< Active low. It is useful for advanced usage
    
     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); // /< Read the input electrical level
     }
    
     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);       ///< Set PA4 to output mode, the default output is low electrical level
         gpio_raw_output_write_status(LED_1_PORT, LED_1_PIN, 1); ///< Change the output electrical level to high
         gpio_raw_init(gpio_inputput_config);    ///< Set PA3 to input mode, input pull-up, and non-interrupt mode
         gpio_int_register(&gpio_inputput_config, __gpio_int_func_t); ///< Set PA3 to input mode, input pull-up, falling edge interrupt mode
     }
    
  • Advanced use of GPIO:

     #define LED_1_PORT  PORT_A
     #define LED_1_PIN   PIN_4
     #define LED_1_MODE  GPIO_MODE_OUTPUT_PP ///< Set to output mode
     #define LED_1_DOUT  GPIO_DOUT_LOW       ///< On its first initialization, output the default electrical level
     #define LED_1_DRIVE GPIO_DOUT_HIGH      ///< Active high. It is useful for advanced usage
     
     #define LED_2_PORT  PORT_D
     #define LED_2_PIN   PIN_15
     #define LED_2_MODE  GPIO_MODE_OUTPUT_PP ///< Set to output mode
     #define LED_2_DOUT  GPIO_DOUT_LOW       ///< On its first initialization, output the default electrical level
     #define LED_2_DRIVE GPIO_DOUT_HIGH      ///< Active high. It is useful for advanced usage
     
     #define KEY_1_PORT  PORT_A
     #define KEY_1_PIN   PIN_3
     #define KEY_1_MODE  GPIO_MODE_INPUT_PULL ///< Input pull-up or pull-down, determined by KEY_1_DOUT
     #define KEY_1_DOUT  GPIO_DOUT_HIGH       ///< Input pull-up 
     #define KEY_1_DRIVE GPIO_LEVEL_LOW       ///< Active low. It is useful for advanced usage
     
     #define KEY_2_PORT  PORT_A
     #define KEY_2_PIN   PIN_5
     #define KEY_2_MODE  GPIO_MODE_INPUT_PULL ///< Input pull-up or pull-down, determined by KEY_1_DOUT
     #define KEY_2_DOUT  GPIO_DOUT_HIGH       ///< Input pull-up 
     #define KEY_2_DRIVE GPIO_LEVEL_LOW       ///< Active low. It is useful for advanced usage
     
     #define KEY_1_INDEX 0   ///< Initialize array index
     #define KEY_2_INDEX 1
     
     #define LED_1_INDEX 0   ///< Initialize array index
     #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) { ///< The key is being held down
                     if(push_time == 3000) { ///< The key is held down for 3,000 ms, and then LED2 starts to flicker
                         dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1 alternately outputs high electrical level for 500 ms and low electrical level for 300 ms to make the LED flicker
                     }
                 }
                 else {
                     if(push_time < 3000) { ///< The key is held down for less than 3,000 ms, which is processed as the short press
                         //TODO:
                     }
                     else {
                         dev_led_stop_blink(LED_2_INDEX, DEV_IO_OFF); // /< LED2 stops flickering and is off
                         //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); // /< Key initialization, filtering 50 ms jitter
         gpio_output_init((gpio_config_t *)gpio_ouput_config, get_array_len(gpio_ouput_config)); // /Output initialization, you can use more advanced functions to control
        
         dev_led_start_blink(LED_1_INDEX, 500, 300, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1 alternately outputs high electrical level for 500 ms and low electrical level for 300 ms to make the LED flicker
     }
    

Serial port usage

The hal_uart.h file introduces the detailed interface definition for your reference. There are restrictions on the use of the serial port. It needs to be used during or after dev_system_on_init. This section explains how to use the serial port through C programming examples.

static void __uart_rx_callback(uint8_t *data, uint16_t len)
{
    // TODO:  Process data received in serial port. This callback function is not in the interrupt environment.
}

static void uart_demo(void)
{
    user_uart_config_t uart_cfg = TYZS3_USART_CONFIG_DEFAULT; // /< The default value of TYZS3 module serial port
    uart_cfg.func = __uart_rx_callback; // /< Fill the callback function of serial port receiving
    user_uart_init(&uart_cfg);
    uint8_t test_data[2] = {0x01, 0x02};
    user_uart_send(UART_ID_UART0, test_data, sizeof(test_data)); // /< Serial port sending
}

Hardware timer usage

The hal_systick_timer.h file introduces the detailed interface definition for your reference. This section explains how to use systick_timer through C programming examples.

void __hardware_timer_func_t(TIMER_ID_T timer_id)
{
    static uint8_t test_times = 100;
    switch(timer_id) {
        case V_TIMER0: { ///< Call it once every 1,000 ms in a cycle
            if((--test_times) == 0) {
                timer_hardware_stop_100us(timer_id); // /< Stop after executing for 100 times
            }
            if(timer_hardware_is_active(timer_id)) {
                //TODO: Timer is active
            }
            else {
                //TODO: Timer is inactive
            }
            //TODO:
            break;
        }
        case V_TIMER1: { ///< Call it once every 500 ms. It is a one-time task.
            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 network interface

The tuya_zigbee_stack.h file introduces the detailed interface definition for your reference. This section explains how to use Zigbee network interface through C programming examples.

Zigbee device creation

The Zigbee device creation is inseparable from network behavior. You need to implement it through dev_power_on_init().

  • Take a relay switch as an example to introduce how to create a Zigbee routing device:

     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 Represents one ON_OFF_LIGHT device, one endpoint
     * Support group, scene, and onoff cluster on the Server
     * Support attributes of GROUP_ATTR_LIST, SCENE_ATTR_LIST, and ON_OFF_PRIVATE_ATTR_LIST respectively
     */
    
     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 Describes what device, how many endpoints, and what cluster and attributes each endpoint has.
         */
    
         dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS); 
         /**
         * @note Configure the Zigbee device settings as a routing device.
         */
      
         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 Configure to enter networking immediately after power-on or remote deletion, and the networking times out after one minute.
         */
      
         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;
     }
    
  • Take a single live wire switch as an example to introduce how to create a Zigbee low power device:

     #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 Represents one ON_OFF_LIGHT device, one endpoint
     * Only support onoff cluster on the Server. Also supports ON_OFF_PRIVATE_ATTR_LIST attributes
     */
    
     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 Describes what device, how many endpoints, and what cluster and attributes each endpoint has.
         */
         dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS); 
         
         /**
         * @note Configure the Zigbee device settings as a routing device.
         */
       
         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;     ///< Enter NET_LOST status after three polling failures (lost connection to parent node)
         zg_dev_config.config.sleep_dev_cfg.poll_conifg.poll_forever_flag = TRUE;  ///< The device keeps polling
         zg_dev_config.config.sleep_dev_cfg.poll_conifg.poll_interval = 250;       ///< The polling interval is 250 ms
         zg_dev_config.config.sleep_dev_cfg.poll_conifg.wait_app_ack_time = 1000;  ///< Send application data followed by (2+wait_app_ack_time/poll_interval) polls, and it is valid when poll_forever_flag is FALSE
         zg_dev_config.config.sleep_dev_cfg.rejoin_config.auto_rejoin_send_data = TRUE;     ///< Automatically rejoin after it failed to send the data
         zg_dev_config.config.sleep_dev_cfg.rejoin_config.next_rejoin_time = 5000;          ///< If this rejoin fails, perform the next rejoin after 5s
         zg_dev_config.config.sleep_dev_cfg.rejoin_config.power_on_auto_rejoin_flag = TRUE; // /< Automatically rejoin after the program starts
         zg_dev_config.config.sleep_dev_cfg.rejoin_config.rejoin_try_times = 5;             ///< The number of beacon requests for a rejoin attempt
         zg_dev_config.config.sleep_dev_cfg.rejoin_config.wake_up_time_after_join = 30000;  ///< Poll duration after successful networking, and it is valid when poll_forever_flag is FALSE
         zg_dev_config.config.sleep_dev_cfg.rejoin_config.wake_up_time_after_rejoin = 5000; // /< Poll duration after successful rejoin, and it is valid when poll_forever_flag is FALSE
         zg_dev_config.beacon_send_interval_for_join = 300;    ///< The interval for sending beacon requests in networking
         zg_dev_config.beacon_send_interval_for_rejoin = 300;  ///< The interval for sending beacon requests when attempting to rejoin
         zg_dev_config.zb_scan_duration = ZB_SCAN_DURATION_3;  ///< The continuous receiving time after sending beacon request
         dev_register_zg_dev_config(&zg_dev_config);
       
         /**
         * @note Configure not to enter ad hoc networking after power-on, and to enter networking after remote deletion. The networking times out after one minute.
         */
       
         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 device joining and leaving network

  • You can leave the previous networking by calling dev_zigbee_join_start() and then start networking.

  • You can just leave the previous networking by calling dev_zigbee_leave_for_user().

For example, the KeyPressed call:

/*
 * @note __dev_key_handle Assume that it is a registered KeyPressed function
*/
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) { ///< The key is being held down
                if(push_time == 3000) { ///< The key is held down for 3,000 ms, and then LED2 starts to flicker
                     dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1 alternately outputs high electrical level for 500 ms and low electrical level for 300 ms to make the LED flicker
                }
            }
            else {
                if(push_time < 3000) { ///< The key is held down for less than 3,000 ms, which is processed as the short press
                    //TODO:
                }
                else {
                    dev_zigbee_join_start(60000); // /< Press and hold the key for more than 3s and then release to start networking. Networking times out after 1 minute
                }
            }
            break;
        }
     
        case KEY_2_INDEX: {
            if(key_st == KEY_ST_PUSH) { ///< The key is being held down
                if(push_time == 3000) { ///< The key is held down for 3,000 ms, and then LED2 starts to flicker
                     dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1 alternately outputs high electrical level for 500 ms and low electrical level for 300 ms to make the LED flicker
                }
            }
            else {
                if(push_time < 3000) { ///< The key is held down for less than 3,000 ms, which is processed as the short press
                    //TODO:
                }
                else {
                    dev_zigbee_leave_for_user(); // /< Press and hold the key for more than 3s and then release to leave networking.
                    dev_led_stop_blink(LED_2_INDEX, DEV_IO_ON); // /< If the light is on, it indicates the device is leaving the network
                }
            }
            break;
        }
        default: {
            break;
        }
    }
}

Zigbee gateway heartbeat management

To maintain the link validity with the gateway, the routing message is refreshed. A heartbeat message is maintained between the device and the gateway. Since the start of the heartbeat depends on the system’s soft timer, the call should be made during or after dev_system_on_init().

By default, the SDK internally generates a periodic heartbeat message based on the device type. You can also make an explicit call to change the default action. The following is an implementation example in dev_system_on_init():

void dev_system_on_init(void)
{
    dev_heartbeat_set(APP_VERSION, 180000); // /< 180s + 30s. Send a heartbeat at a random value
}

Zigbee network status

The SDK provides a callback to report the Zigbee gateway network status of the current device. The network status does not need to be maintained during application development. The example of the callback to be implemented by the application is as follows:

void nwk_state_changed_callback(NET_EVT_T state)
{
    switch(state) {
        case NET_POWER_ON_LEAVE: {
            //TODO: No network access when powered on
            break;
        }
        case NET_JOIN_START: {
            //TODO: Networking starts
            break;
        }
        case NET_JOIN_TIMEOUT: {
            //TODO: Networking fails
            break;
        }
        case NET_POWER_ON_ONLINE: {
            //TODO: Have network access when powered on
            break;
        }
        case NET_JOIN_OK: {
            //TODO: Networking succeeds
            break;
        }
        case NET_REJOIN_OK: {
            //TODO: Reconnecting succeeds
            break;
        }
        case NET_LOST: {
            //TODO: Lose connection to parent node
            break;
        }
        case NET_REMOTE_LEAVE: {
            //TODO: Remote disconnection notification
            break;
        }
        case NET_LOCAL_LEAVE: {
            //TODO: Local disconnection notification
            break;
        }
        case NET_MF_TEST_LEAVE: {
            //TODO: Production test disconnection notification
            break;
        }
        default: {
            break;
        }
    }
}

Data receiving

You can receive the data of the Zigbee gateway through dev_msg_recv_callback(). An example of receiving switch data is as follows:

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: { // Process the private data
            uint8_t len = dev_msg->data.bare_data.len;
            uint8_t *data = dev_msg->data.bare_data.data;
            //TODO: Process the private protocol
            break;
        }
     
        // Process the standard data
        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: Close command
                        break;
                    }
                    case CMD_ON_COMMAND_ID: {
                        //TODO: Open command 
                        break;
                    }
                    case CMD_TOGGLE_COMMAND_ID: {
                        //TODO: Negation command
                        break;
                    }
                    default: {
                        break;
                    }
                }
                break;
            }
        }
        default:
            // Unrecognized cluster ID, error status will apply.
            break;
    }
    return result;
}

dev_msg_write_attr_callback_ext() or dev_msg_write_attr_callback() is used to notify the application layer of Zigbee global write command.

void dev_msg_write_attr_callback(uint8_t endpoint, CLUSTER_ID_T cluster, uint16_t attr_id)
{
    //TODO: Call this function after successfully writing remote attribute
  
    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: Call this function after successfully writing remote attribute, with raw data parameters included
   
    return;
}

Data sending

The following example shows an attribute value is reported to the Zigbee gateway through send_data:

static void __send_result_cb(SEND_ST_T st, dev_send_data_t *msg)
{
    switch(st) {
        case SEND_ST_OK: {
            //TODO: Sent successfully
            break;
        }
        default: {
            //TODO: Failed to send
            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;  ///< User defined ID. This parameter is returned in the callback for a successful or failed sending operation.
    send_data.qos = QOS_1; // /< If the ACK is not received, the data is retransmitted
    send_data.direction = ZCL_DATA_DIRECTION_SERVER_TO_CLIENT;
    send_data.command_id = CMD_REPORT_ATTRIBUTES_COMMAND_ID;       ///< The command to report attributes
    send_data.addr.mode = SEND_MODE_GW;                            ///< Send data to the gateway
    send_data.addr.type.gw.cluster_id = CLUSTER_ON_OFF_CLUSTER_ID; // /< Switch attribute 
    send_data.addr.type.gw.src_ep = 1; // /< Device endpoint
    send_data.delay_time = 0;   ///< Delayed sending time
    send_data.random_time = 0;  ///< Randomly send time range
    send_data.data.zg.attr_sum = 1;  ///< The number of attributes reported
    send_data.data.zg.attr[0].attr_id = ATTR_ON_OFF_ATTRIBUTE_ID; // /< Switch status attribute 
    send_data.data.zg.attr[0].type = ATTR_BOOLEAN_ATTRIBUTE_TYPE; // /< Attribute data type
    send_data.data.zg.attr[0].value_size = 1;                     ///< Attribute data length
    send_data.data.zg.attr[0].value[0] = 1; // /< 1 is for on, and 0 is for off 
    dev_zigbee_send_data(&send_data, __send_result_cb, 1000); // /< Send. 1,000 indicates the longest retransmission duration of the message is 1 second
}

Scene management

When adding a scene, save the user-defined data. Read the data when calling the scene, and call dev_scene_add_callback() interface when the network receives the command to add the scene.

static uint8_t g_onoff_status = 0; // Assuming that it represents switch status
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: Call this function when saving the device status and adding a scene
}

void dev_scene_recall_callback(uint8_t endpoint, const scene_save_data_t *in_data)
{
    //TODO: Execute a scene
    switch(in_data->type) {
        case SCENE_DATA_TYPE_USER_DEFINE: {
            if(in_data->data[0] == 1) {
                g_onoff_status = 1;
                //TODO: Execution is on
            }
            else if(in_data->data[0] == 0) {
                g_onoff_status = 1;
                //TODO: Execution is off
            }
            break;
        }
        default: {
            break;
        }
    }
    return;
}

When the device is a scene panel instead of an execution device, the following operations are required:

  • Determine whether to remove all the scenes under the endpoint before adding a scene. It is used when adding a scene to the scene switch. Return FALSE means not removing all scenes, and return TRUE means removing all scenes. Return FALSE by default.

     bool_t zigbee_sdk_scene_remove_before_add(uint8_t endpoint)
     {
     return TRUE; // The return can only be TRUE for the scene panel
     }
    
  • When scene switch needs to recall a scene, use the interface of 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);  ///< Obtain the scene ID and group ID when endpoint is 1
         if(result) {
             dev_scene_recall_send_command(1, group_id, scene_id); // /< Send the scene when endpoint is 1
         }
     }
    

System interface

Memory management

Currently, the memory allocation provided by the SDK uses general malloc and free functions. The internal SDK shares 10 KB RAM with the application.

Event management

The entire SDK system is event-driven, and all events are one-time events. Examples are as follows:

#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: This event is executed after dev_system_on_init delays 1s
            dev_timer_start_with_callback(SDK_TEST_EVT2, 0, __dev_evt_callback);
            break;
        }
       
        case SDK_TEST_EVT2: {
            //TODO: This event is executed after SDK_TEST_EVT1 delays 0ms
            break;
        }
     
        case SDK_TEST_EVT3: {
            //TODO: This event is executed after dev_system_on_init delays 2s, and register a 2s delay again. The event will be kept executing in the 2s delay cycle.
            dev_timer_start_with_callback(SDK_TEST_EVT3, 2000, __dev_evt_callback);
            break;
        }
      
        case SDK_TEST_EVT4: {
            //TODO: This event is executed after SDK_TEST_EVT1 delays 10s. Cancel the cycle execution of 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);
}

Application layer development

Interface directory

The files in the include directory are header files that each application project needs to use. The file structure is as follows:

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

The description of the files is shown in the following table:

File Description
hal_xxx.h Hardware abstraction layer interface
tuya_app_timer.h Device date and time interface
tuya_mcu_os.h Event interface
tuya_mf_test.h Production test interface
tuya_oem_kit.h Tuya OEM configuration interface
tuya_zigbee_modules.h Tuya module interface
tuya_zigbee_sdk.h Interface that the application layer needs to implement
tuya_zigbee_stack.h Tuya SDK and Zigbee protocol stack interface
zigbee_attr.h Definition of standard Zigbee attribute
zigbee_cmd.h Definition of standard Zigbee command
zigbee_dev_template.h Common definition template of Zigbee device
zigbee_raw_cmd_api.h Encapsulation of sending native command of Silicon Lab’ protocol stack

Corresponding function implementation

The project_name_callback.c file is automatically generated when creating a new project. The included interfaces are the interface functions that the application needs to implement. Examples are as follows:

#include "zigbee_sdk.h"
dev_power_on_init(); // Power-on initialization
dev_system_on_init(); // The protocol stack and basic system components are initialized after startup
dev_recovery_factory(); // User callback of restoring factory settings 
dev_mf_test_callback(); // User callback of production test 
dev_msg_recv_callback(); // Callback of receiving data
dev_scene_add_callback(); // Callback of adding a scene
dev_scene_recall_callback(); // Callback of calling the scene
nwk_state_changed_callback(); // Callback of changing network status
user_evt1_handle(); // Event handlers of user defined event1 
user_evt2_handle(); // Event handlers of user defined event2
user_evt3_handle(); // Event handlers of user defined event3
.....

Firmware information configuration

Configure firmware information in the package.json file under the corresponding project. The major fragments are as follows.

package.json
{
    "fimwareInfo": {
        "name": "oem_si32_zg_plug_10A_USB_dlx", // Firmware name
        "description": "Zigbee switch common", // Firmware description
        "version": "1.0.9", // Software version number
        "bv_version": "1.0", // It is fixed for now
        "ic": "efr32mg13p732gm48", // Zigbee chip model
        "ota_image_type":"0x1602", // It is used for OTA update identification and is fixed for now
        "manufacture_id":"0x1002", // It is used for OTA update identification and is fixed for now
        "model_id":"TS0105", // It is used for the device to quickly identify the device type during networking
        "pid": "ZHahfZRP", // Default product ID, which is assigned by the product manager
        "manufacture_name": "TUYATEC-" // Product ID prefix, which is used for the gateway to determine the interaction protocol
    }
}

Introduction to app framework

The files and folders contained in the app/switch directory are as follows:

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

There are common and project folders for different category files:

  • The common folder contains include and src files. These two folders are used as the common resource pool for this category and can store common C files and H files respectively.
  • The project file is the corresponding application project file, and there is an empty demo project under the project. For example, the demo under the switch is sample_switch1, which can be used for creating new projects.

New project

Take the creation of a switch project as an example. Enter switch/project path, copy sample_switch1 folder under the project folder, and rename it to switch. Enter EFR32MG13P732F512 under the switch folder to see the project overview.

  • The directory structure under the EFR32MG13P732F512 platform of sample_switch1 is as follows:

     app/switch/project/sample_switch1/EFR32MG13P732F512
     │── build
     │    ├── exe
     │    ├── lst
     │    └── obj
     ├── iar_make.bat
     ├── makefile
     ├── package.json
     ├── run.sh
     └── tuya_sdk.eww
    
  • File and directory description:

    Directory/file Description
    run.sh Compile and execute scripts in Linux environment
    tuya_sdk.eww IAR project file
    package.json Configuration file of the basic device information
    exe The final production firmware and OTA firmware are generated in the directory, which are xx_QIO_1.0.x.s37 and xx_QIO_1.0.x.bin respectively

Basic device information

The fimwareInfo in package.json records important information of the device. Tuya provides information including pid, mode_id, andmanufacturing_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"
},

Hardware configuration information

Examples are as follows:

// I/O configuration of key input
const gpio_config_t gpio_input_config[] =
{
    {KEY_1_PORT, KEY_1_PIN, KEY_1_MODE, KEY_1_OUT, KEY_1_DRIVER},
};

// Output interface configuration of LED indicator and relay
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},
}; 

Configuration information of device cluster and attribute

Examples are as follows:

// The configuration template of the cluster and attribute can be found in Zigbee_dev_template.h. You can also customize it according to the template format. 

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 },
};

Initializing function after device powered on

Examples are as follows:

//After the device is powered on, configure the basic device information and hardware information 

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));
}

System initialization function

Examples are as follows:

// Enter this function after the serial port production test times out 

void dev_system_on_init(void)
{
    __dev_status_load();
}

Callback function of Zigbee network status change

Examples are as follows:

// You can trigger corresponding actions when Zigbee network status changes 


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;
        }
    }
}

Callback function of processing device production test

Examples are as follows:

// Enter this function when the device receives the production test command 


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;
}

Callback function of receiving Zigbee information

Examples are as follows:

// Enter this callback function when the device receives information from ZigBee Cluster Library (ZCL) layer 


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;
}

Serial port configuration interface

Examples are as follows:

// The serial port will be used during the serial port production test. After the production test times out, the serial port is disabled. If you need to reconfigure the serial port, you can reinitialize the serial port information in the dev_system_on_init() function


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;
}

Appendix 1: Description of important functions and data interfaces

This function parameters describe all behaviors during the entire network lifecycle of Zigbee. You only need to configure the application layer, and the SDK will execute the network behavior according to the configured parameters. You need to call this function during dev_power_on_init():

extern void dev_register_zg_dev_config(zg_dev_config_t *config);

Important data description:

  • Device type to be created: routing device, non-hibernation terminal device, and hibernation terminal device

     typedef enum {
         ZG_ROUTER = 0,
         ZG_END_DEVICE,
         ZG_SLEEPY_END_DEVICE,
     }ZG_DEV_T;
    
  • The join and rejoin parameters, in millisecond:

     typedef struct {
         uint32_t next_rejoin_time;  when the first round of rejoin fails, the interval of the next round of rejoin
         uint32_t wake_up_time_after_join;  the time to poll after the join is successful
         uint32_t wake_up_time_after_rejoin; the time to poll after successful rejoin
         uint8_t rejoin_try_times; after the parent node is lost, the number of attempts to send beacon requests
         bool_t  power_on_auto_rejoin_flag;  whether to rejoin after power-on
         bool_t  auto_rejoin_send_data; when sending ZCL data, whether to automatically rejoin if the parent node is lost
     }zg_rejoin_config_t;
    

The polling configuration for the hibernation device, in milliseconds

typedef struct {
    uint16_t poll_interval;   //Send ZCL data at polling interval and then send wait_App_ack_time/poll_interval and two polls
    uint16_t wait_App_ack_time; 
    bool_t  poll_forever_flag; // Whether polling is always on 
    uint8_t poll_failed_times; // After several polling failures, the network switches to the status of losing parent node
}zg_poll_config_t;

The configurations that need to be filled in for hibernation devices:

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

The configurations that need to be filled in for non-hibernation terminal devices:

typedef struct {
    zg_rejoin_config_t rejoin_config;
}zg_end_device_config;

For the configurations that need to be filled in for routing devices, currently, the fields are reserved.

typedef struct {
    uint8_t reserved; // There is no need to fill in this configuration
}zg_router_config;

Rejoin configuration of scanning the channel:

typedef enum {
    ZG_SCAN_POLICY_CURR_CHANNEL_FOREVER = 0,  // Only scan the current channel
    ZG_SCAN_POLICY_ALL_CHANNEL_ONCE // Scan the current channel first, and scan all channels for the last time
}ZG_SCAN_POLICY_T;

The wait time for beacon after sending a beacon request. During this time period, the RF reception is always on. The longer the waiting time is, the more power is consumed, but more beacons can be collected at one time, which makes it easier to find a suitable device. Currently, this option is only applicable to non-routing devices, and the routing device is fixed to 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; //  Fill in one of three options, and check it according to dev_type
    uint16_t beacon_send_interval_for_join;   // The interval of sending beacons when joining
    uint16_t beacon_send_interval_for_rejoin; //  The interval of sending beacons when rejoining, and it is valid for non-routing devices
    ZB_SCAN_DURATION_T zb_scan_duration;
}zg_dev_config_t;

Version history

Version Description Date
1.0.1 The first release. March 3, 2020