应用开发

更新时间:2022-11-24 09:20:24下载pdf

为了更高效的进行产品功能开发,涂鸦提供了一套标准的应用模版,应用模版包含了设备激活、网络重置、按键功能、授权、校准检测、串口配置、GPS 功能和 DP 数据通信等基础功能模块使用的详细说明,您可以基于应用模版快速完成底层基础能力的使用,专注于开发自身应用代码,更快完成产品的开发。

应用模板说明

应用模板包含在 OpenCPU SDK 内,您在获取到 SDK 后,解压开发后即可看到应用模板的代码(apps 文件夹下的 template 文件包)。

应用开发

文件说明

apps 文件夹下的应用模版文件结构说明如下:

文件夹名称 作用
template OpenCPU 应用模板,帮助快速开发上层代码
emobile 应用模板开发实例,通过应用模板开发的样例代码
hello 基础示例代码
文件名称 作用
tuya_device.h 存放除 tuya_dp.c 外其他 .c 的结构体,宏定义,产品信息及公共头文件等
tuya_dp.h 存放 tuya_dp.c 的函数定义, DP 定义,产品 PID 等
tuya_application.c 存放应用开发的代码,具体的功能实现
tuya_device.c 存放应用开发前的一些配置信息及应用开发的入口函数
tuya_dp.c 存放 dp 相关的代码,包括下发 dp 及上报 dp 的接口函数
tuya_mf_product.c 存放产测入口函数

编译说明

将 IoT 开发平台下载的功能点调试文件放在 SDK 目录下合适的文件夹中,之后在 apps 同级目录下通过命令行参数将功能调试文件及 DP 的头文件及 .c 文件的路径传参给执行文件,自动生成 DP 功能点的相关函数及定义。

此处示例将测试的 json 文件放在了 template 文件夹下,所以执行的指令如下:

./tools/tuya_dp_tool/tuya_dp_tool ./apps/template/template.json ./apps/template/include/tuya_dp.h ./apps/template/src/tuya_dp.c

然后在 SDK 目录下执行./build.sh指令,生成对应的应用固件。

应用模板功能介绍

在介绍具体的功能前,您需要先了解应用程序启动流程,如下图所示

应用开发

系统初始化

系统完成应用程序加载后,先调用 appimg_enter 函数,用户可以在该函数中完成一系列函数注册,各注册函数说明如下:

  • pre_app_init_hook_register:注册用户定义的 user_pre_app_init函数,user_pre_app_init 函数实现产测之前 app 需要完成的一些初始化动作,如果没有,可以不调用该注册函数,允许用户不实现user_pre_app_init
  • pre_device_init_hook_register:注册用户定义的user_pre_device_init函数,user_pre_device_init函数实现产测之前 device 需要完成的一些初始化动作,如果没有,可以不调用该注册函数,允许用户不实现user_pre_device_init
  • mf_firmware_info_register:注册固件信息,包括固件名称和固件版本号,该信息在产测阶段通过mf_init传入系统使用,如果不调用该函数,则固件信息默认为系统固件名称和版本号。
  • app_init_hook_register:注册用户定义的user_app_init函数,user_app_init函数实现产测之后 app 需要完成的一些初始化动作,如果没有,可以不调用该注册函数,允许用户不实现user_app_init
  • device_init_hook_register:注册用户定义的user_device_init函数,user_device_init函数实现产测之后 device 需要完成的一些初始化动作,如果没有,可以不调用该注册函数,允许用户不实现user_device_init
  • user_main_entry_register:注册用户定义的user_main_entry函数,user_main_entry函数为应用程序入口,通过该入口,完成应用程序功能启动。用户必须实现user_main_entry函数,并调用注册函数完成注册。

应用初始化

系统启动后,通过 user_pre_app_init > user_pre_device_init > mf_init > user_app_init > user_device_init > user_main_entry 完成整个应用程序的初始化过程。

激活

在设备激活前需要设置 apn,否则设备激活将会失败。设置 apn 的函数在 tuya_device.c 文件中,代码示例如下:

