如何开发

更新时间:2023-09-15 09:13:12下载pdf

本文旨在通过一个简单 Demo,使开发者能够了解涂鸦 SDK 的启动流程,带领开发者基于涂鸦提供的 SDK 对模组进行二次开发。

本文内容适用于较低版本的 TuyaOS SDK 开发流程,有关最新的文档更新动态或者产品动态,请访问 TuyaOS 概述TuyaOS 开发者论坛

demo 功能介绍:硬件使用 CBU Nano 板,要求可以通过按键(S2)和手机 APP 去控制板子上自带的 LED 灯(D2),配网状态发生改变时会通过日志将连网状态打印出来。

为了能够通过 Micro USB 进行烧录和打印日志,需要将板子上的四个拨码开关全部打开(拨码位置拨到 ON 一端,非数字一端)。

示例 demo 的 GitHub 仓库地址:https://github.com/Tuya-Community/bk7231n_light1_io_xx

启动流程介绍

在开始开发前,需要了解 SDK 的初始化流程。这里需要重点关注四个函数pre_app_init()pre_device_init()app_init()device_init()。启动流程如下图所示,对于这四个函数的详细介绍请阅读3. tuya_device 文件介绍

如何开发

工程创建

在 SDK 的 apps 目录下新建一个bk7231n_light1_io_xx的文件夹,该文件夹名称就是工程名,也是上传固件时使用的固件标识名。所以大家在创建的时候文件夹名称应改成不同的名字,不然在编译生成固件后,上传到云平台时会因为已经有了该固件标识名导致固件上传失败。

固件命名可以按照:芯片平台+产品类型+产品特性+厂商标识名/个人姓名缩写,也可按照个人喜好来命名。如:固件名称可以命名为bk7231n_light1_io_xxbk7231n表示使用的是 bk7231n 芯片进行开发的,light1表示一路灯,io表示通过 IO 口拉高拉低来控制灯的亮灭,xx为名字缩写。

bk7231n_light1_io_xx文件夹中新建includesrc两个文件夹,include文件夹用来放工程中用到的头文件,src文件夹用来放工程中用到的源文件(可以在includesrc文件夹中创建新的文件夹对不同功能的.c 和.h 文件进行分类管理)。

该工程的目录树如下:

ty_iot_sdk_bk7231n_2.3.1
├── apps
│   ├── bk7231n_light1_io_xx # 新建的工程
│   │   ├── include
│   │   │   ├── dp_process.h
│   │   │   ├── light_system.h
│   │   │   └── tuya_device.h
│   │   └── src
│   │       ├── dp_process.c # DP(data point)处理文件
│   │       ├── light_system.c # 灯驱动文件
│   │       └── tuya_device.c # 该文件十分重要,用来实现 SDK 中需要的一些回调函数。
│   │  
│   ├── tuya_demo_elp_1plug # SDK 中自带的 demo
│   ├── tuya_demo_light_pwm # SDK 中自带的 demo
│   └── tuya_demo_template # SDK 中自带的 demo
├── build_app.sh
├── CHANGELOG.md
├── platforms
├── README.md
└── sdk

在开始编写代码前,我们还需要对涂鸦 SDK 中常用头文件有一个了解。

头文件名称 功能
uni_log.h Tuya IoT OS 日志打印。
tuya_iot_wifi_api.h 提供与 WiFi 相关的接口。常用 API 有初始化 tuya IoT 框架进行设置 Wi-Fi 的工作模式,重置设备再次进入配网状态等
tuya_iot_com_api.h 封装了各个服务组件的对外接口。常用 API 有获取 SDK 信息等
tuya_hal_system.h 系统接口封装。常用 API 有得到系统重启原因,得到系统运行的 ticket 等
tuya_error_code.h 涂鸦对一些错误类型的定义
tuya_cloud_com_defs.h 一些与云端相关的类型定义。
tuya_cloud_types.h 对参数类型的封装。对变量、函数进行类型定义或修饰时应调用这里的函数。
tuya_gpio.h 对 gpio 的 API 进行了封装,需在 tuya IoT 初始化完成后调用。
tuya_key.h 按键功能,需在 tuya IoT 初始化完成后调用。

tuya_device 文件

tuya_device.ctuya_device.h这两个文件主要用来实现产测函数和对实现对涂鸦 IoT 框架的初始化。实现函数列表如下:

