远程固件升级服务
一、FOTA 概述
FOTA 即远程升级功能,此功能可以让客户在不方便大量线刷升级(设备不在身边/量产 PCB 没引出 USB/需要大批量进行功能升级)的情况下,快速进行底层固件/脚本/脚本 + 底层固件 的远程更新。
LuatOS 开发模式下,固件分为两部分:core 和 script
远程升级时:core 为差分升级;script 为全量覆盖升级
远程升级时:可以仅升级 script;也可以仅升级 core;还可以同时升级 core+script
Air8000 支持合宙 iot 平台升级和自建第三方服务器(HTTP)升级
二、演示功能概述
本文将详细讲述 Air8000 如何进行远程升级。
三、准备硬件环境
参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。
本文所需要用到的硬件有 Air8000核心板、USB数据线、SIM卡,核心板实物如下图所示。
四、准备软件环境
1. 烧录工具 Luatools,详细使用说明参考:Luatools工具使用说明 ;
2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V2005_Air8000;此页面有新版本固件的话选用最新版本固件。
3. LuatOS 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS-Air8000/tree/master/Core_board_demo/fota2;
4. lib 脚本文件:使用 Luatools 烧录时,勾选“添加默认lib”选项,使用默认 lib 脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到 Air8000核心板中,将本篇文章中演示使用的项目文件烧录到 Air8000核心板中。
五、API接口说明
六、合宙自有服务器 FOTA 简介
FOTA 有多种方式,可以使用合宙的 iot 平台进行升级,也可以使用用户自建平台升级,可以只升级 core,也可以只升级用户脚本,还可以 core+ 脚本一起升级,接下来先介绍合宙自有服务器升级,第三方升级在下一篇文章中介绍
6.1 云平台配置
使用合宙自建服务器的话,需要先登录合宙 IOT 平台,如下图所示,没有账号的,可以先注册一个。 客户向合宙采购4G模块时,如果采购人员没有告知合宙这批模块放在iot.openluat.com上的哪个项目下,则合宙会以采购人的手机号为账号,默认密码888888,创建一个“Air7XXF标准模块”的项目,此次采购的所有模块都会放在这个项目下,如果你的账号下没有对应imei,可以联系合宙销售帮忙添加模块到对应项目下(最好还是从哪里买的模块,就让他给你转移到你自己名下)
如果不在自己账号下,也可以通过烧录专属固件的方法,把模块归属到您指定的项目下,可以通过点击合宙云平台右上角的问号,此时会弹出帮助中心,然后查看详细说明。
1. 如果你已经登录账户,那么便点击下图中红框对应位置。
2. 再根据下图指示依次点击红框所示的地方,创建一个新项目,本文将项目名称命名为“Air8000”,项目描述可以不填。
3. 在所有项目的最后,找到自己刚刚新建的项目,并且点击红框内的"查看/点击复制"复制所需要的校验码到剪切板,用于后面的升级。
4. 至此,合宙iot平台上的预备动作就做完了。
6.2 仅脚本升级简介
如果用户只是新增一些自己的脚本逻辑,没有更新底层固件,可以选择仅脚本升级。
6.2.1 仅脚本升级示例
源码参考:仅脚本升级 demo 链接
1. 需要将代码中的PRODUCT_KEY
参数修改为刚才复制的校验码,我的校验码是Va1jH6k1emaZkFwGU6kuO7oCqFZt7KZp
,修改后的代码如下。大家不要直接复制我的校验码,一定要用自己项目的校验码,切记!!!
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "fotademo"
-- iot限制,只能上传xxx.yyy.zzz格式的三位数的版本号,但实际上现在只用了XXX和ZZZ,中间yyy暂未使用
-- 需要注意的是,因为yyy不生效,所以111.222.333版本和111.444.333版本,对iot平台来说都一样,所以建议中间那一位永远写000
VERSION = "001.000.000"
-- 使用合宙iot平台时需要这个参数
PRODUCT_KEY = "Va1jH6k1emaZkFwGU6kuO7oCqFZt7KZp" -- 到 iot.openluat.com 创建项目,获取正确的项目id
sys = require "sys"
libfota2 = require "libfota2"
-- 联网函数, 可自行删减
sys.taskInit(function()
-- 默认都等到联网成功
sys.waitUntil("IP_READY")
log.info("4G网络链接成功")
sys.publish("net_ready")
end)
-- 循环打印版本号, 方便看版本号变化, 非必须
sys.taskInit(function()
while 1 do
sys.wait(5000)
log.info("降功耗 找合宙")
-- log.info("fota", "脚本版本号", VERSION)
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
end
end)
-- 升级结果的回调函数
-- 功能:获取fota的回调函数
-- 参数:
-- result:number类型
-- 0表示成功
-- 1表示连接失败
-- 2表示url错误
-- 3表示服务器断开
-- 4表示接收报文错误
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
local function fota_cb(ret)
log.info("fota", ret)
if ret == 0 then
log.info("升级包下载成功,重启模块")
rtos.reboot()
elseif ret == 1 then
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
elseif ret == 2 then
log.info("url错误", "检查url拼写")
elseif ret == 3 then
log.info("服务器断开", "检查服务器白名单配置")
elseif ret == 4 then
log.info("接收报文错误", "检查模块固件或升级包内文件是否正常")
elseif ret == 5 then
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
else
log.info("不是上面几种情况 ret为", ret)
end
end
local ota_opts = {}
-- 使用合宙iot平台进行升级,不需要管下面这段代码
-- 使用第三方服务器时打开下面这段代码
--[[local ota_opts = {
url = "",
-- 合宙IOT平台的默认升级URL, 不填就是这个默认值
-- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
-- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
-- 如果不加###,则默认会上传如下参数
-- 1. opts.version string 版本号, 默认是 BSP版本号.x.z格式
-- 2. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
-- 3. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
-- 4. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
-- 5. opts.firmware_name string 底层版本号
-- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
version = ""
-- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
}]]--
sys.taskInit(function()
-- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
if "123" == _G.PRODUCT_KEY and not ota_opts.url then
while 1 do
sys.wait(1000)
log.info("fota", "请修改正确的PRODUCT_KEY")
end
end
-- 等待网络就行后开始检查升级
sys.waitUntil("net_ready")
log.info("开始检查升级")
sys.wait(500)
libfota2.request(fota_cb, ota_opts)
end)
-- 演示定时自动升级, 每隔4小时自动检查一次
sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, ota_opts)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
2. 打开 Luatools 的“项目管理测试”界面,点击“生成量产文件”,Air8000的纯脚本升级文件放在 Luatools 根目录下的"SOC 量产及远程升级文件\Air8000"目录下。
3. 因为模块烧录的是 001.000.000 版本,所以我们需要给脚本里的版本号改一下,改为 001.000.001 版本。
4. 再将脚本中增加几行打印(为了模拟用户修改脚本的动作)。
5. 然后重新生成一次量产固件
6. 打开刚刚的合宙 iot 平台点击我的项目--固件列表--创建固件
7. 点击选择文件,把刚刚的 bin 后缀文件上传到 iot 平台
8. 文件名、固件名、版本号都是自动识别的,用户无需修改
9. 点击确定,等待上传成功的动画提示,成功后便如下图所示。
10. 接下来添加需要升级的设备。模块刚开机时 Luatools 会打印模块的 imei 号,如下图所示,当然也可以使用手机扫描模块上的二维码获得模块 imei 号,或者将模块屏蔽盖上二维码的下方第一排模块的 imei 号,记录下来。
11. 在刚刚创建固件的地方,按照下图所示操作,添加模块的 imei(同时需要该设备在对应项目下)。
12. 将升级文件上传和添加 imei 工作完成以后,点击 Luatools 上的重启模块或者按照代码里的等待 4 小时,触发远程升级逻辑。
6.2.2 示例效果展示
升级前:因为没有添加升级固件,所以请求升级的结果如下图所示,每隔 5S 会打印一句"降功耗找合宙"和当前版本号
升级中:模块请求升级,下载完升级包以后会进行 MD5 验证升级包有无问题,如果没问题,就会启动重启程序,然后进行升级工作
升级后:升级成功后按我们之前的脚本,每隔 1S,打印五次"降功耗,找合宙"以及当前脚本版本号,可以看出,当前版本号已经由原来的 001.000.000 变为了 001.000.001
当然:升级完成以后,因为脚本中有联网就去请求一次升级的代码,所以,会再去请求一次升级,因为没有更高的版本了,所以会上报"已是最新版本"
6.3 含 core 升级简介
每一次 core 的升级都会带来一些网络上的优化(例如信号差时的网络稳定性)以及一些 bug 修复,所以在发布新版本以后,用户可以先测试下 core 对自己脚本有无明显影响或性能提升,然后进行远程 FOTA。
6.3.1 含 core 升级测试 demo
源码参考:含 core 升级 demo 链接
1. 这里同样需要将代码中的PRODUCT_KEY
参数修改为刚才复制的校验码,我的校验码是Va1jH6k1emaZkFwGU6kuO7oCqFZt7KZp
,修改后的代码如下。大家不要直接复制我的校验码,一定要用自己项目的校验码,切记!!!
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "fotademo"
-- iot限制,只能上传xxx.yyy.zzz格式的三位数的版本号,但实际上现在只用了XXX和ZZZ,中间yyy暂未使用
-- 需要注意的是,因为yyy不生效,所以111.222.333版本和111.444.333版本,对iot平台来说都一样,所以建议中间那一位永远写000
VERSION = "001.000.000"
-- 使用合宙iot平台时需要这个参数
PRODUCT_KEY = "Va1jH6k1emaZkFwGU6kuO7oCqFZt7KZp" -- 到 iot.openluat.com 创建项目,获取正确的项目id
sys = require "sys"
libfota2 = require "libfota2"
-- 联网函数, 可自行删减
sys.taskInit(function()
-- 默认都等到联网成功
sys.waitUntil("IP_READY")
log.info("4G网络链接成功")
sys.publish("net_ready")
end)
-- 循环打印版本号, 方便看版本号变化, 非必须
sys.taskInit(function()
while 1 do
sys.wait(5000)
log.info("降功耗 找合宙")
-- log.info("fota", "脚本版本号", VERSION)
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
end
end)
-- 升级结果的回调函数
-- 功能:获取fota的回调函数
-- 参数:
-- result:number类型
-- 0表示成功
-- 1表示连接失败
-- 2表示url错误
-- 3表示服务器断开
-- 4表示接收报文错误
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
local function fota_cb(ret)
log.info("fota", ret)
if ret == 0 then
log.info("升级包下载成功,重启模块")
rtos.reboot()
elseif ret == 1 then
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
elseif ret == 2 then
log.info("url错误", "检查url拼写")
elseif ret == 3 then
log.info("服务器断开", "检查服务器白名单配置")
elseif ret == 4 then
log.info("接收报文错误", "检查模块固件或升级包内文件是否正常")
elseif ret == 5 then
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
else
log.info("不是上面几种情况 ret为", ret)
end
end
local ota_opts = {}
-- 使用合宙iot平台进行升级,不需要管下面这段代码
-- 使用第三方服务器时打开下面这段代码
--[[local ota_opts = {
url = "",
-- 合宙IOT平台的默认升级URL, 不填就是这个默认值
-- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
-- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
-- 如果不加###,则默认会上传如下参数
-- 1. opts.version string 版本号, 默认是 BSP版本号.x.z格式
-- 2. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
-- 3. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
-- 4. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
-- 5. opts.firmware_name string 底层版本号
-- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
version = ""
-- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
}]]--
sys.taskInit(function()
-- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
if "123" == _G.PRODUCT_KEY and not ota_opts.url then
while 1 do
sys.wait(1000)
log.info("fota", "请修改正确的PRODUCT_KEY")
end
end
-- 等待网络就行后开始检查升级
sys.waitUntil("net_ready")
log.info("开始检查升级")
sys.wait(500)
libfota2.request(fota_cb, ota_opts)
end)
-- 演示定时自动升级, 每隔4小时自动检查一次
sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, ota_opts)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
2. 由于目前 Air8000 最新固件版本为2005,所以我们需要再下载一个2004版本的固件用于演示。此处演示是从2004升级到2005。001.000.001 脚本搭配V2004固件,001.000.002 脚本搭配V2005固件。具体操作不再演示,大家可根据前面的教程自行生成对应的量产文件。生成好的量产文件如下图所示。
3. 接下来就是针对这两个量产文件,制作一个差分文件,用来远程升级(注:远程升级中 core 为差分,脚本为全量升级)。 点击到 Luatools 的主界面,依次点击图中蓝框所示意的地方(注:必须使用 luatools_3.0.9 及其以上版本,要不差分包升级的时候可能会出问题)。
4. 按下图所示选择低版本以及高版本的固件,然后点击开始执行即可,如果不想输出的差分包在 Luatools 根目录下,可以自行选择一个输出路径。
5. 因为包含了 core 中的改动,所以时间会稍长一些,Luatools 可能会出现"假死"情况,不要关闭它,稍等一会即可看到
6. 同样,在你选择的目录下(没选择的在 Luatools 根目录下)看到如下所示,带着脚本的 PROJECT core 版本号 脚本版本号的 bin 后缀的差分文件。
7. 至于 iot 平台的配置还是和上文一样的步骤。登录 iot 平台,打开刚刚的合宙 iot 平台点击我的项目--固件列表--创建固件。
8. 点击选择文件,把刚刚的 bin 后缀文件上传到 iot 平台。
9. 文件名、固件名、版本号都是自动识别的,用户无需修改
10. 点击确定,等待上传成功的动画提示,成功后便如下图所示。
11. 如果上传了其他文件,会提示"升级文件有误"
12. 接下来添加需要升级的设备。模块刚开机时 Luatools 会打印模块的 imei 号,如下图所示,当然也可以使用手机扫描模块上的二维码获得模块 imei 号,或者将模块屏蔽盖上二维码的下方第一排模块的 imei 号,记录下来。
13. 在刚刚创建固件的地方,按照下图所示操作,添加模块的 imei(同时需要该设备在对应项目下)。
14. 将升级文件上传和添加 imei 工作完成以后,为了防止模块当前固件不是 fotademo_001.000.001_LuatOS-SoC_V2004_Air8000 版本,可以点击 Luatools 主界面右边的"下载固件"选择最早的 001.000.001 版本,按住 BOOT 重启模块,然后点击下载,将最早的固件下载进模块里。
6.3.2 示例效果展示
升级前:模块打印当前脚本版本号和 core 版本号
升级中下载差分包,校验差分包完成后就会重启模块进入真正的升级逻辑
因为升级包带 core,所以从重启命令执行下去,到最终升级完成,模块重新开机打印了第一条开机日志,中间隔了不到一分钟,如果 core 中代码改动较多,两分钟三分钟都是正常的。
七、常见问题
7.1 为什么升级后我的模块没有任何反应了,像是变砖一样
有多种可能,
7.1.1 检查脚本
首先先检查下用户自己的脚本,有可能是引起重启/死机的代码写在了最前面,例如新加的某个值或者函数为 nil 但是还是去做了些加减乘除或者判断大小的逻辑。可以直接本地烧录下新版本的 core+ 脚本验证,如果有 fskv 等用到 flash 的代码,可能需要仔细检查才能排除问题,比如下载的时候勾选如下图所示的两个选项。
7.1.2 检查 core
如果是仅脚本升级,但是没注意使用了新 core 中才有的接口,就有可能引起循环重启,如果重启在代码最开头,模块可能来不及打印任何日志就重启了,可以直接本地烧录下新版本的 core+ 脚本验证,如果有 fskv 等用到 flash 的代码,可能需要仔细检查。
7.2 检查过脚本和 core,没问题,为什么会循环升级 6 次以后禁止升级
检查下升级包是否正常,有时候因为人员误操作,经常会出现旧脚本 + 新 core 或者新脚本 + 旧 core 的意外组合,
例如:
本来应该如下表描述的一样
脚本版本号 |
core 版本号 |
|
---|---|---|
旧版本 |
001.000.000 |
V2004 |
新版本 |
001.000.005 |
V2005 |
操作人员失误后变成了如下
脚本版本号 |
core 版本号 |
|
---|---|---|
误操作旧版本(1) |
001.000.005 |
V2004 |
误操作旧版本(2) |
001.000.000 |
V2005 |
误操作新版本(1) |
001.000.000 |
V2005 |
误操作新版本(2) |
001.000.005 |
V2004 |
然后误操作旧版本(1) 和误操作新版本(1)进行差分,这样虽然脚本版本号旧版本大于了新版本,但是 core 的旧版本小于新版本,所以升级平台依旧认为是依次有效的升级,下发了升级包。
升级完成后,模块内部脚本版本号变成了 001.000.000 core 版本号为 V2005,下次模块请求升级的时候,当前固件上报的脚本版本号(001.000.000)依旧小于云平台存储的脚本版本号(001.000.005),然后继续下发升级包,就这么循环 6 次,然后触发合宙 iot 平台的禁止升级规则
在正确生成差分包,并且上传成功后,可以在 iot 平台里解除禁止升级的限制
在"我的设备"中选择升级 imei 所在的项目,然后点击右边的"解除禁止升级",
确定“导致设备循环升级的异常”已经处理完成后,点击确定解除,即可解除限制升级
7.3 我想在服务器发送特定的字符串如"update"时再触发升级,应该怎么做
只需要在你希望的升级逻辑后面加上升级语句即可,例如 mqtt 的 demo 里增加几句话
elseif event == "recv" then
libfota2 = require "libfota2"
log.info("mqtt", "downlink", "topic", data, "payload", payload)
--假定mqtt发过来的字符串为"update"就启动升级
if payload =="update" then
libfota2.request(fota_cb, ota_opts)
end
sys.publish("mqtt_payload", data, payload)
又或者希望按键升级:
--这里假定使用GPIO0进行按键升级
gpio.debounce(0, 3000, 1)
gpio.setup(0, function()
libfota2.request(fota_cb, ota_opts)
end, gpio.PULLUP)