/**
 * @brief   应用初始化前置准备工作
 *
 * @return  VOID_T
 *
 * @note    在此函数中,应用可以执行一些产测前需要完成的配置设置、初始化或者具体功能操作无关的工作
 *
 * @note    应用必须对其进行实现,如果不需要,则实现空函数。
 */
VOID_T user_pre_app_init(VOID_T)
{
    /* 如果当前没有 apn,系统自动设置 */
    tuya_cellular_srv_mds_pdp_active(NULL,NULL,NULL);
    PR_NOTICE("user_pre_app_init succ");
}

在 ``` tuya_application.c ``` 中通过 ``` tuya_iot_cat1_opencpu_dev_init``` 函数接口将固件及产品信息注册给底层。

/**
 * @brief   设备激活/网络状态监测
 *
 * @param   VOID
 *
 * @return  0 成功 -1 失败
 */
OPERATE_RET tuya_device_init()
{
    ...
    op_ret = tuya_iot_cat1_opencpu_dev_init(&wf_cbs, g_dev_info.firware_key, g_dev_info.prod_key, g_dev_info.sys_fw_ver, g_dev_info.app_fw_ver, mcu_sw_ver);
    if (OPRT_OK != op_ret)
    {
        PR_ERR("tuya_device_init err:%d", op_ret);
        return op_ret;
    }
    ...

    return op_ret;
}

授权&校准检测

设备只有经过授权及校准后才能正常使用,您在运行应用代码前,需要确保这两项已经完成,此处在代码中增加了授权及校准检测功能,以及异常 Log 打印,用于检测设备是否正常完成授权和校验,代码实现如下:

OPERATE_RET tuya_device_init()
{
    ...
    /* 授权,校准校验,未授权,返回 error */
    if(0 == get_gw_cntl()->gw_base.auth_key[0] || (0 == get_gw_cntl()->gw_base.uuid[0]))
    {
        PR_DEBUG("please write uuid and auth_key first");
        return OPRT_COM_ERROR;
    }
    ...

    bool ret = tuya_hal_cellular_calibrated();
    if(!ret)
    {
        PR_DEBUG("Not calibrated ret/%d", ret);
        return OPRT_COM_ERROR;
    }
    lan_pro_cntl_disable();

    return op_ret;
}

UART 配置

应用模板以 UART1 为例增添了 UART 的初始化及配置功能,具体的串口业务需要您自行实现。

/**
 * @brief   串口初始化函数
 *
 * @param   VOID
 *
 * @return  0 成功 -1 失败
 *
 * @note    uart 参数配置,如果需要 uart 串口功能,必须先初始化配置
 */
OPERATE_RET uart_init(VOID)
{
    OPERATE_RET op_ret;
    demo_uart = tuya_driver_find(TUYA_DRV_UART, TY_UART_INDEX);
    if (NULL == demo_uart)
    {
        PR_ERR("uart1 find failed");
        return OPRT_COM_ERROR;
    }
    //配置串口参数
    TUYA_UART_8N1_CFG(demo_uart, 115200, 1024, 0);
    //初始化
    op_ret = tuya_uart_init(demo_uart);
    if (OPRT_OK != op_ret)
    {
        PR_ERR("uart init failed", op_ret);
        return OPRT_COM_ERROR;
    }
    return op_ret;
}

GPS 相关功能

SDK 内包含了 GPS 使能和 GPS 速度/经纬度/信号强度等的获取功能,由于 LZ211 模组与 LZ201 模组关于 GPS 的功能有些许差异,因此通过宏定义(TY_MODULE_TYPE)来区分使用,您需要根据自己使用的模组进行配置,SDK 内默认 TY_MODULE_TYPE 为 0xFF 时,不论 LZ201 模组还是 LZ211 模组,都不启动 GPS,当需要启动 GPS 功能时,需要根据当前模组类型设置 TY_MODULE_TYPE,并重启设备,示例如下:

#define TY_MODULE_TYPE 0xFF // 0:"LZ201"; 1:"LZ211"