函数名称 函数功能
mf_user_pre_gpio_test_cb() gpio 测试前置回调函数。
mf_user_enter_callback() 通知应用已经进入到产测,在回调中对应用数据清除。
mf_user_callback() 应用结合需要将授权信息中需要的部分进行写入到 uf 等不加密的空间,加快启动应用获取信息的速度。
mf_user_product_test_cb() 成品测试回调。
pre_app_init() 用来处理需要快速初始化的函数,此时 tuya iot 相关功能还未开始初始化,无法调用和 tuya iot 相关函数。
pre_device_init() tuya iot 初始化完成,WiFi 和 tuya kv flash 还未初始化完成。
app_init() kv flash 初始化完成。
device_init() 所有准备工作都已完成。

在最新版本的 SDK 中,至少需要实现app_init()device_init()这两个函数,其他函数没有需求可以不用包含到 tuya_device.c 中实现。

1 产测函数介绍

mf_user_pre_gpio_test_cb(), mf_user_enter_callback(), mf_user_callback()mf_user_product_test_cb()这四个函数是和产测功能相关的,在最新的 SDK 中对该部分功能无需求的话可以不用实现(在 tuya_device.c 中不用写,SDK 内会有对应 weak 修饰的函数。如果不写这四个函数导致编译不过,提示没有找到这些函数的话,说明你的 SDK 不是最新的,那么还是要加上这四个函数的)。

为了兼容老版本的 SDK,这里在 tuya_device.c 中会加上这四个函数。

/**
* @brief pre_gpio_test gpio test pre-interface, used to prepare for the gpio test
*
* @return none
*/
VOID_T mf_user_pre_gpio_test_cb(VOID_T)
{
    return;
}

/**
* @brief erase application data from flash
*
* @return none
*/
STATIC VOID_T hw_reset_flash_data(VOID_T)
{
    /* erase application data from flash */
    return;
}

/**
* @brief configure to enter the production test callback interface
*
* @return none
*/
VOID_T mf_user_enter_callback(VOID_T)
{
    hw_reset_flash_data();

    return;
}

/**
* @brief configuring the write callback interface
*
* @return none
*/
VOID_T mf_user_callback(VOID_T)
{
    return;
}

/**
/**
* @brief Finished Product test callbacks
*
* @return OPRT_OK
*/
OPERATE_RET mf_user_product_test_cb(USHORT_T cmd,UCHAR_T *data, UINT_T len, OUT UCHAR_T **ret_data,OUT USHORT_T *ret_len)
{
    return OPRT_OK;
}

这里只是实现一个 demo 对于产测部分的功能基本上都没有具体实现,对于量产的产品要按照产品的需要对这些产测函数的功能进行具体的实现。

2 pre_app_init()函数介绍

执行到pre_app_init()的时候,tuya iot 还没有开始初始化,所有和 tuya iot 有关的资源都不能调用。

由于 tuya iot 和 flash 的初始化时间较长,对于一些需要快速启动的资源都需要再该函数内实现。

为了让设备一上电就快速把灯点亮,这里将灯的初始化放到pre_app_init()中进行快速初始化。由于此时 tuya iot 还未开始初始化,那么就不能使用 tuya iot 提供的 GPIO 初始化函数OPERATE_RET tuya_gpio_inout_set(IN CONST TY_GPIO_PORT_E port, IN CONST BOOL_T in);来进行初始化。需要借助"early_init"这个事件来进行快速初始化。

light_system文件相关内容:

#include "tuya_gpio.h"

/**
* @brief need quick start tasks

* @return none
*/
VOID_T fast_boot(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;
    op_ret = light_init();
    if (op_ret != OPRT_OK) {
        PR_ERR("fast boot light init error, %d", op_ret);
        return;
    }
}

/**
* @brief light gpio init
*
* @return none
*/
OPERATE_RET light_init(VOID_T)
{
     OPERATE_RET op_ret = OPRT_OK;

    /* light pin set output */
    op_ret = tuya_gpio_inout_set(LIGHT_PIN, FALSE);
    if (op_ret != OPRT_OK) {
        PR_ERR("light init gpio set inout error!");
        return op_ret;
    }

    /* light pin mode set pullup */
    op_ret = tuya_gpio_mode_set(LIGHT_PIN, TY_GPIO_PULLUP);
    if (op_ret != OPRT_OK) {
        PR_ERR("light init gpio mode set error!");
        return op_ret;
    }

    /* light on */
    op_ret = set_light_status(LIGHT_ON);
    if (op_ret != OPRT_OK) {
        PR_ERR("light init light on error!");
        return op_ret;
    }

    return op_ret;

}

注册"early_init"事件来进行快速初始化,tuya_device.c文件相关内容:

/**
* @brief application initialization prep work
*
* @return none
*/
VOID_T pre_app_init(VOID_T)
{
    ty_subscribe_event(EVENT_SDK_EARLY_INIT_OK, "early_init", fast_boot, FALSE);

    return;
}

3 pre_device_init()函数介绍

