想快速的通过 Tuya Wind IDE
使用 Tuya OS SDK
开发出属于自己的智能硬件产品吗? 这个向导以控制一个LED灯的亮灭为示例,了解使用 Tuya OS SDK
的基本流程,并且利用 Tuya OS SDK
快速进行开发。最后可以在开发板上通过按键以及在 APP 上通过按钮控制 LED 灯的亮灭。
了解 Tuya OS SDK
架构以及基础能力。
了解 Tuya APP
如何控制 TuyaOS
开发的智能设备。
了解 TuyaOS
开发框架开发 Linux 平台屏幕类产品的流程。
Linux 操作系统
Microsoft VS Code
要做完整个工作,我们需要完成以下几个步骤:
让我们愉快的开始吧。
丝印名称 | 芯片引脚 | 备注 |
P16 | 16 | LED 引脚,高电平点亮 |
P28 | 28 | 按键引脚,按下为低电平 |
使用 Tuya Wind IDE
工具获取到此演示程序的开发者,不需要额外的环境准备。其他渠道获取此演示程序的开发者,可以参照环境搭建指引,来完成开发环境的搭建。
头文件名称 | 功能 |
| TuyaOS 日志打印。 |
| 提供与 Wi-Fi 相关的接口。常用 API 有初始化 TuyaOS 框架进行设置 Wi-Fi的工作模式,重置设备再次进入配网状态等。 |
| 封装了各个服务组件的对外接口。常用 API 有获取 SDK 信息等。 |
| 系统接口封装。常用 API 有得到系统重启原因,得到系统运行的 ticket 等。 |
| 涂鸦对一些错误类型的定义 |
| 一些与云端相关的类型定义。 |
| 对参数类型的封装。对变量、函数进行类型定义或修饰时应调用这里的函数。 |
| 对 GPIO 的 API 进行了封装,需在 TuyaOS 初始化完成后调用。 |
此演示程序在 tuya_device.c
文件里实现了 Wi-Fi SoC
基本功能:
函数名称 | 函数功能 |
| 此函数作为统一 app 入口,该函数为开发者唯一需要实现的函数。此函数会调用 |
| 设备初始化接口,初始化设备工作模式、配网模式,产品 PID 和版本信息。应用必须对其进行实现,如果不需要,则实现空函数。 |
| 升级文件下载完成回调,检查升级下载结果。 |
| 升级文件下载回调,将下载下来的升级数据写入文件。 |
| 设备升级启动回调,此接口是升级入口。 |
| 设备运行状态变化回调。 |
| 设备 DP 查询,此回调是响应云端、APP 查询命令,虚返回对应 DP 的值,当查询的 DP 数量为0的时候,需要返回全部 DP。 |
| 设备 DP 命令处理,包含一个或者多个DP,需要对DP命令进行处理。 |
| 设备 RAW DP 命令处理,RAW DP 是一种特殊 DP,传递自定义的原始数据,可以是字符串、二进制、数值等各种类型数据。 |
| 设备重启请求,设备有重启需求之前会调用此接口让应用程序做好重启准备。 |
| 设备网络连接状态变化回调,通知应用设备网络连接状态发生变化。 |
__soc_device_init
开发者需要特别注意 __soc_device_init
函数中的这段代码。由于这只是一个 demo,所以开发者需要手动修改 UUID 和 AUTHKEY。在产品批量生成的过程会使用更加方便授权工具,开发者无需关心这个问题。
#define PID "uiiyltkjmkhwumga"
#define UUID "tuya60dcbf014a5da4c6"
#define AUTHKEY "GPwI9eWvUFzYP3iGMnpx2wa18jq61maj"
/*...*/
STATIC VOID_T __soc_device_init(VOID_T)
{
/*...*/
WF_GW_PROD_INFO_S prod_info = {UUID, AUTHKEY};
op_ret = tuya_iot_set_wf_gw_prod_info(&prod_info);
if (OPRT_OK != op_ret) {
PR_ERR("tuya_iot_set_gw_prod_info err:%d", op_ret);
return -2;
}
/*...*/
}
tuya_iot_init_params
/**
* @brief tuya_iot_init_params
* @desc init tuyaos framework
*
* @param[in] fs_storge_path: filesystem read write storge path
* (if os have no fs,then fs_storge_path is invalid)
* @param[in] p_param: custom init params
*
* @return OPRT_OK: success Other: fail
*/
OPERATE_RET tuya_iot_init_params(IN CONST CHAR_T *fs_storge_path, IN CONST TY_INIT_PARAMS_S *p_param);
此接口是 TuyaOS
的初始化接口,接口内部会对 TuyaOS
的内部基础功能进行资源分配、初始化。一般来说,这个接口会第一个调用。开发者如果有一些需要尽早启动,并且和 TuyaOS
无关的逻辑,可以在此接口调用之前执行。
CHAR_T *fs_storge_path
:数据存储路径,仅在Linux
系统下使用,如果不提供,则默认在程序当前路径下创建数据库目录 tuya_db_files
。TY_INIT_PARAMS_S *p_param
:初始化配置,Linux
系统可以设置为 NULL
。typedef struct {
BOOL_T init_db;// 是否初始化KV,此处为了提高启动速度,一些要求快速启动的设备可以设置成FALSE
CHAR_T sys_env[SYS_ENV_LEN]; // 系统环境,可以不提供,默认为全0
CHAR_T log_seq_path[LOG_SEQ_PATH_LEN]; // 日志序路径,可以不提供,默认为全0
}TY_INIT_PARAMS_S;
tuya_iot_set_wf_gw_prod_info
/***********************************************************
* Function: tuya_iot_set_wf_gw_prod_info
* Input: wf_prod_info
* Output: none
* Return: OPERATE_RET
***********************************************************/
OPERATE_RET tuya_iot_set_wf_gw_prod_info(IN CONST WF_GW_PROD_INFO_S *wf_prod_info)
此接口是 TuyaOS
授权信息写入接口,写入唯一的 UUID
和 AuthKey
信息,用于和涂鸦云平台交互、认证使用。此接口仅用于演示程序使用,实际量产的代码会通过更加高效的功能模块和专门的工具来进行,不需要客户调用接口来处理。
WF_GW_PROD_INFO_S *wf_prod_info
:产测信息,包含了 UUID
和 AuthKey
。typedef struct {
CHAR_T *uuid; // UUID,必须提供,可以从涂鸦云平台申请
CHAR_T *auth_key; // AuthKey,必须提供,可以从涂鸦云平台申请
CHAR_T *ap_ssid; // 默认热点名称,可以不提供,默认为Smartlife_xxxx
CHAR_T *ap_passwd; // 默认热点密码,可以不提供,默认为空密码
}WF_GW_PROD_INFO_S;
tuya_iot_wf_soc_dev_init_param
/**
* @brief tuya_iot_wf_soc_dev_init_param
*
* @param[in] cfg
* @param[in] start_mode
* @param[in] cbs: tuya wifi sdk user callbacks,note cbs->dev_ug_cb is useless
* @param[in] firmware_key
* @param[in] product_key: product key/proudct id,get from tuya open platform
* @param[in] wf_sw_ver: wifi module software version format:xx.xx.xx (0<=x<=9)
*
* @return OPERATE_RET
*/
OPERATE_RET tuya_iot_wf_soc_dev_init_param(IN CONST GW_WF_CFG_MTHD_SEL cfg, IN CONST GW_WF_START_MODE start_mode,
IN CONST TY_IOT_CBS_S *cbs,IN CONST CHAR_T *firmware_key,
IN CONST CHAR_T *product_key,IN CONST CHAR_T *wf_sw_ver);
此接口是 TuyaOS
应用产品初始化接口,通过开发者传递过来的 PID
、固件key、版本信息,到涂鸦云激活,并向云端获取对应 PID
的模型,即 schema
信息对设备进行初始化。此函数接口参数较多,下面逐一解释每个参数的含义。
GW_WF_CFG_MTHD_SEL cfg
:Wi-Fi
设备工作模式。typedef BYTE_T GW_WF_CFG_MTHD_SEL; // wifi config method select
#define GWCM_OLD 0 // 不支持低功耗模式
#define GWCM_LOW_POWER 1 // with low power mode
#define GWCM_SPCL_MODE 2 // special with low power mode
#define GWCM_OLD_PROD 3 // GWCM_OLD mode with product
#define GWCM_LOW_POWER_AUTOCFG 4 // with low power mode && auto cfg
#define GWCM_SPCL_AUTOCFG 5 // special with low power mode && auto cfg
GW_WF_START_MODE start_mode
:Wi-Fi
设备配网模式。typedef BYTE_T GW_WF_START_MODE;
#define WF_START_AP_ONLY 0 // 仅支持AP配网
#define WF_START_SMART_ONLY 1 // 仅支持EZ配网
#define WF_START_AP_FIRST 2 // 支持AP和EZ配网,初始默认AP,通过重置在AP和EZ配网直接切换
#define WF_START_SMART_FIRST 3 // 支持AP和EZ配网,初始默认EZ,通过重置在AP和EZ配网直接切换
#define WF_START_SMART_AP_CONCURRENT 4 // 支持AP和EZ配网,AP、EZ同时存在
TY_IOT_CBS_S *cbs
其中回调函数集合的用途如下表所示,如果应用不需要处理,设置 NULL
即可。
typedef struct {
GW_STATUS_CHANGED_CB gw_status_cb; //通知应用设备状态发生了变化
GW_UG_INFORM_CB gw_ug_cb; //通知应用设备开始升级
GW_RESET_IFM_CB gw_reset_cb; //通知应用设备正准备重置
DEV_OBJ_DP_CMD_CB dev_obj_dp_cb; //通知应用收到了对象型控制命令
DEV_RAW_DP_CMD_CB dev_raw_dp_cb; //通知应用收到了`RAW`型控制命令
DEV_DP_QUERY_CB dev_dp_query_cb; //通知应用收到了查询命令
DEV_UG_INFORM_CB dev_ug_cb; //通知应用开始子设备升级(网关需要)
DEV_RESET_IFM_CB dev_reset_cb; //通知应用开始子设备重置(网关需要)
ACTIVE_SHORTURL_CB active_shorturl; //通知应用获得了二维码,方便展示二维码(带屏幕设备)。
GW_UG_INFORM_CB pre_gw_ug_cb; //通知应用设备即将开始升级
DEV_UG_INFORM_CB pre_dev_ug_cb; //通知应用子设备即将开始升级(网关需要)
}TY_IOT_CBS_S;
tuya_iot_reg_get_wf_nw_stat_cb
/**
* @brief tuya_iot_reg_get_wf_nw_stat_cb_params
*
* @param wf_nw_stat_cb network status change callback
* @param min_interval_s network status monitor time interval
*
* @return
*/
OPERATE_RET tuya_iot_reg_get_wf_nw_stat_cb_params(IN CONST GET_WF_NW_STAT_CB wf_nw_stat_cb, IN CONST INT_T min_interval_s);
此接口注册了一个网络状态监控的回调函数,每1秒钟查询一次设备的网络状态,如果网络状态发生了变化,TuyaOS
会调用注册的回调接口,将变化通知给应用程序。
GET_WF_NW_STAT_CB wf_nw_stat_cb
:网络状态变化通知回调,按照min_interval_s
周期,检测网络状态,如果发生变化则会调用改回调接口。
对DP相关功能的处理都在该文件内实现。
update_all_dp
DP数据通过 OPERATE_RET dev_report_dp_json_async(IN CONST CHAR_T *dev_id,IN CONST TY_OBJ_DP_S *dp_data,IN CONST UINT_T cnt);
函数上报到云端。
DP 上报函数示例:
VOID_T update_all_dp(VOID_T)
{
OPERATE_RET op_ret = OPRT_OK;
INT_T dp_cnt = 1; /* update DP number */
/* no connect router, return */
GW_WIFI_NW_STAT_E wifi_state = STAT_LOW_POWER;
get_wf_gw_nw_status(&wifi_state);
if (wifi_state <= STAT_AP_STA_DISC || wifi_state == STAT_STA_DISC) {
return;
}
TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)tal_malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S));
if(NULL == dp_arr) {
TAL_PR_ERR("malloc failed");
return;
}
dp_arr[0].dpid = DPID_SWITCH; /* DP ID */
dp_arr[0].type = PROP_BOOL; /* DP type */
dp_arr[0].time_stamp = 0;
dp_arr[0].value.dp_bool = get_led_status(); /* DP data */
/* report DP */
op_ret = dev_report_dp_json_async(NULL ,dp_arr, dp_cnt);
/* free requested memory space */
tal_free(dp_arr);
dp_arr = NULL;
if(OPRT_OK != op_ret) {
TAL_PR_ERR("dev_report_dp_json_async relay_config data error,err_num",op_ret);
}
return;
}
deal_dp_proc
云端下发DP数据后,会通过 STATIC VOID_T dev_obj_dp_cb(IN CONST TY_RECV_OBJ_DP_S *dp)
函数进行回调处理,该函数将数据传入到 VOID_T deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
函数里面执行下发的 DP 命令。 DP 处理代码示例:
VOID_T deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
UCHAR_T dpid;
dpid = root->dpid;
TAL_PR_DEBUG("dpid:%d", dpid);
switch(dpid) {
case DPID_SWITCH:
if (root->value.dp_bool == TRUE) {
set_led_status(LED_ON);
TAL_PR_NOTICE("led on");
} else {
set_led_status(LED_OFF);
TAL_PR_NOTICE("led off");
}
/* update device current status to cloud */
update_all_dp();
break;
default :
break;
}
return;
}
对灯的控制的功能都在该文件内进行实现。
app_led_init
初始化 LED 灯的使用引脚。
VOID_T app_led_init(VOID_T)
{
TUYA_GPIO_BASE_CFG_T led_cfg = {
.mode = TUYA_GPIO_PUSH_PULL,
.direct = TUYA_GPIO_OUTPUT,
.level = TUYA_GPIO_LEVEL_HIGH};
tkl_gpio_init(LED_PIN, &led_cfg);
set_led_status(LED_OFF);
app_led_thread_init();
return;
}
set_led_status
通过调用该接口对 LED 进行开关控制,需要注意的是要根据 LED 是低电平点亮还是高电平点亮
VOID_T set_led_status(LED_STATUS_E led_status)
{
if (LED_ON == led_status) {
tkl_gpio_write(LED_PIN, TUYA_GPIO_LEVEL_HIGH);
}
else {
tkl_gpio_write(LED_PIN, TUYA_GPIO_LEVEL_LOW);
}
cur_led_status = led_status;
return;
}
get_led_status
通过该函数可以获取灯当前的开关状态。
LED_STATUS_E get_led_status(void)
{
return cur_led_status;
}
wifi_status_display_task
当设备处于配网模式时,会通过闪烁 LED 灯来指示:
STATIC VOID_T wifi_status_display_task(VOID_T *args)
{
UINT32_T blink_interval = (UINT32_T)(args);
GW_WIFI_NW_STAT_E network_status;
while (1)
{
get_wf_gw_nw_status(&network_status);
if ((network_status >= STAT_UNPROVISION) && (network_status <= STAT_STA_CONN)) {
tkl_gpio_write(LED_PIN, TUYA_GPIO_LEVEL_LOW);
tal_system_sleep(blink_interval);
tkl_gpio_write(LED_PIN, TUYA_GPIO_LEVEL_HIGH);
tal_system_sleep(blink_interval);
}
else {
tal_system_sleep(3000);
}
}
}
VOID_T app_led_thread_init(VOID_T)
{
OPERATE_RET op_ret = OPRT_OK;
UINT32_T blink_interval = 500;
THREAD_CFG_T blink_thread_cfg = {
.thrdname = "led_task",
.priority = THREAD_PRIO_6,
.stackDepth = 1024};
tal_thread_create_and_start(&blink_task_handle,
NULL,
NULL,
wifi_status_display_task,
blink_interval,
&blink_thread_cfg);
}
该文件主要实现配网按键功能。
app_key_init
初始化按键引脚,创建一个线程去检测按键是否按下。
VOID_T app_key_init(VOID_T)
{
/* init key gpio */
TUYA_GPIO_BASE_CFG_T key_cfg = {
.mode = TUYA_GPIO_PULLUP,
.direct = TUYA_GPIO_INPUT,
.level = TUYA_GPIO_LEVEL_HIGH};
tkl_gpio_init(KEY_PIN, &key_cfg);
/* key thread */
THREAD_HANDLE key_task_handle;
THREAD_CFG_T thread_cfg = {
.thrdname = "key_task",
.priority = THREAD_PRIO_6,
.stackDepth = 2048};
tal_thread_create_and_start(&key_task_handle, NULL, NULL, app_key_task, NULL, &thread_cfg);
}
app_key_task
按键任务函数每 100ms 会去轮询检测按键是否按下,并通过调用 tal_system_get_millisecond()
得到系统节拍时间判断按键是短按还是长按。短按按键切换灯光状态,并进行上报。长按按键调用 tuya_iot_wf_gw_unactive()
函数移除设备。
STATIC VOID_T app_key_task(VOID_T *args)
{
OPERATE_RET op_ret = OPRT_OK;
TUYA_GPIO_LEVEL_E read_level = TUYA_GPIO_LEVEL_HIGH;
UINT32_T time_start = 0, timer_end = 0;
for (;;)
{
tkl_gpio_read(KEY_PIN, &read_level);
if (TUYA_GPIO_LEVEL_LOW == read_level) {
tal_system_sleep(3);
tkl_gpio_read(KEY_PIN, &read_level);
time_start = tal_system_get_millisecond();
while (TUYA_GPIO_LEVEL_LOW == read_level) {
tkl_gpio_read(KEY_PIN, &read_level);
tal_system_sleep(50);
}
timer_end = tal_system_get_millisecond();
if (timer_end - time_start >= LONE_PRESS_TIME) {
/* long press, remove device */
op_ret = tuya_iot_wf_gw_unactive();
if (op_ret != OPRT_OK) {
TAL_PR_ERR("long press tuya_iot_wf_gw_unactive error, %d", op_ret);
return;
}
}
else if (timer_end - time_start > 50) {
/* normal press */
if (get_led_status()) {
set_led_status(LED_OFF);
}
else {
set_led_status(LED_ON);
}
update_all_dp();
}
}
tal_system_sleep(100);
}
}
开发者对于 SDK 的启动流程只需要重点关注1个函数tuya_app_main()
,该函数为应用唯一需要实现的函数, 统一 SOC 开发与协议接入开发模式,同时也降低外部开发者理解成本 。
开发者需在在 tuya_app_main()
中完成调用 TuyaOS 初始化函数,完成 TuyaOS 启动 。
使用 Tuya Wind IDE
创建的框架取消了 pre_app_init()
、pre_device_init()
、app_init()
空函数,开发者无需关心这些函数。
开发者在编译代码之前需要获取 PID、UUID、AUTHKEY。开发者可通过登录 IoT生产研发采购购买模组获得授权码清单。 获得授权码清单之后,在 device.c
中修改它,这样你就获得属于自己的一款产品了。
TuyaOS
基础功能,比如控制、状态变化、升级等功能,已经定义了一套回调接口,并且在此演示程序中,已经对这些接口进行了基本的实现,修改此接口即可改变实现方式。__soc_dev_obj_dp_cmd_cb
函数中处理 DP 数据。函数名称 | 函数功能 |
| 升级文件下载完成回调,检查升级下载结果。 |
| 升级文件下载回调,将下载下来的升级数据写入文件。 |
| 设备升级启动回调,此接口是升级入口。 |
| 设备运行状态变化回调。 |
| 设备 |
| 设备 |
| 设备 |
| 设备重启请求,设备有重启需求之前会调用此接口让应用程序做好重启准备。 |
| 设备网络连接状态变化回调,通知应用设备网络连接状态发生变化。 |
右键点击 app 目录下的 demo 文件,点击 Build Project
随后需要输入生成的版本,这里以
1.0.0
为例。 输入版本号之后就等待编译结果,显示
COMPILE SUCCESS
就表明编译成功。
在编译完成后,会将生成的固件按照不同版本放在所编译的工程目录下 output 文件夹内。
例:如果我们编译的是 apps 文件夹中的 tuyaos_demo_quickstart_light
工程,编译版本为1.0.0,那么生成的固件将会放在 apps/tuyaos_demo_quickstart_light/output/1.0.0
中。
编译后的产物如上图所示,这里主要关注的 tuyaos_demo_quickstart_light_QIO_1.0.0.bin
、tuyaos_demo_quickstart_light_UA_1.0.0.bin
和 tuyaos_demo_quickstart_light_UG_1.0.0.bin
这三个文件。
文件名 | 功能 |
| 生产区固件:boot+用户区固件+检测固件。 |
| 用户区固件 |
| 升级固件 |
请开发者查看PC/tools目录,原厂烧录工具及说明文档都在此目录下。
在使用过程中,如果遇到问题,可以访问涂鸦开发者平台Wi-Fi设备接入-故障排除,按照自己遇到的问题分类,寻找合适的解决方法。