分享一个OTA升级相关的应用实践!

时间:2023-04-01 12:10:03


大家好,我是杂烩君。

本次与大家分享一个ota升级相关的应用实践。

应用场景

某项目中,有三块控制板协同工作,WiFi模块挂在其中一块板上:


分享一个OTA升级相关的应用实践!

其中,三块板子都有升级的需求。即board1需要从云端下载各板子的升级文件之后通过串口分发给另外两块板子。

思路及一些缩减代码

作为board1的开发者,除了处理好给board2、board3分包分发升级文件之外,还需要处理好整个升级过程的可视化反馈,即需要在手机APP上显示出当前的升级进度。

为了显示这个升级进度,可能需要考虑如下情况:

1、下载升级包的过程与传输升级包(以下我称为安装)的进度需不需要分开?

这里我们选择分开,即手机APP触发升级,下载过程,手机APP显示下载进度,下载进度走完100之后,显示安装进度。


分享一个OTA升级相关的应用实践!

分享一个OTA升级相关的应用实践!

一方面比较方便地能看出当前处于升级的那个过程,对于研发而言可以方便定位分析问题,对于用户体验上也不会给用户增加更多的使用成本。

此处,下载升级包的过程其实不受我们控制的,这一块阿里的SDK已经给我们做好,但有些云平台的设备SDK可能没有没有做这个固件下载的过程,可能只提供了固件所在文件服务器的http链接,需要我们自行进行下载,固件下载过程可参照这篇文章:C语言实现http下载器(附代码)

我们要处理的其实就是下载完升级包之后的事情,如解压升级包、安装。

2、每次升级可能不是升级所有的板子,如何处理?

启动安装的时候,通过比对各板子升级文件的md5值与本地保存的md5值确认是否是新的固件,并且通过这个比对我们就知道了当前升级包中的需要升级哪些板子。

三块板子组合的升级情况全部列举如下:

左右滑动查看全部代码>>>

// OTA升级情况
#define DEV_NON_SELECTED      0   // 设备没有被选中
#define BOARD1_DEV_SELECTED   1   // board1设备被选中
#define BOARD2_DEV_SELECTED   2   // board2设备被选中
#define BOARD3_DEV_SELECTED   4   // board3设备被选中
typedef enum _ota_update_case
{
    UPDATE_SELECTED_NULL         = -1,                                                              // 初始值
    UPDATE_NON_DEV               = 0,                                                               // 没有设备要升级
    UPDATE_BOARD1_DEV            = BOARD1_DEV_SELECTED,                                             // 只升级board1设备
    UPDATE_BOARD2_DEV            = BOARD2_DEV_SELECTED,                                             // 只升级board2设备
    UPDATE_BOARD3_DEV            = BOARD3_DEV_SELECTED,                                             // 只升级board3设备
    UPDATE_BOARD1_AND_BOARD2_DEV = BOARD1_DEV_SELECTED + BOARD2_DEV_SELECTED,                       // 升级board1与board2设备
    UPDATE_BOARD1_AND_BOARD3_DEV = BOARD1_DEV_SELECTED + BOARD3_DEV_SELECTED,                       // 升级board1与board3设备
    UPDATE_BOARD2_AND_BOARD3_DEV = BOARD2_DEV_SELECTED + BOARD3_DEV_SELECTED,                       // 升级board2与board3设备
    UPDATE_ALL_DEV               = BOARD1_DEV_SELECTED + BOARD2_DEV_SELECTED + BOARD3_DEV_SELECTED, // 升级所有设备
}ota_update_case_e;

获取当前升级包属于哪一种升级情况:

左右滑动查看全部代码>>>

