FOTA服务

一个完整的FOTA功能包含4个部分:

  1. FOTA服务器,新固件存放的地方。OCC或者是用户自行部署的服务器。
  2. 版本检测,由FOTA服务来完成
  3. 固件下载与校验,由FOTA服务来完成
  4. 固件升级,由Bootloader来完成

这里主要介绍第2和第3部分。 YoC定义了一个FOTA服务用于维护FOTA新版本检测、状态机转换维护和下载控制,抽象了netio用于固件包的下载保存。即:

  1. 新版本检测
  2. 新版本固件包(差分、全量)下载与校验

FOTA流程与状态

流程图

@FOTA流程与状态

FOTA状态:

typedef enum fota_status {
    FOTA_INIT = 1,          /*!< create fota task, wait for version check */
    FOTA_DOWNLOAD = 2,      /*!< start to download fota data */
    FOTA_ABORT = 3,         /*!< read or write exception */
    FOTA_FINISH = 4,        /*!< download finish */
} fota_status_e ;

用户启动FOTA后状态为FOTA_INIT,当检测到新版本时,转入FOTA_DOWNLOAD状态,并开始下载固件包。读写失败之后会转入FOTA_ABORT状态,在重试次数范围内会把状态转成FOTA_DOWNLOAD继续尝试读写操作,超出重试次数(retry count)之后状态置为FOTA_INIT。当下载完成之后状态为FOTA_FINISH,并开始校验数据,上报事件给用户,并将状态抓为FOTA_INIT,为下次版本检测下载做准备。 用户可以通过自定义的方式(添加fota_event_cb回调函数)来根据FOTA在各个阶段报出的事件来执行相应的动作(fota event层)。

事件说明

事件定义:

typedef enum {
    FOTA_EVENT_START = 0,       /*!< Start the fota version check and download steps */
    FOTA_EVENT_VERSION,         /*!< Check version from server ok */
    FOTA_EVENT_PROGRESS,        /*!< Downloading the fota data */
    FOTA_EVENT_FAIL,            /*!< This event occurs when there are any errors during execution */
    FOTA_EVENT_VERIFY,          /*!< verify fota data */
    FOTA_EVENT_FINISH,          /*!< fota download flow finish */
    FOTA_EVENT_QUIT,            /*!< Fota task quit */
    FOTA_EVENT_RESTART          /*!< real want to restart */
} fota_event_e;

错误值定义:

typedef enum {
    FOTA_ERROR_NULL = 0,
    FOTA_ERROR_VERSION_CHECK,
    FOTA_ERROR_NET_SEEK,
    FOTA_ERROR_NET_READ,
    FOTA_ERROR_WRITE,
    FOTA_ERROR_MALLOC,
    FOTA_ERROR_VERIFY
} fota_error_code_e;

事件回调示例代码如下:

static int fota_event_cb(void *arg, fota_event_e event)
{
    fota_t *fota = (fota_t *)arg;
    switch (event) {
        case FOTA_EVENT_START:
            LOGD(TAG, "FOTA START :%x", fota->status);
            break;
        case FOTA_EVENT_VERSION:
            LOGD(TAG, "FOTA VERSION CHECK OK :%x", fota->status);
            break;
        case FOTA_EVENT_PROGRESS:
            LOGD(TAG, "FOTA PROGRESS :%x, %d, %d", fota->status, fota->offset, fota->total_size);
            break;
        case FOTA_EVENT_FAIL:
            LOGD(TAG, "FOTA FAIL :%x, %d", fota->status, fota->error_code);
            break;
        case FOTA_EVENT_VERIFY:
            LOGD(TAG, "FOTA VERIFY :%x", fota->status);
            break;
        case FOTA_EVENT_FINISH:
            LOGD(TAG, "FOTA FINISH :%x", fota->status);
            // 可以重启让bootloader去更新固件,也可以不做任何操作
            break;
        case FOTA_EVENT_QUIT:
            LOGD(TAG, "FOTA QUIT :%x", fota->status);
            break;
        default:
            break;
    }
    return 0;
}

FOTA 平台对接

FOTA框架支持接入不同的FOTA云平台。如:COP、COAP 不同的FOTA平台需要通过对接fota_cls,实现该结构体的操作。

typedef struct fota_cls {
    const char *name;
    int (*init)(fota_info_t *info);
    int (*version_check)(fota_info_t *info);
    int (*finish)(fota_info_t *info);
    int (*fail)(fota_info_t *info);
    int (*restart)(void);
} fota_cls_t;