执行到pre_device_init()函数时,tuya iot 初始已经完成,但是这时候 WiFi 和 kv flash 还没有初始化成功,在这里已经可以使用 tuya iot 提供的函数。

在本 demo 中为了能够尽快的可以使用按键功能,由于按键的初始化使用的是 tuya iot 提供的接口,这个位置是能够调用 tuya iot 相关资源最快的位置。

/**
* @brief device initialization prep work
*
* @return none
*/
VOID_T pre_device_init(VOID_T)
{
    /* reset key init */
    wifi_key_init();

    PR_DEBUG("%s",tuya_iot_get_sdk_info()); /* print SDK information */
    PR_DEBUG("%s:%s", APP_BIN_NAME, DEV_SW_VERSION); /* print the firmware name and version */
    PR_NOTICE("firmware compiled at %s %s", __DATE__, __TIME__); /* print firmware compilation time */
    PR_NOTICE("system reset reason:[%s]",tuya_hal_system_get_rst_info()); /* print system reboot causes */

    SetLogManageAttr(TY_LOG_LEVEL_NOTICE); /* set the log level */

    return;
}

tuya_device.c中,按键初始化函数的实现:

#include "uni_log.h"
#include "tuya_iot_wifi_api.h"
#include "tuya_hal_system.h"
#include "tuya_iot_com_api.h"
#include "tuya_error_code.h"
#include "gw_intf.h"
#include "tuya_gpio.h"
#include "tuya_key.h"
#include "base_event_info.h"

#include "tuya_device.h"
#include "light_system.h"
#include "dp_process.h"

/***********************************************************
*************************micro define***********************
***********************************************************/
#define APP_RAW_PRINT_DEBUG 1

/* wifi config */
#define WIFI_WORK_MODE_SEL          GWCM_OLD_PROD   /* select Wi-Fi work mode */
#define WIFI_CONNECT_OVERTIME_S     180             /* connect network timeout time, uint: s */

/* reset key config */
#define WIFI_KEY_PIN                TY_GPIOA_9  /* reset button pin */
#define WIFI_KEY_TIMER_MS           100         /* key scan poll time, default 100ms */
#define WIFI_KEY_LONG_PRESS_MS      3000        /* long press time */
#define WIFI_KEY_SEQ_PRESS_MS       400
#define WIFI_KEY_LOW_LEVEL_ENABLE   TRUE

/**
* @brief initiation reset key
*
* @param[in] none
* @return none
*/
STATIC VOID_T wifi_key_init(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;

    KEY_USER_DEF_S key_def;

    op_ret = key_init(NULL, 0, WIFI_KEY_TIMER_MS);
    if (op_ret != OPRT_OK) {
        PR_ERR("key_init err:%d", op_ret);
        return;
    }

    /* config key parameter */
    memset(&key_def, 0, SIZEOF(key_def));
    key_def.port = WIFI_KEY_PIN;
    key_def.long_key_time = WIFI_KEY_LONG_PRESS_MS;
    key_def.low_level_detect = WIFI_KEY_LOW_LEVEL_ENABLE;
    key_def.lp_tp = LP_ONCE_TRIG;
    key_def.call_back = wifi_key_process;
    key_def.seq_key_detect_time = WIFI_KEY_SEQ_PRESS_MS;

    /* register key */
    op_ret = reg_proc_key(&key_def);
    if (op_ret != OPRT_OK) {
        PR_ERR("reg_proc_key err:%d", op_ret);
    }
}

tuya_device.c中,按键被按下后回调函数的实现:

/**
* @brief button is pressed, call the function to process
*
* @param[in] port: button pin
* @param[in] type: button press type
* @param[in] cnt: press count
* @return none
* @Others: long press enter connect network mode, normal press toggle led
*/
STATIC VOID_T wifi_key_process(TY_GPIO_PORT_E port, PUSH_KEY_TYPE_E type, INT_T cnt)
{
    OPERATE_RET op_ret = OPRT_OK;

    PR_DEBUG("port:%d, type:%d, cnt:%d", port, type, cnt);

    if (port = WIFI_KEY_PIN) {
        if (LONG_KEY == type) { /* long press enter connect network mode */
            op_ret = tuya_iot_wf_gw_unactive();
            if (op_ret != OPRT_OK) {
                PR_ERR("long press tuya_iot_wf_gw_unactive error, %d", op_ret);
                return;
            }
        } else if (NORMAL_KEY == type) {
#if 1 /* turn on or off the button to control the light function */
            if (get_light_status() == LIGHT_OFF) {
                op_ret = set_light_status(LIGHT_ON); /* light turn on */
                if (op_ret != OPRT_OK) {
                    PR_ERR("dp process set light status error, %d", op_ret);
                    return;
                }
            } else {
                op_ret = set_light_status(LIGHT_OFF); /* light turn off */
                if (op_ret != OPRT_OK) {
                    PR_ERR("dp process set light status error, %d", op_ret);
                    return;
                }
            }
            /* update device current status to cloud */
            update_all_dp();
#endif
        } else {
            PR_NOTICE("key type is no deal");
        }
    }
}

