物联网NodeMCU ESP8266连接新版onenet mqtt协议实现温湿度数据上传和LED灯控制

前言

本文详细介绍了如何利用物联网技术,通过NodeMCU ESP8266(ESP-12F)模块连接到新版的OneNet平台,使用MQTT协议实现数据的上传与指令的下发。文中首先对NodeMCU ESP8266模块及其特性进行了简介,随后详细阐述了如何配置和使用MQTT协议连接到OneNet平台,实现温湿度数据的实时上传。同时,文章也演示了如何从OneNet平台下发指令控制远端的LED灯,实现了物联网设备的双向通信。通过本文的步骤指导,读者能够掌握利用ESP8266模块与OneNet平台结合,通过MQTT协议进行数据通信的基本方法,并能够在此基础上开展更复杂的物联网项目开发。本文适合对物联网技术感兴趣,希望了解ESP8266模块与OneNet平台结合应用的读者。

前不久手上有了一个NodeMCUESP8266型号(ESP-12F)的单片机,然后也想做一个基于WIFI功能的远程控制项目,我是用VScode的platformio组件,基本等同于Arduion IDE开发
下面我先说一下这两者的区别,然后对每个部分的代码讲解一下,因为之前在网上搜的教程有点杂乱,就自己写个教程方便自己参考

后续也会出一个微信小程序控制LED,舵机等等的操作

之前也是做过ESP8266-01S WIFI模块
下面放上实物图
NodeMCU ESP8266

NodeMCUESP8266
ESP8266-01S

NodeMCU ESP8266和ESP8266-01S都是基于ESP8266芯片的开发板,它们可以用来开发物联网(IoT)项目。尽管它们都基于相同的ESP8266芯片,但这两个开发板在设计、功能、使用场景上有所不同。

区别

NodeMCU ESP8266

  1. 微控制器:NodeMCU内置的ESP8266芯片带有一个32位的Tensilica L106微控制器,核心频率可达80MHz到160MHz,配备了大约80KB的用户数据RAM和最高上16MB的SPI闪存,用于程序存储。

  2. IO功能丰富:NodeMCU板上一般提供可编程的GPIO引脚达到17个,支持UART、I2C、SPI等通信协议,以及1路ADC(模拟-数字转换器),最大分辨率为10位。

  3. 网络能力:支持IEEE 802.11 b/g/n Wi-Fi标准,集成式天线,可作为STA(客户端)或AP(热点)模式运行,也支持这两种模式的混合运行,非常适合开发需要Wi-Fi功能的物联网项目。

  4. 开发和编程:可以使用Lua语言进行脚本编程,同时支持Arduino IDE和其他ESP8266 SDK开发,方便开发者根据自己的喜好和项目需求进行选择。USB端口直接连接电脑即可编程,也可用于电源供电。

