FOTA服务
一个完整的FOTA功能包含4个部分:
- FOTA服务器,新固件存放的地方。OCC或者是用户自行部署的服务器。
- 版本检测,由FOTA服务来完成
- 固件下载与校验,由FOTA服务来完成
- 固件升级,由Bootloader来完成
这里主要介绍第2和第3部分。 YoC定义了一个FOTA服务用于维护FOTA新版本检测、状态机转换维护和下载控制,抽象了netio用于固件包的下载保存。即:
- 新版本检测
- 新版本固件包(差分、全量)下载与校验
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
- cls: 不同平台实现的接口集合,具体实现接口见
- 返回值:
- 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状态,见
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操作功能注册到netioint netio_register_flash(void)
参数:
- 无
返回值:
- 0:成功
- -1: 失败
coap注册
将coap操作功能注册到netioint 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);