这里我们使用了一个库: PubSubClient
OneNet服务器地址:
开发者文档: https://open.iot.10086.cn/doc/mqtt/
token生成工具: https://open.iot.10086.cn/doc/mqtt/book/manual/auth/tool.html
一. OneNet上创建MQTTs协议产品
1. 添加
创建产品
创建新设备
2. 关键信息
(1). 产品ID, 用户ID,和登录key
(2). 设备名称, ID和 设备key
三. 计算token
OneNet MQTTS用携带token的方式进行鉴权
1. 常见的三种安全方案:
(1). 把信息和key固化在程序中,访问时计算token
访问者(可以为应用或者设备)固化访问密钥于软件中,在需要进行服务访问时,通过密钥计算临时token,通过临时token进行服务访问认证
(2). 访问者访问管理者获取临时token
访问者首先通过访问管理者获取临时访问token,访问管理者可根据需要自定义该token的访问有效期(即过期时间),访问者获取该token后方才能访问OneNET
(3). 固化token
访问管理者直接将密钥授权给访问者(例如,直接为设备烧写key),访问者通过密钥生成token进行访问
2. token计算方法
计算方法: https://open.iot.10086.cn/doc/mqtt/book/manual/auth/token.html
token生成工具: https://open.iot.10086.cn/doc/mqtt/book/manual/auth/tool.html
根据软件界面, 我们应提供 res et key method
(1). res
格式为: products/产品ID/devices/设备名
以我们刚刚创建的产品和设备为例:
products/370098/devices/esp_device001
(2). et
时间戳 (也叫:格林威治时间戳,或者UNIX时间戳)
计算地址:
https://tool.lu/timestamp/
我直接计算到了 2099年
4092512761
(3). key
设备的key (不是产品的access key)
vCrX2mFsOGNoOIyJrNJLJSpydnpNPeeijLiDQa3FKo8=
3. 软件生成token
version=2018-10-31&res=products%2F370098%2Fdevices%2Fesp_device001&et=4092512761&method=md5&sign=MUV%2BKFLzv81a4Bw6BDrChQ%3D%3D
四. 设置主题
相关文档: https://open.iot.10086.cn/doc/mqtt/book/device-develop/topics/introduce.html
MQTTS物联网套件中设备相关服务(存储、命令等)的面向设备的接口,均以 topic 的形式提供,设备可以通过 publish 消息到系统 topic 调用服务接口,也可以订阅系统 topic 用于接收服务消息通知,服务提供的系统 topic 的集合形成了 topic 簇
MQTTS物联网套件目前包含:数据点topic簇、命令topic簇、子设备topic簇、设备影子topic簇,如下图所示:
数据点topic簇
设备可以通过数据点 topic 簇上传数据存储并即时获取数据存储结果
簇中topic 以 $sys/{pid}/{device-name}/dp 开头
通过publish上传数据时,payload需要满足平台约定数据格式
支持一次上报多条数据,支持设备自带时间戳上报
即时通知数据处理结果(需订阅)
1. 数据点 topic 簇
MQTT物联网套件支持用户以数据流-数据点模型(模型详情)将数据上传至平台并进行存储,设备可以通过数据点 topic 簇调用数据点存储服务存储数据,可以通过订阅系统 topic 获取数据处理结果通知,如下图所示:
对于本文中的例子, 应如下订阅:
$sys/370098/esp_device001/dp/post/json //上传数据应订阅此主题 (发布消息)
$sys/370098/esp_device001/dp/post/json/accepted //系统通知订阅该主题者,数据上传成功(订阅消息)
$sys/370098/esp_device001/dp/post/json/rejected //系统通知订阅该主题者, 数据上传失败(订阅消息)
上传的JSON数据必须采用以下规则
{
"id": 123,
"dp": {
"temperatrue": [{
"v": 30,
"t": 1552289676
}],
"power": [{
"v": 4.5,
"t": 1552289676
}],
"status": [{
"v": {
"color": "blue"
},
"t": 1552289677
},
{
"v": {
"color": "red"
},
"t": 1552289678
}
]
}
}
2. 设备命令 topic 簇
MQTT物联网套件支持应用通过API直接向设备发送单播命令,设备可以通过设备命令 topic 簇获取消息并进行消息应答
设备命令交互流程见下图:
topic中{cmdid}为变量,为每条命令的唯一id,可通过通配符的方式进行订阅,比如:$sys/{pid}/{device-name}/cmd/request/+
,或者$sys/{pid}/{device-name}/cmd/#
对于本位中 的例子,应如下订阅:
$sys/370098/esp_device001/cmd/request/# //订阅此主题可以接收系统下发的命令 (订阅消息)
$sys/370098/esp_device001/cmd/response/# //订阅此主题可以应答命令,向系统回复自己收到了命令(发布消息)
$sys/370098/esp_device001/cmd/request/{cmdid}/accepted //订阅此主题系统会告诉你"你的应答成功" (订阅消息)
$sys/370098/esp_device001/cmd/response/{cmdid}/rejected //订阅此主题系统会告诉你"你的应答失败"(订阅消息)
五. 连接示例
本连接实现了设备模拟上传温湿度数据, 设备接收平台下发的命令
(但没有给平台回复收到命令的消息)
#include
#include "WiFi.h"
#include "PubSubClient.h"
#include "Ticker.h"
const char *ssid = "anleng"; //wifi名
const char *password = "al77776666"; //wifi密码
const char *mqtt_server = "183.230.40.96"; //onenet 的 IP地址
const int port = 1883; //端口号
#define mqtt_devid "esp_device001" //设备ID
#define mqtt_pubid "370098" //产品ID
//鉴权信息
#define mqtt_password "version=2018-10-31&res=products%2F370098%2Fdevices%2Fesp_device001&et=4092512761&method=md5&sign=MUV%2BKFLzv81a4Bw6BDrChQ%3D%3D" //鉴权信息
WiFiClient espClient; //创建一个WIFI连接客户端
PubSubClient client(espClient); // 创建一个PubSub客户端, 传入创建的WIFI客户端
char msgJson[75]; //发送信息缓冲区
//信息模板
char dataTemplate[] = "{\"id\":123,\"dp\":{\"temp\":[{\"v\":%.2f}],\"humi\":[{\"v\":%.2f}]}}";
Ticker tim1; //定时器,用来循环上传数据
//连接WIFI相关函数
void setupWifi()
{
delay(10);
Serial.println("连接WIFI");
WiFi.begin(ssid, password);
while (!WiFi.isConnected())
{
Serial.print(".");
delay(500);
}
Serial.println("OK");
Serial.println("Wifi连接成功");
}
//收到主题下发的回调, 注意这个回调要实现三个形参 1:topic 主题, 2: payload: 传递过来的信息 3: length: 长度
void callback(char *topic, byte *payload, unsigned int length)
{
Serial.println("message rev:");
Serial.println(topic);
for (size_t i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
}
Serial.println();
}
//向主题发送模拟的温湿度数据
void sendTempAndHumi()
{
if (client.connected())
{
snprintf(msgJson, 75, dataTemplate, 22.5, 35.6); //将模拟温湿度数据套入dataTemplate模板中, 生成的字符串传给msgJson
Serial.print("public the data:");
Serial.println(msgJson);
client.publish("$sys/370098/esp_device001/dp/post/json", (uint8_t *)msgJson, strlen(msgJson));
//发送数据到主题
}
}
//重连函数, 如果客户端断线,可以通过此函数重连
void clientReconnect()
{
while (!client.connected()) //再重连客户端
{
Serial.println("reconnect MQTT...");
if (client.connect(mqtt_devid, mqtt_pubid, mqtt_password))
{
Serial.println("connected");
client.subscribe("$sys/370098/esp_device001/cmd/request/#"); //订阅命令下发主题
}
else
{
Serial.println("failed");
Serial.println(client.state());
Serial.println("try again in 5 sec");
delay(5000);
}
}
}
void setup()
{
Serial.begin(115200); //初始化串口
delay(3000); //这个延时是为了让我打开串口助手
setupWifi(); //调用函数连接WIFI
client.setServer(mqtt_server, port); //设置客户端连接的服务器,连接Onenet服务器, 使用6002端口
client.connect(mqtt_devid, mqtt_pubid, mqtt_password); //客户端连接到指定的产品的指定设备.同时输入鉴权信息
if (client.connected())
{
Serial.println("OneNet is connected!");//判断以下是不是连好了.
}
client.setCallback(callback); //设置好客户端收到信息是的回调
client.subscribe("$sys/370098/esp_device001/cmd/request/#"); //订阅命令下发主题
tim1.attach(10, sendTempAndHumi); //定时每10秒调用一次发送数据函数sendTempAndHumi
}
void loop()
{
if (!WiFi.isConnected()) //先看WIFI是否还在连接
{
setupWifi();
}
if (!client.connected()) //如果客户端没连接ONENET, 重新连接
{
clientReconnect();
delay(100);
}
client.loop(); //客户端循环检测
}
老样子,写个小点灯.