风筝
发表于: 2021-2-5 09:35:20 | 显示全部楼层

物联网(IoT)在过去几年中呈指数增长。国际数据公司(IDC)的一项新研究估计,到2025年,将有近420亿台互联设备,产生大约80兆字节(ZB)以上的数据。随着物联网设备数量的增长,以及数据量的增长,对可以支持此负载的高级网络工具的需求也随之增长。


但是,如果我们考虑一个通用主机(例如路由器),它可以连接到有限数量的节点,确切地说少于32个。随着越来越多的物联网设备可能出现在我们的家庭或行业中,这还不够。当前,针对此问题有两种解决方案:第一种是使用Mesh路由器,与通用路由器相比,它可以处理更多的连接;或者我们可以使用称为Mesh网络的网络协议。


因此,在本篇文章中,我们将进行一个简单的ESP Mesh网络设置,其中包括四个将通过Wi-Fi Mesh网络相互通信的ESP设备。最后,我们将单个ESP连接到笔记本电脑,以便从网络上获取所有四个传感器的数据。请注意,在本文中我们将同时使用ESP32和ESP8266开发板,以便您可以使用相同的方法创建ESP8266 Mesh网络或ESP32 Mesh网络。


什么是ESP-MESH及其运作方式?

根据ESP-MESH的官方文档,它是一个自组织和自愈的网络,意味着该网络可以自动构建和维护。有关更多信息,请访问ESP-MESH官方文档

ESP-MESH-Networking.jpg


Mesh网状网络是充当单个网络的网络上的一组连接的设备。 ESP-Mesh与传统的网格设置完全不同。在ESP网格中,节点或单个设备可以同时连接到其相邻设备。单个节点可以连接到多个节点,并且它们可以将数据从一个节点中继到另一个节点。这个过程不仅高效,而且是冗余的。如果任何节点之一发生故障,其他节点的数据可以毫无问题地到达目的地。这也打开了实现互连而无需中央节点的可能性,这大大扩展了网状网络的覆盖范围。有了这些功能,该网络就不太容易连接,因为网络中节点的总数不受单个中央节点的限制。


为了简单起见,我们决定使用四个ESP模块。但是如果您要建立此网络,则可以使用尽可能多的ESP设备。为了建立Mesh网络,我们将使用Arduino的painlessMesh库,该库同时支持ESP8266和ESP32模块。


所需的组件

●    NodeMCU(ESP8266)开发板

●    ESP32开发板

●    BMP280传感器-

●    DHT22传感器-1

●    DS18B20传感器

●    面包板


ESP Wi-Fi Mesh-电路图

以下所示原理图用于制作基于ESP8266和ESP32的Wi-Fi Mesh网络的硬件部分。

ESP-Wi-Fi-Mesh-Circuit-Diagram.png


在此电路中,我们将两个BME280传感器与ESP32开发板连接,将DHT22传感器连接至其中一个ESP8266开发板,并且将DS18B20传感器与另一个ESP8266开发板连接。在本文,我们将在不同的NodeMCU / ESP32开发板上使用它们,然后通过ESP Mesh网络将它们全部连接起来。硬件设置的图像如下所示。

ESP-Mesh-Network.jpg


对ESP8266和ESP32进行Mesh网络编程

在本文中,我们将使用Arduino IDE对ESP32和ESP8266板进行编程。我们将使用Painless Mesh库来构建我们的网格网络。要安装库,请转到Sketch->Include Library->Manage Libraries,然后搜索painlessMesh。完成后,只需单击安装,该库就会安装在Arduino IDE中。如下图所示,单击“Install ”后,该库将要求您安装其他依赖项。您需要安装这些文件才能使库正常工作。

Painless-Mesh-Library.jpg


从硬件部分您已经知道,我们将使用DS18B20传感器、DHT22传感器和两个BME 280传感器。我们需要安装所有这些,并且可以使用板管理器方法轻松完成。

Arduino-IDE-Library.png


将代码上传到连接了BME280传感器的ESP32开发板:

