256 lines
7.9 KiB
Lua
256 lines
7.9 KiB
Lua
|
|
--- 模块功能:阿里云物联网套件客户端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
|
|||
|
|
功能 :新固件文件下载进度上报
|
|||
|
|
参数 :
|
|||
|
|
step:1到100代表下载进度比;-2代表下载失败
|
|||
|
|
desc:描述信息,可为空或者nil
|
|||
|
|
返回值:无
|
|||
|
|
]]
|
|||
|
|
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
|
|||
|
|
功能 :收到云端固件升级通知消息时的回调函数
|
|||
|
|
参数 :
|
|||
|
|
payload:消息负载(原始编码,收到的payload是什么内容,就是什么内容,没有做任何编码转换)
|
|||
|
|
返回值:无
|
|||
|
|
]]
|
|||
|
|
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时的回调函数
|
|||
|
|
参数 :
|
|||
|
|
result:true表示上报成功,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
|