aliyun.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. --[[
  2. @module aliyun
  3. @summary AliYun阿里云物联网平台
  4. @version 1.0
  5. @date 2023.06.07
  6. @author wendal
  7. @demo aliyun
  8. @usage
  9. -- 请查阅demo
  10. ]]
  11. _G.sys = require("sys")
  12. --[[特别注意, 使用http库需要下列语句]]
  13. _G.sysplus = require("sysplus")
  14. local libfota = require("libfota")
  15. -- 总的库对象
  16. local aliyun = {}
  17. local mqttc = nil
  18. local ClientId,PassWord,UserName,SetClientidFnc,SetDeviceTokenFnc,SetDeviceSecretFnc
  19. local EvtCb = {}
  20. local opts = {}
  21. -------------------------------------------------------
  22. ---- FOTA 相关
  23. -------------------------------------------------------
  24. -- fota的回调函数
  25. local function libfota_cb(result)
  26. log.info("fota", "result", result)
  27. -- fota成功
  28. if result == 0 then
  29. rtos.reboot()
  30. end
  31. end
  32. --收到云端固件升级通知消息时的回调函数
  33. local function aliyun_upgrade(payload)
  34. local jsonData, result = json.decode(payload)
  35. if result and jsonData.data and jsonData.data.url then
  36. log.info("aliyun", "ota.url", jsonData.data.url)
  37. libfota.request(EvtCb["ota"] or libfota_cb, jsonData.data.url)
  38. end
  39. end
  40. -------------------------------------------------------
  41. --- 用户侧的回调处理
  42. -------------------------------------------------------
  43. --底层libMQTT回调函数,上层的回调函数,通过 aliyun.on注册
  44. local function mqtt_cbevent(mqtt_client, event, data, payload,metas)
  45. log.debug("aliyun", "event", event, "data", data)
  46. if event == "conack" then
  47. log.info("aliyun", "conack")
  48. -- if opts.ProductKey and opts.DeviceName then
  49. aliyun.subscribe("/ota/device/upgrade/"..opts.ProductKey.."/"..opts.DeviceName,1)
  50. aliyun.publish("/ota/device/inform/"..opts.ProductKey.."/"..opts.DeviceName,1,"{\"id\":1,\"params\":{\"version\":\"".._G.VERSION.."\"}}")
  51. -- end
  52. sys.publish("aliyun_conack")
  53. if EvtCb["connect"] then
  54. EvtCb["connect"](true)
  55. end
  56. elseif event == "recv" then -- 服务器下发的数据
  57. log.debug("aliyun", "downlink", "topic", data, "payload", payload)
  58. --OTA消息
  59. if data =="/ota/device/upgrade/".. opts.ProductKey.."/".. opts.DeviceName then
  60. aliyun_upgrade(payload)
  61. end
  62. if EvtCb["receive"] then
  63. EvtCb["receive"](data, payload, metas.qos, metas.retain, metas.dup)
  64. end
  65. elseif event == "sent" then
  66. log.info("aliyun", "sent", data)
  67. if data then
  68. sys.publish("aliyun_evt", "sent", data)
  69. if EvtCb["publish"] then
  70. EvtCb["publish"](data)
  71. end
  72. end
  73. elseif event == "disconnect" then
  74. log.info("aliyun", "disconnect")
  75. if EvtCb["connect"] then
  76. EvtCb["connect"](false)
  77. end
  78. end
  79. end
  80. local function mqtt_task(mqtt_host, mqtt_port, mqtt_isssl, client_id, user_name, password)
  81. mqttc = mqtt.create(nil,mqtt_host, mqtt_port or 1883, mqtt_isssl) --mqtt客户端创建
  82. log.debug("aliyun", "mqtt三元组", client_id,user_name,password)
  83. mqttc:auth(client_id,user_name,password) --mqtt三元组配置
  84. mqttc:keepalive(300) -- 默认值240s
  85. mqttc:autoreconn(true, 20000) -- 自动重连机制
  86. mqttc:on(mqtt_cbevent) --mqtt回调注册
  87. -- mqttc:debug(true)
  88. mqttc:connect()
  89. end
  90. ---------------------------------------------------
  91. --- 一型一密
  92. --------------------------------------------------
  93. -- 二次连接, 也就是真正的连接
  94. local function clientDataTask(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl,passtoken,Registration)
  95. sys.taskInit(function()
  96. if not Registration then -- 预注册
  97. local client_id,user_name,password = iotauth.aliyun(opts.ProductKey, opts.DeviceName,SetDeviceSecretFnc)
  98. -- mqttc = mqtt.create(nil,opts.mqtt_host, opts.mqtt_port, opts.mqtt_isssl) --mqtt客户端创建
  99. -- mqttc:auth(client_id,user_name,password) --mqtt三元组配置
  100. mqtt_task(mqtt_host or opts.mqtt_host, mqtt_port or opts.mqtt_port, mqtt_isssl or opts.mqtt_isssl, client_id, user_name, password)
  101. else -- 免预注册
  102. -- mqttc = mqtt.create(nil,opts.mqtt_host, opts.mqtt_port, opts.mqtt_isssl) --mqtt客户端创建
  103. -- mqttc:auth(opts.DeviceName, opts.ProductKey, passtoken) --mqtt三元组配置
  104. mqtt_task(mqtt_host or opts.mqtt_host, mqtt_port or opts.mqtt_port, mqtt_isssl or opts.mqtt_isssl, opts.DeviceName, opts.ProductKey, passtoken)
  105. end
  106. end)
  107. end
  108. --根据返回的数据进行二次加密
  109. local function directProc(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl,Registration)
  110. if not Registration then
  111. local ClientId = DeviceName.."|securemode=3,signmethod=hmacmd5,timestamp=789|"
  112. local UserName = DeviceName.."&"..ProductKey
  113. local content = "ClientId"..DeviceName.."deviceName"..DeviceName.."productKey"..ProductKey.."timestamp789"
  114. local signKey= SetDeviceSecretFnc
  115. PassWord = crypto.hmac_md5(content,signKey)
  116. clientDataTask(ClientId,UserName,PassWord,mqtt_host,mqtt_port,mqtt_isssl,DeviceName,ProductKey)
  117. else
  118. local ClientId = SetClientidFnc.."|securemode=-2,authType=connwl|"
  119. local UserName = DeviceName.."&"..ProductKey
  120. local PassWord = SetDeviceTokenFnc
  121. clientDataTask(ClientId,UserName,mqtt_host,mqtt_port,mqtt_isssl,PassWord,Registration)
  122. end
  123. end
  124. --获取一型一密的连接参数
  125. local function clientEncryptionTask(Registration,DeviceName,ProductKey,ProductSecret,InstanceId,mqtt_host,mqtt_port,mqtt_isssl)
  126. sys.taskInit(function()
  127. --预注册
  128. if not Registration then
  129. ClientId = DeviceName.."|securemode=2,authType=register,random=123,signmethod=hmacmd5|"
  130. --免预注册
  131. else
  132. if InstanceId and #InstanceId > 0 then
  133. ClientId = DeviceName.."|securemode=-2,authType=regnwl,random=123,signmethod=hmacmd5,instanceId="..InstanceId.."|"
  134. else
  135. ClientId = DeviceName.."|securemode=-2,authType=regnwl,random=123,signmethod=hmacmd5|"
  136. end
  137. end
  138. local UserName = DeviceName.."&"..ProductKey
  139. local content = "deviceName"..DeviceName.."productKey"..ProductKey.."random123"
  140. local PassWord = crypto.hmac_md5(content, ProductSecret)
  141. local mqttClient = mqtt.create(nil, mqtt_host, mqtt_port, true) --客户端创建
  142. if mqttClient == nil then
  143. log.error("aliyun", "一型一密要求固件支持TLS加密, 当前固件不支持!!!")
  144. return
  145. end
  146. log.debug("aliyun", "一型一密认证三元组", ClientId, UserName, PassWord)
  147. mqttClient:auth(ClientId,UserName,PassWord) --三元组配置
  148. mqttClient:autoreconn(true, 30000)
  149. local flag = true
  150. mqttClient:on(function(mqtt_client, event, data, payload) --mqtt回调注册
  151. if event == "recv" then
  152. -- 无需订阅topic, 阿里云会主动下发通知
  153. log.info("aliyun", "downlink", "topic", data, "payload", payload)
  154. if payload then
  155. local tJsonDecode,res = json.decode(payload)
  156. if not Registration then
  157. --预注册
  158. if res and tJsonDecode["deviceName"] and tJsonDecode["deviceSecret"] then
  159. SetDeviceSecretFnc = tJsonDecode["deviceSecret"]
  160. log.debug("aliyun", "一型一密(预注册)", tJsonDecode["deviceName"], SetDeviceSecretFnc)
  161. if EvtCb["reg"] then
  162. EvtCb["reg"](tJsonDecode)
  163. else
  164. aliyun.store(tJsonDecode)
  165. end
  166. mqttClient:autoreconn(false)
  167. mqttClient:disconnect()
  168. flag = false
  169. clientDataTask(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl)
  170. end
  171. else
  172. --免预注册
  173. if res and tJsonDecode["deviceName"] and tJsonDecode["deviceToken"] then
  174. SetDeviceTokenFnc = tJsonDecode["deviceToken"]
  175. SetClientidFnc = tJsonDecode["clientId"]
  176. log.debug("aliyun", "一型一密(免预注册)", SetClientidFnc, SetDeviceTokenFnc)
  177. if EvtCb["reg"] then
  178. EvtCb["reg"](tJsonDecode)
  179. else
  180. aliyun.store(tJsonDecode)
  181. end
  182. mqttClient:autoreconn(false)
  183. mqttClient:disconnect()
  184. flag = false
  185. directProc(DeviceName, ProductKey, mqtt_host, mqtt_port, mqtt_isssl, Registration)
  186. end
  187. end
  188. end
  189. end
  190. end)
  191. mqttClient:connect()
  192. while flag do
  193. sys.wait(1000)
  194. end
  195. end)
  196. end
  197. ---------------------------------------------
  198. -- 一机一密
  199. ---------------------------------------------
  200. local function confiDentialTask()
  201. sys.taskInit(function()
  202. local client_id,user_name,password = iotauth.aliyun(opts.ProductKey, opts.DeviceName, opts.DeviceSecret)
  203. mqtt_task(opts.mqtt_host, opts.mqtt_port, opts.mqtt_isssl, client_id, user_name, password)
  204. end)
  205. end
  206. --正常连接 预注册一型一密获取DeviceSecret后就是正常的一机一密连接
  207. function aliyun.clientGetDirectDataTask(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl,Registration,DeviceSecret,deviceToken,cid)
  208. sys.taskInit(function()
  209. if not Registration then
  210. local client_id,user_name,password = iotauth.aliyun(ProductKey,DeviceName,DeviceSecret)
  211. -- mqttc = mqtt.create(nil,mqtt_host, mqtt_port,mqtt_isssl) --mqtt客户端创建
  212. -- mqttc:auth(client_id,user_name,password) --mqtt三元组配置
  213. mqtt_task(mqtt_host, mqtt_port,mqtt_isssl, client_id, user_name, password)
  214. else
  215. local clientId = cid.."|securemode=-2,authType=connwl|"
  216. local client_id,user_name,password = iotauth.aliyun(ProductKey,DeviceName,deviceToken)
  217. -- mqttc = mqtt.create(nil,mqtt_host, mqtt_port,mqtt_isssl) --mqtt客户端创建
  218. -- mqttc:auth(clientId, user_name, deviceToken) --mqtt三元组配置
  219. mqtt_task(mqtt_host, mqtt_port,mqtt_isssl, clientId, user_name, deviceToken)
  220. end
  221. end)
  222. end
  223. --[[
  224. 订阅主题
  225. @api aliyun.subscribe(topic,qos)
  226. @string 主题内容为UTF8编码
  227. @number qos为number类型(0/1,默认1)
  228. @return nil
  229. @usage
  230. aliyun.subscribe("/b0FMK1Ga5cp/862991234567890/get", 1)
  231. ]]
  232. function aliyun.subscribe(topic,qos)
  233. if mqttc and mqttc:ready() then
  234. mqttc:subscribe(topic,qos or 1)
  235. end
  236. end
  237. --[[
  238. 发布一条消息
  239. @api aliyun.publish(topic,qos,payload,cbFnc,cbPara)
  240. @string UTF8编码的主题
  241. @number qos质量等级,0/1,默认0
  242. @string payload 负载内容,UTF8编码
  243. @function cbFnc 消息发布结果的回调函数,回调函数的调用形式为:cbFnc(result,cbPara)。result为true表示发布成功,false或者nil表示订阅失败;cbPara为本接口中的第5个参数
  244. @param cbPara 消息发布结果回调函数的回调参数
  245. @return nil
  246. @usage
  247. aliyun.publish("/b0FMK1Ga5cp/862991234567890/update",0,"test")
  248. aliyun.publish("/b0FMK1Ga5cp/862991234567890/update",1,"test",cbFnc,"cbFncPara")
  249. ]]
  250. function aliyun.publish(topic,qos,payload,cbFnc,cbPara)
  251. if mqttc and mqttc:ready() then
  252. local pkgid = mqttc:publish(topic, payload, qos)
  253. if cbFnc then
  254. if pkgid then
  255. sys.taskInit(function()
  256. local timeout = 15000
  257. while timeout > 0 do
  258. local result, evt, tmp = sys.waitUntil("aliyun_evt", 1000)
  259. -- log.debug("aliyun", "等待publish的sent事件", result, evt, tmp)
  260. if evt == "sent" and pkgid == tmp then
  261. cbFnc(true, cbPara)
  262. return
  263. end
  264. timeout = timeout - 1000
  265. end
  266. cbFnc(false, cbPara)
  267. end)
  268. else
  269. cbFnc(true, cbPara)
  270. end
  271. end
  272. else
  273. cbFnc(false, cbPara)
  274. end
  275. end
  276. --[[
  277. 注册事件的处理函数
  278. @api aliyun.on(evt,cbFnc)
  279. @string evt事件,
  280. "connect"表示接入服务器连接结果事件,
  281. "receive"表示接收到接入服务器的消息事件,
  282. "publish"表示发送消息的结果事件
  283. @function cbFnc 事件的处理函数
  284. 当evt为"connect"时,cbFnc的调用形式为:cbFnc(result),result为true表示连接成功,false或者nil表示连接失败,
  285. 当evt为"receive"时,cbFnc的调用形式为:cbFnc(topic,payload),topic为UTF8编码的主题(string类型),payload为原始编码的负载(string类型),
  286. 当evt为"publish"时,cbFnc的调用形式为:cbFnc(result),result为true表示发送成功,false或者nil表示发送失败
  287. @return nil
  288. @usage
  289. aliyun.on("connect",cbFnc)
  290. ]]
  291. function aliyun.on(evt,cbFnc)
  292. EvtCb[evt] = cbFnc
  293. end
  294. --[[
  295. @api aliyun.getDeviceSecret()
  296. @return string 预注册一型一密阿里云返回的DeviceSecret
  297. 可以在应用层使用kv区来保存该参数并使用判断来避免重启后无法连接
  298. ]]
  299. function aliyun.getDeviceSecret()
  300. return SetDeviceSecretFnc
  301. end
  302. --[[
  303. @api aliyun.getDeviceToken()
  304. @return string 免预注册一型一密阿里云返回的DeviceToken
  305. 可以在应用层使用kv区来保存该参数并使用判断来避免重启后无法连接
  306. ]]
  307. function aliyun.getDeviceToken()
  308. return SetDeviceTokenFnc
  309. end
  310. --[[
  311. @api aliyun.getClientid()
  312. @return string 免预注册一型一密阿里云返回的Clientid
  313. 可以在应用层使用kv区来保存该参数并使用判断来避免重启后无法连接
  314. ]]
  315. function aliyun.getClientid()
  316. return SetClientidFnc
  317. end
  318. --[[
  319. 配置阿里云物联网套件的产品信息和设备信息
  320. @api aliyun.setup(tPara)
  321. @table 阿里云物联网套件的产品信息和设备信息
  322. @return nil
  323. @usage
  324. aliyun.setup(tPara)
  325. -- 参数说明
  326. 一机一密认证方案时,ProductSecret参数传入nil
  327. 一型一密认证方案时,ProductSecret参数传入真实的产品密钥
  328. Registration 是否是预注册 已预注册为false,未预注册为true
  329. DeviceName 设备名称
  330. ProductKey 产品key
  331. ProductSecret 产品secret,根据此信息判断是一机一密还是一型一密
  332. DeviceSecret 设备secret
  333. InstanceId 如果没有注册需要填写实例id,在实例详情页面
  334. mqtt_port mqtt端口
  335. mqtt_isssl 是否使用ssl加密连接,true为无证书最简单的加密
  336. ]]
  337. function aliyun.setup(tPara)
  338. opts = tPara
  339. aliyun.opts = opts
  340. if not opts.mqtt_host then
  341. if opts.host then
  342. opts.mqtt_host = opts.host
  343. elseif tPara.InstanceId and #tPara.InstanceId > 0 then
  344. opts.mqtt_host = tPara.InstanceId..".mqtt.iothub.aliyuncs.com"
  345. else
  346. opts.mqtt_host = tPara.ProductKey .. ".iot-as-mqtt."..tPara.RegionId..".aliyuncs.com"
  347. end
  348. end
  349. -- log.debug("aliyun", "mqtt host", opts.mqtt_host)
  350. if not tPara.ProductSecret or #tPara.ProductSecret == 0 then
  351. log.info("aliyun", "一机一密模式")
  352. confiDentialTask()
  353. else
  354. log.info("aliyun", string.format("一型一密(%s)模式 - %s", tPara.Registration and "预注册" or "免预注册", tPara.reginfo and "已获取注册信息" or "开始获取注册信息"))
  355. if tPara.reginfo then
  356. aliyun.clientGetDirectDataTask(tPara.DeviceName,tPara.ProductKey, opts.mqtt_host, 1883, tPara.mqtt_isssl,
  357. tPara.Registration,
  358. tPara.deviceSecret, tPara.deviceToken, tPara.clientId)
  359. else
  360. clientEncryptionTask(tPara.Registration,tPara.DeviceName,tPara.ProductKey,tPara.ProductSecret,tPara.InstanceId, opts.mqtt_host, 1883,tPara.mqtt_isssl)
  361. end
  362. end
  363. end
  364. --[[
  365. 判断阿里云物联网套件是否已经连接
  366. @api aliyun.ready()
  367. @return boolean 阿里云物联网套件是否已经连接
  368. @usage
  369. -- 本函数于2024.6.17新增
  370. if aliyun.ready() then
  371. log.info("aliyun", "已连接")
  372. end
  373. ]]
  374. function aliyun.ready()
  375. if mqttc and mqttc:ready() then
  376. return true
  377. end
  378. end
  379. --[[
  380. 获取或存储注册信息
  381. @api aliyun.store(result)
  382. @table result 注册结果,如果为nil则表示获取注册信息
  383. @return table 注册信息,如果为nil则表示获取失败
  384. @usage
  385. -- 获取注册信息
  386. local store = aliyun.store()
  387. -- 存储注册信息
  388. aliyun.store(result)
  389. ]]
  390. function aliyun.store(result)
  391. if result then
  392. log.debug("aliyun", "注册结果", json.encode(result))
  393. if fskv then
  394. fskv.set("ProductKey", result["productKey"])
  395. fskv.set("DeviceName",result["deviceName"])
  396. if result["deviceSecret"] then
  397. fskv.set("deviceSecret",result["deviceSecret"])
  398. else
  399. fskv.set("deviceToken",result["deviceToken"])
  400. fskv.set("clientId", result["clientId"])
  401. end
  402. else
  403. log.debug("aliyun", "fskv not found, use io/fs")
  404. io.writeFile("/alireg.json", json.encode(result))
  405. end
  406. else
  407. local store = {}
  408. if fskv then
  409. store.deviceName = fskv.get("DeviceName") or fskv.get("deviceName")
  410. store.productKey = fskv.get("ProductKey") or fskv.get("productKey")
  411. store.deviceSecret = fskv.get("deviceSecret")
  412. store.deviceToken = fskv.get("deviceToken")
  413. store.clientid = fskv.get("clientid")
  414. else
  415. local tmp = io.readFile("/alireg.json")
  416. if tmp then
  417. store = json.decode(tmp)
  418. if not store then
  419. store = {}
  420. end
  421. end
  422. end
  423. return store
  424. end
  425. end
  426. return aliyun