4 app_init()函数介绍

执行到app_init()时,WiFi 和 kv flash 初始化完成。可以在app_init()中使用 tuya sdk 提供的加密 flash 接口,比如:wd_common_write、wd_common_read、wd_user_param_write、wd_user_param_read 等加密的 flash 接口。

/**
* @brief application initialization interface
*
* @return none
*/
VOID_T app_init(VOID_T)
{
    return;
}

5 device_init()函数介绍

执行到device_init()时,初始化工作都已完成,这时需要对 SDK 中的一些回调函数进行完善和注册。这些函数可以分为二部分:需要实现一些回调函数用来初始化 IoT 框架和实现 Wi-Fi 连网状态改变后的回调函数。也就是对下面的两个函数的实现。

涂鸦 IoT 框架初始化函数:

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

WiFi 状态回调函数:

#define tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_stat_cb) \
    tuya_iot_reg_get_wf_nw_stat_cb_params(wf_nw_stat_cb, 1)
__TUYA_IOT_WIFI_API_EXT \
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);

涂鸦 IoT 框架初始化函数介绍

涂鸦 IoT 框架初始化函数:

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);
参数 说明
cfg WiFi 工作模式选择,可设置参数请看下面“WiFi 工作模式定义”
start_mode WiFi 启动时模式设置,可设置参数请看下面“WiFi 启动模式定义”
*cbs 用来注册在 tuya SDK 运行过程中所必须的回调函数,各回调函数作用,请看“tuya iot 回调函数说明”
*firmware_key 固件 key,二次开发的非 oem 类型的一般填写NULL
*product_key 产品 ID,从涂鸦 IoT 平台创建产品获取,在 tuya_device.h 中宏定义。
*wf_sw_ver 软件版本号

WiFi 工作模式定义:

/* tuya sdk definition of wifi work mode */
typedef BYTE_T GW_WF_CFG_MTHD_SEL;  // wifi config method select
#define GWCM_OLD                0   // do not have low power mode
#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

WiFi 工作模式主要是包括 WiFi 工作状态和进入成品产测模式两部分功能。关于成品产测,下列所有模式中,成品产测的功能需要用户自己实现。

Wi-Fi 工作模式 描述
GWCM_OLD 涂鸦将不再进行任何关于是否需要进入产测的判断。关于如何触发从而进入产测模式需要用户自己实现。
GWCM_LOW_POWER 配网前上电低功耗常亮,需要手工切换才能进入配网状态;
配网状态下,10 秒内没配网成功,手工重启保持上次配网状态,10s 后未配网,手工重启进入低功耗常亮状态;15 分钟未配网,自动进入低功耗常亮状态,其中 15 分钟可以设置;
配网成功后,app 移除设备自动重启,进入配网状态,默认smartcfg配网模式,擦除保存的ssidpassword;手工移除,设备自动进入配网状态,擦除保存的ssidpassword
设备只有在低功耗状态下,重启设备才会扫描产测路由,进入产测模式。
GWCM_SPCL_MODE 配网前上电低功耗常亮,需要手工切换才能进入配网状态;
配网状态下,10 秒内没配网成功,手工重启保持上次配网状态,10s 后未配网,手工重启进入低功耗常亮状态,15 分钟未配网,自动进入低功耗常亮状态,其中 15 分钟可以设置;
配网成功后,app 移除设备自动重启,进入配网状态,默认smartcfg配网模式,擦除保存的ssidpassword;手工移除,设备自动进入配网状态,进入防误触模式,不擦除保存的ssidpassword
设备只有在低功耗状态下,重启设备才会扫描产测路由,进入产测模式。
GWCM_OLD_PROD 上电即可进入配网状态,并且一直处于配网状态;
设备在配网状态下,重启设备都会主动扫描产测路由,进入产测模式。
GWCM_LOW_POWER_AUTOCFG 配网前上电进入smartcfg配网模式,smartcfgap模式来回切换;
配网状态下,15 分钟内重启,保持,保持上次配网状态,15 分钟未配网自动进入低功耗常亮状态,其中 15 分钟可以设置;
配网成功后,app 移除设备自动重启,进入配网状态,默认smartcfg配网模式;手工移除,设备自动进入配网状态,默认smartcfg配网模式;
设备在配网状态下,重启设备都会主动扫描产测路由,进入产测模式。
GWCM_SPCL_AUTOCFG 配网前上电进入smartcfg配网模式,smartcfgap模式来回切换;
配网状态下,15 分钟内重启,保持上次配网状态,15 分钟未配网自动进入低功耗常亮状态,其中 15 分钟可以设置;
配网成功后,app 移除设备自动重启,进入配网状态,默认smartcfg配网模式,擦除保存的ssidpassword,15 分钟内重启,保持上次配网状态,15 分钟未配网自动进入低功耗常亮状态,其中 15 分钟可以设置;手工移除,设备自动进入配网状态,进入防误触模式,不擦除保存的ssidpassword,10 秒内重启,保持上次配网状态,10 秒后重启,立即连接已经配网路由器,15 分钟内重启,保持,保持上次配网状态,15 分钟未配网自动进入低功耗常亮状态,其中 15 分钟可以设置;;
设备在配网状态下,重启设备都会主动扫描产测路由,进入产测模式。

