aLiYunOta.lua 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. --- 模块功能:阿里云物联网套件客户端OTA功能.
  2. -- 目前固件签名算法仅支持MD5
  3. -- @module aLiYunOta
  4. -- @author openLuat
  5. -- @license MIT
  6. -- @copyright openLuat
  7. -- @release 2018.04.16
  8. require"log"
  9. require"http"
  10. module(..., package.seeall)
  11. --gVersion:固件版本号字符串,如果用户没有调用本文件的setVer接口设置,则默认为_G.PROJECT.."_".._G.VERSION.."_"..sys.getcorever()
  12. --gName:阿里云iot网站上配置的新固件文件下载后,在模块中的保存路径,如果用户没有调用本文件的setName接口设置,则默认为/luazip/update.bin
  13. --gCb:新固件下载成功后,要执行的回调函数
  14. local gVersion,gName,gCb = _G.PROJECT.."_".._G.VERSION.."_"..rtos.get_version()
  15. local gFilePath,gFileSize
  16. local processed = 0
  17. local flowMd5
  18. --productKey:产品标识
  19. --deviceName:设备名称
  20. local productKey,deviceName
  21. --verRpted:版本号是否已经上报
  22. local verRpted,sConnected
  23. --lastStep:最后一次上报的下载新固件的进度
  24. local lastStep
  25. --下载中标志
  26. local downloading
  27. local function otaCb(result,filePath,md5,size)
  28. log.info("aLiYunOta.otaCb",gCb,result,filePath,size,(type(gName) =="string") and io.fileSize(filePath) or "function")
  29. downloading = false
  30. --校验MD5
  31. if result then
  32. if type(gName) == "string" then
  33. local calMD5 = crypto.md5(filePath,"file")
  34. result = (string.upper(calMD5) == string.upper(md5))
  35. log.info("aLiYunOta.otaCb cmp md5",result,calMD5,md5)
  36. else
  37. local calMD5 = flowMd5:hexdigest()
  38. result = (string.upper(calMD5) == string.upper(md5))
  39. log.info("aLiYunOta.otaCb cmp md5",result,calMD5,md5)
  40. end
  41. end
  42. rtos.fota_end()
  43. if not result then
  44. rtos.fota_start()
  45. rtos.fota_end()
  46. end
  47. if gCb then
  48. gCb(result,filePath)
  49. else
  50. if result then
  51. sys.restart("ALIYUN_OTA")
  52. end
  53. end
  54. end
  55. --[[
  56. 函数名:upgradeStepRpt
  57. 功能 :新固件文件下载进度上报
  58. 参数 :
  59. step:1到100代表下载进度比;-2代表下载失败
  60. desc:描述信息,可为空或者nil
  61. 返回值:无
  62. ]]
  63. local function upgradeStepRpt(step,desc)
  64. log.info("aLiYunOta.upgradeStepRpt",step,desc,sConnected)
  65. if sConnected then
  66. if step<=0 or step==100 then sys.timerStop(getPercent) end
  67. lastStep = step
  68. aLiYun.publish("/ota/device/progress/"..productKey.."/"..deviceName,"{\"id\":1,\"params\":{\"step\":\""..step.."\",\"desc\":\""..(desc or "").."\"}}")
  69. end
  70. end
  71. function getPercent()
  72. local step
  73. if type(gName) == "string" then
  74. step = io.fileSize(gName)*100/gFileSize
  75. else
  76. step = processed*100/gFileSize
  77. end
  78. log.info("aLiYunOta.getPercent",step)
  79. if step~=0 and step~=lastStep then
  80. upgradeStepRpt(step)
  81. end
  82. sys.timerStart(getPercent,5000)
  83. end
  84. local function downloadCbFnc(result,prompt,head,filePath)
  85. log.info("aLiYunOta.downloadCbFnc",result,prompt,filePath)
  86. sys.publish("ALIYUN_OTA_DOWNLOAD_IND",result)
  87. end
  88. local function saveUpdata(pdata,binlen,statusCode)
  89. log.info("saveUpdata",binlen,statusCode)
  90. if statusCode == "206" and pdata and binlen then
  91. if rtos.fota_process(pdata,binlen) ~=0 then --返回-94表示没有进行初始化,需要调用rtos.fota_start()进行更新初始化
  92. log.info("updata.fota_process","fail!!")
  93. return
  94. else
  95. flowMd5:update(pdata)
  96. --打印此升级包的长度跟总包长度
  97. processed = processed + pdata:len()
  98. log.info("updata.fota_process",processed,binlen)
  99. end
  100. end
  101. end
  102. local function downloadTask(url,size,md5,ver)
  103. log.info("aLiYunOta.downloadTask1",downloading,url,size,md5)
  104. if not downloading then
  105. downloading = true
  106. gFileSize = size
  107. local rangeBegin,retryCnt = 0,0
  108. sys.timerStart(getPercent,5000)
  109. sys.publish("LIB_ALIYUN_OTA_DOWNLOAD_BEGIN",ver)
  110. while true do
  111. if not gName then
  112. gName = saveUpdata
  113. end
  114. http.request("GET",url,nil,{["Range"]="bytes="..rangeBegin.."-"},"",20000,downloadCbFnc,gName)
  115. --if rangeBegin==0 then os.remove(gName) end
  116. local _,result = sys.waitUntil("ALIYUN_OTA_DOWNLOAD_IND")
  117. log.info("aLiYunOta.downloadTask2",result)
  118. if result then
  119. upgradeStepRpt(100,0)
  120. sys.timerStart(otaCb,5000,true,gName,md5,size)
  121. break
  122. else
  123. retryCnt = retryCnt+1
  124. if retryCnt>=30 then
  125. upgradeStepRpt(-2,"timeout")
  126. otaCb(false,gName)
  127. break
  128. end
  129. end
  130. if type(gName) == "string" then
  131. rangeBegin = io.fileSize(gName)
  132. else
  133. rangeBegin = processed
  134. end
  135. end
  136. end
  137. end
  138. --[[
  139. 函数名:upgrade
  140. 功能 :收到云端固件升级通知消息时的回调函数
  141. 参数 :
  142. payload:消息负载(原始编码,收到的payload是什么内容,就是什么内容,没有做任何编码转换)
  143. 返回值:无
  144. ]]
  145. function upgrade(payload)
  146. local result
  147. local jsonData, result = json.decode(payload)
  148. log.info("aLiYunOta.upgrade", result, payload)
  149. if result and jsonData.data and jsonData.data.url then
  150. if rtos.fota_start() ~= 0 then
  151. log.info("fota_start fail")
  152. sys.timerStart(verRpt, 10000)
  153. return
  154. end
  155. flowMd5 = crypto.flow_md5()
  156. sys.taskInit(downloadTask, jsonData.data.url, jsonData.data.size, jsonData.data.md5, jsonData.data.version)
  157. end
  158. end
  159. --[[
  160. 函数名:verRptCb
  161. 功能 :上报固件版本号给云端后,收到PUBACK时的回调函数
  162. 参数 :
  163. result:true表示上报成功,false或者nil表示失败
  164. 返回值:无
  165. ]]
  166. local function verRptCb(result)
  167. log.info("aLiYunOta.verRptCb",result)
  168. verRpted = result
  169. if not result then sys.timerStart(verRpt,20000) end
  170. end
  171. --[[
  172. 函数名:verRpt
  173. 功能 :上报固件版本号给云端
  174. 参数 :无
  175. 返回值:无
  176. ]]
  177. function verRpt()
  178. log.info("aLiYunOta.verRpt",sConnected,gVersion)
  179. if sConnected then
  180. aLiYun.publish("/ota/device/inform/"..productKey.."/"..deviceName,"{\"id\":1,\"params\":{\"version\":\""..gVersion.."\"}}",1,verRptCb)
  181. end
  182. end
  183. function connectCb(result,key,name)
  184. sConnected = result
  185. if result then
  186. log.info("aLiYunOta.connectCb",verRpted)
  187. productKey,deviceName = key,name
  188. --订阅主题
  189. aLiYun.subscribe({["/ota/device/upgrade/"..key.."/"..name]=0, ["/ota/device/upgrade/"..key.."/"..name]=1})
  190. if not verRpted then
  191. --上报固件版本号给云端
  192. verRpt()
  193. end
  194. else
  195. sys.timerStop(verRpt)
  196. end
  197. end
  198. function isDownloading()
  199. return downloading
  200. end
  201. --- 设置当前的固件版本号
  202. -- @string version 当前固件版本号
  203. -- @return nil
  204. -- @usage
  205. -- aLiYunOta.setVer("MCU_VERSION_1.0.0")
  206. function setVer(version)
  207. local oldVer = gVersion
  208. gVersion = version
  209. if verRpted and version~=oldVer then
  210. verRpted = false
  211. verRpt()
  212. end
  213. end
  214. --- 设置新固件保存的文件名
  215. -- @string name 新固件下载后保存的文件名;注意此文件名并不是保存的完整路径,完整路径通过setCb设置的回调函数去获取
  216. -- @return nil
  217. -- @usage
  218. -- aLiYunOta.setName("MCU_FIRMWARE.bin")
  219. function setName(name)
  220. gName = name
  221. end
  222. --- 设置新固件下载后的回调函数
  223. -- @function cbFnc 新固件下载后的回调函数
  224. -- 回调函数的调用形式为:cbFnc(result,filePath),result为下载结果,true表示成功,false或者nil表示失败;filePath为新固件文件保存的完整路径
  225. -- @return nil
  226. -- @usage
  227. -- aLiYunOta.setCb(cbFnc)
  228. function setCb(cbFnc)
  229. gCb = cbFnc
  230. end