涂鸦智能模组SDK开发系列课程——5.对模组二次开发

上期精彩回顾:Wi-Fi模组二次开发课程——4.烧录授权

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

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

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

示例Demo的GitHub仓库地址:GitHub - Tuya-Community/bk7231n_light1_io_xx: Module secondary development example demo

1. 启动流程介绍

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

涂鸦智能模组SDK开发系列课程——5.对模组二次开发_第1张图片

2. 工程创建

在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 初始化完成后调用。

3. 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中实现。

3.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对于产测部分的功能基本上都没有具体实现,对于量产的产品要按照产品的需要对这些产测函数的功能进行具体的实现。

3.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.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");
        }
    }
}

3.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;
}

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

3.5.1 涂鸦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配网共存。

上述的几种配网模式都默认支持蓝牙配网。

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; ilen; 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;
    }

3.5.2 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;
}

3.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__*/

4. dp_process文件介绍

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

4.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);
    }
}

4.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;
    }
}

5. light_system文件介绍

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

5.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;
    
}

5.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;
}

5.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;
}

以上就是基于涂鸦提供的SDK对模组进行二次开发的全部过程。本章也是Wi-Fi 模组二次开发系列课程的最后一节。有需要的同学可以收藏本系列,有任何疑问或建议也欢迎评论区留言~

Wi-Fi模组二次开发系列课程汇总:Wi-Fi模组二次开发课程——0.课程介绍

你可能感兴趣的:(#,SDK,二次开发,嵌入式,课程)