WiFi 启动模式定义:

/* tuya sdk definition of wifi start mode */
typedef BYTE_T GW_WF_START_MODE;
#define WF_START_AP_ONLY        0   // only have ap-cfg mode
#define WF_START_SMART_ONLY     1   // only have smart-cfg mode
#define WF_START_AP_FIRST       2   // have both ap-cfg and smart-cfg. default is ap-cfg mode
#define WF_START_SMART_FIRST    3   // have both ap-cfg and smart-cfg. default is smart-cfg mode
#define WF_START_SMART_AP_CONCURRENT    4   //  ap-cfg and smart-cfg is concurrent
配网模式 描述
WF_START_AP_ONLY 仅支持AP配网模式。
WF_START_SMART_ONLY 仅支持smartcfg配网模式。
WF_START_AP_FIRST 支持AP配网或者smartcfg配网模式,默认AP配网模式,但是经过重置,可以切换成smartcfg配网模式。
WF_START_SMART_FIRST 支持AP配网或者smartcfg配网模式,默认smartcfg配网模式,但是经过重置,可以切换成AP配网模式。
WF_START_SMART_AP_CONCURRENT 涂鸦万能配网模式,支持APsmartcfg配网共存。

如果模组是 WiFi&BLE 双模模组的话,上述的几种配网模式默认都支持蓝牙配网。


tuya iot 回调函数说明(结构体内的注释在代码里是不能有的,否则编译不过的):

TY_IOT_CBS_S wf_cbs = {
	status_changed_cb,\  //网关状态改变回调
	gw_ug_inform_cb,\    //ota 升级通知回调函数
	gw_reset_cb,\        //设备重置回调函数
	dev_obj_dp_cb,\      //云端下发 obj(bool, value, enum, string 和 fault) 类型的数据后调用该函数
	dev_raw_dp_cb,\      //云端下发 raw 类型数据后调用该函数
	dev_dp_query_cb,\    //app 进入面板后触发查询 dp 点状态时调用该函数
	NULL,
};

涂鸦 IoT 框架初始化函数实现示例:

/**
* @brief device initialization interface
*
* @return OPRT_OK: success, others: please refer to tuya error code description document
*/
OPERATE_RET device_init(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;

    TY_IOT_CBS_S wf_cbs = {
        status_changed_cb,\
        gw_ug_inform_cb,
        gw_reset_cb,\
        dev_obj_dp_cb,\
        dev_raw_dp_cb,\
        dev_dp_query_cb,\
        NULL,
    };

    /* tuya IoT framework initialization */
    op_ret = tuya_iot_wf_soc_dev_init_param(WIFI_WORK_MODE_SEL, WF_START_SMART_FIRST, &wf_cbs, NULL, PRODECT_ID, DEV_SW_VERSION);
    if(OPRT_OK != op_ret) {
        PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret);
        return op_ret;
    }
	...
    return op_ret;
}