正如您在硬件原理图中所看到的,我们已经连接了BME280传感器。为此,您需要取消注释BME_280传感器的宏并为其赋予唯一的节点名称。在本文中,我们为两个已连接BME280传感器的ESP32开发板使用了Node_1和Node_2

  1. #define BME_280
  2. //#define DHT22
  3. //#define DS18B20
  4. //#define ENABLE_LOG
  5. String nodeName = "NODE_1";
复制代码

将代码上传到连接了DHT传感器的ESP8266开发板:

在我们的ESP8266开发板上,有一个DHT22传感器,在另一个中,使用DS18B20传感器。要将代码上传到DHT22板上,我们必须遵循相同的过程。首先,我们取消对DHT22宏的注释,然后为BME280注释掉宏,并将代码上传到我们已将DHT 22传感器连接到的开发板上。

  1. //#define BME_280
  2. #define DHT22
  3. //#define DS18B20
  4. //#define ENABLE_LOG
  5. String nodeName = "NODE_3";
复制代码

将代码上传到连接了DS18B20传感器的ESP8266开发板:

该电路板的过程也完全相同。我们取消注释DS18B20传感器的宏,并注释其他宏。

  1. //#define BME_280
  2. //#define DHT22
  3. #define DS18B20
  4. //#define ENABLE_LOG
  5. String nodeName = "NODE_3";
复制代码

最后,要启用或禁用其他日志语句,可以取消注释ENABLE_LOG宏。现在我们已经了解了代码的工作原理,可以进一步介绍代码并进行解释。


首先我们将包含painlessMesh库和Arduino_JSON库。

  1. #include <painlessMesh.h>
  2. #include <Arduino_JSON.h>
复制代码

接下来,我们定义一些宏,这些宏将用于启用或禁用代码的各个部分。这是必需的,因为并非所有节点都使用相同的传感器类型。因此,为了包含或排除部分代码,我们可以将四个不同的代码放入一个文件中。

  1. //#define BME_280
  2. #define DHT22
  3. //#define DS18B20
  4. //#define ENABLE_LOG
复制代码

接下来,我们定义一个String类型的变量nodeName。这将用于唯一地标识网络中的节点。除此之外,我们还定义了float类型变量来存储温度、湿度和大气压力数据。

  1. String nodeName = "NODE_4"; // Name needs to be unique
  2. float temp(NAN), hum(NAN), pres(NAN);
复制代码

从这一步开始,我们将使用#ifdef和#endif宏来包含或排除部分代码。代码的第一部分适用于BME280传感器。如前所述,我们将从#ifdef BME_280语句开始。接下来,我们定义BME280传感器的所有必需库。 BME传感器还使用wire库,因此我们也对其进行定义。接下来,我们制作一个BME280I2C对象bme。使用作用域解析运算符,我们访问类的变量,并使用endif语句结束该变量。

  1. #ifdef BME_280
  2. #include <BME280I2C.h>
  3. #include <Wire.h>
  4. BME280I2C bme;
  5. BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
  6. BME280::PresUnit presUnit(BME280::PresUnit_Pa);
  7. #endif
复制代码

我们也对DHT库执行相同的操作。我们从#ifdef DHT22语句开始,然后包含DHT.h库。接下来,我们为DHT定义引脚,并为DHT22添加原型。此后,我们通过传递上面定义的语句来创建对象。我们以#endif语句结束。

  1. #ifdef DHT22
  2. #include "DHT.h"
  3. #define DHTPIN 4
  4. #define DHTTYPE DHT22
  5. DHT dht(DHTPIN, DHTTYPE);
  6. #endif
复制代码

我们对DS18B20传感器也做同样的事情。我们从#ifdef DS18B20语句开始。


由于DS18B20传感器需要OneWire库,因此我们将其与DallasTemperature库一起包括在内。接下来,我们定义传感器的引脚,并通过传递pin变量创建一个OneWire对象。接下来,我们通过制作一个新的DallasTemperature对象,将OneWire对象的地址传递给DallasTemperature对象。

  1. #ifdef DS18B20
  2. #include <OneWire.h>
  3. #include <DallasTemperature.h>
  4. const int oneWireBus = 4;
  5. OneWire oneWire(oneWireBus);
  6. DallasTemperature ds18b20(&oneWire);
  7. #endif
