跳转至

fota - 底层固件升级

孟伟

一、概述

fota 库是 LuatOS 系统中的底层固件升级模块,用于实现设备固件的远程更新功能。该模块支持多种升级方式,包括:

  • 逐段接收并写入升级包数据
  • 直接从文件系统读取升级包文件进行升级

fota 模块支持灵活的存储位置配置,可以使用内部存储或外部 SPI Flash 进行固件升级,同时提供了完整的升级流程控制和状态查询功能。

1.1 主要功能

  • 初始化升级流程,配置存储位置
  • 逐段接收并写入升级包数据或直接从文件读取升级包
  • 检查升级状态和进度
  • 完成升级流程并重启系统

1.2 函数调用时序

  1. fota.init 初始化 fota 流程;
  2. fota.wait() 等待底层准备就绪。等待底层固件升级流程初始化完成,包括存储设备就绪、升级上下文准备等;
  3. fota.run(buf) 或者 fota.file("/xxx"),选择一个调用;
  4. 查询 fota.isDone ,检查 fota 流程是否成功,成功后需要重启更新,才能更新为新版本;注意:重启后不一定成功,可能存在设备断电、硬件异常等情况。判断升级是否成功,可以在重启后通过查询版本号来确认。
  5. 结束 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 目录下是单脚本升级的升级包