TY_IOT_CBS_S回调函数的具体实现:

  • status_changed_cb,在网关状态发生改变后调用该函数进行处理:

    /**
    * @brief report all dp status
    *
    * @return none
    */
    VOID_T status_changed_cb(IN CONST GW_STATUS_E status)
    {
        PR_NOTICE("status_changed_cb is status:%d",status);
    
        return;
    }
    
  • gw_ug_inform_cb,ota 升级通知,功能实现参考如下:

    /**
    * @brief firmware download content process callback
    *
    * @param[in] fw: firmware info
    * @param[in] total_len: firmware total size
    * @param[in] offset: offset of this download package
    * @param[in] data && len: this download package
    * @param[out] remain_len: the size left to process in next cb
    * @param[in] pri_data: private data
    * @return OPRT_OK: success  Other: fail
    */
    STATIC OPERATE_RET get_file_data_cb(IN CONST FW_UG_S *fw, IN CONST UINT_T total_len, IN CONST UINT_T offset,
                                         IN CONST BYTE_T *data, IN CONST UINT_T len, OUT UINT_T *remain_len, IN PVOID_T
    pri_data)
    {
        PR_DEBUG("Rev File Data");
        PR_DEBUG("Total_len:%d ", total_len);
        PR_DEBUG("Offset:%d Len:%d", offset, len);
    
        return OPRT_OK;
    }
    
    /**
    * @brief firmware download finish result callback
    *
    * @param[in] fw: firmware info
    * @param[in] download_result: 0 means download succes. other means fail
    * @param[in] pri_data: private data
    * @return none
    */
    VOID_T upgrade_notify_cb(IN CONST FW_UG_S *fw, IN CONST INT_T download_result, IN PVOID_T pri_data)
    {
        PR_DEBUG("download  Finish");
        PR_DEBUG("download_result:%d", download_result);
    
        return;
    }
    
    /**
    * @brief ota inform callback
    *
    * @param[in] fw: firmware info
    * @return
    */
    STATIC INT_T gw_ug_inform_cb(IN CONST FW_UG_S *fw)
    {
        PR_DEBUG("Rev GW Upgrade Info");
        PR_DEBUG("fw->fw_url:%s", fw->fw_url);
        PR_DEBUG("fw->sw_ver:%s", fw->sw_ver);
        PR_DEBUG("fw->file_size:%d", fw->file_size);
    
        return tuya_iot_upgrade_gw(fw, get_file_data_cb, upgrade_notify_cb, NULL);
    }
    
  • gw_reset_cb设备重置回调,当设备被重置或 app 上移除设备后调用该函数擦除用户数据和其他重置后需要执行的操作。

    /**
    * @brief called after reset device or app remove device
    *
    * @param[in] type: gateway reset type
    * @return none
    * @others reset factory clear flash data
    */
    STATIC VOID_T gw_reset_cb(IN CONST GW_RESET_TYPE_E type)
    {
        PR_DEBUG("gw_reset_cb type:%d",type);
        if(GW_REMOTE_RESET_FACTORY != type) {
            PR_DEBUG("type is GW_REMOTE_RESET_FACTORY");
            return;
        }
    
        hw_reset_flash_data();
    
        return;
    }
    
  • dev_obj_dp_cb云端下发 dp(Data Point)点后调用该函数进行处理,bool, value, enum, string 和 fault)类型通过该函数进行回调处理,raw类型通过dev_raw_dp_cb进行回调处理。

    /**
    * @brief called after the cloud sends data of type bool, value, enum, string or fault
    *
    * @param[in] dp: obj dp info
    * @return none
    */
    STATIC VOID_T dev_obj_dp_cb(IN CONST TY_RECV_OBJ_DP_S *dp)
    {
        UCHAR_T i = 0;
    
        PR_DEBUG("dp->cid:%s dp->dps_cnt:%d", dp->cid, dp->dps_cnt);
    
        for(i = 0;i < dp->dps_cnt;i++) {
            deal_dp_proc(&(dp->dps[i]));
        }
    
        return;
    }
    
  • dev_raw_dp_cb云端下发raw类型的 DP(Data Point)点后调用该函数进行处理。

    /**
    * @brief called after the cloud sends data of type raw
    *
    * @param[in] dp: raw dp info
    * @return none
    */
    STATIC VOID_T dev_raw_dp_cb(IN CONST TY_RECV_RAW_DP_S *dp)
    {
        PR_DEBUG("raw data dpid:%d", dp->dpid);
        PR_DEBUG("recv len:%d", dp->len);
    #if APP_RAW_PRINT_DEBUG
        INT_T i = 0;
        for(i=0; i<dp->len; i++) {
            PR_DEBUG_RAW("%02X ", dp->data[i]);
        }
    #endif
        PR_DEBUG_RAW("\n");
        PR_DEBUG("end");
    
        return;
    }
    
  • dev_dp_query_cb,app 进入面板后触发查询 dp(Data Point)点状态。

    /**
    * @brief report all dp status
    *
    * @return none
    */
    VOID_T hw_report_all_dp_status(VOID_T)
    {
        /* report all dp status */
        update_all_dp();
    
        return;
    }
    
    /**
    * @brief query device dp status
    *
    * @param[in] dp_qry: query info
    * @return none
    */
    STATIC VOID_T dev_dp_query_cb(IN CONST TY_DP_QUERY_S *dp_qry)
    {
        PR_NOTICE("Recv DP Query Cmd");
    
        hw_report_all_dp_status();
    
        return;
    }
    

Wi-Fi 网络状态改变回调函数介绍

