fota - 底层固件升级
孟伟
一、概述
fota 库是 LuatOS 系统中的底层固件升级模块,用于实现设备固件的远程更新功能。该模块支持多种升级方式,包括:
- 逐段接收并写入升级包数据
- 直接从文件系统读取升级包文件进行升级
fota 模块支持灵活的存储位置配置,可以使用内部存储或外部 SPI Flash 进行固件升级,同时提供了完整的升级流程控制和状态查询功能。
1.1 主要功能
- 初始化升级流程,配置存储位置
- 逐段接收并写入升级包数据或直接从文件读取升级包
- 检查升级状态和进度
- 完成升级流程并重启系统
1.2 函数调用时序
- fota.init 初始化 fota 流程;
- fota.wait() 等待底层准备就绪。等待底层固件升级流程初始化完成,包括存储设备就绪、升级上下文准备等;
- fota.run(buf) 或者 fota.file("/xxx"),选择一个调用;
- 查询 fota.isDone ,检查 fota 流程是否成功,成功后需要重启更新,才能更新为新版本;注意:重启后不一定成功,可能存在设备断电、硬件异常等情况。判断升级是否成功,可以在重启后通过查询版本号来确认。
- 结束 fota。
fota.finish 用于结束 fota 流程,并告知底层本次升级的结果(成功或失败);fota.finish(false)是用户层告诉底层本次升级用户取消了,除非需要中断当前的 fota 过程,一般不需要使用;除了用户取消,也可能因为升级过程中出错而调用 fota.finish(false)。
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、使用 fota 核心库,可以开发非常灵活的固件升级应用,适合高阶开发者;如果仅仅是通过 http 从服务器下载升级包,完成升级设备的话,可以使用更简单的 libfota2 扩展库。demo 请参考 LuatOS 仓库 中各个产品目录下的 demo/fota2
-- 如果仅仅是通过 http 从服务器下载升级包,完成升级设备的话,可以使用更简单的libfota2 扩展库;
-- 以下是从其他途径获取更新包后, 调用本fota库的基本逻辑
-- 逐段传入
function fota_test()
fota.init()
while not fota.wait() do
sys.wait(100)
end
while 1 do
local buf = xxx -- 这里是从其他途径获取的升级包片段
-- buf 可以是zbuff 也可以是string
-- 每次写入的数据长度最大不应超过4k
local result, isDone, cache = fota.run(buf)
if not result then
log.info("fota", "出错了")
break
end
if isDone then
while true do
local succ,fotaDone = fota.isDone()
if not succ then
log.info("fota", "出错了")
break
end
if fotaDone then
log.info("fota", "已完成")
break
end
sys.wait(100)
end
break
end
sys.wait(100)
end
end
sys.taskInit(fota_test)
-- 使用文件一次性传入
local function otaTask()
sys.wait(10000)
log.info("开始升级")
-- 下载到文件系统里升级
fota.init()
while not fota.wait() do
sys.wait(100)
end
local result, isDone, cache = fota.file("/luadb/update.bin")
log.info("升级结果", result, isDone, cache)
-- os.remove("/update.bin")
local result, isDone = fota.isDone()
log.info("升级完成", result, isDone)
if isDone then
log.info("升级成功")
rtos.reboot()
else
log.info("升级失败")
end
end
sys.taskInit(otaTask)
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
每个常量对应的常量取值仅做日志打印时查询使用,不要将这个常量取值用做具体的业务逻辑判断,因为LuatOS内核固件可能会变更每个常量对应的常量取值;
如果用做具体的业务逻辑判断,一旦常量取值发生改变,业务逻辑就会出错;
fota 核心库没有常量。
四、函数详解
4.1 fota.init()
功能
初始化 fota 流程。
参数
无
返回值
local result = fota.init()
result
参数含义:初始化是否成功;成功返回true,失败返回false
数据类型:boolean;
取值范围:true/false;
例子
-- 示例
local result = fota.init()
if result then
log.info("fota", "初始化成功")
else
log.error("fota", "初始化失败")
end
4.2 fota.wait()
功能
等待底层 fota 流程准备好,等待底层固件升级流程初始化完成,包括存储设备就绪、升级上下文准备等。
参数
无
返回值
local isDone = fota.wait()
isDone
参数含义:是否准备好;准备好返回true,否则返回false
数据类型:boolean;
取值范围:true/false;
例子
-- 在开始写入数据前等待准备
fota.init()
while not fota.wait() do
sys.wait(100)
end
log.info("fota", "底层准备就绪,开始升级")
4.3 fota.run(buff, offset, len)
功能
写入 fota 数据,支持逐段写入升级包。
注意事项:如果传入的是 zbuff,写入成功后,请自行清空 zbuff 内的数据
参数
buff
参数含义:fota数据,尽量用zbuff以提高性能
数据类型:zbuff或string
取值范围:有效的二进制数据
是否必选:是
offset
参数含义:起始偏移量,仅在buff参数传入时有效
数据类型:number
取值范围:非负整数,不超过zbuff大小
是否必选:可选,默认是0
len
参数含义:写入长度,仅在传入zbuff时有效
数据类型:number
取值范围:非负整数,不超过zbuff剩余空间
是否必选:可选,默认是zbuff:used()
返回值
local result, isDone, cache = fota.run(buff, offset, len)
result
参数含义:写入是否成功;成功返回true,失败返回false
数据类型:boolean;
取值范围:true/false;
isDone
参数含义:是否写入到最后一块;写入到最后一块返回true,否则返回false
数据类型:boolean;
取值范围:true/false;
cache
参数含义:已接收但尚未写入Flash的缓存数据量(单位:字节)
数据类型:number
取值范围:非负整数,超过64K必须做等待
例子
fota.init()
while not fota.wait() do
sys.wait(100)
end
local zb = zbuff.create(4096) -- 创建4KB的zbuff
-- 从某种数据源填充zbuff,data_source可以是http下载的,可以是串口接收的,可以是mqtt接收的升级包分段数据
while true do
local zb = data_source:read(4096)
if zb:len() == 0 then break end
_-- 提示:如果传入的是zbuff,写入成功后,请自行清空zbuff内的数据_
local result, isDone, cache = fota.run(zb, 0, read_count)
zb:del() -- 清空zbuff内容
if not result then
log.error("fota", "写入失败")
break
end
if isDone then
log.info("fota", "升级包接收完成")
check_upgrade_result()
break
end
sys.wait(50)
end
4.4 fota.file(path)
功能
从指定文件读取 fota 数据并写入
参数
path
参数含义:升级文件的文件路径
数据类型:string
取值范围:有效的文件系统路径
是否必选:是
返回值
local result, isDone, cache = fota.file("/xxx.bin")
result
参数含义:写入是否成功;有异常返回false,无异常返回true
数据类型:boolean;
取值范围:true/false;
isDone
参数含义:接收到最后一块返回true,否则返回false
数据类型:boolean;
取值范围:true/false;
cache
参数含义:已接收但尚未写入Flash的缓存数据量(单位:字节)
数据类型:number
取值范围:非负整数,超过64K必须做等待
例子
-- 从文件系统直接升级
local result, isDone, cache = fota.file("/luadb/update.bin")
if result then
log.info("fota", "文件升级启动成功")
-- 等待升级完成
while true do
local succ, fotaDone = fota.isDone()
if not succ then
log.error("fota", "升级过程出错")
break
end
if fotaDone then
log.info("fota", "文件升级完成")
fota.finish(true)
sys.wait(1000)
rtos.reboot() -- 重启设备
break
end
sys.wait(500)
end
else
log.error("fota", "文件升级启动失败")
end
-- 2.先下载升级包到文件,然后升级
function monitor_upgrade_progress()
while true do
local succ, done = fota.isDone()
if not succ then
log.error("fota", "升级出错")
break
end
if done then
log.info("fota", "升级完成,准备重启")
fota.finish(true)
sys.wait(2000)
rtos.reboot()
break
end
log.info("fota", "升级进行中...")
sys.wait(1000)
end
end
function http_download_fota()
-- 下载升级包
http.request("GET", "https://www.air32.cn/", nil, nil, {dst=xxx.bin}).wait()
log.info("fota", "下载完成")
-- 初始化FOTA
fota.init()
while not fota.wait() do
sys.wait(100)
end
-- 从文件升级
local result, isDone, cache = fota.file("xxx.bin")
if result then
monitor_upgrade_progress()
end
end
sys.taskInit(http_download_fota)
4.5 fota.isDone()
功能
等待底层 fota 流程完成
参数
无
返回值
local result, isDone = fota.isDone()
result
参数含义:有异常返回false,无异常返回true
数据类型:boolean;
取值范围:true/false;
isDone
参数含义:写入到最后一块返回true,否则返回false
数据类型:boolean;
取值范围:true/false;
例子
local result, isDone = fota.isDone()
log.info("升级状态", result, isDone)
if result then
if isDone then
log.info("升级成功")
rtos.reboot()
else
log.info("升级失败")
end
end
4.6 fota.finish(is_ok)
功能
结束 fota 流程
参数
is_ok
参数含义:是否完整走完流程,
true表示正确走完流程了,
false是用户层告诉底层本次升级用户取消了,除非需要中断当前的fota过程,一般不需要使用;
除了用户取消,也可能因为升级过程中出错而调用fota.finish(false)
数据类型:boolean
取值范围:true或false
是否必选:是
返回值
local result = fota.finish(is_ok)
result
参数含义:成功返回true, 失败返回false
数据类型:boolean
取值范围:true或false
例子
--升级成功
succ,fotaDone = fota.isDone()
if succ then
if fotaDone then
fota.finish(true)
log.info("FOTA完成")
done = true
rtos.reboot() --如果还有其他事情要做,就不要立刻reboot
break
end
end
--升级失败
succ,fotaDone,nCache = fota.run(rbuff)
if succ then
total = total + rbuff:used()
else
log.error("fota写入异常,结束本次流程")
fota.finish(false)
done = true
end
五、产品支持说明
支持 LuatOS 开发的所有产品都支持 fota 核心库
5.1 不同模组的升级包制作
LuatOS 开发模式下,固件分为两部分:core(底层固件) 和 script(脚本)
远程升级时:可以仅升级 script;也可以同时升级 core+script
仅 script 脚本升级时对于 Air780EXX 系列、Air8000 系列、Air8101/6101 系列是全量升级。
core+script 都升级时对于 Air780EXX 系列、Air8000 系列是差分升级,对于 Air8101/6101 系列是全量升级
Air780EXX 系列、Air8000 系列仅 script 升级时升级包制作:
如果用户只是新增一些自己的脚本逻辑,没有更新底层,可以选择仅脚本升级
更新完自己脚本后,修改版本号,点击生成量产文件,生成的量产文件中以.bin 结尾的就是仅脚本升级的升级包。


Air780EXX 系列、Air8000 系列 core+script 都升级时升级包制作:
每一次 core 的升级都会带来一些网络上的优化(例如信号差时的网络稳定性)以及一些 bug 修复,所以在发布新版本以后,用户可以先测试下 core 对自己脚本有无明显影响或性能提升,然后进行远程 FOTA。
对于含 core 升级的话需要制作差分包,原始版本生成一次量产文件,新版本生成一次量产文件。
针对这两个量产文件,制作一个差分文件,点击到 luatools 的主界面,依次点击图中蓝框所示意的地方(注:必须使用 luatools_3.0.9 及其以上版本,要不差分包升级的时候可能会出问题)

按下图所示选择低版本以及高版本的固件,然后点击开始执行即可,如果不想输出的差分包在 luatools 根目录下,可以自行选择一个输出路径

在你选择的目录下看到如下所示,.bin 文件就是升级差分包。

Air8101/6101 系列升级包制作:
因为此系列都是全量升级,所以在 luatools 中生成一次量产文件就行,生成的量产包如下:

其中 full_fota 文件夹下为 core+script 的全量升级包,script_ota 目录下是单脚本升级的升级包