概述
简介
本教程中主要讲解使用阿里云Iot监测控制NodeMCU的方法。
设备从MQTT数据上传、监测、控制的全流程如下图所示,本教程仅仅讲解从设备与Aliyun平台之间的交互,业务服务器部分(App开发)会在后面的教程中进行讲解,敬请期待。
欢迎关注笔者关于NodeMCU-ESP8266的学习教程,里面整理了很多快速上手的实例案例和一些特别好的学习教程,可以减少很多走弯路的机会!
友情提醒:多看官方文档,官方文档什么都有。
开发环境
- Arduino IDE
- NodeMCU1.0芯片包
- ArduinoJson库
- PubSubClient库
PubSubClient
作者名:Nick O’Leary
官网地址:https://pubsubclient.knolleary.net/
GitHub:https://github.com/knolleary/pubsubclient/
百度网盘下载: https://pan.baidu.com/s/12MHGbdfiOdwOGip5RMSSEQ 提取码: sizy
如果不知道怎么安装芯片包和导入第三方库,自行research
上一节讲述了NoduMCU通过软串口控制Arduino的例程,这一次笔者将NodeMCU接入AliyunIot平台,通过MQTT来远程监测控制NodeMCU,再通过NodeMCU来控制Arduino。
前述知识
MQTT协议
这里不做过多介绍,笔者默认您已经掌握了该知识,如果对MQTT不够了解,可以参考零基础入门学用物联网 – MQTT基础篇
物模型
物模型是产品数字化的描述,定义了产品的功能,物模型将不同品牌不同品类的产品功能抽象归纳,形成“标准物模型”,便于各方用统一的语言描述、控制、理解产品功能。
物模型由若干条“参数”组成,参数按描述的功能类型不同,又分为属性、方法和事件。
更为详细的物模型解析
为什么要有物模型?当下的云平台为了兼容不同的通信协议,在通信层之间构建了一个物模型,这才是最重要的事,为了让不同通信协议的设备都可以整合在一个平台内进行运行,因此需要一个云平台们非常需要一个中间层做整合,物模型由此诞生。本例程中采用MQTT模型,其订阅发布的逻辑也因为加了物模型层而有所不同。
虽然在本例程中的原理还是基于订阅发布的物模型,但是订阅发布的主题和要处理的数据已经与传统MQTT意义上的数据有所不同,下面将做简单的讲解。
基于物模型的MQTT协议
MQTT协议的使用方式在物模型的产生后所迭代,下面我举两个例子就可以看出传统MQTT和基于MQTT的物模型之间的差别了。
传统MQTT
在以上图示中一共有三个MQTT客户端。它们分别是汽车,手机和电脑。MQTT服务端在管理MQTT通讯时使用了“主题”来对信息进行管理的。比如上图所示,假设我们需要利用手机和电脑获取汽车的速度,那么我们首先要利用电脑和手机向MQTT服务器订阅主题“汽车速度”。接下来,当汽车客户端向服务端的“汽车速度”主题发布信息后,服务端就会首先检查以下都有哪些客户端订阅了“汽车速度”这一主题的信息。当它发现订阅了该主题的客户端有一个手机和一个电脑,于是服务端就会将刚刚收到的“汽车速度”信息转发给订阅了该主题的手机和电脑客户端。
我们现在做一个假设,汽车发布了多少数据手机端就像看到多少数据。而现在汽车只发布了一个速度的属性,如果这个时候汽车还想把自己车内温度上传上去,那么就要向一个名为“CarTemperture”的主题发布数据,这样一来,一个循环内,小车就要向两个主题发布两次消息,而手机也因此需要多订阅一个主题。在小车向n个主题发布数据的状态下,如果手机端想要全部接收到,难道就要一次一次把所有的主题都订阅了吗?这样来看就有些臃肿了。
而云平台的定位就和我们刚才所述的手机端一样,云平台想要看到设备发布的所有数据,因此不可能让云平台一个一个主题的去订阅,能不能把所有数据全部发送给云平台让云平台自己去解析呢?由此就产生了物模型。
基于物模型的MQTT
物模型的作用就相当于定义了主题名叫“post”,汽车只要把所有的数据放在一起发送给“post”就可以了。
如果您想要将汽车的速度和温度的属性进行上传,那么您可以先告诉云平台你的汽车要上传“速度”和“温度”两个属性,让云平台先了解你将要上传的数据。然后这时汽车就可以对云平台的“post”主题发送特定格式的字符串,这个字符串是云平台规定好的。比如发送如下字符串:
publish topic=/sys/xxx/yyy/thing/event/property/post, payload={"id":123321,"params":{"carTemperture":12312341,"speed":50},"version":"1.0","method":"thing.event.property.post"}
快速上手
下面开始讲解开发步骤,大致流程如下。
注册Aliyun帐号
进入Aliyun平台注册一个帐号,自己操作
在物联网平台创建实例
首先打开控制台;
打开物联网平台;
自行创建一个实例,这里的实例在本例程中你可以当做它是一个MQTT服务器。
创建产品
创建完实例后,进入实例,新建一个产品
创建一个设备,如下图所示。
添加物模型
在产品中,选择功能定义,编辑功能;
选择自定义功能;
创建一个温度属性,如下;
创建设备
切换页面,点击添加设备。
填写设备信息,如下图,这里的NodeMCU-1是笔者创建的产品;
Tip:产品和设备的管理你可以类比以下例子:Iphone 11 PRO是一个产品,我的11和你的11是两个设备。从这里可以看出,同一个产品的设备具有一样的属性,但是同一个产品不同设备属性的值可能不一样,比如说我的11还有100G内存,你的11只有10G内存的,这里的内存就可以当做一个属性。
创建完设备就可以进行代码的开发了。
Arduino IDE代码开发
下面讲解代码部分,代码部分主要分为几个部分:
- wifi连接
- mqtt初始化
- ntp网络时间获取
- 数据上报
- 数据接收
注意事项:
- 这里的数据上报就是对"post"主题发布信息
- 这里的数据接收功能当前只能接收到云平台发送了什么内容,这个内容是需要解析的,在后面的教程中会讲解如何解析数据。
在定义物模型的时候,笔者定义了两个属性,分别是开门时间和开门指令,这两个数据分别演示数据上报和数据下发。在NodeMCU运行的时候,会上传当前时间;而在另外一边,云平台会下发开门指令给NoduMCU。
例程
/* 目的:该例程为NodeMCU连接阿里云Iot平台的例程
* 作者:Zeeland
* 最后修改时间:2021年12月2日 18:33:56
* https://gitee.com/zeeland/projects
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFiMulti.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp1.aliyun.com",60*60*8, 30*60*1000);
String currentTime;
/* 设备证书信息*/
#define PRODUCT_KEY
"PRODUCT_KEY"
#define DEVICE_NAME
"DEVICE_NAME"
#define DEVICE_SECRET
"DEVICE_SECRET"
#define REGION_ID
"cn-shanghai"
/* Aliyun线上环境域名和端口号,不需要改 */
#define MQTT_SERVER
PRODUCT_KEY ".iot-as-mqtt." REGION_ID ".aliyuncs.com"
#define MQTT_PORT
1883
#define MQTT_USRNAME
DEVICE_NAME "&" PRODUCT_KEY
#define CLIENT_ID
"gmvzwtDHC6.ntance1|securemode=2,signmethod=hmacsha256,timestamp=254604000000|"
// MQTT连接报文参数,请参见MQTT-TCP连接通信文档,文档地址:https://help.aliyun.com/document_detail/73742.html
// 加密明文是参数和对应的值(clientIdesp8266deviceName${deviceName}productKey${productKey}timestamp1234567890)按字典顺序拼接
// 密钥是设备的DeviceSecret
//要使用加密工具,输入以上证书信息加密(时间戳可以省略)
#define MQTT_PASSWD
"a29f727768b161f9073e199ab6e37ee0e3e75f1320d0219a0a204b5bbb1420"
// 发送报文的json格式
#define ALINK_BODY_FORMAT
"{"id":"123","version":"1.0","method":"thing.event.property.post","params":%s}"
// 上报报文主题
#define ALINK_TOPIC_PROP_POST
"/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"
unsigned long lastMs = 0;
WiFiClient espClient;
PubSubClient
client(espClient);
ESP8266WiFiMulti wifiMulti;
unsigned int pwm_r=0,pwm_g=0,pwm_b=0;
int openFlag = 0;
void setup()
{
Serial.begin(9600);
Serial.println("[info] Demo Start");
Serial.print("[info] CLIENT_ID:");Serial.println(CLIENT_ID);
Serial.print("[info] MQTT_USRNAME:");Serial.println(MQTT_USRNAME);
Serial.print("[info] MQTT_PASSWD:");Serial.println(MQTT_PASSWD);
// 连接WIFI
wifiInit();
// 初始化Mqtt服务
mqttServeInit();
// 初始化NTP时间服务
timeClient.begin();
}
void loop()
{
//millis()是系统启动到目前的总时间,以下为5s上传一次数据
if (millis() - lastMs >= 3000)
{
// 获取当前时间
lastMs = millis();
// 检查连接状态
mqttCheckConnect();
timeClient.update();
// 获取当前时间
currentTime = timeClient.getFormattedTime();
Serial.print("[info] now time is :");
Serial.println(currentTime);
// 上报消息
mqttIntervalPost();
// 根据下发的数据进行反馈
work();
}
client.loop();
}
void work() {
if(openFlag ==1){
Serial.println("[info] this is the truly answer!!!");
}
}
// 初始化Mqtt服务
void mqttServeInit() {
// 设置MQTT服务器和端口号
client.setServer(MQTT_SERVER, MQTT_PORT);
// 设置MQTT订阅回调函数
client.setCallback(callback);
}
// 收到信息后的回调函数
void callback(char *topic, byte *payload, unsigned int length)
{
Serial.print("[info]Message arrived,the topic is [");
Serial.print(topic);
Serial.println("] ");
payload[length] = '