当 WiFi 网络状态发生改变后调用该函数进行处理。

函数原型:

#define tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_stat_cb) \
    tuya_iot_reg_get_wf_nw_stat_cb_params(wf_nw_stat_cb, 1)
__TUYA_IOT_WIFI_API_EXT \
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);

WiFi 的网络状态:

/* tuya sdk definition of wifi-net status */
typedef BYTE_T GW_WIFI_NW_STAT_E;
#define STAT_LOW_POWER          0   // idle status,use to external config network
#define STAT_UNPROVISION        1   // smart config status
#define STAT_AP_STA_UNCFG       2   // ap WIFI config status
#define STAT_AP_STA_DISC        3   // ap WIFI already config,station disconnect
#define STAT_AP_STA_CONN        4   // ap station mode,station connect
#define STAT_STA_DISC           5   // only station mode,disconnect
#define STAT_STA_CONN           6   // station mode connect
#define STAT_CLOUD_CONN         7   // cloud connect
#define STAT_AP_CLOUD_CONN      8   // cloud connect and ap start
#define STAT_REG_FAIL           9   // register fail
#define STAT_OFFLINE            10   // offline
#define STAT_MQTT_ONLINE        11
#define STAT_MQTT_OFFLINE       12
#define STAT_UNPROVISION_AP_STA_UNCFG		13 //smart-cfg and ap-cfg concurrent config status

功能实现示例:

/**
* @brief This function is called when the state of the device connection has changed
*
* @param[in] stat: curr network status
* @return none
*/
STATIC VOID_T wf_nw_status_cb(IN CONST GW_WIFI_NW_STAT_E stat)
{
    /* print current Wi-Fi status */
    PR_NOTICE("wf_nw_status_cb, wifi_status:%d", stat);

    /* report all DP status when connected to the cloud */
    if (stat == STAT_CLOUD_CONN || stat == STAT_AP_CLOUD_CONN) {
        hw_report_all_dp_status();
    }

    return;
}

/**
* @brief device initialization interface
*
* @return OPRT_OK: success, others: please refer to tuya error code description document
*/
OPERATE_RET device_init(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;

	...

    /* register Wi-Fi connection status change callback function */
    op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb);
    if(OPRT_OK != op_ret) {
        PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d", op_ret);
        return op_ret;
    }

    return op_ret;
}

6 修改 PID

在涂鸦 IoT 平台中,PID 就相当于产品的身份证。如果你在使用智能生活 APP 进行配网的时候发现搜索到的产品和你创建的产品不一致(此时设备已经进入和配网模式),很有可能就是 PID 没有修改。当你新开发一个产品时,一定要记住要将tuya_device.h 中的 PRODECT_ID 改成你的 PID

tuya_device.h文件中:

/**
* @file tuya_device.h
* @author www.tuya.com
* @brief
* @version 0.1
* @date 2021-08-19
*
* @copyright Copyright (c) tuya.inc 2021
*
*/

#ifndef __TUYA_DEVICE_H__
#define __TUYA_DEVICE_H__

#include "tuya_cloud_com_defs.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifdef _TUYA_DEVICE_GLOBAL
    #define _TUYA_DEVICE_EXT
#else
    #define _TUYA_DEVICE_EXT extern
#endif /* _TUYA_DEVICE_GLOBAL */

/***********************************************************
*************************micro define***********************
***********************************************************/
/* device information define */
#define DEV_SW_VERSION USER_SW_VER
#define PRODECT_ID "fnrwpglflmbhjvvh"

/***********************************************************
***********************typedef define***********************
***********************************************************/

/***********************************************************
***********************variable define**********************
***********************************************************/

/***********************************************************
***********************function define**********************
***********************************************************/

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /*__TUYA_DEVICE_H__*/

dp_process 文件介绍

对 DP 相关功能的处理都在该文件内实现。

1 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);函数上报到云端。

上报代码示例:

/**
* @brief upload all dp data
*
* @return none
*/
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 *)Malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S));
    if(NULL == dp_arr) {
        PR_ERR("malloc failed");
        return;
    }

    /* initialize requested memory space */
    memset(dp_arr, 0, dp_cnt*SIZEOF(TY_OBJ_DP_S));

    dp_arr[0].dpid = DPID_LIGHT_SWITCH; /* DP ID */
    dp_arr[0].type = PROP_BOOL; /* DP type */
    dp_arr[0].time_stamp = 0;
    dp_arr[0].value.dp_bool = get_light_status(); /* DP data */

    /* report DP */
    op_ret = dev_report_dp_json_async(NULL ,dp_arr, dp_cnt);

    /* free requested memory space */
    Free(dp_arr);
    dp_arr = NULL;
    if(OPRT_OK != op_ret) {
        PR_ERR("dev_report_dp_json_async relay_config data error,err_num",op_ret);
    }
}