详细的代码实现,您可以参考 SDK 中应用模板的代码。

电池电量及模组信号强度的获取

您可以通过定时器定时获取电池电量及模组信号强度,并调用上报接口上报到云端用于显示,示例代码如下:

STATIC VOID send_battery_info_msg(UINT_T timerID, PVOID_T pTimerArg)
{
    PR_DEBUG("enter get_gps_info");
    tuya_msg_send(PROC_BAT_EVENT,1);
}

OPERATE_RET tuya_add_timer_func(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;
    ...
    /* 获取电池电量/信号强度 */
    op_ret = sys_add_timer(send_battery_info_msg, NULL, &ty_msg.get_battery_info_timer);
    if(OPRT_OK != op_ret)
    {
        return op_ret;
    }

    return op_ret;
}

STATIC VOID ty_msg_proc(VOID *param)
{
    UCHAR_T option  = 0;
    OPERATE_RET ret = OPRT_OK;

    while(1)
    {
        ret = tuya_hal_queue_fetch(ty_msg.pQueue_event, &option, 1000);
        if (ret == OPRT_OK)
        {
            ...
            if(option == PROC_BAT_EVENT)
            {
                UCHAR_T battery_val;
                SCHAR_T rssi;
                /*电量处理*/
                tuya_cellular_battery_get_rsoc(&battery_val);
                /*获取当前 gsm 信号强度等级*/
                tuya_hal_cellular_get_rssi(&rssi);
            }
        }
    }
}

DP 业务处理

tuya_device_init 函数中,注册了 DP 数据处理的回调函数 tuya_obj_dp_cmd_cb,当 App 接收到数据时,tuya_obj_dp_cmd_cb 调用 ty_obj_datapoint_proc 对接收到的 DP 数据处理,并执行相关操作,然后调用 上报函数将状态返回给 App 用于展示。

OPERATE_RET tuya_device_init()
{
    OPERATE_RET op_ret = OPRT_OK;

    /* 设备激活,dp 回调函数注册,网络状态监测 */
    TY_IOT_CBS_S wf_cbs = {
        .dev_obj_dp_cb   = tuya_obj_dp_cmd_cb,
        .dev_raw_dp_cb   = tuya_raw_dp_cmd_cb,
        .dev_dp_query_cb = tuya_dp_query_cb,
    };
    ...
    return op_ret;
}

/**
 * @brief   raw 型 dp 处理函数
 *
 * @param   dp
 *
 * @return  无
 *
 * @note    如无需要,可不实现
 */
VOID tuya_obj_dp_cmd_cb(IN CONST TY_RECV_OBJ_DP_S *dp)
{
    PR_DEBUG("app_obj_dp_cmd_cb ... ...");

    if (!get_gw_dev_cntl()->dev_if.bind)
    {
        PR_ERR("dev is not bind");
        return;
    }

    if (!dp || dp->dps_cnt == 0)
    {
        PR_ERR("no valid dp");
        return;
    }
    INT_T i = 0;

    //解析云端下发的  DP 
    for (;;)
    {
        tuya_obj_datapoint_proc((TY_OBJ_DP_S *)&dp->dps[i]);
        if (++i < dp->dps_cnt)
            tuya_hal_system_sleep(40);
        else
            break;
    }
}

/**
 * @brief  obj 型 dp 处理函数
 *
 * @param  obj_dp
 *
 * @return 无
 *
 * @note   如无需要,可不实现
 */
STATIC VOID tuya_obj_datapoint_proc(TY_OBJ_DP_S *obj_dp)
{
    switch(obj_dp->dpid)
    {
    //该处代码属于自动生成部分
[TEMPLATE_DP_HANDLE]

        default:
        break;
    }
}

DP 自适应功能

应用模板中的 tuya_dp.ctuya_dp.h 两部分代码,为涂鸦提供的自适应生成代码,您拿到应用模板代码进行编译后,会在 tuya_dp.c/tuya_dp.h 中生成对应的接口函数,包括 DP 的定义、PID 的定义,以及下发/上报 DP 的处理函数,此处以部分 DP 定义的自动生成为例。

  • 生成前:
    应用开发

  • 生成后:
    应用开发

