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.
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 |
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.
Install IAR Embedded Workbench IDE software (use ARM 8.40.1 or higher)
Open the IAR project
Compile with IAR (sample_switch1 as an example)
Go to silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512
Double click tuya_sdk.eww to open the project
Then, start compilation
Note: Do not open the project by opening IAR and then “Open workspace”. Otherwise, compilation errors might occur.
The SDK provides a GCC compiler. There is no need to install other compilers.
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)
If an error is reported during the first compilation, check the configuration of file permissions. The modification methods are as follows:
Enter the home directory of silicon_lib_zigbee.
Run the following command line in this directory.
chmod –R 777 *
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
This part describes the common hardware interfaces and demonstrates how to use them through C programming examples.
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
}
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
}
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);
}
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.
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;
}
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;
}
}
}
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
}
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;
}
}
}
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;
}
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
}
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
}
}
Currently, the memory allocation provided by the SDK uses general malloc and free functions. The internal SDK shares 10 KB RAM with the application.
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);
}
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 |
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
.....
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
frameworkThe 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.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 |
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"
},
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},
};
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 },
};
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));
}
Examples are as follows:
// Enter this function after the serial port production test times out
void dev_system_on_init(void)
{
__dev_status_load();
}
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;
}
}
}
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;
}
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;
}
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;
}
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 | Description | Date |
---|---|---|
1.0.1 | The first release. | March 3, 2020 |
Is this page helpful?
YesFeedbackIs this page helpful?
YesFeedback