// 获取升级情况
static ota_update_case_e get_ota_update_case(void)
{
    md5sum_t calc_board1_img_md5 = {0};
    md5sum_t calc_board2_img_md5 = {0};
    md5sum_t calc_board3_img_md5 = {0};
    ota_update_case_e ota_update_case = UPDATE_SELECTED_NULL;
    ota_update_case_e board1_update_case = UPDATE_SELECTED_NULL;
    ota_update_case_e board2_update_case = UPDATE_SELECTED_NULL;
    ota_update_case_e board3_update_case = UPDATE_SELECTED_NULL;

    // 读取升级文件md5参数表
    bzero(&s_flash_ota_file_md5_list, sizeof(s_flash_ota_file_md5_list));
    read_ota_md5_list_from_file(&s_flash_ota_file_md5_list, sizeof(s_flash_ota_file_md5_list)); 

    // 计算各升级文件的md5值
    calc_file_md5(OTA_BOARD1_DEV_IMG_FILE, &calc_board1_img_md5);
    calc_file_md5(OTA_BOARD2_DEV_IMG_FILE, &calc_board2_img_md5);
    calc_file_md5(OTA_BOARD3_DEV_IMG_FILE, &calc_board3_img_md5);

    // 检测是否选中board1设备
    if (0 == access(OTA_BOARD1_DEV_IMG_FILE, F_OK) && 
        FALSE == check_md5_equal(&calc_board1_img_md5, &s_flash_ota_file_md5_list[OTA_IMG_INDEX_BOARD1_DEV]))
    {
        LOG_D("BOARD1_DEV_SELECTED");
        board1_update_case = BOARD1_DEV_SELECTED;
    }
    else
    {
        LOG_D("board1 install file not exist or unchanged, not install");
        board1_update_case = DEV_NON_SELECTED;
    }

    // 检测是否选中board2设备
    if (0 == access(OTA_BOARD2_DEV_IMG_FILE, F_OK) && 
        FALSE == check_md5_equal(&calc_board2_img_md5, &s_flash_ota_file_md5_list[OTA_IMG_INDEX_BOARD2_DEV]))
    {
        LOG_D("BOARD2_DEV_SELECTED");
        board2_update_case = BOARD2_DEV_SELECTED;
    }
    else
    {
        LOG_D("board2 install file not exist or unchanged, not install");
        board2_update_case = DEV_NON_SELECTED;
    }

    // 检测是否选中board3设备
    if (0 == access(OTA_BOARD3_DEV_IMG_FILE, F_OK) && 
        FALSE == check_md5_equal(&calc_board3_img_md5, &s_flash_ota_file_md5_list[OTA_IMG_INDEX_BOARD3_DEV]))
    {
        LOG_D("BOARD3_DEV_SELECTED");
        board3_update_case = BOARD3_DEV_SELECTED;
    }
    else
    {
        LOG_D("board3 install file not exist or unchanged, not install");
        board3_update_case = DEV_NON_SELECTED;
    }

    // 升级的情况
    ota_update_case = board1_update_case + board2_update_case + board3_update_case;
    LOG_D("ota_update_case = %d", ota_update_case);

    return ota_update_case;
}

3、各种不同升级包情况,如何处理?

因为WiFi挂在board1上,所以整个升级过程,如果board1需要升级的话,board1需要放在最后升级,因为board1升级的过程中会与手机APP断连。

三块板的固件安装:

static void start_board1_ota_install(void);
static void start_board2_ota_install(void);
static void start_board3_ota_install(void);
  • start_board1_ota_install为安装board1固件的过程。安装board1固件的过程很简单,可以看做一次板子重启,升级安装命令如:
#define OTA_BOARD1_DEV_IMG_FILE   OTA_FIREWARE_EXTRACT_FILE_PATH "board1_update.img"         // board1固件
#define SHELL_CMD_START_BOARD1_DEV_OTA_INSTALL    "updateEngine --image_url=" OTA_BOARD1_DEV_IMG_FILE " --misc=update --savepath=" OTA_BOARD1_DEV_IMG_FILE " --reboot"
  • start_board2_ota_install为安装board2固件的过程。安装board2固件的过程即board1往board2发送board2固件的过程,整个过程通过数据传输量占总数据量的大小可表明安装进度。
  • start_board3_ota_install安装board3固件的过程,安装board3固件的过程即board1往board3发送board3固件的过程,整个过程通过数据传输量占总数据量的大小可表明安装进度。

各升级情况的统一处理如:

左右滑动查看全部代码>>>

static ota_update_case_e s_ota_update_case = UPDATE_SELECTED_NULL;        // 升级情况
static ota_update_case_e s_not_yet_update_case = UPDATE_SELECTED_NULL;    // 还未升级的情况