通过int fota_register(const fota_cls_t *cls)接口注册。

对接接口定义

云平台相关的初始化

int (*init)(fota_info_t *info);
  • 参数:
    • info:fota_info_t
  • 返回值:
    • 0成功,否则失败

版本检测

int (*version_check)(fota_info_t *info);
  • 参数:
    • info: fota_info_t
  • 返回值:
    • 0成功,否则失败

结构体:fota_info_t,存储固件的URL

typedef struct {
    char *cur_version;     /*!< the local image version*/
    char *local_changelog; /*!< the local image changelog, read from kv*/
    char *new_version;     /*!< the incoming image version, read from cloud server*/
    char *changelog;       /*!< the incoming image changelog, read from cloud server*/
    char *fota_url;        /*!< the incoming image url, read from cloud server*/
    int timestamp;         /*!< the incoming image timestamp, read from cloud server*/
} fota_info_t;

fota结束之后相关操作

int (*finish)(fota_info_t *info);
  • 参数:
    • info:fota_info_t
  • 返回值:
    • 0成功,否则失败

fota失败之后相关的操作

int (*fail)(fota_info_t *info);
  • 参数:
    • info:fota_info_t
  • 返回值:
    • 0成功,否则失败

对接示例

static int cop_version_check(fota_info_t *info) {
    // TODO:
    return 0;
}

static int cop_finish(void)
{
    // TODO:
    return 0;
}

const fota_cls_t fota_cop_cls = {
    "cop",
    NULL,
    cop_version_check,
    cop_finish,
    NULL,
};

int fota_register_cop(void)
{
    return fota_register(&fota_cop_cls);
}

FOTA 下载

FOTA框架对FOTA下载和存放进行了抽象,形成netio 类,用于可通过netio类来对接不同的下载和存放方式,例如对接ftp下载方式,对接新的 uart 存放方式等。 通过实现结构体 netio_cls 来对接

struct netio_cls {
    const char *name;
    int (*open)(netio_t *io, const char *path);
    int (*close)(netio_t *io);

    int (*read)(netio_t *io, uint8_t *buffer, int length, int timeoutms);
    int (*write)(netio_t *io, uint8_t *buffer, int length, int timeoutms);
    int (*remove)(netio_t *io);
    int (*seek)(netio_t *io, size_t offset, int whence);

    void *private;
};

用户需要实现 netiocls 接口的 open、close、read、write、remove、seek接口,并通过netio_register接口注册进FOTA。详细请看_NETIO接口章节

对接实现

fota组件中目前已实现对接了coap协议和http协议的数据读取和flash的数据写入。具体代码在netio文件夹下。目录下有http.c是一个轻量级的http实现;而httpc则是使用了httpclient组件的功能,支持https和重定向功能,因此推荐使用httpc的实现,只需要在初始化注册的时候调用netio_register_httpc这个函数即可。

FOTA 升级

固件下载完毕之后会设备会重启进入固件升级模式,等待升级完毕之后设备正常启动。一个完整的FOTA流程结束。

FOTA数据校验

FOTA数据下载完成之后,必须要有完整性的校验,这里默认采用的是SHA1的哈希校验。如果有更高等级的需求,可以再增加真实性的校验。

接口定义

FOTA接口

typedef struct fota {
    const fota_cls_t *cls;          /*!< the fota server ops */

    netio_t *from;                  /*!< the read netio handle */
    netio_t *to;                    /*!< the write netio handle */
    fota_status_e status;           /*!< the fota status, see enum `fota_status_e` */
    char *from_path;                /*!< where the fota data read from, url format */
    char *to_path;                  /*!< where the fota data write to, url format*/
    uint8_t *buffer;                /*!< buffer for reading data from net */
    int offset;                     /*!< downloaded data bytes */
    int total_size;                 /*!< total length of fota data */
    int quit;                       /*!< fota task quit flag */
    aos_task_t task;                /*!< fota task handle */
    aos_sem_t sem;                  /*!< semaphore for waiting fota task quit */
    aos_sem_t sem_download;         /*!< semaphore for starting download */
    aos_sem_t do_check_event;       /*!< the semaphore for checking version loop or force */
    fota_event_cb_t event_cb;       /*!< the event callback */
    fota_error_code_e error_code;   /*!< fota error code, get it when event occurs */
    fota_config_t config;           /*!< fota config */
    fota_info_t info;               /*!< fota information */
    aos_timer_t restart_timer;      /*!< the timer to norify to restart */
    void *private;                  /*!< user data context */
} fota_t;

