NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

简介

本教程中主要讲解使用阿里云Iot监测控制NodeMCU的方法。

设备从MQTT数据上传、监测、控制的全流程如下图所示,本教程仅仅讲解从设备与Aliyun平台之间的交互,业务服务器部分(App开发)会在后面的教程中进行讲解,敬请期待。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

友情提醒:多看官方文档,官方文档什么都有。

开发环境

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基础篇

物模型

物模型是产品数字化的描述,定义了产品的功能,物模型将不同品牌不同品类的产品功能抽象归纳,形成“标准物模型”,便于各方用统一的语言描述、控制、理解产品功能。

物模型由若干条“参数”组成,参数按描述的功能类型不同,又分为属性、方法和事件。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

更为详细的物模型解析

为什么要有物模型?当下的云平台为了兼容不同的通信协议,在通信层之间构建了一个物模型,这才是最重要的事,为了让不同通信协议的设备都可以整合在一个平台内进行运行,因此需要一个云平台们非常需要一个中间层做整合,物模型由此诞生。本例程中采用MQTT模型,其订阅发布的逻辑也因为加了物模型层而有所不同。

虽然在本例程中的原理还是基于订阅发布的物模型,但是订阅发布的主题和要处理的数据已经与传统MQTT意义上的数据有所不同,下面将做简单的讲解。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

基于物模型的MQTT协议

MQTT协议的使用方式在物模型的产生后所迭代,下面我举两个例子就可以看出传统MQTT和基于MQTT的物模型之间的差别了。

传统MQTT

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

在以上图示中一共有三个MQTT客户端。它们分别是汽车,手机和电脑。MQTT服务端在管理MQTT通讯时使用了“主题”来对信息进行管理的。比如上图所示,假设我们需要利用手机和电脑获取汽车的速度,那么我们首先要利用电脑和手机向MQTT服务器订阅主题“汽车速度”。接下来,当汽车客户端向服务端的“汽车速度”主题发布信息后,服务端就会首先检查以下都有哪些客户端订阅了“汽车速度”这一主题的信息。当它发现订阅了该主题的客户端有一个手机和一个电脑,于是服务端就会将刚刚收到的“汽车速度”信息转发给订阅了该主题的手机和电脑客户端。

我们现在做一个假设,汽车发布了多少数据手机端就像看到多少数据。而现在汽车只发布了一个速度的属性,如果这个时候汽车还想把自己车内温度上传上去,那么就要向一个名为“CarTemperture”的主题发布数据,这样一来,一个循环内,小车就要向两个主题发布两次消息,而手机也因此需要多订阅一个主题。在小车向n个主题发布数据的状态下,如果手机端想要全部接收到,难道就要一次一次把所有的主题都订阅了吗?这样来看就有些臃肿了。

而云平台的定位就和我们刚才所述的手机端一样,云平台想要看到设备发布的所有数据,因此不可能让云平台一个一个主题的去订阅,能不能把所有数据全部发送给云平台让云平台自己去解析呢?由此就产生了物模型。

基于物模型的MQTT

物模型的作用就相当于定义了主题名叫“post”,汽车只要把所有的数据放在一起发送给“post”就可以了。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

如果您想要将汽车的速度和温度的属性进行上传,那么您可以先告诉云平台你的汽车要上传“速度”和“温度”两个属性,让云平台先了解你将要上传的数据。然后这时汽车就可以对云平台的“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"}

快速上手

下面开始讲解开发步骤,大致流程如下。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

注册Aliyun帐号

进入Aliyun平台注册一个帐号,自己操作

在物联网平台创建实例

首先打开控制台;

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

打开物联网平台;

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

自行创建一个实例,这里的实例在本例程中你可以当做它是一个MQTT服务器。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

创建产品

创建完实例后,进入实例,新建一个产品

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

创建一个设备,如下图所示。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

添加物模型

在产品中,选择功能定义,编辑功能;

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

选择自定义功能;

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

创建一个温度属性,如下;

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

创建设备

切换页面,点击添加设备。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

填写设备信息,如下图,这里的NodeMCU-1是笔者创建的产品;

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

Tip:产品和设备的管理你可以类比以下例子:Iphone 11 PRO是一个产品,我的11和你的11是两个设备。从这里可以看出,同一个产品的设备具有一样的属性,但是同一个产品不同设备属性的值可能不一样,比如说我的11还有100G内存,你的11只有10G内存的,这里的内存就可以当做一个属性。

创建完设备就可以进行代码的开发了。

Arduino IDE代码开发

下面讲解代码部分,代码部分主要分为几个部分:

  • wifi连接
  • mqtt初始化
  • ntp网络时间获取
  • 数据上报
  • 数据接收

注意事项:

  • 这里的数据上报就是对"post"主题发布信息
  • 这里的数据接收功能当前只能接收到云平台发送了什么内容,这个内容是需要解析的,在后面的教程中会讲解如何解析数据。