2 DP 下发处理函数

云端下发 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 处理代码示例:

/**
* @brief handle dp commands from the cloud
*
* @param[in] root: pointer header for dp data
* @return none
*/
VOID_T deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
    OPERATE_RET op_ret = OPRT_OK;

    UCHAR_T dpid;
    dpid = root->dpid;
    PR_DEBUG("dpid:%d", dpid);

     switch(dpid) {
        case DPID_LIGHT_SWITCH:
            if (root->value.dp_bool == TRUE) {
                op_ret = set_light_status(LIGHT_ON);
                if (op_ret != OPRT_OK) {
                    PR_ERR("dp process set light status error, %d", op_ret);
                    return;
                }
            } else {
                op_ret = set_light_status(LIGHT_OFF);
                if (op_ret != OPRT_OK) {
                    PR_ERR("dp process set light status error, %d", op_ret);
                    return;
                }
            }

            /* update device current status to cloud */
            update_all_dp();
            break;

        default :
            break;
    }
}

light_system 文件介绍

对灯的控制的功能都在该文件内进行实现。

1 灯初始化函数

为了能对灯快速初始化,这里对灯的 IO 的初始化操作使用的是原厂提供的函数接口进行初始化的。

light_system.h文件:

#include "tuya_cloud_types.h"
#include "tuya_gpio.h"

#define LIGHT_PIN   TY_GPIOA_16

light_system.c文件:

#include "light_system.h"
#include "uni_log.h"

STATIC LED_STATUS_E cur_light_status = LIGHT_ON;

/**
* @brief need quick start tasks

* @return none
*/
VOID_T fast_boot(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;
    op_ret = light_init();
    if (op_ret != OPRT_OK) {
        PR_ERR("fast boot light init error, %d", op_ret);
        return;
    }
}

/**
* @brief light gpio init
*
* @return none
*/
OPERATE_RET light_init(VOID_T)
{
     OPERATE_RET op_ret = OPRT_OK;

    /* light pin set output */
    op_ret = tuya_gpio_inout_set(LIGHT_PIN, FALSE);
    if (op_ret != OPRT_OK) {
        PR_ERR("light init gpio set inout error!");
        return op_ret;
    }

    /* light pin mode set pullup */
    op_ret = tuya_gpio_mode_set(LIGHT_PIN, TY_GPIO_PULLUP);
    if (op_ret != OPRT_OK) {
        PR_ERR("light init gpio mode set error!");
        return op_ret;
    }

    /* light on */
    op_ret = set_light_status(LIGHT_ON);
    if (op_ret != OPRT_OK) {
        PR_ERR("light init light on error!");
        return op_ret;
    }

    return op_ret;

}

2 灯控制函数

对灯的控制函数也是调用原厂的函数接口进行实现的。

light_system.h文件:

typedef LED_STATUS_E LED_STATUS_E;
#define LIGHT_OFF   0
#define LIGHT_ON    1

light_system.c文件:

/**
* @brief light on
*
* @return none
*/
STATIC OPERATE_RET light_on(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;

    /* pin set low level, light on */
    op_ret = tuya_gpio_write(LIGHT_PIN, FALSE);
    if (op_ret != OPRT_OK) {
        PR_ERR("light on error!");
        return op_ret;
    }

    cur_light_status = LIGHT_ON;

    return op_ret;
}

/**
* @brief light off
*
* @return none
*/
STATIC OPERATE_RET light_off(VOID_T)
{
    OPERATE_RET op_ret = OPRT_OK;

    /* pin set high level, light off */
    op_ret = tuya_gpio_write(LIGHT_PIN, TRUE);
    if (op_ret != OPRT_OK) {
        PR_ERR("light off error!");
        return op_ret;
    }

    cur_light_status = LIGHT_OFF;

    return op_ret;
}

/**
* @brief set light status
*
* @param[in] status: LIGHT_ON or LIGHT_OFF
* @return none
*/
OPERATE_RET set_light_status(LED_STATUS_E status)
{
    OPERATE_RET op_ret = OPRT_OK;

    if (status == LIGHT_ON) {
        op_ret = light_on();
        if (op_ret != OPRT_OK) {
            return op_ret;
        }
    } else {
        light_off();
        if (op_ret != OPRT_OK) {
            return op_ret;
        }
    }

    return op_ret;
}

3 获取灯状态函数

通过该函数可以获取灯当前的开关状态。

/**
* @brief get light status
*
* @return light status: LIGHT_ON or LIGHT_OFF
*/
LED_STATUS_E get_light_status(VOID_T)
{
    return cur_light_status;
}