FOTA 云平台注册

注册一个云平台,可参考fota/fota_cop.c文件的实现。

int fota_register(const fota_cls_t *cls)
  • 参数:
    • cls: 不同平台实现的接口集合,具体实现接口见fota_cls_t
  • 返回值:
    • 0: 成功
    • -1: 内存不足

FOTA初始化

fota_t *fota_open(const char *fota_name, const char *dst, fota_event_cb_t event_cb)
  • 参数:
    • fota_name: FOTA平台,比如cop、coap
    • dst: 差分包存储url, 比如flash://misc
    • event_cb: 用户事件回调
  • 返回值:
    • fota: fota 句柄
    • NULL: 失败

FOTA彻底关闭

关闭FOTA功能,退出FOTA服务,释放所有资源。如果调用此函数,下次打开FOTA功能需要调用所有的初始化和注册函数。具体见FOTA代码示例章节。

int fota_close(fota_t *fota);
  • 参数:
    • fota: fota 句柄
  • 返回值:
    • 0: 成功
    • -1: 失败

FOTA 开始

创建FOTA服务,进入FOTA检测下载流程

int fota_start(fota_t *fota)
  • 参数:
    • fota: fota 句柄
  • 返回值:
    • 0: 成功
    • -1: 失败

FOTA 主动停止

停止FOTA功能,退出FOTA服务

int fota_stop(fota_t *fota)
  • 参数:
    • fota: fota 句柄
  • 返回值:
    • 0: 成功
    • -1: 失败

强制版本检测

版本检测本来是间隔sleep_time时间循环检测的。调用此接口可以立即进行检测。

void fota_do_check(fota_t *fota)
  • 参数:
    • fota: fota 句柄
  • 返回值:

自动检测设置

void fota_set_auto_check(fota_t *fota, int enable)
  • 参数:
    • fota:fota句柄
    • enable:0表示不自动检测,1表示自动检测
  • 返回值:

获取自动检测是否使能

int fota_get_auto_check(fota_t *fota)
  • 参数:
    • fota:fota句柄
  • 返回值:
    • 0:未使能
    • 1:使能

获取当前的FOTA状态

fota_status_e fota_get_status(fota_t *fota)
  • 参数:
    • fota:fota句柄
  • 返回值:
    • fota状态,见fota_status_e

FOTA参数配置

void fota_config(fota_t *fota, fota_config_t *config)
  • 参数:
    • fota:fota句柄
    • config:配置数据指针,具体见 fota_config_t
  • 返回值:

fota_config_t:

typedef struct {
    int read_timeoutms;         /*!< read timeout, millisecond */
    int write_timeoutms;        /*!< write timeout, millisecond */
    int retry_count;            /*!< when download abort, it will retry to download again in retry_count times */
    int sleep_time;             /*!< the sleep time for auto-check task */
    int auto_check_en;          /*!< whether check version automatic */
} fota_config_t;

FOTA数据校验

对已经下载好的FOTA数据进行校验,一般是完整性校验,等级更高的可以开启真实完整性校验。

int fota_data_verify(void)
  • 参数:
  • 返回值:
    • 0: 成功
    • -1: 失败

cop平台注册

注册为cop平台,即从cop平台下载固件

int fota_register_cop(void)
  • 参数:
  • 返回值:
    • 0: 成功
    • -1: 失败

NETIO接口

typedef struct {
    const netio_cls_t *cls;     /*!< netio ops */
    size_t offset;              /*!< offset for seek */
    size_t size;                /*!< file size or partition size */
    size_t block_size;          /*!< the size for transmission(sector size) */

    void *private;              /*!< user data */
} netio_t;
  • cls netio 操作接口
  • offset 续传偏移
  • size 文件大小
  • block_size 传输块大小
  • private 私有变量

netio 注册

int netio_register(const netio_cls_t *cls);
  • 参数:
    • cls: netio 操作接口
  • 返回值:
    • 0: 成功
    • -1: 内存不足

netio 打开

netio_t *netio_open(const char *path);
  • 参数:
    • path:下载或者储存 url
  • 返回值:
    • netio:netio 句柄
    • NULL:失败

netio 关闭

int netio_close(netio_t *io);
  • 参数:
    • io:netio 句柄
  • 返回值:
    • 0: 成功
    • -1: 失败

netio 读取

