涂鸦 Wi-Fi 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文件介绍

Start-up_flowchart

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.hTuya 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);
参数说明
cfgWiFi工作模式选择,可设置参数请看下面“WiFi工作模式定义”
start_modeWiFi启动时模式设置,可设置参数请看下面“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; 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;
    }

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.课程介绍

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: STK Commu是一种常用的通信软件开发工具包(SDK),它具有丰富的功能和灵活的二次开发能力,可以用于各种通信领域的应用。在STK Commu的二次开发过程中,我们可以根据具体的需求进行定制开发,以实现特定的通信功能。 首先,我们可以利用STK Commu的API接口来实现与其他通信设备的连接和数据交互。例如,我们可以通过SDK提供的接口实现与传感器、终端设备或其他软件的通信,从而获得实时数据或进行远程控制。 其次,STK Commu还具备网络通信功能,可以支持TCP/IP、UDP等常见的通信协议。我们可以利用这些协议进行网络通信的二次开发,实现数据的传输和通信的稳定性。 此外,STK Commu还提供了丰富的数据处理和管理功能。我们可以使用SDK提供的数据处理接口,对接收到的数据进行解析、处理和存储。同时,STK Commu还支持多种数据格式,如JSON、XML等,使得数据的交互更加灵活多样。 在STK Commu的二次开发中,我们还可以根据需求进行界面定制和用户交互设计。通过SDK提供的界面开发工具,我们可以自定义通信软件的界面风格和布局,使其更加符合用户的需求和习惯。 总之,STK Commu作为一款通信软件开发工具包,具备强大的功能和灵活的二次开发能力,可以满足各种通信应用的需求。通过STK Commu的二次开发,我们可以实现与其他通信设备的连接和数据交互、网络通信功能、数据处理和管理以及界面定制等功能,从而开发出更加强大和定制化的通信软件。 ### 回答2: STK COMMU是一款功能强大的通信工具,具备二次开发的潜力。二次开发是指在原有软件基础上进行定制和扩展,以满足用户的特定需求。 对于STK COMMU的二次开发来说,首先需要了解其提供的功能和接口。通过查阅官方文档或联系软件开发商,可以获取相关资源和技术支持。 在二次开发过程中,我们可以通过编写插件或使用软件开发工具包(SDK)来实现定制化的功能。例如,插件可以增加新的通信协议、改进用户界面、增加数据分析功能等。 此外,我们还可以利用STK COMMU提供的API(应用程序接口)进行开发。API是一组预定义的函数和协议,可以帮助我们与软件进行交互。通过API,我们可以实现与其他软件的集成、数据传输、自动化控制等功能。 对于开发人员来说,具备一定的编程知识和技能是必要的。常见的编程语言如Python、Java、C++等都可以与STK COMMU进行集成。通过编写代码,我们可以进行高度的定制和扩展,以满足特定的需求和业务流程。 总之,STK COMMU的二次开发为用户提供了很多定制化的可能性。通过插件、SDK、API等手段,我们可以为用户量身定制通信工具,扩展其功能和性能,满足其特定的需求。这不仅提高了软件的灵活性和适用性,也促进了更广泛的应用和创新。 ### 回答3: stk commu二次开发是指在原有的stk commu软件基础上进行修改和扩展的开发工作。stk commu是一款用于通信协议仿真和分析的软件,通过对通信协议的建模和仿真,可以帮助用户测试和优化通信系统的性能。 在进行stk commu二次开发时,我们可以根据用户的需求来进行功能定制和改进,以满足特定的应用场景。可能的二次开发方向包括但不限于: 1. 新增通信协议支持:根据需要,可以扩展stk commu的协议库,使其支持更多的通信协议,如蓝牙、WiFi、LTE等。 2. 用户界面定制:可以根据用户的习惯和需求,进行界面的定制和优化,使其更符合用户的使用习惯和工作流程。 3. 数据分析功能增强:可以增加一些数据分析功能,如数据可视化、统计分析等,方便用户获取对通信系统性能的更全面和准确的认识。 4. 集成其他工具和平台:可以将stk commu与其他常用的工具和平台进行集成,如MATLAB、Python等,以便更方便地进行数据分析和算法验证等工作。 5. 性能优化和bug修复:可以对stk commu进行性能优化和bug修复,以提升软件的稳定性和可靠性。 在进行stk commu二次开发时,需要了解stk commu的内部架构和功能模块,并根据用户需求进行相应的开发工作。同时,要充分考虑软件的可扩展性和兼容性,确保二次开发的成果能够与原有的stk commu软件相互兼容和无缝集成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值