在定义物模型的时候,笔者定义了两个属性,分别是开门时间和开门指令,这两个数据分别演示数据上报和数据下发。在NodeMCU运行的时候,会上传当前时间;而在另外一边,云平台会下发开门指令给NoduMCU。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

例程

/* 目的:该例程为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] = '\0';

    const char* json = (char *)payload;
    Serial.println("收到的json:");
    Serial.println(json);
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, json);
    JsonObject root = doc.as<JsonObject>();

    //云端下发的数据只有一个数据点,因此要判断是哪一个数据点下发了数据
    if( root["params"].containsKey("openDoor") )  //containsKey方法为判断json对象是否包含指定字段
    {  
       openFlag = root["params"]["openDoor"];
    }
    

}

// 连接wifi
void wifiInit()
{
  wifiMulti.addAP("LAPTOP-RIH1JO89 5592", "12345678"); // 将需要连接的一系列WiFi ID和密码输入这里
  wifiMulti.addAP("MI 9", "12345678"); // ESP8266-NodeMCU再启动后会扫描当前网络
  wifiMulti.addAP("LAPTOP9#337", "xy1229033519"); // 环境查找是否有这里列出的WiFi ID。如果有
  Serial.println("[info] Connecting ...");                            // 则尝试使用此处存储的密码进行连接。
  
  int i = 0;                                 
  while (wifiMulti.run() != WL_CONNECTED) {  // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前
    delay(1000);                             // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
    Serial.print(i++); Serial.print(' ');    // 将会连接信号最强的那一个WiFi信号。
  }                                          // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。这也是
                                             // 此处while循环判断是否跳出循环的条件。

  // WiFi连接成功后将通过串口监视器输出连接成功信息 
  Serial.print("[info] Connected to ");
  Serial.println(WiFi.SSID());              // 通过串口监视器输出连接的WiFi名称
  Serial.print("[info] IP address:\t");
  Serial.println(WiFi.localIP());           // 通过串口监视器输出ESP8266-NodeMCU的IP

}

// 检查设备与MQTT服务器连接情况
void mqttCheckConnect()
{
    while (!client.connected())
    {
        Serial.println("Connecting to MQTT Server ...");
        if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD))
        {
            Serial.println("MQTT Connected!");
        }
        else
        {
            Serial.print("MQTT Connect err:");
            Serial.println(client.state());
            delay(5000);
        }
    }
    if (client.connected()){
      Serial.println("[info] keeping alive");
    }
}

/* 上报消息 */
void mqttIntervalPost()
{
    char param[128];
    char jsonBuf[128];

    // 将current转换为字符数组
    const char * temp = currentTime.c_str();
    
    //上传的数据在这里编辑,该例程将上报的数据为当前时间
    sprintf(param, "{\"openTime\":\"%s\"}",temp);
    
    sprintf(jsonBuf, ALINK_BODY_FORMAT, param);
    Serial.println("[info] 上传的json:");
    Serial.println(jsonBuf);

    // 上传数据
    boolean d = client.publish( ALINK_TOPIC_PROP_POST, jsonBuf);
    if(d==1){
      Serial.println("[info] 发送成功");
    }else{
      Serial.println("[info] 发送失败"); 
    }
}

运行之后,NodeMCU首先会连接Wifi,然后连接aliyunIot平台的服务的对应设备,连接成功了之后,将会把当前的时间的数据上报至云服务器;而在NodeMCU接收到Iot平台发送的开门指令之后,Serial也会做出对应的反应。

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

图为上报数据

NodeMCU-ESP8266连接阿里云Iot平台进行数据监测

图为成功接收下发数据

注意事项:当出现MQTT连不上时,错误返回值2表示客户端标识符不正确, -4表示用户名或者密码错误。 请做以下检查:

  • 先检查一下库文件PubSubClient.h文件中定义的 MQTT_MAX_PACKET_SIZE的值, 最好要大于1024, MQTT_KEEPALIVE 大于60;
  • 检查一下你的签名和接入参数的设置,可以参考文档 https://help.aliyun.com/document_detail/73742.html?spm=a2c4g.11186623.6.650.3820619bBWPshh 。
    官方文档:CONNECT指令中需包含Keep Alive(保活时间)。保活心跳时间取值范围为30至1200秒。如果心跳时间不在此区间内,物联网平台会拒绝连接。建议取值300秒以上。如果网络不稳定,将心跳时间设置高一些。

参考资料

nodemcu+阿里云(ArduinoIDE)

【NodeMCU_LUA系列】NodeMCU连接阿里云

(五)air800订阅云端数据并进行解析

string、char *、char[] 相互转换转换

上一篇:ESP8266之间通信


下一篇:三元表达式转if