Files
BR_YKC/4G/code/lib/aLiYunOta.lua

256 lines
7.9 KiB
Lua
Raw Normal View History

2026-03-31 15:46:04 +08:00
--- 模块功能阿里云物联网套件客户端OTA功能.
-- 目前固件签名算法仅支持MD5
-- @module aLiYunOta
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.04.16
require"log"
require"http"
module(..., package.seeall)
--gVersion固件版本号字符串如果用户没有调用本文件的setVer接口设置则默认为_G.PROJECT.."_".._G.VERSION.."_"..sys.getcorever()
--gName阿里云iot网站上配置的新固件文件下载后在模块中的保存路径如果用户没有调用本文件的setName接口设置则默认为/luazip/update.bin
--gCb新固件下载成功后要执行的回调函数
local gVersion,gName,gCb = _G.PROJECT.."_".._G.VERSION.."_"..rtos.get_version()
local gFilePath,gFileSize
local processed = 0
local flowMd5
--productKey产品标识
--deviceName设备名称
local productKey,deviceName
--verRpted版本号是否已经上报
local verRpted,sConnected
--lastStep最后一次上报的下载新固件的进度
local lastStep
--下载中标志
local downloading
local function otaCb(result,filePath,md5,size)
log.info("aLiYunOta.otaCb",gCb,result,filePath,size,(type(gName) =="string") and io.fileSize(filePath) or "function")
downloading = false
--校验MD5
if result then
if type(gName) == "string" then
local calMD5 = crypto.md5(filePath,"file")
result = (string.upper(calMD5) == string.upper(md5))
log.info("aLiYunOta.otaCb cmp md5",result,calMD5,md5)
else
local calMD5 = flowMd5:hexdigest()
result = (string.upper(calMD5) == string.upper(md5))
log.info("aLiYunOta.otaCb cmp md5",result,calMD5,md5)
end
end
rtos.fota_end()
if not result then
rtos.fota_start()
rtos.fota_end()
end
if gCb then
gCb(result,filePath)
else
if result then
sys.restart("ALIYUN_OTA")
end
end
end
--[[
upgradeStepRpt
step1100-2
descnil
]]
local function upgradeStepRpt(step,desc)
log.info("aLiYunOta.upgradeStepRpt",step,desc,sConnected)
if sConnected then
if step<=0 or step==100 then sys.timerStop(getPercent) end
lastStep = step
aLiYun.publish("/ota/device/progress/"..productKey.."/"..deviceName,"{\"id\":1,\"params\":{\"step\":\""..step.."\",\"desc\":\""..(desc or "").."\"}}")
end
end
function getPercent()
local step
if type(gName) == "string" then
step = io.fileSize(gName)*100/gFileSize
else
step = processed*100/gFileSize
end
log.info("aLiYunOta.getPercent",step)
if step~=0 and step~=lastStep then
upgradeStepRpt(step)
end
sys.timerStart(getPercent,5000)
end
local function downloadCbFnc(result,prompt,head,filePath)
log.info("aLiYunOta.downloadCbFnc",result,prompt,filePath)
sys.publish("ALIYUN_OTA_DOWNLOAD_IND",result)
end
local function saveUpdata(pdata,binlen,statusCode)
log.info("saveUpdata",binlen,statusCode)
if statusCode == "206" and pdata and binlen then
if rtos.fota_process(pdata,binlen) ~=0 then --返回-94表示没有进行初始化需要调用rtos.fota_start()进行更新初始化
log.info("updata.fota_process","fail!!")
return
else
flowMd5:update(pdata)
--打印此升级包的长度跟总包长度
processed = processed + pdata:len()
log.info("updata.fota_process",processed,binlen)
end
end
end
local function downloadTask(url,size,md5,ver)
log.info("aLiYunOta.downloadTask1",downloading,url,size,md5)
if not downloading then
downloading = true
gFileSize = size
local rangeBegin,retryCnt = 0,0
sys.timerStart(getPercent,5000)
sys.publish("LIB_ALIYUN_OTA_DOWNLOAD_BEGIN",ver)
while true do
if not gName then
gName = saveUpdata
end
http.request("GET",url,nil,{["Range"]="bytes="..rangeBegin.."-"},"",20000,downloadCbFnc,gName)
--if rangeBegin==0 then os.remove(gName) end
local _,result = sys.waitUntil("ALIYUN_OTA_DOWNLOAD_IND")
log.info("aLiYunOta.downloadTask2",result)
if result then
upgradeStepRpt(100,0)
sys.timerStart(otaCb,5000,true,gName,md5,size)
break
else
retryCnt = retryCnt+1
if retryCnt>=30 then
upgradeStepRpt(-2,"timeout")
otaCb(false,gName)
break
end
end
if type(gName) == "string" then
rangeBegin = io.fileSize(gName)
else
rangeBegin = processed
end
end
end
end
--[[
upgrade
payloadpayload是什么内容
]]
function upgrade(payload)
local result
local jsonData, result = json.decode(payload)
log.info("aLiYunOta.upgrade", result, payload)
if result and jsonData.data and jsonData.data.url then
if rtos.fota_start() ~= 0 then
log.info("fota_start fail")
sys.timerStart(verRpt, 10000)
return
end
flowMd5 = crypto.flow_md5()
sys.taskInit(downloadTask, jsonData.data.url, jsonData.data.size, jsonData.data.md5, jsonData.data.version)
end
end
--[[
verRptCb
PUBACK时的回调函数
resulttrue表示上报成功false或者nil表示失败
]]
local function verRptCb(result)
log.info("aLiYunOta.verRptCb",result)
verRpted = result
if not result then sys.timerStart(verRpt,20000) end
end
--[[
verRpt
]]
function verRpt()
log.info("aLiYunOta.verRpt",sConnected,gVersion)
if sConnected then
aLiYun.publish("/ota/device/inform/"..productKey.."/"..deviceName,"{\"id\":1,\"params\":{\"version\":\""..gVersion.."\"}}",1,verRptCb)
end
end
function connectCb(result,key,name)
sConnected = result
if result then
log.info("aLiYunOta.connectCb",verRpted)
productKey,deviceName = key,name
--订阅主题
aLiYun.subscribe({["/ota/device/upgrade/"..key.."/"..name]=0, ["/ota/device/upgrade/"..key.."/"..name]=1})
if not verRpted then
--上报固件版本号给云端
verRpt()
end
else
sys.timerStop(verRpt)
end
end
function isDownloading()
return downloading
end
--- 设置当前的固件版本号
-- @string version 当前固件版本号
-- @return nil
-- @usage
-- aLiYunOta.setVer("MCU_VERSION_1.0.0")
function setVer(version)
local oldVer = gVersion
gVersion = version
if verRpted and version~=oldVer then
verRpted = false
verRpt()
end
end
--- 设置新固件保存的文件名
-- @string name 新固件下载后保存的文件名注意此文件名并不是保存的完整路径完整路径通过setCb设置的回调函数去获取
-- @return nil
-- @usage
-- aLiYunOta.setName("MCU_FIRMWARE.bin")
function setName(name)
gName = name
end
--- 设置新固件下载后的回调函数
-- @function cbFnc 新固件下载后的回调函数
-- 回调函数的调用形式为cbFnc(result,filePath)result为下载结果true表示成功false或者nil表示失败filePath为新固件文件保存的完整路径
-- @return nil
-- @usage
-- aLiYunOta.setCb(cbFnc)
function setCb(cbFnc)
gCb = cbFnc
end