复制代码

接下来,我们定义Wi-Fi凭证以及端口号。对于网络中的所有节点,此凭据和端口号应保持不变。

  1. #define   MESH_PREFIX "whateverYouLike"
  2. #define   MESH_PASSWORD "somethingSneaky"
  3. #define   MESH_PORT 5555
复制代码

我们创建三个实例。一个用于调度程序,另一个用于painlessMesh,最后一个用于JSON库JSONVar。

  1. Scheduler userScheduler; // to control your task
  2. painlessMesh  mesh;
  3. JSONVar myVar;
复制代码

接下来,我们创建了一个任务,该任务就像一个线程,该线程始终运行并在一段时间后调用一个函数。下面定义的任务将用于向所有节点发送广播消息。该任务立即具有三个参数。首先定义任务将多久调用一次函数,其次,它询问任务的生存周期,最后,它获取指向调用函数的指针。

  1. Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
复制代码

接下来,我们有调用函数sendMessage()。该函数调用另一个返回JSON字符串的函数。顾名思义,sendMessage任务用于将消息发送到所有节点。

  1. void sendMessage() {
  2.   String msg = construnct_json();
  3.   mesh.sendBroadcast( msg );
  4.   taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
  5. }
复制代码

我们定义了一个receiveCallback()函数。每当收到新消息时,此函数都会调用。如果您想对收到的消息进行处理,则需要调整此函数以完成您的工作。此函数采用两个参数-节点ID和消息作为指针。

  1. void receivedCallback( uint32_t from, String &msg ) {
  2.   Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
  3. }
复制代码

接下来,我们定义newConnectionCallback()函数。每当有新设备添加到网络时,此函数都会获得一个回调函数,并且它将打印语句发送到串口监视器。

  1. void newConnectionCallback(uint32_t nodeId) {
  2.   Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
  3. }
复制代码

我们定义了nodeTimeAdjustedCallback()函数。该回调函数可满足painless网格所需的所有计时需求。

  1. void nodeTimeAdjustedCallback(int32_t offset) {
  2.   Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
  3. }
复制代码

在setup()函数中,我们初始化串口并打印节点名称。这很有用,因为一旦对所有节点进行了编程,我们就可以使用串口监视器轻松识别节点。我们还有网状对象的setDebugMsgTypes()类,用于记录任何错误| STARTUP消息。接下来,我们通过将SSID、密码和端口号传递给init()函数来初始化网格。

  1. Serial.begin(115200);
  2. Serial.println(nodeName);
  3. mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages
  4. mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
复制代码

现在,我们将初始化上面讨论的所有回调函数。每当需要执行某些任务时,将调用这些回调函数。

  1. mesh.onReceive(&receivedCallback);
  2. mesh.onNewConnection(&newConnectionCallback);
  3. mesh.onChangedConnections(&changedConnectionCallback);
  4. mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
复制代码

现在,我们将任务添加到任务计划程序中,并在taskSendMessage.enable()方法的帮助下将其启用。当它执行时,不同的任务开始同时在后台运行。

  1. userScheduler.addTask( taskSendMessage );
  2. taskSendMessage.enable();
复制代码

接下来,在setup函数部分,我们具有所有必需的ifdef和endif宏,这些宏用于根据需要初始化不同的传感器。首先,我们将配置BME280传感器。下面的代码初始化BME280传感器并检查传感器的版本,因为BME280传感器随附许多不同的版本。

  1. #ifdef BME_280
  2.   Wire.begin();
  3.   while (!bme.begin())
  4.   {
  5.     Serial.println("Could not find BME280 sensor!");
  6.     delay(1000);
  7.   }
  8.   // bme.chipID(); // Deprecated. See chipModel().
  9.   switch (bme.chipModel())
  10.   {
  11.     case BME280::ChipModel_BME280:
  12.       Serial.println("Found BME280 sensor! Success.");
  13.       break;
  14.     case BME280::ChipModel_BMP280:
  15.       Serial.println("Found BMP280 sensor! No Humidity available.");
  16.       break;
  17.     default:
  18.       Serial.println("Found UNKNOWN sensor! Error!");
  19.   }
  20. #endif