// 启动固件安装
void start_firmware_install(void)
{
    app_install_result_t install_board2_result = {0};
    app_install_result_t install_board3_result = {0};

    memset(&s_install_info, 0, sizeof(app_install_info_t));
    send_install_info_to_app(&s_install_info);
    memset(&install_board2_result, 0, sizeof(app_install_result_t));
    memset(&install_board3_result, 0, sizeof(app_install_result_t));
    send_board2_install_result_to_app(&install_board2_result);
    send_board3_install_result_to_app(&install_board3_result);

    s_ota_update_case = get_ota_update_case();
    s_install_info.install_case = s_ota_update_case;

    switch (s_ota_update_case)
    {
        // 固件包相同,不升级
        case UPDATE_NON_DEV:
        {
            LOG_D("UPDATE_INSTALL_SUCCESS --- UPDATE_NON_DEV");

            s_install_info.total_install_status = UPDATE_INSTALL_SUCCESS;
            send_install_info_to_app(&s_install_info);
            storage_write_file(APP_OTA_INSTALL_RESULT_FILE, &s_install_info.total_install_status, sizeof(install_status_e));
            break;
        }
        // 只升级board1设备
        case UPDATE_BOARD1_DEV:
        {
            LOG_D("UPDATE_BOARD1_DEV");
            // 保存新的md5值
            md5sum_t calc_board1_img_md5 = {0};
            read_ota_md5_list_from_file(&s_flash_ota_file_md5_list, sizeof(s_flash_ota_file_md5_list)); 
            calc_file_md5(OTA_BOARD1_DEV_IMG_FILE, &calc_board1_img_md5);
            memcpy(s_flash_ota_file_md5_list[OTA_IMG_INDEX_BOARD1_DEV].md5, &calc_board1_img_md5, sizeof(md5sum_t));   
            write_ota_md5_list_to_file(&s_flash_ota_file_md5_list, sizeof(s_flash_ota_file_md5_list));

            start_board1_ota_install();
            break;
        }
        // 只升级board2设备
        case UPDATE_BOARD2_DEV:
        {
            LOG_D("UPDATE_BOARD2_DEV");
            start_board2_ota_install();
            s_not_yet_update_case = UPDATE_BOARD2_DEV;
            break;
        }
        // 只升级board3设备
        case UPDATE_BOARD3_DEV:
        {
            LOG_D("UPDATE_BOARD3_DEV");
            start_board3_ota_install();
            s_not_yet_update_case = UPDATE_BOARD3_DEV;
            break;
        }
        // 升级board1与board2设备
        case UPDATE_BOARD1_AND_BOARD2_DEV:
        {
            LOG_D("UPDATE_BOARD1_AND_BOARD2_DEV");
            start_board2_ota_install();
            s_not_yet_update_case = UPDATE_BOARD1_AND_BOARD2_DEV;
            break;
        }
        // 升级board1与board3设备
        case UPDATE_BOARD1_AND_BOARD3_DEV:
        {
            LOG_D("UPDATE_BOARD1_AND_BOARD3_DEV");
            start_board3_ota_install();
            s_not_yet_update_case = UPDATE_BOARD1_AND_BOARD3_DEV;
            break;
        }
        // 升级board2设备与board3设备
        case UPDATE_BOARD2_AND_BOARD3_DEV:
        {
            LOG_D("UPDATE_BOARD2_AND_BOARD3_DEV");
            start_board2_ota_install();
            start_board3_ota_install();
            s_not_yet_update_case = UPDATE_BOARD2_AND_BOARD3_DEV;
            break;
        }
        // 升级所有设备
        case UPDATE_ALL_DEV:
        {
            LOG_D("UPDATE_ALL_DEV");
            start_board2_ota_install();
            start_board3_ota_install();
            s_not_yet_update_case = UPDATE_ALL_DEV;
            break;
        }
        default:
            break;
    }
}

4、安装进度只有一个,如何处理三块板子的安装进度?

手机APP上并不会去区分各个板子的安装进度,即安装进度只有一个。因为用户并不会关注设备里有几块板子的固件在安装。所以这个安装进度需要表明以上各种情况的安装。

需要明确的是:

  • 安装进度总是以board2、board3中的较大固件为主。
  • 安装某个板子异常则表明这个安装过程算是失败。为了防止这类情况:假如需要升级board1与board2,正好本次更新是增加了之间的一些协议数据,如果单单升级成功其中一块,另一块没升级成功,这种情况下也要去使用的话,就有可能会出现问题,协议数据对不上,轻则某些数据错误,程序中防错机制设计得不好的话重则可能程序会崩溃。
  • 虽然board2与board3的安装进度不直接显示在手机APP上,但无论是给手机APP做逻辑使用还是供我们调试实用,都有必要创建他们各自的属性数据。比如会往云端上报各种情况的board2与board3的安装信息属性数据,如:
typedef struct app_install_result
{
    uint8_t install_status;     // 安装状态
    uint8_t install_progress;   // 安装进度
}app_install_result_t;