实例代码开发说明

此章节以智能电动车为例,使用 OpenCPU SDK 及应用模板进行开发,演示应用模板使用过程,本实例在应用模板现有功能的基础上添加了单/多 DP 上报处理及固件升级的内容。

设备和云端交互

以下是关于实例代码的介绍设备与涂鸦云端交互的逻辑主要概括如下:

  1. 涂鸦设备与云端使用 JSON 数据格式进行交互,数据的标识和定义是在平台第一步选择功能点处配置的,实例代码在 tuya_dp.h 文件中自动生成 DP 信息。
  2. 在涂鸦 IoT 开发平台创建产品会产生 PID 信息,PID 是设备的唯一标识,产品的配置都是和 PID 绑定的,比如 App UI 面板、云端配置等,因此需要将 PID 信息写入到代码中,实例代码在 tuya_dp.h 中通过宏定义写入 PID 信息。

生成 DP 相关函数与定义

  1. 在 SDK 目录下将 template 文件夹(应用模板的代码)创建一个副本,重命名为自己创建的产品名称。由于该实例产品名为 emobile,所以本例中将副本文件夹命名为 emobile。

  2. 如果想要清除生成的产品固件,在 SDK 目录下执行如下命令:

    ./build.sh clean

    选择对应的 target 数字,如下图所示:

    应用开发

    如果不想要清除生成的固件,可以跳过该步骤,并不会产生其他的影响,执行固件编译时会将老的固件覆盖掉。

  3. 将下载的功能点调试文件放入自己创建的对应产品目录下。由于本例中创建的产品对应目录为 emobile,所以此处将 JSON 文件放在 OpenCPU SDK 目录的 /apps/emobile/ 下,如下图所示:

    应用开发
  4. 在 SDK apps 同级目录下执行如下命令,生成 DP 相关信息:

    ./tools/tuya_dp_tool/tuya_dp_tool ./apps/emobile/emobile.json ./apps/emobile/include/tuya_dp.h     ./apps/emobile/src/tuya_dp.c
    
  5. 进入 tuya_dp.h/tuya_dp.c 函数查看 DP 相关信息是否已生成。

  6. 执行编译指令./build.sh emobile 0.0.1,编译应用代码,参数 emobile 为编译目标代码,0.0.1 为指定的生成版本号

DP 业务

主动上报业务不论单个 DP 还是多个 DP 组合上报,依赖于定时器函数,通过定时器函数来按需定时上报。

/**
 * @brief   用于添加定时器的接口函数
 *
 * @param   VOID
 *
 * @return  0 成功 其他 失败
 */
OPERATE_RET tuya_add_timer_func(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;
	...
    /* 获取电池电量/信号强度 */
    op_ret = sys_add_timer(send_battery_info_msg, NULL, &ty_msg.get_battery_info_timer);
    if(OPRT_OK != op_ret)
    {
        return op_ret;
    }

    return op_ret;
}

/**
 * @brief   启动对应定时器
 *
 * @param   VOID
 *
 * @return  0 成功 其他 失败
 */
OPERATE_RET ty_start_timer_func(VOID)
{
    OPERATE_RET op_ret = OPRT_OK;
	...
    op_ret = sys_start_timer(ty_msg.get_battery_info_timer, BAT_INFO_REPORT_INTERVAL, TIMER_CYCLE);
    if (op_ret != OPRT_OK)
    {
        PR_DEBUG("bat info report timer start failed!");
        return op_ret;
    }
    return op_ret;
}

单 DP 上报

单个 DP 的上报,只需要将状态发生变化的 dpid 对应的状态值,通过接口函数上报即可,以 value 类型为例,示例代码如下:

/**
 * @brief   具体的事件处理线程
 *
 * @param   param 线程参数可为空
 *
 * @return  无
 */
