| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- --[[
- @module aliyun
- @summary AliYun阿里云物联网平台
- @version 1.0
- @date 2023.06.07
- @author wendal
- @demo aliyun
- @usage
- -- 请查阅demo
- ]]
- _G.sys = require("sys")
- --[[特别注意, 使用http库需要下列语句]]
- _G.sysplus = require("sysplus")
- local libfota = require("libfota")
- -- 总的库对象
- local aliyun = {}
- local mqttc = nil
- local ClientId,PassWord,UserName,SetClientidFnc,SetDeviceTokenFnc,SetDeviceSecretFnc
- local EvtCb = {}
- local opts = {}
- -------------------------------------------------------
- ---- FOTA 相关
- -------------------------------------------------------
- -- fota的回调函数
- local function libfota_cb(result)
- log.info("fota", "result", result)
- -- fota成功
- if result == 0 then
- rtos.reboot()
- end
- end
- --收到云端固件升级通知消息时的回调函数
- local function aliyun_upgrade(payload)
- local jsonData, result = json.decode(payload)
- if result and jsonData.data and jsonData.data.url then
- log.info("aliyun", "ota.url", jsonData.data.url)
- libfota.request(EvtCb["ota"] or libfota_cb, jsonData.data.url)
- end
- end
- -------------------------------------------------------
- --- 用户侧的回调处理
- -------------------------------------------------------
- --底层libMQTT回调函数,上层的回调函数,通过 aliyun.on注册
- local function mqtt_cbevent(mqtt_client, event, data, payload,metas)
- log.debug("aliyun", "event", event, "data", data)
- if event == "conack" then
- log.info("aliyun", "conack")
- -- if opts.ProductKey and opts.DeviceName then
- aliyun.subscribe("/ota/device/upgrade/"..opts.ProductKey.."/"..opts.DeviceName,1)
- aliyun.publish("/ota/device/inform/"..opts.ProductKey.."/"..opts.DeviceName,1,"{\"id\":1,\"params\":{\"version\":\"".._G.VERSION.."\"}}")
- -- end
- sys.publish("aliyun_conack")
- if EvtCb["connect"] then
- EvtCb["connect"](true)
- end
- elseif event == "recv" then -- 服务器下发的数据
- log.debug("aliyun", "downlink", "topic", data, "payload", payload)
- --OTA消息
- if data =="/ota/device/upgrade/".. opts.ProductKey.."/".. opts.DeviceName then
- aliyun_upgrade(payload)
- end
- if EvtCb["receive"] then
- EvtCb["receive"](data, payload, metas.qos, metas.retain, metas.dup)
- end
- elseif event == "sent" then
- log.info("aliyun", "sent", data)
- if data then
- sys.publish("aliyun_evt", "sent", data)
- if EvtCb["publish"] then
- EvtCb["publish"](data)
- end
- end
- elseif event == "disconnect" then
- log.info("aliyun", "disconnect")
- if EvtCb["connect"] then
- EvtCb["connect"](false)
- end
- end
- end
- local function mqtt_task(mqtt_host, mqtt_port, mqtt_isssl, client_id, user_name, password)
- mqttc = mqtt.create(nil,mqtt_host, mqtt_port or 1883, mqtt_isssl) --mqtt客户端创建
- log.debug("aliyun", "mqtt三元组", client_id,user_name,password)
- mqttc:auth(client_id,user_name,password) --mqtt三元组配置
- mqttc:keepalive(300) -- 默认值240s
- mqttc:autoreconn(true, 20000) -- 自动重连机制
- mqttc:on(mqtt_cbevent) --mqtt回调注册
- -- mqttc:debug(true)
- mqttc:connect()
- end
- ---------------------------------------------------
- --- 一型一密
- --------------------------------------------------
- -- 二次连接, 也就是真正的连接
- local function clientDataTask(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl,passtoken,Registration)
- sys.taskInit(function()
- if not Registration then -- 预注册
- local client_id,user_name,password = iotauth.aliyun(opts.ProductKey, opts.DeviceName,SetDeviceSecretFnc)
- -- mqttc = mqtt.create(nil,opts.mqtt_host, opts.mqtt_port, opts.mqtt_isssl) --mqtt客户端创建
- -- mqttc:auth(client_id,user_name,password) --mqtt三元组配置
- 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)
- else -- 免预注册
- -- mqttc = mqtt.create(nil,opts.mqtt_host, opts.mqtt_port, opts.mqtt_isssl) --mqtt客户端创建
- -- mqttc:auth(opts.DeviceName, opts.ProductKey, passtoken) --mqtt三元组配置
- 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)
- end
- end)
- end
- --根据返回的数据进行二次加密
- local function directProc(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl,Registration)
- if not Registration then
- local ClientId = DeviceName.."|securemode=3,signmethod=hmacmd5,timestamp=789|"
- local UserName = DeviceName.."&"..ProductKey
- local content = "ClientId"..DeviceName.."deviceName"..DeviceName.."productKey"..ProductKey.."timestamp789"
- local signKey= SetDeviceSecretFnc
- PassWord = crypto.hmac_md5(content,signKey)
- clientDataTask(ClientId,UserName,PassWord,mqtt_host,mqtt_port,mqtt_isssl,DeviceName,ProductKey)
- else
- local ClientId = SetClientidFnc.."|securemode=-2,authType=connwl|"
- local UserName = DeviceName.."&"..ProductKey
- local PassWord = SetDeviceTokenFnc
- clientDataTask(ClientId,UserName,mqtt_host,mqtt_port,mqtt_isssl,PassWord,Registration)
- end
- end
- --获取一型一密的连接参数
- local function clientEncryptionTask(Registration,DeviceName,ProductKey,ProductSecret,InstanceId,mqtt_host,mqtt_port,mqtt_isssl)
- sys.taskInit(function()
- --预注册
- if not Registration then
- ClientId = DeviceName.."|securemode=2,authType=register,random=123,signmethod=hmacmd5|"
- --免预注册
- else
- if InstanceId and #InstanceId > 0 then
- ClientId = DeviceName.."|securemode=-2,authType=regnwl,random=123,signmethod=hmacmd5,instanceId="..InstanceId.."|"
- else
- ClientId = DeviceName.."|securemode=-2,authType=regnwl,random=123,signmethod=hmacmd5|"
- end
- end
- local UserName = DeviceName.."&"..ProductKey
- local content = "deviceName"..DeviceName.."productKey"..ProductKey.."random123"
- local PassWord = crypto.hmac_md5(content, ProductSecret)
- local mqttClient = mqtt.create(nil, mqtt_host, mqtt_port, true) --客户端创建
- if mqttClient == nil then
- log.error("aliyun", "一型一密要求固件支持TLS加密, 当前固件不支持!!!")
- return
- end
- log.debug("aliyun", "一型一密认证三元组", ClientId, UserName, PassWord)
- mqttClient:auth(ClientId,UserName,PassWord) --三元组配置
- mqttClient:autoreconn(true, 30000)
- local flag = true
- mqttClient:on(function(mqtt_client, event, data, payload) --mqtt回调注册
- if event == "recv" then
- -- 无需订阅topic, 阿里云会主动下发通知
- log.info("aliyun", "downlink", "topic", data, "payload", payload)
- if payload then
- local tJsonDecode,res = json.decode(payload)
- if not Registration then
- --预注册
- if res and tJsonDecode["deviceName"] and tJsonDecode["deviceSecret"] then
- SetDeviceSecretFnc = tJsonDecode["deviceSecret"]
- log.debug("aliyun", "一型一密(预注册)", tJsonDecode["deviceName"], SetDeviceSecretFnc)
- if EvtCb["reg"] then
- EvtCb["reg"](tJsonDecode)
- else
- aliyun.store(tJsonDecode)
- end
- mqttClient:autoreconn(false)
- mqttClient:disconnect()
- flag = false
- clientDataTask(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl)
- end
- else
- --免预注册
- if res and tJsonDecode["deviceName"] and tJsonDecode["deviceToken"] then
- SetDeviceTokenFnc = tJsonDecode["deviceToken"]
- SetClientidFnc = tJsonDecode["clientId"]
- log.debug("aliyun", "一型一密(免预注册)", SetClientidFnc, SetDeviceTokenFnc)
- if EvtCb["reg"] then
- EvtCb["reg"](tJsonDecode)
- else
- aliyun.store(tJsonDecode)
- end
- mqttClient:autoreconn(false)
- mqttClient:disconnect()
- flag = false
- directProc(DeviceName, ProductKey, mqtt_host, mqtt_port, mqtt_isssl, Registration)
- end
- end
- end
- end
- end)
- mqttClient:connect()
- while flag do
- sys.wait(1000)
- end
- end)
- end
- ---------------------------------------------
- -- 一机一密
- ---------------------------------------------
- local function confiDentialTask()
- sys.taskInit(function()
- local client_id,user_name,password = iotauth.aliyun(opts.ProductKey, opts.DeviceName, opts.DeviceSecret)
- mqtt_task(opts.mqtt_host, opts.mqtt_port, opts.mqtt_isssl, client_id, user_name, password)
- end)
- end
- --正常连接 预注册一型一密获取DeviceSecret后就是正常的一机一密连接
- function aliyun.clientGetDirectDataTask(DeviceName,ProductKey,mqtt_host,mqtt_port,mqtt_isssl,Registration,DeviceSecret,deviceToken,cid)
- sys.taskInit(function()
- if not Registration then
- local client_id,user_name,password = iotauth.aliyun(ProductKey,DeviceName,DeviceSecret)
- -- mqttc = mqtt.create(nil,mqtt_host, mqtt_port,mqtt_isssl) --mqtt客户端创建
- -- mqttc:auth(client_id,user_name,password) --mqtt三元组配置
- mqtt_task(mqtt_host, mqtt_port,mqtt_isssl, client_id, user_name, password)
- else
- local clientId = cid.."|securemode=-2,authType=connwl|"
- local client_id,user_name,password = iotauth.aliyun(ProductKey,DeviceName,deviceToken)
- -- mqttc = mqtt.create(nil,mqtt_host, mqtt_port,mqtt_isssl) --mqtt客户端创建
- -- mqttc:auth(clientId, user_name, deviceToken) --mqtt三元组配置
- mqtt_task(mqtt_host, mqtt_port,mqtt_isssl, clientId, user_name, deviceToken)
- end
- end)
- end
- --[[
- 订阅主题
- @api aliyun.subscribe(topic,qos)
- @string 主题内容为UTF8编码
- @number qos为number类型(0/1,默认1)
- @return nil
- @usage
- aliyun.subscribe("/b0FMK1Ga5cp/862991234567890/get", 1)
- ]]
- function aliyun.subscribe(topic,qos)
- if mqttc and mqttc:ready() then
- mqttc:subscribe(topic,qos or 1)
- end
- end
- --[[
- 发布一条消息
- @api aliyun.publish(topic,qos,payload,cbFnc,cbPara)
- @string UTF8编码的主题
- @number qos质量等级,0/1,默认0
- @string payload 负载内容,UTF8编码
- @function cbFnc 消息发布结果的回调函数,回调函数的调用形式为:cbFnc(result,cbPara)。result为true表示发布成功,false或者nil表示订阅失败;cbPara为本接口中的第5个参数
- @param cbPara 消息发布结果回调函数的回调参数
- @return nil
- @usage
- aliyun.publish("/b0FMK1Ga5cp/862991234567890/update",0,"test")
- aliyun.publish("/b0FMK1Ga5cp/862991234567890/update",1,"test",cbFnc,"cbFncPara")
- ]]
- function aliyun.publish(topic,qos,payload,cbFnc,cbPara)
- if mqttc and mqttc:ready() then
- local pkgid = mqttc:publish(topic, payload, qos)
- if cbFnc then
- if pkgid then
- sys.taskInit(function()
- local timeout = 15000
- while timeout > 0 do
- local result, evt, tmp = sys.waitUntil("aliyun_evt", 1000)
- -- log.debug("aliyun", "等待publish的sent事件", result, evt, tmp)
- if evt == "sent" and pkgid == tmp then
- cbFnc(true, cbPara)
- return
- end
- timeout = timeout - 1000
- end
- cbFnc(false, cbPara)
- end)
- else
- cbFnc(true, cbPara)
- end
- end
- else
- cbFnc(false, cbPara)
- end
- end
- --[[
- 注册事件的处理函数
- @api aliyun.on(evt,cbFnc)
- @string evt事件,
- "connect"表示接入服务器连接结果事件,
- "receive"表示接收到接入服务器的消息事件,
- "publish"表示发送消息的结果事件
- @function cbFnc 事件的处理函数
- 当evt为"connect"时,cbFnc的调用形式为:cbFnc(result),result为true表示连接成功,false或者nil表示连接失败,
- 当evt为"receive"时,cbFnc的调用形式为:cbFnc(topic,payload),topic为UTF8编码的主题(string类型),payload为原始编码的负载(string类型),
- 当evt为"publish"时,cbFnc的调用形式为:cbFnc(result),result为true表示发送成功,false或者nil表示发送失败
- @return nil
- @usage
- aliyun.on("connect",cbFnc)
- ]]
- function aliyun.on(evt,cbFnc)
- EvtCb[evt] = cbFnc
- end
- --[[
- @api aliyun.getDeviceSecret()
- @return string 预注册一型一密阿里云返回的DeviceSecret
- 可以在应用层使用kv区来保存该参数并使用判断来避免重启后无法连接
- ]]
- function aliyun.getDeviceSecret()
- return SetDeviceSecretFnc
- end
- --[[
- @api aliyun.getDeviceToken()
- @return string 免预注册一型一密阿里云返回的DeviceToken
- 可以在应用层使用kv区来保存该参数并使用判断来避免重启后无法连接
- ]]
- function aliyun.getDeviceToken()
- return SetDeviceTokenFnc
- end
- --[[
- @api aliyun.getClientid()
- @return string 免预注册一型一密阿里云返回的Clientid
- 可以在应用层使用kv区来保存该参数并使用判断来避免重启后无法连接
- ]]
- function aliyun.getClientid()
- return SetClientidFnc
- end
- --[[
- 配置阿里云物联网套件的产品信息和设备信息
- @api aliyun.setup(tPara)
- @table 阿里云物联网套件的产品信息和设备信息
- @return nil
- @usage
- aliyun.setup(tPara)
- -- 参数说明
- 一机一密认证方案时,ProductSecret参数传入nil
- 一型一密认证方案时,ProductSecret参数传入真实的产品密钥
- Registration 是否是预注册 已预注册为false,未预注册为true
- DeviceName 设备名称
- ProductKey 产品key
- ProductSecret 产品secret,根据此信息判断是一机一密还是一型一密
- DeviceSecret 设备secret
- InstanceId 如果没有注册需要填写实例id,在实例详情页面
- mqtt_port mqtt端口
- mqtt_isssl 是否使用ssl加密连接,true为无证书最简单的加密
- ]]
- function aliyun.setup(tPara)
- opts = tPara
- aliyun.opts = opts
- if not opts.mqtt_host then
- if opts.host then
- opts.mqtt_host = opts.host
- elseif tPara.InstanceId and #tPara.InstanceId > 0 then
- opts.mqtt_host = tPara.InstanceId..".mqtt.iothub.aliyuncs.com"
- else
- opts.mqtt_host = tPara.ProductKey .. ".iot-as-mqtt."..tPara.RegionId..".aliyuncs.com"
- end
- end
- -- log.debug("aliyun", "mqtt host", opts.mqtt_host)
- if not tPara.ProductSecret or #tPara.ProductSecret == 0 then
- log.info("aliyun", "一机一密模式")
- confiDentialTask()
- else
- log.info("aliyun", string.format("一型一密(%s)模式 - %s", tPara.Registration and "预注册" or "免预注册", tPara.reginfo and "已获取注册信息" or "开始获取注册信息"))
- if tPara.reginfo then
- aliyun.clientGetDirectDataTask(tPara.DeviceName,tPara.ProductKey, opts.mqtt_host, 1883, tPara.mqtt_isssl,
- tPara.Registration,
- tPara.deviceSecret, tPara.deviceToken, tPara.clientId)
- else
- clientEncryptionTask(tPara.Registration,tPara.DeviceName,tPara.ProductKey,tPara.ProductSecret,tPara.InstanceId, opts.mqtt_host, 1883,tPara.mqtt_isssl)
- end
- end
- end
- --[[
- 判断阿里云物联网套件是否已经连接
- @api aliyun.ready()
- @return boolean 阿里云物联网套件是否已经连接
- @usage
- -- 本函数于2024.6.17新增
- if aliyun.ready() then
- log.info("aliyun", "已连接")
- end
- ]]
- function aliyun.ready()
- if mqttc and mqttc:ready() then
- return true
- end
- end
- --[[
- 获取或存储注册信息
- @api aliyun.store(result)
- @table result 注册结果,如果为nil则表示获取注册信息
- @return table 注册信息,如果为nil则表示获取失败
- @usage
- -- 获取注册信息
- local store = aliyun.store()
- -- 存储注册信息
- aliyun.store(result)
- ]]
- function aliyun.store(result)
- if result then
- log.debug("aliyun", "注册结果", json.encode(result))
- if fskv then
- fskv.set("ProductKey", result["productKey"])
- fskv.set("DeviceName",result["deviceName"])
- if result["deviceSecret"] then
- fskv.set("deviceSecret",result["deviceSecret"])
- else
- fskv.set("deviceToken",result["deviceToken"])
- fskv.set("clientId", result["clientId"])
- end
- else
- log.debug("aliyun", "fskv not found, use io/fs")
- io.writeFile("/alireg.json", json.encode(result))
- end
- else
- local store = {}
- if fskv then
- store.deviceName = fskv.get("DeviceName") or fskv.get("deviceName")
- store.productKey = fskv.get("ProductKey") or fskv.get("productKey")
- store.deviceSecret = fskv.get("deviceSecret")
- store.deviceToken = fskv.get("deviceToken")
- store.clientid = fskv.get("clientid")
- else
- local tmp = io.readFile("/alireg.json")
- if tmp then
- store = json.decode(tmp)
- if not store then
- store = {}
- end
- end
- end
- return store
- end
- end
- return aliyun
|