app_install_result_t install_board2_result = {0};
app_install_result_t install_board3_result = {0};
send_board2_install_result_to_app(&install_board2_result);
send_board3_install_result_to_app(&install_board3_result);

5、安装结束的检测?

安装过程包含如下几种情况,挨个进行处理即可:


分享一个OTA升级相关的应用实践!

对应代码如(注:以下为了方便说明全都写在一块,实际中可以进行拆分):

// 检测是否升级结束
void check_whether_update_end(void)
{
    app_install_result_t install_board2_result = {0};
    app_install_result_t install_board3_result = {0};

    // 包含board1的升级情况(board1+board2、board1+board3、board1+board2+board3)。board1最后安装
    if (UPDATE_BOARD1_AND_BOARD2_DEV == s_ota_update_case || 
        UPDATE_BOARD1_AND_BOARD3_DEV == s_ota_update_case || 
        UPDATE_ALL_DEV == s_ota_update_case)
    {
        // 正常安装且剩下board1没安装了,开始安装board1
        if (UPDATE_BOARD1_DEV == s_not_yet_update_case)
        {
            s_ota_update_case = UPDATE_SELECTED_NULL;
            s_not_yet_update_case = UPDATE_SELECTED_NULL;
            // 1. 记录新的版本号
            // 2. 保存新的md5值至本地文件
            // 3.重启之前上报安装成功给手机APP
            // 4.重启进行安装board1
        }
        // 升级安装失败
        else
        {
            // 只升级board1与board2的情况
            if (UPDATE_BOARD1_AND_BOARD2_DEV == s_ota_update_case && UPDATE_BOARD1_AND_BOARD2_DEV == s_not_yet_update_case)
            {
                if (UPDATE_STATUS_BOARD2_DATA_ACK_ERR == s_board2_update_status)
                {
                    s_ota_update_case = UPDATE_SELECTED_NULL;
                    s_not_yet_update_case = UPDATE_SELECTED_NULL;
                    // 1. 上报board2安装失败
                    // 2. board2安装失败 -> 上报总的安装失败
                }
            }
            // 只升级board1与board3的情况
            else if (UPDATE_BOARD1_AND_BOARD3_DEV == s_ota_update_case && UPDATE_BOARD1_AND_BOARD3_DEV == s_not_yet_update_case)
            {
                if (UPDATE_STATUS_BOARD3_DATA_ACK_ERR == s_board3_update_status)
                {
                    s_ota_update_case = UPDATE_SELECTED_NULL;
                    s_not_yet_update_case = UPDATE_SELECTED_NULL;
                    // 1. 上报board3安装失败
                    // 2. board3安装失败 -> 上报总的安装失败
                }
            }
            // 升级全部设备
            else
            {
                // board2安装失败,board3安装成功
                if (UPDATE_STATUS_BOARD2_DATA_ACK_ERR == s_board2_update_status && 
                    UPDATE_STATUS_BOARD3_DATA_FINISH_ACK_OK == s_board3_update_status)
                {
                    s_ota_update_case = UPDATE_SELECTED_NULL;
                    s_not_yet_update_case = UPDATE_SELECTED_NULL;                    
                    // 1. 上报board2安装失败
                    // 2. 上报board3安装成功
                    // 3. board2安装失败 -> 上报总的安装失败
                }

                // board2安装成功,board3安装失败
                if (UPDATE_STATUS_BOARD2_DATA_FINISH_ACK_OK == s_board2_update_status && UPDATE_STATUS_BOARD3_DATA_ACK_ERR == s_board3_update_status)
                {
                    s_ota_update_case = UPDATE_SELECTED_NULL;
                    s_not_yet_update_case = UPDATE_SELECTED_NULL;
                    // 1. 上报board2安装成功
                    // 2. 上报board3安装失败
                    // 3. board3安装失败 -> 上报总的安装失败
                }

                // board2与board3都安装失败
                if (UPDATE_STATUS_BOARD2_DATA_ACK_ERR == s_board2_update_status && UPDATE_STATUS_BOARD3_DATA_ACK_ERR == s_board3_update_status)
                {
                    s_ota_update_case = UPDATE_SELECTED_NULL;
                    s_not_yet_update_case = UPDATE_SELECTED_NULL;
                    // 1. 上报board2安装失败
                    // 2. 上报board3安装失败
                    // 3. board2、board3安装失败 -> 上报总的安装失败
                }
            }
        }
    }
    
    // 只升级board2及board3的情况。如果数据都已经传输数据完成,则上报安装完成
    if (UPDATE_BOARD2_AND_BOARD3_DEV == s_ota_update_case) 
    {
        // 升级安装成功
        if (UPDATE_NON_DEV == s_not_yet_update_case)
        {
            s_ota_update_case = UPDATE_SELECTED_NULL;
            s_not_yet_update_case = UPDATE_SELECTED_NULL;
            // 1. 上报board2安装成功
            // 2. 上报board3安装成功
            // 3. board2、board3安装成功 -> 上报总的安装成功
        }
        // 升级安装失败
        else
        {
            // board2安装失败,board3安装成功
            if (UPDATE_STATUS_BOARD2_DATA_ACK_ERR == s_board2_update_status && UPDATE_STATUS_BOARD3_DATA_FINISH_ACK_OK == s_board3_update_status)
            {
                s_ota_update_case = UPDATE_SELECTED_NULL;
                s_not_yet_update_case = UPDATE_SELECTED_NULL;
                // 1. 上报board2安装失败
                // 2. 上报board3安装成功
                // 3. board2安装失败 -> 上报总的安装失败
            }

            // board2安装成功,board3安装失败
            if (UPDATE_STATUS_BOARD2_DATA_FINISH_ACK_OK == s_board2_update_status && UPDATE_STATUS_BOARD3_DATA_ACK_ERR == s_board3_update_status)
            {
                s_ota_update_case = UPDATE_SELECTED_NULL;
                s_not_yet_update_case = UPDATE_SELECTED_NULL;
                // 1. 上报board2安装成功
                // 2. 上报board3安装失败
                // 3. board3安装失败 -> 上报总的安装失败
            }

            // board2与board3都安装失败
            if (UPDATE_STATUS_BOARD2_DATA_ACK_ERR == s_board2_update_status && UPDATE_STATUS_BOARD3_DATA_ACK_ERR == s_board3_update_status)
            {
                s_ota_update_case = UPDATE_SELECTED_NULL;
                s_not_yet_update_case = UPDATE_SELECTED_NULL;
                // 1. 上报board2安装失败
                // 2. 上报board3安装失败
                // 3. board2、board3安装失败 -> 上报总的安装失败
            }
        }
    }

    // 只升级board2的情况
    if (UPDATE_BOARD2_DEV == s_ota_update_case)
    {
        if (UPDATE_NON_DEV == s_not_yet_update_case)
        {
            s_ota_update_case = UPDATE_SELECTED_NULL;
            s_not_yet_update_case = UPDATE_SELECTED_NULL;
            // 1. 上报board2安装成功
            // 2. board2安装成功 -> 上报总的安装成功
        }
        else
        {
            if (UPDATE_STATUS_BOARD2_DATA_ACK_ERR == s_board2_update_status)
            {
                s_ota_update_case = UPDATE_SELECTED_NULL;
                s_not_yet_update_case = UPDATE_SELECTED_NULL;
                // 1. 上报board2安装失败
                // 2. board2安装失败 -> 上报总的安装失败
            }
        }
    }

    // 只升级board3的情况
    if (UPDATE_BOARD3_DEV == s_ota_update_case)
    {
        if (UPDATE_NON_DEV == s_not_yet_update_case)
        {
            s_ota_update_case = UPDATE_SELECTED_NULL;
            s_not_yet_update_case = UPDATE_SELECTED_NULL;
            // 1. 上报board3安装成功
            // 2. board3安装成功 -> 上报总的安装成功
        }
        else
        {
            if (UPDATE_STATUS_BOARD3_DATA_ACK_ERR == s_board3_update_status)
            {
                s_ota_update_case = UPDATE_SELECTED_NULL;
                s_not_yet_update_case = UPDATE_SELECTED_NULL;
                // 1. 上报board3安装失败
                // 2. board3安装失败 -> 上报总的安装失败
            }
        }
    } 
}

前面我们的升级情况进行加法的组合,比如:

升级所有设备 = BOARD1 + BOARD2 + BOARD3 = 1 + 2 + 4

那么,我们每安装完一个设备,可以减掉对应的值,这样可以区分当前的安装处于哪个阶段。

以上就是本次的分享,大家还有什么其它思路吗?

猜你喜欢

实用 | 分享几个非常实用的开源项目

STM32如何收发float类型数据?

如何查看Linux命令工具的源码?

分享一款嵌入式人必备绘图工具!

在公众号聊天界面回复1024,可获取嵌入式资源;回复 m ,可查看文章汇总。

文章都看完了

分享一个OTA升级相关的应用实践!

不点个

分享一个OTA升级相关的应用实践!