STATIC VOID ty_msg_proc(VOID *param)
{
    UCHAR_T option  = 0;
    OPERATE_RET ret = OPRT_OK;

    while(1)
    {
        ret = tuya_hal_queue_fetch(ty_msg.pQueue_event, &option, 1000);
        if (ret == OPRT_OK)
        {
            PR_DEBUG("ty_msg_proc option = %d", option);
            if (option == PROC_GPS_EVENT)
            {
                DWORD_T speed = ty_gps_get_speedinfo();
                ...
                tuya_dp_value_update(DPID_SPEED, speed);
                ...
            }
            ...
        }
    }
}

/**
 * @brief   将 dpid 及对应的值传给上报函数
 *
 * @param   dpid:创建产品时添加 dp 功能对应的 dpid
 * @param   dp_value: DP 处理完成后的值
 */
VOID tuya_dp_value_update(UCHAR_T dpid, INT_T dp_value)
{
    TY_OBJ_DP_S dp;
    memset(&dp, 0, sizeof(dp));
    switch(dpid)
    {
    case DPID_SPEED:
	{
        dp.dpid = dpid;
        dp.type = PROP_VALUE;
        dp.value.dp_value = dp_value;
        OPERATE_RET op_ret = dev_report_dp_json_async(get_gw_cntl()->gw_if.id, &dp, 1);
        if(OPRT_OK != op_ret)
        {
            PR_ERR("dev_report_dp_json_async op_ret:%d",op_ret);
        }
    }
    break;

    default:
    break;
    }
}

多 DP 组合上报

多个 DP 组合为一包数据上报时,需要将其按如下结构体进行封装:

typedef struct {
    UINT_T          dps_cnt;
    TY_OBJ_DP_S     *dps;
} TY_DP_DATA_S;

具体代码实现以电池电量及模组信号强度为例如下:

/**
 * @brief   具体的事件处理线程
 *
 * @param   param 线程参数可为空
 *
 * @return  无
 */
STATIC VOID ty_msg_proc(VOID *param)
{
    UCHAR_T option  = 0;
    OPERATE_RET ret = OPRT_OK;

    while(1)
    {
        ret = tuya_hal_queue_fetch(ty_msg.pQueue_event, &option, 1000);
        if (ret == OPRT_OK)
        {
            PR_DEBUG("ty_msg_proc option = %d", option);
            ...
            
            if (option == PROC_BAT_EVENT)
            {
                Multiple_Dp_Report_Test(battery_val, rssi);
                ...
            }
        }
    }
}

/**
 * @brief   多 dp 组合一个包上报示例
 *
 * @param  无
 *
 * @return  无
 */
STATIC VOID Multiple_Dp_Report_Test(UCHAR_T battery_val, SCHAR_T rssi)
{
    //示例代码如下
    TY_DP_DATA_S multiple_dp;
    OPERATE_RET ret;
    multiple_dp.dps = tuya_hal_system_malloc(128);
    multiple_dp.dps_cnt = 2;

    multiple_dp.dps[0].dpid = DPID_BATTERY_PERCENTAGE;
    multiple_dp.dps[0].type = PROP_VALUE;
    multiple_dp.dps[0].value.dp_value = battery_val;
    multiple_dp.dps[1].dpid = DPID_SIGNAL_STRENGTH;
    multiple_dp.dps[1].type = PROP_VALUE;
    multiple_dp.dps[1].value.dp_value = rssi;

    if (ty_msg.netstatus == 4)
    {
        PR_DEBUG("enter Multiple_Dp_Report_Test");
        ret = dev_report_dp_json_async(get_gw_cntl()->gw_if.id, multiple_dp.dps, multiple_dp.dps_cnt);
        if (ret != OPRT_OK)
        {
            PR_DEBUG("dp report failed");
        }
        else
        {
            PR_DEBUG("dp report successed");
        }
    }
    return;
}

固件升级

扩展固件升级

扩展固件指您基于 OpenCPU SDK 开发编译的应用程序固件,扩展固件升级功能在系统固件中已经实现,您只需要在 IoT 开发平台上设置好升级策略,并上传升级固件。如设置为 App 提醒升级,云端会自动推送提示升级的消息,用户手机 App 可以根据需要选择是否升级。如下图所示:

