此文档以开发一个低功耗门磁设备为示例,让开发者了解 TuyaOS Zigbee 开发包的基本原理,并且利用 TuyaOS Zigbee 开发包快速进行开发。最后可以在开发板上实现按键配网、门磁状态上报、电量上报等功能。
了解 TuyaOS Zigbee 开发包架构以及基础能力;
了解如何开发低功耗子设备;
了解安防设备如何进行注册和数据交互。
windows 操作系统;
USB 转 TTL 工具;
带 ZS3L 模组的一块开发板;
要做完整个工作,我们需要完成以下几个步骤:
让我们愉快的开始吧。
涂鸦三明治 Zigbee SoC 主控板(ZS3L)是方便开发者快速实现各种智能硬件产品原型的一款开发板。您可通过涂鸦 Zigbee SoC 主控板(ZS3L),搭配其他功能电路模组或电路板,实现对应的产品功能。
实验中用到的引脚介绍如下,点击查看Zigbee SoC 主控板(ZS3L)的详细介绍。
丝印名称 | 芯片引脚编号 | 备注 |
PB00 | GPIO_NUM_7 | 按键引脚,初始化高电平,按下为低电平 |
PA03 | GPIO_NUM_3 | 门磁状态检测引脚 |
PA05 | GPIO_NUM_5 | 串口0 TX 引脚 |
PA06 | GPIO_NUM_6 | 串口0 RX 引脚 |
该开发板提供了烧录口(从左往右依次是电源、时钟、数据、复位、地线、地线、TXD、RXD),可以通过 J-Link 和串口使用涂鸦云模组烧录平台或对应的烧录工具进行烧录。
注意:ZS3L 是 efr32mg21a020f768im32 芯片平台,需要在 appconfig.json 中 修改chip_id;
{
"firmwareInfo": {
"description": "this is a demon project",
"dev_role":"sleep_end_dev",
"image_type":"0x1602",
"manufacture_id":"0x1002",
"model_id":"TS0203",
"pid": "cz8yd6r2",
"manufacture_name": "_TZ3000_",
"module_name":"ZSU",
"chip_id":"efr32mg21a020f768im32"
}
}
在开始开发前,需要了解 SDK 的初始化流程。启动流程如下图所示。 Tips: 绿底步骤 需要用户关注,进行重写或实现。
头文件名称 | 功能 |
| Tuya IoT OS 通用工具函数接口 |
| 提供统一适配系统基础接口,常用 API 有得到系统重启原因,得到系统运行的 ticket 等 |
| 封装了内存管理接口 |
| 涂鸦对一些错误类型的定义 |
| 对参数类型的封装。对变量、函数进行类型定义或修饰时应调用这里的函数。 |
tuya_sdk_callback.c
和tuya_sdk_callback.h
这两个文件是用户基于 tuyaOS Zigbee 开发包实现应用业务开发的回调接口集合。需要用户根据实际业务场景的需要实现相关回调、虚函数的重写,应用逻辑功能代码的填充等。一些重要的回调函数/虚函数列表如下:
函数名称 | 函数功能 |
| 用户侧进行硬件初始化 |
| 用于 Zigbee 相关初始化、如 Zigbee 设备注册等 |
| 进入产测前回调接口 |
| 退出产测后回调接口 |
| 主循环回调接口,谨慎使用,不可阻塞! |
| ZCL general command 接收回调接口 |
| ZCL specific command 接收回调接口 |
| Zigbee 网络状态改变回调接口 |
| add scene/store scene 命令回调接口 |
| recall scene 命令回调接口 |
| 添加群组命令回调接口 |
| 移除群组命令回调接口 |
| 恢复出厂设置命令回调接口 |
执行到tuya_init_first()
的时候,tuyaOS Zigbee SDK 还没有开始初始化,所有和 Zigbee 有关的资源都不能调用。
一般在tuya_init_first()
中进行硬件相关的初始化动作,例如初始化打印串口,业务相关硬件资源,关闭双Dongle测试功能等。
/**
* @brief Generally used for peripheral initialization
*
* @return OPERATE_RET
*/
OPERATE_RET tuya_init_first(VOID_T)
{
app_debug_uart_init();
tal_log_create_manage_and_init(TAL_LOG_LEVEL_TRACE, 128, app_uart_output);
tal_mf_test_disable_beacon_test();
TAL_PR_DEBUG("/*********first init*********/\r\n");
return OPRT_OK;
}
tuya_init_second()
接口需要用户实现和 Zigbee 相关的初始化,包括固件信息、ep 注册、Zigbee 节点配置、入网配置、心跳设置等;
/**
* @brief Generally used for register Zigbee device
*
* @return OPERATE_RET
*/
OPERATE_RET tuya_init_second(VOID_T)
{
dev_Zigbee_init();
}
/**
* @brief register Zigbee parameter
* @return none
*/
VOID_T dev_Zigbee_init(VOID_T)
{
//register Zigbee endpoint
tal_zg_endpoint_register(dev_endpoint_desc, GET_ARRAY_LEN(dev_endpoint_desc));
//Zigbee node configuration
app_end_device_node_init();
//Zigbee joining network configuration
dev_joined_config();
tal_zg_nwk_recovery_disable(TRUE);
}
tuya_init_third()
接口在进入产测前会回调,用户自定义实现。
OPERATE_RET tuya_init_third(VOID_T)
{
// TODO:
return OPRT_OK;
}
tuya_init_last()
接口在产测结束后会回调,用户自定义实现,此 API 也是进入 while(1) 前最后的回调函数。
OPERATE_RET tuya_init_last(VOID_T)
{
// TODO:
return OPRT_OK;
}
TuyaOS Zigbee 开发包基于前后台软件框架,提供了主循环内的回调接口tuya_main_loop()
,由用户依据需求自定义实现相关操作注入系统主循环中。 注意:tuya_main_loop()
主要用于用户侧添加调试、验证性的操作,需谨慎使用。此回调接口占用过多时间片会影响整个系统框架的稳定性!!!
/**
* @brief user-defined callback interface in main loop.do not block!!!
*
* @return OPERATE_RET
*/
OPERATE_RET tuya_main_loop(VOID_T)
{
// TODO:
return OPRT_OK;
}
当 Zigbee 网络状态发生改变后会回调tal_zg_nwk_status_changed_callback()
接口,由应用侧自行定义相应的处理。
/**
* @brief Zigbee network network change callback(user can rewrite this API)
*
* @param[in] status: network status
* @return VOID_T
*/
VOID_T tal_zg_nwk_status_changed_callback(TAL_ZG_NWK_STATUS_E status)
{
switch (status)
{
case TAL_ZG_NWK_POWER_ON_LEAVE:
{
TAL_PR_TRACE("power_on_leave---\r\n");
break;
}
case TAL_ZG_NWK_POWER_ON_ONLINE:
{
TAL_PR_TRACE("power_on_online---\r\n");
tal_sw_timer_start(etimer_network_up_delay_event_id, 5000, TAL_TIMER_ONCE);
break;
}
case TAL_ZG_NWK_JOIN_START:
{
TAL_PR_TRACE("nwk_join_start---\r\n");
#if APP_LED_SUPPORT
dev_start_join_indicate();
#endif
break;
}
case TAL_ZG_NWK_JOIN_OK:
{
TAL_PR_TRACE("nwk_join_ok---\r\n");
#if APP_LED_SUPPORT
dev_join_success_indicate();
#endif
tal_sw_timer_start(etimer_network_up_delay_event_id, 5000, TAL_TIMER_ONCE);
app_battery_start();
break;
}
case TAL_ZG_NWK_REJOIN_OK:
{
TAL_PR_TRACE("nwk_rejoin_ok---\r\n");
app_battery_start();
break;
}
case TAL_ZG_NWK_JOIN_TIMEOUT:
{
TAL_PR_TRACE("nwk_join_timeout---\r\n");
break;
}
case TAL_ZG_NWK_LOST:
{
TAL_PR_TRACE("nwk_lost---\r\n");
break;
}
case TAL_ZG_NWK_REMOTE_LEAVE:
{
TAL_PR_TRACE("nwk_remote_leave---\r\n");
tal_zg_join_start(Zigbee_JOIN_TIMEOUT_MS);
TAL_PR_TRACE("Zigbee join start %d.\r\n", Zigbee_JOIN_TIMEOUT_MS);
break;
}
case TAL_ZG_NWK_LOCAL_LEAVE:
{
TAL_PR_TRACE("nwk_local_leave---\r\n");
break;
}
case TAL_ZG_NWK_MF_TEST_LEAVE:
{
TAL_PR_TRACE("nwk_mf_test_leave---\r\n");
break;
}
default:
{
break;
}
}
}
Zigbee 的网络状态定义如下:
/*
* Zigbee network status
*/
typedef enum {
TAL_ZG_NWK_IDLE = 0, ///< inner using
TAL_ZG_NWK_POWER_ON_LEAVE, ///< power on and device is not joined network
TAL_ZG_NWK_POWER_ON_ONLINE, ///< power on and device is already joined network
TAL_ZG_NWK_JOIN_START, ///< start joining network
TAL_ZG_NWK_JOIN_TIMEOUT, ///< network joining timeout
TAL_ZG_NWK_JOIN_OK, ///< network joined success
TAL_ZG_NWK_LOST, ///< network lost, lost parent
TAL_ZG_NWK_REJOIN_OK, ///< network rejoin ok
TAL_ZG_NWK_REMOTE_LEAVE, ///< remove device by remote device
TAL_ZG_NWK_LOCAL_LEAVE, ///< remove device by local
TAL_ZG_NWK_MF_TEST_LEAVE, ///< remove device by PC test tools
TAL_ZG_NWK_ZLL_JOINED, ///< network joined zll network
TAL_ZG_NWK_ZLL_LEAVE, ///< remove device Zll Reset To Factory New
} TAL_ZG_NWK_STATUS_E;
tal_zcl_general_msg_recv_callback()
接口处理收到 ZCL 标准定义的 general command 帧。接口定义如下:
/**
* @brief general message receive callback
*
* @param msg
* @return TAL_MSG_RET_E
*/
TAL_MSG_RET_E tal_zcl_general_msg_recv_callback(TAL_ZCL_MSG_T *msg)
{
tal_main_debug("app gen msg cb: cluster 0x%02x, cmd %d\r\n", msg->cluster, msg->command);
return ZCL_MSG_RET_SUCCESS;
}
ZCL 标准 general command 帧定义如下:
tal_zcl_general_msg_recv_callback()
接口处理收到 ZCL 标准定义的 specific command 帧。接口定义如下:
/**
* @brief specific message receive callback
*
* @param msg
* @return TAL_MSG_RET_E
*/
TAL_MSG_RET_E tal_zcl_specific_msg_recv_callback(TAL_ZCL_MSG_T *msg)
{
TAL_PR_TRACE("app spec msg cb: cluster 0x%02x, cmd 0x%02x\r\n", msg->cluster, msg->command);
Zigbee_CMD_E app_cmd_type = Zigbee_CMD_SINGLE;
if (msg->mode == ZG_UNICAST_MODE)
{
app_cmd_type = Zigbee_CMD_SINGLE;
TAL_PR_TRACE("receive single message");
}
else
{
app_cmd_type = Zigbee_CMD_GROUP;
TAL_PR_TRACE("receive group message");
}
TAL_PR_TRACE("app command type %d", app_cmd_type);
switch (msg->cluster)
{
#if IAS_ZONE_SUPPORT
case CLUSTER_IAS_ZONE_CLUSTER_ID: {
ias_zone_cluster_server_command_handler(msg->command, msg->payload, msg->length, app_cmd_type);
break;
}
#endif
default:
break;
}
return ZCL_MSG_RET_SUCCESS;
}
tal_zg_send_data()
接口用于用户侧发送 Zigbee 网络数据,接口原型如下:
/**
* @enum Zigbee data for sending
*/
typedef struct {
UINT16_T delay_time; ///< send delay time with ms
UINT16_T random_time; ///< send random times with ms
UINT16_T manu_code; ///< manufacturer code
UINT8_T manu_spec; ///< 1: for enable manufacturer code
UINT8_T zcl_id; ///< applicaiton sequence number(0x00~0xF0 for user, 0xF0~0xFF for SDK)
UINT8_T command_id; ///< zcl command id, the detail in tuya_Zigbee_commmand.h
ZG_ZCL_FRAME_TYPE_E frame_type; ///< zcl frame type
ZG_ZCL_DATA_DIRECTION_E direction; ///< data transmission direction
TAL_SEND_QOS_E qos; ///< data service quality
TAL_ZG_ADDR_T addr; ///< Zigbee data address
union {
TAL_ZG_DATA_T zg;
TAL_PRIVATE_DATA_T private;
} data;
} TAL_ZG_SEND_DATA_T;
typedef VOID_T (*TAL_SEND_RESULT_CB)(TAL_SEND_ST_E, TAL_ZG_SEND_DATA_T *);
/**
* @brief receive ack handle
*
* @param[in] pdata: point data to be send
* @param[in] callback: send result callback
* @param[in] timeout: data send timeout
* @return TRUE: none
*/
VOID_T tal_zg_send_data(TAL_ZG_SEND_DATA_T *pdata, TAL_SEND_RESULT_CB callback, UINT_T timeout);
其中,callback
参数用于注册发送回调,用户可以在此回调接口中获取发送状态信息。
tal_attribute_rw.h
头文件中提供了 Zigbee 属性的读写接口。接口定义如下:
/**
* @brief write Zigbee attribute
* NOTE: only for server clusters attributes
*
* @param[in] endpoint: endpoint id
* @param[in] cluster: cluster id
* @param[in] attr_id: attribute id
* @param[in] data: point to attribute data to be write
* @param[in] type: type of attribute
* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
*/
OPERATE_RET tal_zg_write_attribute(UINT8_T endpoint,
UINT16_T cluster,
UINT16_T attr_id,
VOID_T* data,
ZG_ATTR_TYPE_E type);
/**
* @brief read Zigbee attribute
* NOTE: only for server clusters attributes
*
* @param[in] endpoint: endpoint id
* @param[in] cluster: cluster id
* @param[in] attr_id: attribute id
* @param[in] data: point to attribute data to be read
* @param[in] length: length of attribute data
* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
*/
OPERATE_RET tal_zg_read_attribute(UINT8_T endpoint,
UINT16_T cluster,
UINT16_T attr_id,
VOID_T *data,
UINT8_T length);
sdk_package
├── apps
│ ├── tal_Zigbee_sample
│ │ ├── include
│ │ │ ├── app_battery.h
│ │ │ ├── app_config.h
│ │ │ ├── app_dev_register.h
│ │ │ ├── app_gpio.h
│ │ │ ├── app_ias_zone_attribute.h
│ │ │ ├── app_ias_zone.h
│ │ │ ├── app_key.h
│ │ │ ├── app_led.h
│ │ │ ├── app_mfg.h
│ │ │ ├── attribute-size.h
│ │ │ ├── tuya_hal_gpio.h
│ │ │ ├── tuya_sdk_callback.h
│ │ │ ├── app_common.h
│ │ └── src
│ │ │ ├── tuya_sdk_callback.c # 应用回调接口集文件
│ │ │ ├── app_battery.c
│ │ │ ├── app_config.c
│ │ │ ├── app_dev_register.c
│ │ │ ├── app_gpio.c
│ │ │ ├── app_gpio_callback.c
│ │ │ ├── app_ias_zone_attribute.c
│ │ │ ├── app_ias_zone.c
│ │ │ ├── app_key_callback.c
│ │ │ ├── app_key.c
│ │ │ ├── app_led.c
│ │ │ ├── app_mfg.c
│ │ │ ├── tuya_hal_gpio.c
│ │ │ ├── tuya_sdk_callback.c
│ │ │ ├── app_common.c
│ └── tuya_demo_xx # 用户新建的demo
├── adapter # 适配层接口文件
├── build # 适配层接口文件
├── docs # 相关文档
├── include # 上层头文件
├── libs # 静态库文件
├── samples # 用例集文件
├── scripts # 脚本集
├── tools # 工具文件集
├── build_app.bat # 编译入口脚本
└── findiar.py # 查找编译器(IAR)路径脚本
可以根据下面的提示将 PID 信息改为你创建的产品的 PID 信息,也可以不对代码进行任何更改,跳过该步骤直接进入下一步编译生成固件继续操作。
将 /apps/tuyaos_demo_zg_door_sensor
目录下的 appconfig.json
中 修改固件信息,注意 dev_role 和 chip_id 需填写正确,否则可能出现运行异常。
{
"firmwareInfo": {
"description": "this is a demon project",
"dev_role":"sleep_end_dev",
"image_type":"0x1602",
"manufacture_id":"0x1002",
"model_id":"TS0203",
"pid": "cz8yd6r2",
"manufacture_name": "_TZ3000_",
"module_name":"ZSU",
"chip_id":"efr32mg21a020f768im32"
}
}
配网按键引脚可在 tuya_sdk_callback.h
中的,通过修改 KEY_PIN
进行修改配网按键的引脚,如要使能按键功能,需将 APP_KEY_SUPPORT 宏置 1。
门磁检测引脚可在 tuya_sdk_callback.h
中的,通过修改 GPIO_SENSOR_ALARM_1_PIN
进行修改检测的引脚,如要使能门磁检功能,需将宏 IAS_ZONE_SUPPORT 及 GPIO_SENSOR_ALARM_1_ENABLE 置 1。
使用云模组烧录授权平台进行烧录授权或者用 [原厂烧录工具] 对编译生成的固件进行烧录。
首先设置波特率,点击左上角文件中选中设置,选中波特率为 115200
点击右侧输入生产凭证,选择生产凭证,点击固件下载,并输入对应的固件 Token,选择烧录授权,点击确定
左侧选择串口端口号,信道选择 11 信道,选择对应的 J-Link 设备,点击运行即可进行烧录授权,假如提示烧录失败,请检查串口及 J-Link 接线是否正确
烧录器使用的是 J-Link,烧录工具为芯科下载器,在与开发板连接完成后,从 J-Link Device 下拉框中,选择要使用的烧录器,并点击左上角 adapter connect
按钮,选择完毕后点击 Target connect
按钮
识别芯片,如提示连接失败,尝试插拔 J-Link 并检查接线; 成功识别芯片后,可以从 device info 界面查看芯片的相关信息
点击左侧 Flash 进入烧录界面,再每次烧录前可以点击 Erase chip 来擦除 flash 中的用户数据;需注意,擦除 flash 后,因为设备的配网信息保存在 flash 中,因此擦除后设备会失去之前的网络配置信息需重新配网。擦除 flash 后点击上方 browse 来选择要烧录的固件,并点击 flash 进行烧录。
烧录完成后通过涂鸦 APP 与设备配网即可实现对设备的上报。
可以通过本地按键 5s 进行配网,配网开始时,设备网络指示灯会闪烁提示开始配网,配网结束后网络指示灯长亮3s后停止闪烁。设备成功配网后,会定时上报电量状态,当触发门磁开关时,会上报门磁状态,可于APP上实时显示。