设备OTA开发

UIoT-Core 支持设备通过 OTA(Over-the-Air Technology) 进行固件升级。本章描述如何利用设备端 C-SDK 提供的API进行设备端 OTA 功能开发。下面的讲解对应于 C-SDK 示例代码 samples/ota/ota_sample.c 。

功能说明

  • C-SDK 提供支持固件下载及校验的 API,但固件存储以及烧录需要用户在应用程序中实现。
  • 建议在 OTA 功能设计之初预留充足的存储容量以便存放固件,同时充分考虑到烧录的风险,如有必要提前设计烧录失败的回退逻辑。

开发步骤

准备

1. 在控制台创建一个产品和一个设备,替换 ota_sample.c 中的设备四元组信息。

2. 上传一个固件(版本号任选,与示例代码中的 "1.0.0" 不同即可)。

3. 在示例代码启动并上报版本之后,在控制台发起设备升级操作,将设备升级到上传的固件版本。

初始化

在使用 OTA 功能之前,首先需要进行初始化,包括 MQTT 客户端的创建、OTA 的初始化以及版本上报。可参照 ota_sample.c 中 main 函数的初始化部分代码。

//1. 首先创建MQTT客户端,并与云端建立MQTT连接
void *client = IOT_MQTT_Construct(&init_params);
if (client != NULL) {
    LOG_INFO("MQTT Construct Success");
} else {
    LOG_ERROR("MQTT Construct Failed");
    return FAILURE;
}

//2. OTA 初始化,获取句柄
void *h_ota = IOT_OTA_Init(UIOT_MY_PRODUCT_SN, UIOT_MY_DEVICE_SN, client);
if (NULL == h_ota) {
    LOG_ERROR("init OTA failed");
    return FAILURE;
}

//3. 上报初始版本,未上报版本的设备无法升级
if (IOT_OTA_ReportVersion(h_ota, "1.0.0") < 0) {
    LOG_ERROR("report OTA version failed");
    return FAILURE;
}

//4. (可选)主动请求设备 OTA 信息,用于防止设备离线期间错过 OTA 升级消息等特殊情况
if (IOT_OTA_RequestFirmware(h_ota, "1.0.0") < 0) {
    LOG_ERROR("Request firmware failed");
    return FAILURE;
}

下载及升级流程

流程包括固件的下载、校验、存储和烧录等步骤。由于硬件平台等差异,用户可根据实际情况仿照示例代码实现整个流程。

//打开一个文件,用于存储固件
if (NULL == (fp = fopen("ota.bin", "wb+"))) {
    LOG_ERROR("open file failed");
    return FAILURE;
}

do {
    uint32_t firmware_valid;
    LOG_INFO("wait for ota upgrade command...");
    //接收下行 OTA 消息,如果收到升级信息 IOT_OTA_IsFetching(h_ota) 将会更新为 1
    IOT_MQTT_Yield(client, 100);
    //进入固件下载状态
    if (IOT_OTA_IsFetching(h_ota)) {
        char version[33], md5sum[33];
        uint32_t size_downloaded, size_file;
        do {
            //下载固件至缓冲区
            int len = IOT_OTA_FetchYield(h_ota, buf_ota, OTA_BUF_LEN, 1);
            if (len > 0) {
                //将缓冲区的数据写入文件
                if (1 != fwrite(buf_ota, len, 1, fp)) {
                    LOG_ERROR("write data to file failed");
                    upgrade_fetch_success = false;
                    break;
                }
            } else if (len < 0) {
                LOG_ERROR("download fail rc=%d", len);
                upgrade_fetch_success = false;
                break;
            }

            //获取 OTA 信息
            IOT_OTA_Ioctl(h_ota, OTA_IOCTL_FETCHED_SIZE, &size_downloaded, 4);
            IOT_OTA_Ioctl(h_ota, OTA_IOCTL_FILE_SIZE, &size_file, 4);
            IOT_OTA_Ioctl(h_ota, OTA_IOCTL_MD5SUM, md5sum, 33);
            IOT_OTA_Ioctl(h_ota, OTA_IOCTL_VERSION, version, 33);
            IOT_OTA_Ioctl(h_ota, OTA_IOCTL_VERSION, msg_version, 33);

            IOT_MQTT_Yield(client, 100);
        } while (!IOT_OTA_IsFetchFinish(h_ota));

        //下载固件完成
        if (upgrade_fetch_success) {
            //固件校验
            IOT_OTA_Ioctl(h_ota, OTA_IOCTL_CHECK_FIRMWARE, &firmware_valid, 4);
            if (0 == firmware_valid) {
                LOG_ERROR("The firmware is invalid");
                upgrade_fetch_success = false;
            } else {
                LOG_INFO("The firmware is valid");
                upgrade_fetch_success = true;
            }
        }
        ota_over = 1;
    }

    HAL_SleepMs(2000);
} while(!ota_over);

if (upgrade_fetch_success)
{
    //可在此处处理固件烧录相关操作
    HAL_SleepMs(1000);
    //上报升级成功
    IOT_OTA_ReportSuccess(h_ota, msg_version);
}

资源释放

固件升级完成后,需要释放 OTA 过程中使用的资源。