点击更新会对当前所有需要更新的固件依次更新。

MCU 固件升级

MCU 固件升级需要遵循涂鸦的串口协议传输,详细的实现如下所示:

  • 串口协议。

    协议格式如下:
    帧头 + 版本 + 命令字 + data_len + data + 校验

    • 帧头:固定格式 0x7e81
    • 版本:0x00 代表模组,0x03 代表 mcu
    • 命令字:控制 mcu 与模组交互命令 0x0a:升级开始,0x0b:数据传输,0x0d:获取 mcu 版本
    • data_len:命令字下交互数据的长度
    • data:命令字下交互的数据
    • 校验位
  • 函数实现。

    MCU 固件升级的操作原理同扩展固件,设备与 MCU 之间的数据传输通过 UART 完成,下图为实现逻辑:

    应用开发

    在使用 MCU 升级前需要先定 MCU_EXIST 宏 #define MCU_EXIST,代码实现参考以下步骤。

    1. UART 初始化部分代码已在应用模板中实现,需要在初始化后添加 UART 串口处理线程新增代码如下:
    OPERATE_RET uart_init(void)
    {
        OPERATE_RET op_ret;
        ...
    #ifdef MCU_EXIST
        op_ret = tuya_hal_mutex_create_init(&ty_msg.mutex);
        if (op_ret != OPRT_OK)
        {
            PR_ERR("create mutex err");
            return op_ret;
        }
        op_ret = tuya_hal_semaphore_create_init(&ty_msg.mcu_ug_sem, 0, 1);
        if (op_ret != OPRT_OK)
        {
            PR_ERR("create semphore err");
            return op_ret;
        }
        //创建串口数据监控线程
        PR_DEBUG("creat uart_init uart recv task");
        THRD_PARAM_S thrd_uart_param;
        THRD_HANDLE ty_uart_handle = NULL;
        thrd_uart_param.priority = TUYA_UART_MONITOR_TASK_PRIORITY;
        thrd_uart_param.stackDepth = TUYA_UART_MONITOR_TASK_HEAP_SIZE;
        thrd_uart_param.thrdname = "ty_uart_monitor_thread_task";
        op_ret = CreateAndStart(&ty_uart_handle, NULL, NULL,
                                ty_uart_monitor_thread, NULL, &thrd_uart_param);
        //启动获取 mcu 版本的定时器
        sys_start_timer(ty_msg.get_cur_mcu_ver_timer, GET_MCU_VER_TIME,TIMER_CYCLE);
    #endif
        return op_ret;
    }
    
    1. 注册 MCU 升级回调函数。
    OPERATE_RET device_init(void)
    {
        ...
        if (0 != tuya_cat1_register_mcu_fota_inform_hook(mcu_start_upgrade_cb))
        {
        PR_ERR("register mcu fota inform hook failed");
        }
        if (0 != tuya_cat1_register_mcu_fota_process_hook(mcu_upgrade_process_cb))
        {
        PR_ERR("register mcu fota process hook failed");
        }
        if(0 != tuya_cat1_register_mcu_fota_notify_hook(mcu_ug_complete_notify_cb))
        {
            PR_ERR("register mcu fota notify hook failed");
        }
        PR_NOTICE("register mcu fota hooks succ");
        op_ret = tuya_iot_cat1_opencpu_dev_init(&wf_cbs, firmware_key,
                                                product_key, sys_fw_ver,
                                                app_fw_ver, mcu_sw_ver);
        ...
        return op_ret;
    }
    

    注意:回调函数的注册一定要在 tuya_iot_cat1_opencpu_dev_init 函数之前完成。

    1. 通过以下代码实现回调函数。

      1. 单击 更新升级 后,模组会将文件大小及版本号通过 mcu_start_upgrade_cb 函数下发,该函数的主要功能是通知 MCU 准备升级。

        void mcu_start_upgrade_cb(UINT_T fileSize, const CHAR_T* version, const CHAR_T* fwHMAC)
        {
            PR_NOTICE("mcu_start_upgrade_cb start to upgrade mcu firmware");
            PR_NOTICE("fw_hmac: %s", fwHMAC);
            PR_NOTICE("fw version: %s", version);
            PR_NOTICE("fw fileSize: %d", fileSize);
            tuya_hal_semaphore_post(ty_msg.mcu_ug_start_sem);
            ...
            
            UCHAR_T buf[11] = {0x7E,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff};
            ty_msg.mcu_file_size = fileSize;
            buf[3] = 0x0A;
            buf[5] = 0x04;
            buf[6] = (fileSize >> 24) & 0xFF;
            buf[7] = (fileSize >> 16) & 0xFF;
            buf[8] = (fileSize >> 8) & 0xFF;
            buf[9] = fileSize & 0xFF;
            UCHAR_T num = getCheckSum(buf, 10);
            buf[10] = num;
            
            //通过 uart 写函数,发送开始数据给 mcu
            tuya_uart_write(demo_uart, buf, sizeof(buf));
        
            //考虑超时情况,如果超时,重新发送
            sys_start_timer(ty_msg.mcu_ug_fw_timer, MCU_RECV_TIME * 1000, TIMER_ONCE);
            ...
            return;
        }
        
      2. mcu_upgrade_process_cb 函数中完成对升级数据的处理与发送。

        //demo 假定 uart 按 256 传输,该长度可配 512、1024 等,以实际开发为准
        static void mcu_upgrade_process_cb(UINT_T offset, const BYTE_T* data, UINT_T len)
        {
            ...
            if (dev_proc.send_len + mcu_ug_buff_len + len >= dev_proc.file_size)
                is_last_file_pack = TRUE; //是否为最后一包数据的标志位
        
            copy_len = MIN(send_unit_len - mcu_ug_buff_len, len);
            memcpy(&(buff[mcu_ug_buff_len]), data, copy_len);
            mcu_ug_buff_len += copy_len;
            data_offset = copy_len;
            
            if (mcu_ug_buff_len == send_unit_len)
            {
                mcu_ug_buff_len = 0;
                ret = upgrade_file_cb(buff, send_unit_len); //通过 uart 发送数据
                if (OPRT_OK != ret)
                {
                    PR_ERR("MCU upgrade file failed");
                    return;
                }
                dev_proc.send_len += send_unit_len;
            }
            
            while (data_offset + send_unit_len <= len)
            {
                ret = upgrade_file_cb(&(data[data_offset]), send_unit_len);
                if (OPRT_OK != ret)
                {
                    PR_ERR("MCU upgrade file failed");
                    return;
                }
                data_offset += send_unit_len;
                dev_proc.send_len += send_unit_len;
            }
        
            if (is_last_file_pack)
            {
                if (mcu_ug_buff_len)
                {
                    ret = upgrade_file_cb(buff, mcu_ug_buff_len);
                    mcu_ug_buff_len = 0;
                    if (OPRT_OK != ret)
                    {
                        PR_ERR("MCU upgrade file failed");
                        return;
                    }
                    dev_proc.send_len += mcu_ug_buff_len;
                }
            
                if (data_offset < len)
                {
                    ret = upgrade_file_cb(&(data[data_offset]), len - data_offset);
                    if (OPRT_OK != ret)
                    {
                        PR_ERR("MCU upgrade file failed");
                        return;
                    }
                    dev_proc.send_len += len - data_offset;
                }
            
                PR_NOTICE("[MCU_FOTA]: upgrade mcu fw succ: %d, %d",
                        dev_proc.send_len, dev_proc.file_size);
            }
            else if (data_offset < len)
            {
                memcpy(buff, &(data[data_offset]), len - data_offset);
                mcu_ug_buff_len = len - data_offset;
            }
            return;
        }
        
      3. mcu_ug_complete_notify_cb 函数中,该函数指示云端完成 MCU 升级固件的传输,应用程序可以在该函数中实现串口协议,通知 MCU 升级数据传输完毕。

        static VOID mcu_ug_complete_notify_cb(INT_T result)
        {
            if (OPRT_OK == result)
            {
                ...
                PR_NOTICE("mcu upgrade succ");
                return;
            }
            ...
        }
        
      4. MCU 升级数据传输完毕后,MCU 完成将固件写入 Flash 的操作。然后需要通过串口,按照协议将新的 MCU 版本号传给应用程序,应用程序接收到新的版本号之后,通过调用 SDK 提供的 tuya_iot_cat1_update_mcu_ver 函数,将新的版本号上传至云端,从而通知手机 App,MCU 版本升级成功。

        static INT_T ty_uart_data_proc(UCHAR_T* data)
        {
            OPERATE_RET op_ret = OPRT_OK;
            UCHAR_T buf[] = {0};
        
            TY_FRAME_S *ty_frame = (TY_FRAME_S *)data;
            PR_DEBUG("frame type: %d", ty_frame->fr_type);
            switch (ty_frame->fr_type)
            {
            ...
            case GET_MCU_VER:
            {
                dev_sw_ver = tuya_hal_system_malloc(11);
                if (IsThisSysTimerRun(ty_msg.get_cur_mcu_ver_timer))
                {
                    sys_stop_timer(ty_msg.get_cur_mcu_ver_timer);
                }
                buf[0] = ty_frame->data[0];
                buf[1] = ty_frame->data[1];
                buf[2] = ty_frame->data[2];
                memset(dev_sw_ver, 0, 11);
                sprintf(dev_sw_ver, "%d.%d.%d", buf[0], buf[1], buf[2]);
                PR_DEBUG("ty_uart_monitor_thread mcu_ug_ver: %s\r\n", dev_sw_ver);
                if (!ty_msg.init_flag)
                {
                    ty_msg.init_flag = 1;
                    //与云端连接实现
                    op_ret = device_init();
                    if (OPRT_OK != op_ret)
                    {
                        PR_ERR("app_init_entry device_init err: %d", op_ret);
                        return op_ret;
                    }
                }
                //更新升级后的 mcu 版本号至云端 .ty_msg.mcu_ug_ctrl 在升级数据传输结束后置为 1
                else if (ty_msg.mcu_ug_ctrl)
                {
                    sys_stop_timer(ty_msg.mcu_ug_fw_timer);
                    op_ret = tuya_iot_cat1_update_mcu_ver(mcu_sw_ver);
                    if (op_ret != OPRT_OK)
                    {
                        sys_start_timer(ty_msg.mcu_sync_ver_timer, 100, TIMER_ONCE);
                        PR_DEBUG("op_ret/%d", op_ret);
                    }
                }
        
                tuya_hal_system_free(mcu_sw_ver);
            }
            break;
            default:
            break;
            }
            return op_ret;
        }
        