复制代码

最后,我们通过ifdef和#endif方法配置了DHT22和DS18B20传感器。

  1. #ifdef DHT22
  2.   Serial.println(F("DHTxx Begin!"));
  3.   dht.begin();
  4. #endif
  5. #ifdef DS18B20
  6.   ds18b20.begin();
  7.   Serial.println(F("DS18B20 Begin!"));
  8. #endif
复制代码

在loop()函数中,这部分代码的作用并不大,它只是借助mesh.update()方法来更新网格。它负责所有任务。

  1. void loop()
  2. {
  3.   mesh.update();
  4.   // construnct_json();
  5. }
复制代码

接下来,我们定义construnct_json函数。此函数构造JSON字符串,并将其返回给调用函数。在此函数中,我们首先调用bme.read()方法,然后传递所有使用新值更新的预定义变量。接下来,将压力值除以100,因为我们要将其转换为毫巴。接下来,我们定义JSON数组,并放置传感器名称、节点名称、温度、压力值,然后使用JSON.stringify()函数返回这些值。最后,我们定义另一个ifdef和endif宏来启用或禁用日志参数。

  1. String construnct_json()
  2. {
  3. #ifdef BME_280
  4.   bme.read(pres, temp, hum, tempUnit, presUnit);
  5.   pres = pres / 100;
  6.   myVar["Sensor Type"] = "BME280";
  7.   myVar["Node Name"] = nodeName;
  8.   myVar["Temperature"] = serialized(String(temp, 2));
  9.   myVar["pres"] = serialized(String(pres, 2));
  10. #ifdef ENABLE_LOG
  11.   Serial.println(JSON.stringify(myVar));
  12. #endif
  13.   return JSON.stringify(myVar);
  14. #endif
复制代码

最后,我们对DHT22和DS18B20传感器代码执行相同的操作。

  1. #ifdef DHT22
  2.   temp = dht.readTemperature();
  3.   hum = dht.readHumidity();
  4.   myVar["Sensor Type"] = "DHT22";
  5.   myVar["Node Name"] = nodeName;
  6.   myVar["Temperature"] = serialized(String(temp));
  7.   myVar["Humidity"] = serialized(String(hum));
  8. #ifdef ENABLE_LOG
  9.   Serial.println(JSON.stringify(myVar));
  10. #endif
  11.   return JSON.stringify(myVar);
  12. #endif
  13. #ifdef DS18B20
  14.   ds18b20.requestTemperatures();
  15.   temp = ds18b20.getTempCByIndex(0);
  16.   myVar["Sensor Type"] = "DS18B20";
  17.   myVar["Node Name"] = nodeName;
  18.   myVar["Temperature"] = serialized(String(temp));
  19. #ifdef ENABLE_LOG
  20.   Serial.println(JSON.stringify(myVar));
  21. #endif
  22.   return JSON.stringify(myVar);
  23. #endif
  24. }
复制代码

现在,代码部分完成了。根据要上传的开发板,我们在顶部注释或取消注释已定义的宏;接下来,我们给每个节点一个唯一的名称,然后上传代码。如果一切正常,代码将正确编译并正确上载,没有任何错误。


测试基于ESP8266和ESP32的Mesh网络

ESP8266-and-ESP32-based-Mesh-Network.jpg


基于ESp8266和esp32的网状网络的测试设置如下所示。 如上图所示,我已经为所有esp模块连接了电源,并且您还可以在笔记本电脑的屏幕上看到输出数据。 串口监视器窗口的屏幕截图如下所示。

Mesh-Network-Testing.png


在上面的窗口中,您可以看到我们正在轻松地从所有四个传感器接收数据。


希望您喜欢这个项目, 如果您有任何疑问,请随时在本帖下面回复。

跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 700 | 回复: 1479



手机版|

GMT+8, 2024-3-29 01:13 , Processed in 0.098131 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表