FOTA升级(第三方服务器)
FOTA升级(第三方服务器)
一、FOTA概述
FOTA即远程升级功能,此功能可以让客户在不方便大量线刷升级(设备不在身边/量产PCB没引出USB/需要大批量进行功能升级)的情况下,快速进行底层固件/脚本/脚本+底层固件的远程更新。
LuatOS开发模式下,固件分为两部分:core和script
远程升级时:core为差分升级;script为全量覆盖升级
远程升级时:可以仅升级script;可以仅升级core;也可以同时升级core+script
780E支持合宙iot平台升级和自建第三方服务器(HTTP)升级
二、演示功能概述
上一篇文章讲了使用合宙平台进行OTA的完整流程,本文将详细讲述如何使用自建服务器进行远程升级。
三、准备硬件环境
“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”
3.1 Air780E 开发板
本次使用的开发板为 Air780E 核心板,如下图所示:
点击链接购买:Air780E 核心板淘宝购买链接 ;
此核心板的详细使用说明参考:Air780E 产品手册 中的《开发板 Core_Air780E 使用说明 VX.X.X.pdf》,写这篇文章时最新版本的使用说明为:《开发板 Core_Air780E 使用说明 V1.0.5.pdf》 ;核心板使用过程中遇到任何问题,可以直接参考这份使用说明 pdf 文档。
3.2 SIM 卡
请准备一张可正常上网的SIM卡,该卡可以是物联网卡或您的个人手机卡。
特别提醒:请确保SIM卡未欠费且网络功能正常,以便顺利进行后续操作。
3.3 PC 电脑
请准备一台配备USB接口且能够正常上网的电脑。电脑操作系统为:WIN10以及以上版本的WINDOWS系统(mac以及win8及其以下电脑请更换为win10及其以上系统)
3.4 数据通信线
请准备一根用于连接Air780E开发板和PC电脑的数据线,该数据线将实现业务逻辑的控制与交互。
- USB数据线(其一端为Type-C接口,用于连接Air780E开发板)。通常,这种数据线的外观如下示意图所示:
普通的手机 USB 数据线一般都可以直接使用;
在本教程中,我们将采用USB数据线配置进行测试和数据查看:
- USB数据线:此数据线不仅用于为测试板供电,还用于查看数据日志。其一端为Type-C接口,连接Air780E开发板;另一端为标准USB接口,连接PC电脑。
3.5 组装硬件环境
3.5.1 请按照SIM卡槽上的指示方向正确插入SIM卡,务必确保插入方向正确,避免插反导致损坏!
通常,插入SIM卡的步骤如下:
- 将SIM卡的金属接触面朝下,对准卡槽的开口。
- 用力平稳地将SIM卡推入卡槽,直至听到“咔嚓”一声,表示SIM卡已正确安装到位。
3.5.2 USB 数据线,连接电脑和 Air780E 开发板,如下图所示:
四、FOTA软硬件参考
FOTA和硬件基本无关,软件API参考https://docs.openluat.com/air780e/luatos/api/ext/libfota2/
五、准备软件环境
“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。
5.1 Luatools 工具
要想烧录 AT 固件到 4G 模组中,需要用到合宙的强大的调试工具:Luatools(特别注意,luatools一定要是3.0.9及其以后的版本);
下载地址:Luatools v3 下载调试工具。
Luatools 工具集具备以下几大核心功能:
- 一键获取最新固件:自动连接合宙服务器,轻松下载最新的合宙模组固件。
- 固件与脚本烧录:便捷地将固件及脚本文件烧录至目标模组中。
- 串口日志管理:实时查看模组通过串口输出的日志信息,并支持保存功能。
- 串口调试助手:提供简洁的串口调试界面,满足基本的串口通信测试需求。
Luatools 下载之后, 无需安装, 解压到你的硬盘,点击 Luatools_v3.exe 运行,出现如下界面,就代表 Luatools 安装成功了:
5.2 烧录代码
首先要说明一点: 脚本代码, 要和固件的 LuatOS-SoC_V1112_EC618_FULL.soc 文件一起烧录。
5.2.1 烧录所需文件
底层core+脚本+lib
脚本所在位置:https://gitee.com/openLuat/LuatOS-Air780E/blob/master/demo/fota2/main.lua
lib所在位置:https://gitee.com/openLuat/LuatOS-Air780E/tree/master/script/libs
5.2.2 找到烧录的固件文件
官网下载,底层core下载地址:LuatOS底层core 注:本demo使用如图所示固件(V1112)以及历史版本中的V1111
5.2.3 正确连接电脑和4G模组电路板
使用带有数据通信功能的数据线,不要使用仅有充电功能的数据线;
5.2.4 识别4G模组的boot引脚
在下载之前,要用模组的boot引脚触发下载, 也就是说,要把4G模组的boot引脚拉到 1.8v,或者直接把boot引脚和VDD_EXT引脚相连。我们要在按下BOOT按键时让模块开机,就可以进入下载模式了。
具体到 Air780E开发板,
- 当我们模块没开机时,按着BOOT键然后长按PWR开机。
- 当我们模块开机时,按着BOOT键然后点按重启键即可。
5.2.5 识别电脑的正确端口
判断是否进入BOOT模式:模块上电,此时在电脑的设备管理器中,查看串口设备, 会出现一个端口表示进入了boot下载模式,如下图所示:
如果设备管理器出现了3个连续数字的com端口,这时候, 证明硬件连接就绪,模块正常开机且端口识别正常
此时按住boot按键,再按下重启按键就可以进行烧录了!
5.2.6 新建项目
首先,确保你的 Luatools 的版本大于或者等于3.0.9版本.
在 Luatools 的左上角上有版本显示的,如图所示:
Luatools 版本没问题的话, 就点击 Luatools 右上角的“项目管理测试”按钮,如下图所示:
这时会弹出项目管理和烧录管理的对话框,如下图:
点击下载后,我们需要进入boot模式才能正常下载。
六、第三方服务器FOTA简介
FOTA有多种方式,可以使用合宙的iot平台进行升级,也可以使用用户自建平台升级,可以只升级core,可以只升级用户脚本,也可以core+脚本一起升级,接下来介绍第三方服务器升级
6.1 云平台配置
每一家的云平台都不一样,但是本质上都一样,都是给模块下发到正确的升级文件。建议平台做一些验证,比如imei验证、版本号验证,循环请求验证,这样能够有效的避免因为升级包下载不对导致的模块循环升级,不跑代码其他逻辑,引起的假死机现象。
6.2 仅脚本升级简介
如果用户只是新增一些自己的脚本逻辑,没有更新底层,可以选择仅脚本升级
6.2.1 仅脚本升级示例
在进行脚本改动前,需要在你的电脑上随便生成一个.bin后缀大小不为0的文件(可以随便往里面写点什么),然后将这个bin文件上传到模块请求的地址去,目的是为了获取升级的url
比如我的bin文件升级url为"http://airtest.openluat.com:2900/download/HH_testota.bin"
这个文件里是乱码,没有其他东西,仅仅作为占位符存在,接下来开始修改代码
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "other_fota_demo"
-- iot限制,只能上传xxx.yyy.zzz格式的三位数的版本号,但实际上现在只用了XXX和ZZZ,中间yyy暂未使用
-- 需要注意的是,因为yyy不生效,所以111.222.333版本和111.444.333版本,对iot平台来说都一样,所以建议中间那一位永远写000
VERSION = "001.000.000"
-- 使用合宙iot平台时需要这个参数
PRODUCT_KEY = "123" -- 到 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
-- 使用合宙iot平台进行升级,不需要管下面这段代码
-- 使用第三方服务器时打开下面这段代码
local ota_opts = {
url = "###http://airtest.openluat.com:2900/download/HH_testota.bin"
-- 合宙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 = rtos.version().."_"..VERSION
-- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
}
sys.taskInit(function()
----这个判断是提醒要设置url的,且不要使用本文中的测试服务器,实际生产请删除
if not ota_opts.url or string.find(ota_opts.url,"airtest.openluat.com") then
while 1 do
sys.wait(1000)
log.info("fota", "当前URL",ota_opts.url,"请修改正确的url")
end
end
-- 等待网络就行后开始检查升级
sys.waitUntil("net_ready")
log.info("开始检查升级")
sys.wait(500)
if VERSION == "001.000.001" then
log.info("当前已经是001.000.001版本,为了避免用户做首次测试时循环升级特做此判断")
log.info("如您的平台上已经做好了版本号的判断,可以删除此判断")
return
else
libfota2.request(fota_cb, ota_opts)
end
end)
-- 演示定时自动升级, 每隔4小时自动检查一次
sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, ota_opts)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
打开luatools的项目管理界面,点击生成量产文件,780E的纯脚本升级文件放在luatools根目录下的"SOC量产及远程升级文件\EC618"目录下
因为模块烧录的是001.000.000版本,所以我们需要给脚本里的版本号改一下,改为001.000.001版本
再将脚本中增加几行打印(为了模拟用户修改脚本的动作)
最后将url改成bin文件存放的地址:
注:一定要注意url前的"###"标志位,如果不加###,模块进行HTTP请求时,会在请求头里加上version、project_key,imei,firmware_name等参数
然后重新生成一次量产固件
找到这个文件以后,右键重命名成刚刚创建的bin文件同名
再将这个bin文件放在之前上传到服务器对应地点。
6.2.2 示例效果展示
升级中:模块请求升级,下载完升级包以后会进行MD5验证升级包有无问题,如果没问题,就会启动重启程序,然后进行升级工作
升级后:升级成功后按我们之前的脚本,每隔5S,打印当前脚本版本号和core版本号,可以看出,当前版本号已经由原来的001.000.000变为了001.000.001
6.3 含core升级简介
每一次core的升级都会带来一些网络上的优化(例如信号差时的网络稳定性)以及一些bug修复,所以在发布新版本以后,用户可以先测试下core对自己脚本有无明显影响或性能提升,然后进行远程FOTA
6.3.1 含core升级测试demo
demo示例(和上文几乎一样,只是每秒的打印增加了一个core版本号)
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "fotademo"
-- iot限制,只能上传xxx.yyy.zzz格式的三位数的版本号,但实际上现在只用了XXX和ZZZ,中间yyy暂未使用
-- 需要注意的是,因为yyy不生效,所以111.222.333版本和111.444.333版本,对iot平台来说都一样,所以建议中间那一位永远写000
VERSION = "001.000.003"
-- 使用合宙iot平台时需要这个参数
PRODUCT_KEY = "UFsrnKt6g3h1IKV7ryKkpZnGrjnPg0xR" -- 到 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,"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
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()之后后面不要加任何语句!!!!!
由于1112已经本文写作时最新版本的core了,所以为了演示方便,001.000.000脚本选择搭配的core为V1111版本,再次点击"生成量产文件",稍等即可在luatools根目录下的"SOC量产及远程升级文件\EC618"目录下,找到core版本号为1111且脚本版本号为001.000.000的soc后缀文件
生成了这个版本以后,再去脚本中改动脚本版本号为001.000.003(只要前后两位有一位大于1,一位大于0即可(当然第一位大于1的时候,整个版本号自然大于001.000.000),如002.000.200,001.000.001,030.000.311等)
脚本版本号分为A.B.C三段;
因为历史原因,中间这一段B没有任何意义,但是必须存在;
假设旧的脚本版本号为A1.B1.C1,新的脚本版本号为A2.B2.C2;
当新旧脚本版本号满足以下任何一种条件时,版本号允许升级:
1、A2等于A1,同时 C2大于C1;
2、A2大于A1,同时 C2大于或者等于C1;
3、B1 B2无意义
然后在luatools里重新选择core为V1112版本,同样点击生成固件(注:即使是同一个版本号的core,因为功能着重点的不同,依然分为了好几个core,可以用后缀去区分这几个版本,关于不同core之间不同的功能着重点,可以点击这里查看,远程升级时,不同后缀的core不能进行相互间的升级),稍等即可在在luatools根目录下的"SOC量产及远程升级文件\EC618"目录下,找到core版本号为1112且脚本版本号为001.000.003的soc后缀文件
接下来就是针对这两个量产文件,制作一个差分文件,用来远程升级(注:远程升级中core为差分,脚本为全量升级)
点击到luatools的主界面,依次点击图中蓝框所示意的地方(注:必须使用luatools_3.0.9及其以上版本,要不差分包升级的时候可能会出问题)
按下图所示选择低版本以及高版本的固件,然后点击开始执行即可,如果不想输出的差分包在luatools根目录下,可以自行选择一个输出路径
如果生成差分固件的时候提示"固件超过大小"首先检查下自己的新旧固件,如下图就是错误选择了新版本binpkg固件。
因为包含了core中的改动,所以时间会稍长一些,luatools可能会出现"假死"情况,不要关闭它,稍等一会即可看到
同样,在你选择的目录下(没选择的在luatools根目录下)看到如下所示,带着脚本的PROJECT core版本号 脚本版本号的bin后缀的差分文件。
依然是将这个文件改名为HH_testota.bin上传到对应位置
将升级文件上传和添加imei工作完成以后,为了防止模块当前固件不是最早的fotademo_001.000.000_LuatOS-SoC_V1111_EC618版本,可以点击luatools主界面右边的"下载固件"选择最早的001.000.000版本,按住boot重启模块,然后点击下载,将最早的固件下载进模块里
6.3.2 示例效果展示
升级前:模块打印当前脚本版本号和core版本号
升级中下载差分包,校验差分包完成后就会重启模块进入真正的升级逻辑
因为升级包带core,所以从重启命令执行下去,到最终升级完成,模块重新开机打印了第一条开机日志,中间隔了快一分钟,如果core中代码改动较多,两分钟三分钟都是正常的
升级后:打印脚本版本号为001.000.001 core版本号为V1112 即证明升级成功
七、常见问题
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 | V1111 |
新版本 | 001.000.005 | v1112 |
操作人员失误后变成了如下
脚本版本号 | core版本号 | |
---|---|---|
误操作旧版本(1) | 001.000.005 | V1111 |
误操作旧版本(2) | 001.000.000 | V1112 |
误操作新版本(1) | 001.000.000 | V1112 |
误操作新版本(2) | 001.000.005 | V1111 |
然后误操作旧版本(1) 和误操作新版本(1)进行差分,这样虽然脚本版本号旧版本大于了新版本,但是core的旧版本小于新版本,所以升级平台依旧认为是依次有效的升级,下发了升级包。
升级完成后,模块内部脚本版本号变成了001.000.000 core版本号为V1112 ,下次模块请求升级的时候,当前固件上报的脚本版本号(001.000.000)依旧小于云平台存储的脚本版本号(001.000.005),然后继续下发升级包,就这么循环升级,直到流量耗尽,建议可以做一个类似合宙iot平台的禁止升级规则
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)
给读者的话
本篇文章由
黄何
开发;本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题;
请先登录合宙技术交流论坛,然后点击右边链接文档找错赢奖金-Air780E-LuatOS-软件指南-LuatOS基础-FOTA升级
用截图标注 + 文字描述的方式跟帖回复,记录清楚您发现的问题;
我们会迅速核实并且修改文档;
同时也会为您累计找错积分,您还可能赢取月度找错奖金!