腾讯云
一、简介
腾讯云物联网开发平台(IoT Explorer)为各行业的设备制造商、方案商及应用开发商提供一站式设备智能化服务。平台提供海量设备连接与管理能力及小程序应用开发能力,并打通腾讯云基础产品及 AI 能力,提升传统行业设备智能化的效率,降低用户的开发运维成本,助力用户业务发展。
二、演示功能概述
本教程教你如何用 Air724 开发板,如何接入腾讯云,并且会介绍如何进行腾讯云的上下行交互处理。
三、准备硬件环境
3.1 开发板准备
使用 EVB_Air724 开发板,如下图所示:
淘宝购买链接:Air724UG-NFM 开发板淘宝购买链接 ;
此开发板的详细使用说明参考:Air724UG 产品手册 中的《EVB_Air724UG_AXX 开发板使用说明》,写这篇文章时最新版本的使用说明为:《EVB_Air724UG_A14 开发板使用说明》;开发板使用过程中遇到任何问题,可以直接参考这份使用说明文档。
api:https://doc.openluat.com/wiki/21?wiki_page_id=2068
3.2 数据通信线
USB 数据线一根(micro USB)。
3.3 PC 电脑
WIN7 以及以上版本的 WINDOWS 系统。
3.4 SIM 卡
中国大陆环境下,可以上网的 SIM 卡。一般来说,使用移动,电信,联通的物联网卡或者手机卡都行。
3.5 组装硬件环境
USB 数据线插入 USB 口,另一端与电脑相连,拨码开关全部拨到 ON,串口切换开关选择 UART1,USB 供电的 4V 对应开关拨至 ON 档,SIM 卡放到 SIM 卡槽中锁紧,如下图所示。
四、准备软件环境
4.1 下载调试工具
使用说明参考:Luatools 下载和详细使用
4.2 源码及固件
1.底层 core 下载
下载底层固件,并解压
链接:https://docs.openluat.com/air724ug/luatos/firmware/
如下图所示,红框的是我们要使用到的。
2.本教程使用的 demo:
https://gitee.com/openLuat/LuatOS-Air724UG/tree/master/script_LuaTask/demo/txiot
4.3 下载固件和脚本到开发板中
打开 Luatools,开发板上电开机,如开机成功 Luatools 会打印如下信息。
点击项目管理测试选项。
进入管理界面,如下图所示。
- 点击选择文件,选择底层固件,我的文件放在 D:\luatOS\Air724 路径中
- 点击增加脚本或资源文件,选择程序源码,如下图所示。
- 点击下载底层和脚本,下载完成如下图所示。
五、代码示例介绍
5.1 API 说明
模块功能:MQTT 客户端
5.1.1 mqtt.client(clientId, keepAlive, username, password, cleanSession, will, version)
创建一个 mqtt client 实例
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
clientId | string | 确保设备唯一性 |
keepAlive | number | 可选参数,默认为 300 心跳间隔(单位为秒),默认 300 秒 |
username | string | 可选参数,默认为"" 用户名,用户名为空配置为""或者 nil |
password | string | 可选参数,默认为"" 密码,密码为空配置为""或者 nil |
cleanSession | number | 可选参数,默认为 1 1/0 |
will | table | 可选参数,默认为 nil 遗嘱参数,格式为{qos=,retain=,topic=,payload=} |
version | string | 可选参数,默认为"3.1.1" MQTT 版本号,仅支持"3.1"和"3.1.1" |
- 返回值
table mqttc client 实例
- 例子
mqttc = mqtt.client("clientid-123")
mqttc = mqtt.client("clientid-123",200)
mqttc = mqtt.client("clientid-123",nil,"user","password")
mqttc = mqtt.client("clientid-123",nil,"user","password",nil,{qos=0,retain=0,topic="willTopic",payload="willTopic"},"3.1")
5.1.2 mqttc:connect(host, port, transport, cert, timeout)
连接 mqtt 服务器
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
host | string | 服务器地址 |
port | param | string 或者 number 类型,服务器端口 |
transport | string | 可选参数,默认为"tcp" “tcp"或者"tcp_ssl” |
cert | table | 可选参数,默认为 nil table 或者 nil 类型,ssl 证书,当 transport 为"tcp_ssl"时,此参数才有意义。cert 格式如下:{caCert = “ca.crt”, --CA 证书文件(Base64 编码 X.509 格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验 clientCert = “client.crt”, --客户端证书文件(Base64 编码 X.509 格式),服务器对客户端的证书进行校验时会用到此参数 clientKey = “client.key”, --客户端私钥文件(Base64 编码 X.509 格式)clientPassword = “123456”, --客户端证书文件密码[可选]} |
timeout | number | 可选参数,默认为 120 可选参数,socket 连接超时时间,单位秒 |
- 返回值
result true 表示成功,false 或者 nil 表示失败
- 例子
mqttc = mqtt.client("clientid-123", nil, nil, false); mqttc:connect("mqttserver.com", 1883, "tcp", 5)
5.1.3 mqttc:subscribe(topic, qos)
订阅主题
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
topic | param | string 或者 table 类型,一个主题时为 string 类型,多个主题时为 table 类型,主题内容为 UTF8 编码 |
qos | param | 可选参数,默认为 0 number 或者 nil,topic 为一个主题时,qos 为 number 类型(0/1/2,默认 0);topic 为多个主题时,qos 为 nil |
- 返回值
bool true 表示成功,false 或者 nil 表示失败
- 例子
mqttc:subscribe("/abc", 0) _-- subscribe topic "/abc" with qos = 0_
mqttc:subscribe({["/topic1"] = 0, ["/topic2"] = 1, ["/topic3"] = 2}) _-- subscribe multi topic_
5.1.4 mqttc:unsubscribe(topic)
取消订阅主题
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
topic | param | string 或者 table 类型,一个主题时为 string 类型,多个主题时为 table 类型,主题内容为 UTF8 编码 |
- 返回值
bool true 表示成功,false 或者 nil 表示失败
- 例子
mqttc:unsubscribe("/abc") _-- unsubscribe topic "/abc"_
mqttc:unsubscribe({"/topic1", "/topic2", "/topic3"}) _-- unsubscribe multi topic_
5.1.5 mqttc:publish(topic, payload, qos, retain)
发布一条消息
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
topic | string | UTF8 编码的字符串 |
payload | string | 用户自己控制 payload 的编码,mqtt.lua 不会对 payload 做任何编码转换 |
qos 0/1/2, default | number | 可选参数,默认为 00 |
retain | number | 可选参数,默认为 0 0 或者 1 |
- 返回值
bool 发布成功返回 true,失败返回 false
- 例子
mqttc = mqtt.client("clientid-123", nil, nil, false)
mqttc:connect("mqttserver.com", 1883, "tcp")
mqttc:publish("/topic", "publish from luat mqtt client", 0)
5.1.6 mqttc:receive(timeout, msg)
接收消息
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
timeout | number | 接收超时时间,单位毫秒 |
msg | string | 可选参数,默认为 nil 可选参数,控制 socket 所在的线程退出 recv 阻塞状态 |
- 返回值
result 数据接收结果,true 表示成功,false 表示失败
data
如果 result 为 true,表示服务器发过来的 mqtt 包
如果 result 为 false,超时失败,data 为"timeout"
如果 result 为 false,msg 控制退出,data 为 msg 的字符串
如果 result 为 false,socket 连接被动断开控制退出,data 为"CLOSED"
如果 result 为 false,PDP 断开连接控制退出,data 为"IP_ERROR_IND"
如果 result 为 false,mqtt 不处于连接状态,data 为 nil
如果 result 为 false,收到了 PUBLISH 报文,发送 PUBACK 或者 PUBREC 报文失败,data 为 nil
如果 result 为 false,收到了 PUBREC 报文,发送 PUBREL 报文失败,data 为 nil
如果 result 为 false,收到了 PUBREL 报文,发送 PUBCOMP 报文失败,data 为 nil
如果 result 为 false,发送 PINGREQ 报文失败,data 为 nil
param 如果是 msg 控制退出,param 的值是 msg 的参数;其余情况无意义,为 nil
- 例子
true, packet = mqttc:receive(2000)
false, error_message = mqttc:receive(2000)
false, msg, para = mqttc:receive(2000,"APP_SEND_DATA")
5.1.7 mqttc:disconnect()
断开与服务器的连接
- 参数
无
- 返回值
nil
- 例子
mqttc = mqtt.client("clientid-123", nil, nil, false)
mqttc:connect("mqttserver.com", 1883, "tcp")
process data
mqttc:disconnect()
5.2 txiot.lua 代码
本文件主要功能是,实现了一个完整的设备注册、MQTT 连接、消息处理的流程,适合用于基于腾讯云 IoT 的设备开发。可以根据具体需求对消息处理逻辑进行扩展和修改。
将 ProductId 改为自己的产品 ID。
将 ProductSecret 改为自己的产品动态注册密钥。
--- 模块功能:腾讯云平台
-- @module txiot
-- @author Dozingfiretruck
-- @license MIT
-- @copyright OpenLuat.com
-- @release 2020.12.31
module(...,package.seeall)
require "ntp"
require "misc"
require "mqtt"
require "utils"
require "patch"
require "socket"
require "http"
require "common"
-- 产品ID和产品动态注册密钥
local ProductId = "5FCW79CXYD"
local ProductSecret = "XOBuiSs4EUCjmP5NcFWrwdOe"
local tx_mqttClient
--[[
函数名:getDeviceName
功能 :获取设备名称
参数 :无
返回值:设备名称
]]
local function getDeviceName()
--默认使用设备的IMEI作为设备名称,用户可以根据项目需求自行修改
return misc.getImei()
end
function txiot_publish()
--sys.publish("APP_SOCKET_SEND_DATA")
--mqtt发布主题
tx_mqttClient:publish("$thing/up/property/"..ProductId.."/"..getDeviceName(), "publish from luat mqtt client", 0)
end
-- 无网络重启时间,飞行模式启动时间
local rstTim, flyTim = 600000, 300000
local enrol_end = false
local mqtt_ready = false
--- MQTT连接是否处于激活状态
-- @return 激活状态返回true,非激活状态返回false
-- @usage mqttTask.isReady()
function isReady()
return mqtt_ready
end
--- MQTT客户端数据接收处理
-- @param mqttClient,MQTT客户端对象
-- @return 处理成功返回true,处理出错返回false
-- @usage mqttInMsg.proc(mqttClient)
local function proc(mqttClient)
local result,data
while true do
result,data = mqttClient:receive(120000,"APP_SOCKET_SEND_DATA")
--接收到数据
if result then
log.info("mqttInMsg.proc",data.topic,string.toHex(data.payload))
--TODO:根据需求自行处理data.payload
else
break
end
end
return result or data=="timeout" or data=="APP_SOCKET_SEND_DATA"
end
local function cbFnc(result,prompt,head,body)
log.info("testHttp.cbFnc",result,prompt,head,body)
local dat, result, errinfo = json.decode(body)
if result then
if dat.code==0 then
io.writeFile("/txiot.dat", body)
log.info("腾讯云注册设备成功:", body)
else
log.info("腾讯云设备注册失败:", body)
end
enrol_end = true
end
end
local function device_enrol()
local deviceName = getDeviceName()
local nonce = math.random(1,100)
local timestamp = os.time()
local data = "deviceName="..deviceName.."&nonce="..nonce.."&productId="..ProductId.."×tamp="..timestamp
local hmac_sha1_data = crypto.hmac_sha1(data,#data,ProductSecret,#ProductSecret):lower()
local signature = crypto.base64_encode(hmac_sha1_data,#hmac_sha1_data)
local tx_body = {
deviceName=deviceName,
nonce=nonce,
productId=ProductId,
timestamp=timestamp,
signature=signature,
}
local tx_body_json = json.encode(tx_body)
http.request("POST","https://ap-guangzhou.gateway.tencentdevices.com/register/dev",nil,{["Content-Type"]="application/json; charset=UTF-8"},tx_body_json,30000,cbFnc)
end
local function tencent_iot()
if not io.exists("/txiot.dat") then device_enrol() while not enrol_end do sys.wait(100) end end
if not io.exists("/txiot.dat") then device_enrol() log.warn("设备注册失败或设备已注册") return end
local dat = json.decode(io.readFile("/txiot.dat"))
local clientid = ProductId .. getDeviceName() --生成 MQTT 的 clientid 部分, 格式为 ${productid}${devicename}
local connid = math.random(10000,99999)
local expiry = tostring(os.time() + 3600)
local username = string.format("%s;12010126;%s;%s", clientid, connid, expiry) --生成 MQTT 的 username 部分, 格式为 ${clientid};${sdkappid};${connid};${expiry}
local payload = json.decode(crypto.aes_decrypt("CBC","ZERO",crypto.base64_decode(dat.payload, #dat.payload),string.sub(ProductSecret,1,16),"0000000000000000"))
local password
if payload.encryptionType==2 then
local raw_key = crypto.base64_decode(payload.psk, #payload.psk) --生成 MQTT 的 设备密钥 部分
password = crypto.hmac_sha256(username, raw_key):lower() .. ";hmacsha256" --根据物联网通信平台规则生成 password 字段
elseif payload.encryptionType==1 then
io.writeFile("/client.crt", payload.clientCert)
io.writeFile("/client.key", payload.clientKey)
end
while true do
if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then sys.restart("网络初始化失败!") end
--创建一个MQTT客户端
tx_mqttClient = mqtt.client(clientid,300,username,password)
--阻塞执行MQTT CONNECT动作,直至成功
if payload.encryptionType==2 then
while not tx_mqttClient:connect(ProductId..".iotcloud.tencentdevices.com",1883,"tcp",nil,2000) do sys.wait(2000) end
elseif payload.encryptionType==1 then
while not tx_mqttClient:connect(ProductId..".iotcloud.tencentdevices.com",8883,"tcp_ssl",{clientCert="/client.crt",clientKey="/client.key"},2000) do sys.wait(2000) end
end
log.info("mqtt连接成功")
tx_mqttClient:publish("$thing/up/property/"..ProductId.."/"..getDeviceName(), "publish from luat mqtt client", 0)
--mqtt订阅主题
local txiot_subscribetopic = {
["$thing/down/property/"..ProductId.."/"..getDeviceName()]=0
}
--订阅主题
if tx_mqttClient:subscribe(txiot_subscribetopic, nil) then
log.info("mqtt订阅成功")
--循环处理接收和发送的数据
while true do
mqtt_ready = true
if not proc(tx_mqttClient) then log.error("mqttTask.mqttInMsg.proc error") break end
end
else
log.info("mqtt订阅失败")
end
mqtt_ready = false
--断开MQTT连接
tx_mqttClient:disconnect()
end
end
local function iot()
ntp.timeSync()
if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then sys.restart("网络初始化失败!") end
while not ntp.isEnd() do sys.wait(1000) end
tencent_iot()
end
net.switchFly(false)
-- NTP同步失败强制重启
local tid = sys.timerStart(function()
net.switchFly(true)
sys.timerStart(net.switchFly, 5000, false)
end, flyTim)
sys.subscribe("IP_READY_IND", function()
sys.timerStop(tid)
log.info("---------------------- 网络注册已成功 ----------------------")
end)
sys.taskInit(iot)
5.3 main.lua 代码
本代码为主程序脚本,系统启动后首先会对 4G 网络进行配置,等待网络连接成功,然后加载测试模块。
六、腾讯云操作
6.1 产品操作
打开腾讯云,进入控制台选择物联网开发平台进入。(记住是物联网开发平台不是物联网通信)物联网开放平台
切换到老版本(腾讯新出了个 v2 版本开发平台,V2 较 V1 版本去掉了证书校验,去掉了自动注册中自动创建设备功能,自动注册的自动创建设备改没了,自动注册要手动一个一个创建设备)
点击新建项目
6.2 设备操作
进入项目点击新建产品,认证方式可选密钥认证或证书认证
进入产品记住产品 ID
打开动态注册并记住 ProductSecret
创建完成后,自动跳转新建设备界面,点击设备调试,再点击新建设备,设备名称我们可以直接填写模组的 imei 号
这样我们就创建好了设备,我们点进去看下设备信息!
可以看到我们需要的数据已经被贴心的加了一键复制,我们要记录下 设备名称、产品 ID、设备密钥 这三个数据,使用这三个数据我们就可以上云了!
6.3 设备注册
腾讯云 v2 开发平台支持两种注册方式,分别为密钥校验(手动注册)和动态注册,本文使用的动态注册方式。
6.3.1 动态注册
开启动态注册后设备无需一一烧录设备证书/密钥,同一产品下的所有设备可烧录相同的产品密钥(ProductId 和 ProductSecret ),云端授权通过后下发设备证书/密钥,您可以根据业务需要选择是否开启。本例采用动态注册的方式进行注册并连接云平台。
注意:动态注册需要在产品开发界面打开动态注册按钮, 随后会生成动态注册上方的产品密钥,我们复制产品 ID 和产品密钥即可。
将上面记录的产品 ID 填入 ProductId 中,产品密钥填入 ProductSecret 中。
-- 产品ID和产品动态注册密钥
local ProductId = "8ZAWHGG8AU"
local ProductSecret = "B7l0lX3GeotzY2HnlYQ83Zli"
6.3.2 密钥校验(手动注册)
手动注册不需要在产品开发界面打开动态注册按钮,但是设备要先在平台上注册。将上面记录的产品 ID 填入 ProductId 中,产品密钥填入 ProductSecret 中。
-- 产品ID和产品手动注册密钥
local ProductId = "8ZAWHGG8AU"
local ProductSecret = "B7l0lX3GeotzY2HnlYQ83Zli"
七、开机调试
7.1 开发板开机
连接好硬件并下载固件后,启动 Luatools 软件,系统运行信息将显示在界面中。红框中为开发板连接到 PC 机后正常打印的信息,如下图所示。
7.2 功能调试
- 使用设备的 IMEI 作为设备名称。
- 通过 HTTP POST 请求向腾讯云注册设备,并生成所需的签名。
- 创建 MQTT 客户端并连接到腾讯云物联网平台。
订阅 MQTT 主题,处理接收到的消息。
服务器下发属性测试
服务器发送属性界面:
7.3 腾讯云上查看设备状态
八、常见问题
8.1 腾讯云接入失败,怎么排查?
答:先检查设备配置,确保设备的 ID,名称,密钥等配置信息正确无误;同时也要检查下网络连接,确保 SIM 卡是正常入网状态,若还是不行,请再仔细看下教程,看是否有哪个步骤有疏漏。
8.2 MQTT 连接腾讯云 , 不断的重连
确认一下是不是时间戳不对导致的,腾讯云注册是需要时间戳的。查看时间戳代码如下。
local timestamp = os.time()
log.info("时间戳:", timestamp )
给读者的话
本篇文章由
杨超
开发;本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题;
请登录合宙技术交流论坛,点击文档找错赢奖金-Air724UG-LuatOS-软件指南-公有云对接-腾讯云;
用截图标注+文字描述的方式跟帖回复,记录清楚您发现的问题;
我们会迅速核实并且修改文档;
同时也会为您累计找错积分,您还可能赢取月度找错奖金!