更新时间:2022-02-23 06:44:20下载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 | 如果多个功能有公共编译文件可以放到这里面 |
涂鸦 Zigbee SDK 应用工程编译依赖于 Python 脚本, 如果使用 Linux 或者 Windows 开发调试, 需要在对应的环境下安装 Python 2.7.X 解析器。
本小节以 silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512
为例,介绍系统环境的编译要求。
安装 IAR Embedded Workbench IDE 软件 (使用 ARM 8.40.1 或更高版本)
打开 IAR 工程
用 IAR 编译( sample_switch1 为例)
进入目录 silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512
双击 tuya_sdk.eww 即可打开工程
打开工程后,进行开发编译
注意:不要通过打开 IAR 后,然后 Open workspace 方式来打开工程,否则编译可能出错
由于 SDK 自带 GCC 编译器,无需再安装编译器。
进入工程 sample_switch1\EFR32MG13P732F512
,运行以下命令行。
#./run.sh clean // 清除编译输出的问题
#./run.sh build 0 // 编译 release 版本
#./run.sh build 1 // 编译 debug 版本(带串口打印信息)
如果第一次编译报错的话需要注意下文件权限的配置,具体修改方法如下:
进入 silicon_lib_zigbee 主目录下。
在该目录下运行以下命令行。
chmod –R 777 *
如果编译报动态库相关的错误(.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 语言示例的方式来展示使用方法。
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);
}
tuya_zigbee_stack.h 文件有详细的接口定义供您参考,本小节通过 C 语言示例来说明 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;
}
通过调用 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;
}
}
}
为了维护和网关之间的链路有效性,刷新路由信息。设备和网关之间会维护一个心跳信息。心跳的启动会依赖系统的软定时服务,所以需要在 dev_system_on_init()
或更晚的地方调用。
SDK 内部默认会根据设备类型产生一个周期心跳消息,您也可以显性调用更改默认行为。下面是一个在 dev_system_on_init() 实现的例子:
void dev_system_on_init(void)
{
dev_heartbeat_set(APP_VERSION, 180000); // /< 180s+30s随机值发送一次心跳
}
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
不同的品类文件下有 common
和 project
两个文件夹:
common
文件夹下包含 include
和 src
两个文件,这两个文件夹做为该品类公共资源池文件夹,分别可以存放公共的 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.s37 和 xx_QIO_1.0.x.bin |
package.json
里的 fimwareInfo
记录设备的重要信息(pid
、mode_id
、manufacture_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 和 属性配置,可以在 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网络发生变动时,做出相应的动作
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;
}
相关使用示例如下:
// 设备收到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 |
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