//关闭文件
fclose(fp);
//释放OTA资源
IOT_OTA_Destroy(h_ota);
//(可选)释放 MQTT Client 资源
IOT_MQTT_Destroy(&client);

API 列表

IOT_OTA_Init

初始化 OTA 模块

void *IOT_OTA_Init(const char *product_sn, const char *device_sn, void *ch_signal);

参数列表

参数 数据类型 参数类型 说明
product_sn const char * 输入 指向产品序列号的指针
device_sn const char * 输入 指向设备序列号的指针
ch_signal void * 输入 指定的信号通道,目前为 IOT_MQTT_Construct 返回的句柄
ret void * 返回值 初始化成功,返回句柄;初始化失败,返回 NULL

IOT_OTA_Destroy

释放 OTA 相关的资源

int IOT_OTA_Destroy(void *handle);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
ret int 返回值 <0: 失败 =0: 成功

IOT_OTA_ReportVersion

向 OTA 服务器报告固件版本信息

int IOT_OTA_ReportVersion(void *handle, const char *version);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
version const char * 输入 以字符串格式指定固件版本
ret int 返回值 <0: 上报失败 >0: 成功,返回对应 publish 的 packet id

IOT_OTA_ReportProgress

向 OTA 服务器报告详细进度

int IOT_OTA_ReportProgress(void *handle, int progress, IOT_OTA_ProgressState state);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
progress int 输入 下载或升级进度,范围为 0-100
state IOT_OTA_ProgressState 输入 当前的 OTA 状态
ret int 返回值 <0: 上报失败 >0: 成功,返回对应 publish 的 packet id

IOT_OTA_ReportSuccess

向 OTA 服务器上报升级成功

int IOT_OTA_ReportSuccess(void *handle, const char *version);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
version const char * 输入 以字符串格式指定的当前固件版本,如果版本错误,云端认为固件升级失败
ret int 返回值 <0: 上报失败 >0: 成功,返回对应 publish 的 packet id

IOT_OTA_ReportFail

向 OTA 服务器上报失败信息

int IOT_OTA_ReportFail(void *handle, IOT_OTA_ReportErrCode err_code);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
err_code IOT_OTA_ReportErrCode 输入 错误码
ret int 返回值 <0: 上报失败 >0: 成功,返回对应 publish 的 packet id

IOT_OTA_IsFetching

检查是否处于下载固件的状态

int IOT_OTA_IsFetching(void *handle);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
ret int 返回值 0: No 1: Yes

IOT_OTA_IsFetchFinish

检查固件是否已经下载完成

int IOT_OTA_IsFetchFinish(void *handle);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
ret int 返回值 0: No 1: Yes

IOT_OTA_FetchYield

从远程服务器获取固件

int IOT_OTA_FetchYield(void *handle, char *buf, size_t buf_len, uint32_t timeout_s);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
buf char * 输出 指定存储固件数据的缓冲区
buf_len size_t 输入 buf 的长度
timeout_s uint32_t 输入 超时时间
ret int 返回值 <0: 对应的错误码 0: 在 timeout_s 超时期间内没有任何数据被下载 (0, len] : 在 timeout_s 超时时间内下载数据的长度

IOT_OTA_Ioctl

获取指定的 OTA 信息

int IOT_OTA_Ioctl(void *handle, IOT_OTA_CmdType type, void *buf, size_t buf_len);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
type IOT_OTA_CmdType 输入 指定想要的信息
buf void * 输出 为数据交换指定缓冲区
buf_len size_t 输入 buf 的长度
ret int 返回值 <0: 失败 0: 成功

说明

  • 如果 type==OTAIOCTLFETCHEDSIZE, 'buf' 需要传入 uint32t 类型指针, 'buf_len' 需指定为 4
  • 如果 type==OTAIOCTLFILESIZE, 'buf' 需要传入 uint32t 类型指针, 'buf_len' 需指定为 4
  • 如果 type==OTAIOCTLMD5SUM, 'buf' 需要传入 buffer, 'buf_len' 需指定为 33
  • 如果 type==OTAIOCTLVERSION, 'buf' 需要传入 buffer, 'buflen' 需指定为 OTAVERSIONLENMAX
  • 如果 type==OTAIOCTLCHECKFIRMWARE, 'buf' 需要传入 uint32t 类型指针, 'buf_len'需指定为 4 。返回:0, 固件MD5校验不通过, 固件无效; 1, 固件有效

IOT_OTA_GetLastError

获取最后一个错误码

int IOT_OTA_GetLastError(void *handle);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
ret int 返回值 对应错误的错误码

IOT_OTA_RequestFirmware

请求固件更新消息。设备离线时,不能接收服务端推送的升级消息,需要通过 MQTT 协议接入物联网平台的设备再次上线后,主动请求固件更新消息

int IOT_OTA_RequestFirmware(void *handle, const char *version);

参数列表

参数 数据类型 参数类型 说明
handle void * 输入 IOT_OTA_Init 返回的句柄
version const char * 输入 以字符串格式指定的当前版本号
ret int 返回值 <0: 上报失败 >0: 成功,返回对应publish的packet id