固件编译

SDK 内提供编译脚本,您可以通过如下命令调用脚本可实现代码编译。

  • 编译指令:进入到编译脚本所在目录,执行 ./build_app.sh <项目名称> <版本号>。

  • 命令示例: ./build.sh emobile 0.0.1
    编译成功后会在 apps/项目名称/output/版本号/ 目录下生成烧录用的二进制(pac)文件,如下图片所示:

    应用开发

最后将对应的 项目名称_版本号 .pac 文件烧录到设备中。

代码调试

设备烧录完成后,您可以使用智能生活 App 对设备进行配网和功能调试。

添加设备

添加设备有两种方式:扫码/蓝牙,您可以根据自己的需要选择。

  • 扫码添加。
    该二维码需要使用涂鸦的生产打印工具生成,通常在生产阶段使用。
    应用开发

  • 通过蓝牙添加。

    1. 下载智能生活 App 并注册登录。
      应用开发

    2. 轻按 添加设备 > 自动发现 > 开启蓝牙 > 开始搜索
      应用开发)

    3. 搜索到设备后轻按 下一步 开始注册配网,配网完成后默认进入设备面板。

DP 业务测试

添加设备完成后进入面板,进行数据收发测试,您可以在下方信息框内看到上报和下发的 DP 信息。

应用开发)