int netio_read(netio_t *io, uint8_t *buffer, size_t lenght, int timeoutms);
  • 参数:
    • io:netio 句柄
    • buffer:用于存放读取数据的buffer
    • lenght:buffer大小
    • timeoutms:超时时长
  • 返回值:
    • len: 读取的长度
    • 0:读文件完
    • -1: 超时失败

netio 写

int netio_write(netio_t *io, uint8_t *buffer, size_t lenght, int timeoutms);
  • 参数:
    • io:netio 句柄
    • buffer:用于存放写入数据的buffer
    • length:buffer大小
    • timeoutms:超时时长
  • 返回值:
    • len: 写入的长度
    • -1: 超时失败

netio 寻址

int netio_seek(netio_t *io, size_t offset, int whence);
  • 参数:
    • io:netio 句柄
    • offset:偏移
    • whence:偏移方向
  • 返回值:
    • 0:成功
    • -1: 失败

http注册

将http操作功能注册到netio

int netio_register_http(void)
  • 参数:
  • 返回值:

    • 0:成功
    • -1: 失败

      httpc注册

      将httpclient操作功能注册到netio,支持https和重定向功能。
      int netio_register_httpc(const char *cert)
      
  • 参数:

    • cert:https证书,可以为NULL
  • 返回值:

    • 0:成功
    • -1: 失败

      flash注册

      将flash操作功能注册到netio
      int netio_register_flash(void)
      
  • 参数:

  • 返回值:

    • 0:成功
    • -1: 失败

      coap注册

      将coap操作功能注册到netio
      int netio_register_coap(void)
      
  • 参数:

  • 返回值:
    • 0:成功
    • -1: 失败

ab flash注册

将ab flash操作功能注册到netio

int netio_register_flashab(void)
  • 参数:
  • 返回值:
    • 0:成功
    • -1: 失败

资源配置

以下宏定义,用户可以在自己方案的package.yaml中的def_config字段中进行重新定义。

FOTA服务任务栈大小

CONFIG_FOTA_TASK_STACK_SIZE 定义在yoc/fota.h,默认为4096字节。

HTTP客户端选择

CONFIG_FOTA_USE_HTTPC 定义在yoc/fota.h,默认为0,使用内部的http实现。如果为1,则使用httpclient组件,此时需要依赖cjson和httpclient组件。

数据读写的块大小

CONFIG_FOTA_BUFFER_SIZE 定义在yoc/netio.h,默认512。用户可配成跟FLASH SECTOR大小一致的数值。

KV存储

fota_offset

记录下载数据的偏移值。用来做断电续传使用。

使用场景

FOTA服务常驻,循环检测

此场景下初始化代码参考如下:

fota_t *g_fota_handle;
fota_register_cop();
netio_register_httpc(NULL);
netio_register_flash();
g_fota_handle = fota_open("cop", "flash://misc", fota_event_cb);
g_fota_handle->sleep_time = 60000;
g_fota_handle->timeoutms = 10000;
g_fota_handle->retry_count = 0;
g_fota_handle->auto_check_en = 1;
fota_start(g_fota_handle);

如果某些情况下需要暂停FOTA检测下载功能,那么只需要调用函数 fota_set_auto_check(g_fota_handle, 0)关闭自动检测功能即可。

根据需要开启FOTA服务

此场景下初始化代码参考如下:

fota_t *g_fota_handle;
fota_register_cop();
netio_register_httpc(NULL);
netio_register_flash();
g_fota_handle = fota_open("cop", "flash://misc", fota_event_cb);
g_fota_handle->sleep_time = 60000;
g_fota_handle->timeoutms = 10000;
g_fota_handle->retry_count = 0;
g_fota_handle->auto_check_en = 0;

用户可以根据自己的需求调用fota_start(g_fota_handle)函数。然后在需要的地方调用fota_stop(g_fota_handle)函数即可退出FOTA的服务。

FOTA代码示例

本示例以cop平台的FOTA流程为例,展示在fota_demo的app/src/cop_fota.c中,FOTA模块的初始化过程:
fota_t *g_fota_handle;
fota_register_cop();
netio_register_httpc(NULL);
netio_register_flash();
g_fota_handle = fota_open("cop", "flash://misc", fota_event_cb);
g_fota_handle->sleep_time = 60000;
g_fota_handle->timeoutms = 10000;
g_fota_handle->retry_count = 0;
fota_start(g_fota_handle);

results matching ""

    No results matching ""