使用场景展开

  • 环境监测:利用其GPIO接口连接多个传感器,例如温湿度传感器、PM2.5空气质量监测传感器,实现环境监测站。
  • 智能家居控制中心:作为家中智能设备的中枢,控制灯光、空调、窗帘等。
  • 远程监控系统:配合摄像头使用,通过WiFi传输视音频数据实现远程监控。
  • ESP8266-01S

    1. 核心功能集中:ESP8266-01S是ESP8266系列的一种简化模块,尽管引脚较少,但提供了基本的GPIO、TX、RX等引脚,适合不需要大量外设连接的应用。

    2. 体积小巧:尺寸小,适合空间受限的设计,如穿戴设备、小型传感器模块等。

    3. 电源要求:需要稳定的3.3V电源供电,电流需求较高时(比如在WiFi通信时)至少为500mA,这对电源设计提出了一定的要求。

    使用场景展开

  • 无线数据通讯模块:为其他不具备WiFi功能的微控制器或设备添加无线数据通讯能力。
  • 物联网节点:在节点数量众多但每个节点功能相对简单的物联网应用场景,如简单数据采集和传输,温湿度监测等。
  • 家居自动化简单项目:可以控制一两个设备的打开关闭,例如智能插座。
  • 创建OneNET新版 MQTT协议

    这里我就直接放之前的文章,那些文章是使用ESP8266-01S配合其他型号单片机(不具备WIFI功能如51/stm32单片机)连接WIFI实现数据上传和下发,外加微信小程序段教程
    创建OneNET新版MQTT设备:实现远程控制单片机 为微信小程序与单片机通信打基础(微信小程序通信单片机前置任务)

    这里查看上面的文章创立图中的物模型,如果没有经验的话强烈建议和我创建的内容一模一样,等后续代码跑通了再自己按需修改

    编写代码芯片选择


    串口设置

    注意事项

    你需要将代码改为你信息,信息获取在上面的文章中有详细的讲解

    有个重要的事项
    在数据上传那里,你必须确保上报的标识符和数据大小是符合你创建的物模型属性的,否则串口显示上报成功,onenet也会过滤你的信息,你可以每次一个一个的测试
    例如你的温度范围是0-100,步长是0.1,你上传了一个100.01或者101.1都是错的,你整个上报的所有内容都失效

    完整讲解

    #include <Arduino.h>
    #include "ESP8266WiFi.h"
    #include <PubSubClient.h>
    String ssid= "ESP";
    String password = "123456789";
    
    const char* mqtt_server = "mqtts.heclouds.com";
    const int mqtt_port = 1883;
    const char* deviceID = "test";  // 您的设备ID
    const char* productID = "eb4Lr0apkE";  // 您的产品ID
    const char* apiKey = "version=2018-10-31&res=products%2Feb4Lr0apkE%2Fdevices%2Ftest&et=2017757596000&method=md5&sign=y6ej0XbtuWmRH7gQKGUeSg%3D%3D";  // 您的APIKey
    
    String commandTopic = "$sys/" + String(productID) + "/" + String(deviceID) + "/thing/property/set";
    
    WiFiClient espClient;
    PubSubClient OneNET(espClient);
    
    void setup_wifi(String ssid,String password){
      WiFi.begin(ssid, password);
      static uint8_t count = 0;
      Serial.print("WiFi connecting");
       while (WiFi.status() != WL_CONNECTED) {
        if(++count >= 25) break;
        delay(500);
        Serial.print(".");
      }
      if(WiFi.status() == WL_CONNECTED){
        Serial.println("");
        Serial.println("WiFi connected!");
        Serial.println("IP: ");
        Serial.println(WiFi.localIP());
      }else if(WiFi.status() != WL_CONNECTED){
        Serial.println("");
        Serial.println("WiFi connected fail!");
      }
    }
    
    
    void reconnect() {
        while (!OneNET.connected()) {
        Serial.print("Attempting MQTT connection...");
        // 尝试连接
        if (OneNET.connect(deviceID, productID, apiKey)) {
          Serial.println("connected");
          // 一旦连接上,订阅相应的主题
          OneNET.subscribe(commandTopic.c_str());
        } else {
          Serial.print("failed, rc=");
          Serial.print(OneNET.state());
          Serial.println(" try again in 5 seconds");
          delay(5000);
        }
      }
    }
    
    void callback(char* topic, byte* payload, unsigned int length) {
      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.println("] ");
      for (size_t i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
      }
      Serial.println();
    }
    
    void publishSensorData(int count, String identifiers[], float values[], int stringCount, String stringIdentifiers[], String stringValues[]) {
      String jsonData = "{\"id\":\"123\",\"params\":{";
      
      // 首先处理数字类型的数据
      for(int i = 0; i < count; ++i) {
        jsonData += "\"" + identifiers[i] + "\":{\"value\":" + String(values[i], 1) + "}";
        if (i < count - 1 || stringCount > 0) { // 如果后面还有数据,就添加一个逗号
          jsonData += ",";
        }
      }
      
      // 处理字符串类型的数据
      for (int j = 0; j < stringCount; ++j) {
        jsonData += "\"" + stringIdentifiers[j] + "\":{\"value\":\"" + stringValues[j] + "\"}";
        if (j < stringCount - 1) { // 如果不是最后一个字符串数据,就添加一个逗号
          jsonData += ",";
        }
      }
    
      jsonData += "}}";
    
      // MQTT主题,假设已经定义
      String topic = "$sys/eb4Lr0apkE/test/thing/property/post";
      
      Serial.println(jsonData); // 输出验证
      
      // 将String类型的JSON数据转换为字符数组
      char jsonBuffer[jsonData.length() + 1];
      jsonData.toCharArray(jsonBuffer, sizeof(jsonBuffer));
    
      // 假设OneNET.publish函数和Serial对象已经定义
      if (!OneNET.publish(topic.c_str(), jsonBuffer)) {
        Serial.println("数据发布失败");
      } else {
        Serial.println("数据发布成功");
      }
    }
    
    void setup() {
      Serial.begin(115200);
      setup_wifi(ssid,password);
      OneNET.setServer(mqtt_server, mqtt_port);
      OneNET.setCallback(callback);
      // 连接MQTT
      if (OneNET.connect(deviceID, productID, apiKey)) {
        Serial.println("MQTT Connected");
    
        // 订阅命令下发Topic
        if(OneNET.subscribe(commandTopic.c_str())) {
          Serial.println("Subscribed to command topic");
        } else {
          Serial.println("Subscription failed");
        }
      }
       // 模拟数据
      String identifiers[] = {"temp", "humi","adcx"};
      float values[] = {12.1, 23.2,34.3};
      int numCount = sizeof(values) / sizeof(values[0]);
      
      String stringIdentifiers[] = {"Time", "command"};
      String stringValues[] = {"OFF", "close"};
      int stringCount = sizeof(stringValues) / sizeof(stringValues[0]);
    
      // 调用函数
      publishSensorData(1, identifiers, values, 1, stringIdentifiers, stringValues);
      //publishSensorData(numCount, identifiers, values, 0, stringIdentifiers, stringValues);
      //publishSensorData(numCount, identifiers, values, stringCount, stringIdentifiers, stringValues);
    }
    void loop() {
      if (!OneNET.connected()) {
          reconnect();
        }
        OneNET.loop(); // 处理接收到的消息和保持MQTT连接
    }
    
    

    代码讲解

    连接WIFI

    连接WIFI部分大家应该都理解,这里就不展开说了
    主要的代码就是
    #include "ESP8266WiFi.h"

    WiFi.begin(ssid, password);

    连接OneNET MQTT协议设备

    这里需要安装一个库:#include <PubSubClient.h>
    然后创建两个对象
    WiFiClient espClient
    PubSubClient OneNET(espClient)
    在成功连接WIFI后,我们还要下面代码
    分别是连接OneNET的mqtt服务器和设置订阅的回调函数
    OneNET.setServer(mqtt_server, mqtt_port);
    OneNET.setCallback(callback);

    void callback(char* topic, byte* payload, unsigned int length) {
      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.println("] ");
      for (size_t i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
      }
      Serial.println();
    }
    

    连接好OneNET的mqtt服务器后就是需要登录(设备id,产品id,API)
    OneNET.connect(deviceID, productID, apiKey)
    登录成功后就能在OneNET官网上看到自己的设备在线了

    订阅主题

    然后就可以订阅主题
    我这里订阅的是属性设置,因为我们创建的是OneJson流设备而不是数据流设备,所有不能用MQTT下发指令,但是这个属性设置的意思是一样的作用,订阅主题后我们就可以实现单片机接收平台上发送的内容了,(当时一直在测试MQTT下发指令,弄了很久都不行,最后发了个工单客服告诉我物模型不能用,我哭死)

    下面的这个手册上写了我们对应订阅的功能和作用
    OneNET MQTT通信主题 官方手册
    OneNET.subscribe(commandTopic.c_str())

    发送数据到OneNET

    这里的代码是用Json格式,这里我使用的手动拼装Json,当然也可以直接用库写
    下面是官方给的示例,这里如果对时间准确度不要求的话 ,可以不用时间戳

    这是最初的上传代码
    有很明显的缺点,就是只能固定的上传两个数字类型,如果要改动非常不方便

    String defined_humi = "humi";
    String defined_temp = "temp";
    void publishSensorData(float temperature, float humidity) {
      // 动态构造符合OneNET要求的JSON数据,去除时间戳
      String jsonData = "{\"id\":\"123\",\"params\":{\""+ defined_humi + "\":{\"value\":" + String(humidity, 1) + "},\"" + defined_temp + "\":{\"value\":" + String(temperature, 1) + "}}}";
      // MQTT主题,已填写示例中的产品ID和设备名称
      Serial.println(jsonData);
      String topic = "$sys/eb4Lr0apkE/test/thing/property/post";
    
      // 将String类型的JSON数据转换为字符数组,因为publish函数需要字符数组作为参数
      char jsonBuffer[jsonData.length() + 1];
      jsonData.toCharArray(jsonBuffer, sizeof(jsonBuffer));
    
      // 发布到MQTT
      if (!OneNET.publish(topic.c_str(), jsonBuffer)) {
        Serial.println("数据发布失败");
      } else {
        Serial.println("数据发布成功");
      }
    }
    

    对应上面说的情况我就改进了一下代码

    void publishSensorData(int count, String identifiers[], float values[]) {
      String jsonData = "{\"id\":\"123\",\"params\":{";
      for(int i = 0; i < count; ++i) {
        jsonData += "\"" + identifiers[i] + "\":{\"value\":" + String(values[i], 1) + "}";
        if (i < count - 1) {
          jsonData += ","; // 如果不是最后一个参数,就添加一个逗号
        }
      }
      jsonData += "}}";
    
      // MQTT主题,假设已经定义
      String topic = "$sys/eb4Lr0apkE/test/thing/property/post";
      
      Serial.println(jsonData); // 输出验证
      
      // 将String类型的JSON数据转换为字符数组
      char jsonBuffer[jsonData.length() + 1];
      jsonData.toCharArray(jsonBuffer, sizeof(jsonBuffer));
      
      // 假设OneNET.publish函数和Serial对象已经定义
      if (!OneNET.publish(topic.c_str(), jsonBuffer)) {
        Serial.println("数据发布失败");
      } else {
        Serial.println("数据发布成功");
      }
    }
    下面为初始函数的代码
    	String identifiers[] = {"temp", "humi","adcx"};
      float values[] = {12.1, 23.2,34.3};
      int numCount = sizeof(values) / sizeof(values[0]);
      // 调用函数
      publishSensorData(numCount , identifiers, values);
    
    

    这样子的话我就可以实现如果我需要多少个我就创建多少个标识符和数据,然后只需要修改数组就能更改上传的数据
    但是又有一个问题,我还有字符串的数据,我一开始想着是两个函数实现分别的发送,但是OneNET规定了每次上传数据的间隔要大于2s,所以为了避免一些其他问题,我决定再修改一下函数实现字符串和数据同时发送

    这就有了目前的版本

    void publishSensorData(int count, String identifiers[], float values[], int stringCount, String stringIdentifiers[], String stringValues[]) {
      String jsonData = "{\"id\":\"123\",\"params\":{";
      
      // 首先处理数字类型的数据
      for(int i = 0; i < count; ++i) {
        jsonData += "\"" + identifiers[i] + "\":{\"value\":" + String(values[i], 1) + "}";
        if (i < count - 1 || stringCount > 0) { // 如果后面还有数据,就添加一个逗号
          jsonData += ",";
        }
      }
      
      // 处理字符串类型的数据
      for (int j = 0; j < stringCount; ++j) {
        jsonData += "\"" + stringIdentifiers[j] + "\":{\"value\":\"" + stringValues[j] + "\"}";
        if (j < stringCount - 1) { // 如果不是最后一个字符串数据,就添加一个逗号
          jsonData += ",";
        }
      }
    
      jsonData += "}}";
    
      // MQTT主题,假设已经定义
      String topic = "$sys/eb4Lr0apkE/test/thing/property/post";
      
      Serial.println(jsonData); // 输出验证
      
      // 将String类型的JSON数据转换为字符数组
      char jsonBuffer[jsonData.length() + 1];
      jsonData.toCharArray(jsonBuffer, sizeof(jsonBuffer));
    
      // 假设OneNET.publish函数和Serial对象已经定义
      if (!OneNET.publish(topic.c_str(), jsonBuffer)) {
        Serial.println("数据发布失败");
      } else {
        Serial.println("数据发布成功");
      }
    下面为初始函数的代码
      // 模拟数据
      String identifiers[] = {"temp", "humi","adcx"};
      float values[] = {12.1, 23.2,34.3};
      int numCount = sizeof(values) / sizeof(values[0]);
      
      String stringIdentifiers[] = {"Time", "command"};
      String stringValues[] = {"OFF", "close"};
      int stringCount = sizeof(stringValues) / sizeof(stringValues[0]);
    
      // 调用函数
      publishSensorData(numCount , identifiers, values, stringCount , stringIdentifiers, stringValues);
    }
    

    这样子就可以实现有几个标识符和数据就发送几个了,不限数据个数,最大灵活性

    操作演示

    单片机上传数据

    平台下发数据


    这里你接收到的数据,例如:“Open XXX”,你可以用JSON解析出来,然后再自己创建指令规则,用来控制其他的内容
    这里你可以不回应平台的操作,平台会收到超时,但是不影响正常使用,如果你要回应平台,可以参考数据手册中的“最佳实例–物模型”
    官方手册

    总结

    自从去年开始接触ESP8266-01S和OneNET平台以来,我便投入了大量的时间和精力,撰写了许多有关联网的教程和代码。初期,我遇到了不少挑战:手头的教程内容过时,不适应平台的新版本;对于错误信息缺乏足够的理解,经常出现连接MQTT服务时的失败情况。这一系列问题让我一度想要放弃。

    然而,通过不懈的努力,我开始自己查阅资料,尝试解决这些问题。在这个过程中,我学到了如何独立分析问题,并逐步找到了导致问题的根源。最终,我不仅成功解决了连接MQTT服务的难题,对联网技术的理解和应用能力也有了显著的提高。这段经历让我深刻认识到,面对挑战和困难时,不要轻易放弃。通过仔细分析问题的原因,往往能够找到解决问题的办法。

    因此,我想告诉大家:在学习技术或解决技术问题的过程中,难免会遇到各种挑战和困难。当遇到问题时,不要急于放弃。耐心地分析问题,寻找问题的根源所在,你就会发现,很多时候,问题并没有想象中那么难以解决。

    通过这段文字,我希望能鼓励更多的朋友在遇到技术上的困惑和难题时,保持乐观和坚持的态度,不断学习和探索,相信终有一日,你将会像我一样,找到属于自己的解决之道,收获成长与成功。

    作者:送外卖的CV工程师

    物联沃分享整理
    物联沃-IOTWORD物联网 » 物联网NodeMCU ESP8266连接新版onenet mqtt协议实现温湿度数据上传和LED灯控制

    发表回复