制作一个简单的ESP Mesh网络

通信网络

650人已加入

描述

  在过去的几年中,物联网 (IoT) 呈指数级增长。国际数据公司 (IDC) 的一项新研究估计,到 2025 年,将有近 420 亿台联网设备,产生超过 80 泽字节 (ZB) 的数据。随着物联网设备数量的增长;数据量的增长,随之而来的是对高级网络仪器的需求;可以支持这个负载。

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

  因此,在本文中,我们将制作一个简单的ESP Mesh 网络设置,它由四个 ESP 设备组成,它们将在Wi-Fi Mesh 网络的帮助下相互通信。最后,我们要将单个 ESP 连接到我们的笔记本电脑,以便从网络上的所有四个传感器获取数据。请注意,我们将在本教程中同时使用 ESP32 和 ESP8266 板,以便您可以 使用相同的方法创建ESP8266 Mesh 网络或ESP32 Mesh 网络。

  什么是 ESP-MESH 及其工作原理?

  根据 ESP-MESH 的官方文档,它是一个自组织和自愈网络,意味着网络可以自主构建和维护。

网络协议

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

  为了简单起见,我们决定使用四个 ESP 模块;但是如果你正在构建这个网络,你可以使用尽可能多的 ESP 设备。为了构建网状网络,我们将使用 Arduino 的painlessMesh 库,它支持ESP8266和ESP32模块。

  使用 ESP 构建 Mesh 网络所需的组件

  该项目所需的组件列表如下。为了构建这个项目,我使用了非常通用的组件,您可以在当地的爱好商店中找到它们。

  NodeMCU(ESP8266) - 2

  ESP32 开发板 - 2

  BMP280 传感器 - 2

  DHT22 传感器 - 1

  DS18B20 传感器 - 1

  面包板

  USB 数据线(用于电源和数据)

  ESP Wi-Fi Mesh - 威廉希尔官方网站 图

  下图用于构建基于 ESP8266 和 ESP32 的 Wi-Fi Mesh 网络的硬件部分。

网络协议

  对于这个威廉希尔官方网站 ,我们将两个BME280 传感器连接到ESP32 板,我们将 DHT22 传感器连接到其中一个ESP8266 板,并将 DS18B20 传感器连接到另一个 ESP8266 板。我们之前曾在不同的项目中单独使用过所有这些传感器。在这里,我们将在不同的 NodeMCU/ESP32 板上使用它们,然后通过ESP Mesh 网络将它们全部连接起来。硬件设置的图像如下所示。

网络协议

  为网状网络编程 ESP8266 和 ESP32

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

网络协议

  正如您在硬件部分已经知道的那样,我们将使用一个 DS18B20 传感器、一个 DHT22 传感器和两个 BME 280 传感器。我们需要安装所有这些,并且可以使用板管理器方法简单地完成。

网络协议

  您也可以从下面给出的链接下载这些库。一旦我们下载并安装了所有必需的库,我们就可以继续创建我们的代码。

  注意:您接下来看到的代码解释是所有四个板中使用的代码。我们设计了代码,以便我们可以稍微调整一下,我们可以将它上传到我们的任何 ESP 板上,尽管它是 ESP32 或 ESP8266 板。

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

正如您在硬件示意图中看到的,我们已经连接了一个 BME280 传感器。为此,您需要取消注释BME_280传感器的宏并为其指定一个唯一的节点名称。在我们的例子中,我们将Node_1和Node_2用于我们连接 BME280 传感器的两个 ESP32 板

 

#define BME_280
//#定义DHT22
//#定义DS18B20
//#define ENABLE_LOG
字符串节点名 = "NODE_1";

 

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

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

 

//#define BME_280
#定义DHT22
//#定义DS18B20
//#define ENABLE_LOG
字符串节点名 = "NODE_3";

 

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

该威廉希尔官方网站 板的过程也完全相同。我们取消注释 DS18B20 传感器的宏,并对其他宏进行注释。

 

//#define BME_280
//#定义DHT22
#define DS18B20
//#define ENABLE_LOG
字符串节点名 = "NODE_3";

 

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

我们将通过包含 painlessMesh 库和 Arduino_JSON 库来开始我们的代码。 

 

#include 
#include 

 

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

 

//#define BME_280
#定义DHT22
//#定义DS18B20
//#define ENABLE_LOG

 

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

 

字符串节点名 = "NODE_4"; // 名称需要唯一
浮动温度(NAN),嗡嗡声(NAN),压力(NAN);

 

从这一步开始,我们将使用#ifdef和#endif宏来包含或排除部分代码。代码的第一部分用于 BME280 传感器。如前所述,我们将从#ifdef BME_280语句开始。接下来,我们为 BME280 传感器定义所有必需的库。BME 传感器也使用线库,所以我们也定义了它。接下来,我们制作一个BME280I2C对象bme。接下来,使用范围解析运算符,我们访问类的变量,并使用 endif 语句完成它。

 

#ifdef BME_280
#include 
#include 
BME280I2C bme;
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
BME280::PresUnit presUnit(BME280::PresUnit_Pa);
#万一

 

我们也对 DHT 库做同样的事情。我们从#ifdef DHT22语句开始,然后包含DHT.h库。接下来,我们为 DHT 定义 PIN,并为 DHT22 添加一个原型。此后,我们通过传递上述定义的语句来创建一个对象。我们以#endif语句结束。 

 

#ifdef DHT22
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
#万一

 

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

由于 DS18B20 传感器需要OneWire库,我们将其与DallasTemperature库一起提供。接下来,我们为传感器定义引脚并通过传递引脚变量创建一个 OneWire 对象。接下来,我们通过创建一个新的 DallasTemperature 对象将 OneWire 对象的地址传递给 DallasTemperature 对象。

 

#ifdef DS18B20
#include 
#include <达拉斯温度.h>
常量 int oneWireBus = 4;
单线单线(oneWireBus);
DallasTemperature ds18b20(&oneWire);
#万一

 

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

 

#define MESH_PREFIX "whateverYouLike"
#define MESH_PASSWORD "somethingSneaky"
#define MESH_PORT 5555

 

接下来,我们制作三个实例。一个用于调度器,另一个用于painlessMesh,最后一个用于 JSON 库JSONVar

 

调度器用户调度器;// 控制你的任务
无痛网眼;
JSONVar myVar;

 

接下来,我们创建了一个任务,它就像一个线程,总是在一段时间后运行并调用一个函数。下面定义的任务将用于向所有节点发送广播消息。该任务立即采用三个参数。第一个定义任务调用函数的频率,接下来,它要求任务的生命周期,最后,它需要一个指向调用函数的指针。

 

任务taskSendMessage(TASK_SECOND * 1, TASK_FOREVER, &sendMessage);

 

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

 

无效发送消息(){
  字符串味精 = construnct_json();
  网格.sendBroadcast(味精);
  taskSendMessage.setInterval(随机(TASK_SECOND * 1, TASK_SECOND * 5));
}

 

接下来,我们有我们的receivedCallback()函数。每当有新消息到达时,此函数就会被调用。如果你想对收到的消息做些什么,你需要调整这个函数来完成你的工作。这个函数有两个参数——节点id和作为指针的消息。

 

无效接收回调(uint32_t来自,字符串&msg){
  Serial.printf("startHere: 接收自 %u msg=%s\n", from, msg.c_str());
}

 

接下来,我们有我们的newConnectionCallback( ) 函数。每当有新设备添加到网络时,此函数都会调用,并将打印语句发送到串行监视器。

 

无效新连接回调(uint32_t nodeId){
  Serial.printf("--> startHere: 新连接, nodeId = %u\n", nodeId);
}

 

接下来,我们有我们的nodeTimeAdjustedCallback()。此回调函数负责无痛网格所需的所有时间必需品。

 

无效节点时间调整回调(int32_t 偏移量){
  Serial.printf("调整时间 %u.Offset = %d\n", mesh.getNodeTime(), offset);
}

 

接下来,我们有我们的setup()函数。在设置中,我们初始化串口并打印节点名称。这很有帮助,因为一旦对所有节点进行了编程,我们就可以使用串行监视器轻松识别节点。我们还有记录任何 ERROR | 的网格对象的setDebugMsgTypes()类。启动消息。接下来,我们通过将 SSID、密码和端口号传递给init()函数来初始化网格。请记住,这些函数还需要指向调度程序的指针才能正常工作。

 

序列号.开始(115200);
Serial.println(nodeName);
网格.setDebugMsgTypes(错误|启动);// 在 init() 之前设置,以便您可以看到启动消息
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);

 

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

 

网格.onReceive(&receivedCallback);
 网格.onNewConnection(&newConnectionCallback);
 网格.onChangedConnections(&changedConnectionCallback);
 mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

 

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

 

userScheduler.addTask(taskSendMessage);
taskSendMessage.enable();

 

接下来,在设置部分,我们拥有所有必要的ifdef和endif宏,它们用于根据要求初始化不同的传感器。首先,我们将配置 BME280 传感器。下面的代码初始化 BME280 传感器并检查传感器的版本,因为 BME280 传感器带有许多不同的版本。

 

#ifdef BME_280
  Wire.begin();
  而(!bme.begin())
  {
    Serial.println("找不到 BME280 传感器!");
    延迟(1000);
  }
  // bme.chipID(); // 已弃用。请参阅芯片模型()。
  开关 (bme.chipModel())
  {
    案例 BME280::ChipModel_BME280:
      Serial.println("找到 BME280 传感器!成功。");
      休息;
    案例 BME280::ChipModel_BMP280:
      Serial.println("找到 BMP280 传感器!没有可用的湿度。");
      休息;
    默认:
      Serial.println("发现未知传感器!错误!");
  }
#万一

 

最后,我们在ifdef和#endif方法的帮助下配置了 DHT22 和 DS18B20 传感器。这标志着setup()函数的结束。

 

#ifdef DHT22
  Serial.println(F("DHTxx 开始!"));
  dht.begin();
#万一
#ifdef DS18B20
  ds18b20.begin();
  Serial.println(F("DS18B20 开始!"));
#万一

 

接下来,我们有我们的循环。在这段代码中,循环 剂量没有做太多,它只是在mesh.update()方法的帮助下更新网格。它负责所有任务。如果此更新方法不存在,这些任务将不起作用。

 

无效循环()
{
  网格。更新();
  // construnct_json();
}

 

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

 

字符串 construnct_json()
{
#ifdef BME_280
  bme.read(pres, temp, hum, tempUnit, presUnit);
  压力 = 压力 / 100;
  myVar["传感器类型"] = "BME280";
  myVar["节点名称"] = nodeName;
  myVar["Temperature"] = serialized(String(temp, 2));
  myVar["pres"] = 序列化(String(pres, 2));
#ifdef ENABLE_LOG
  Serial.println(JSON.stringify(myVar));
#万一
  返回 JSON.stringify(myVar);
#万一

 

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

 

#ifdef DHT22
  temp = dht.readTemperature();
  嗡嗡声 = dht.readHumidity();
  myVar["传感器类型"] = "DHT22";
  myVar["节点名称"] = nodeName;
  myVar["Temperature"] = 序列化(String(temp));
  myVar["湿度"] = 序列化(String(hum));
#ifdef ENABLE_LOG
  Serial.println(JSON.stringify(myVar));
#万一
  返回 JSON.stringify(myVar);
#万一
#ifdef DS18B20
  ds18b20.requestTemperatures();
  temp = ds18b20.getTempCByIndex(0);
  myVar["传感器类型"] = "DS18B20";
  myVar["节点名称"] = nodeName;
  myVar["Temperature"] = 序列化(String(temp));
#ifdef ENABLE_LOG
  Serial.println(JSON.stringify(myVar));
#万一
  返回 JSON.stringify(myVar);
#万一
}

 

  现在,随着编码过程的完成;我们注释或取消注释顶部定义的宏;根据我们要上传的板。接下来,我们给每个节点一个唯一的名称,我们只需上传代码。如果一切正常,代码将正确编译和上传,没有任何错误。

  基于 ESP8266 和 ESP32 的网状网络 - 测试

网络协议

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

网络协议

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

#include
#include
//#define BME_280
#定义DHT22
//#定义DS18B20
//#define ENABLE_LOG
字符串节点名 = "NODE_4"; // 名称需要唯一
浮动温度(NAN),嗡嗡声(NAN),压力(NAN);
//########################## Init_BME280 #################### #####
#ifdef BME_280
#include
#include
BME280I2C bme;
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
BME280::PresUnit presUnit(BME280::PresUnit_Pa);
#万一
//__________________________ _BME280 结束 __________________________
//########################## Init_DHT22 ##################### #####
#ifdef DHT22
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
#万一
//__________________________ DHT22 结束 __________________________
//######################### Init_Ds18B20 #################### #####
#ifdef DS18B20
#include
#include <达拉斯温度.h>
常量 int oneWireBus = 4;
单线单线(oneWireBus);
DallasTemperature ds18b20(&oneWire);
#万一
//__________________________ DHT22 结束 __________________________
#define MESH_PREFIX "whateverYouLike"
#define MESH_PASSWORD "somethingSneaky"
#define MESH_PORT 5555
调度器用户调度器;// 控制你的个人任务
无痛网眼;
JSONVar myVar;
无效发送消息(){
字符串味精 = construnct_json();
网格.sendBroadcast(味精);
taskSendMessage.setInterval(随机(TASK_SECOND * 1, TASK_SECOND * 5));
}
任务taskSendMessage(TASK_SECOND * 1, TASK_FOREVER, &sendMessage);
// 无痛库需要
无效接收回调(uint32_t来自,字符串&msg){
Serial.printf("startHere: 接收自 %u msg=%s\n", from, msg.c_str());
}
无效新连接回调(uint32_t nodeId){
Serial.printf("--> startHere: 新连接, nodeId = %u\n", nodeId);
}
无效更改连接回调(){
Serial.printf("改变的连接\n");
}
无效节点时间调整回调(int32_t 偏移量){
Serial.printf("调整时间 %u.Offset = %d\n", mesh.getNodeTime(), offset);
}
无效设置()
{
序列号.开始(115200);
Serial.println(nodeName);
//mesh.setDebugMsgTypes(错误|启动|MESH_STATUS|连接|同步|通信|一般|MSG_TYPES|远程); // 所有类型开启
网格.setDebugMsgTypes(错误|启动);// 在 init() 之前设置,以便您可以看到启动消息
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
网格.onReceive(&receivedCallback);
网格.onNewConnection(&newConnectionCallback);
网格.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
userScheduler.addTask(taskSendMessage);
taskSendMessage.enable();
#ifdef BME_280
Wire.begin();
而(!bme.begin())
{
Serial.println("找不到 BME280 传感器!");
延迟(1000);
}
// bme.chipID(); // 已弃用。请参阅芯片模型()。
开关 (bme.chipModel())
{
案例 BME280::ChipModel_BME280:
Serial.println("找到 BME280 传感器!成功。");
休息;
案例 BME280::ChipModel_BMP280:
Serial.println("找到 BMP280 传感器!没有可用的湿度。");
休息;
默认:
Serial.println("发现未知传感器!错误!");
}
#万一
#ifdef DHT22
Serial.println(F("DHTxx 测试!"));
dht.begin();
#万一
#ifdef DS18B20
ds18b20.begin();
#万一
}
无效循环()
{
网格。更新();
// construnct_json();
}
字符串 construnct_json()
{
#ifdef BME_280
bme.read(pres, temp, hum, tempUnit, presUnit); // 用新值更新
压力 = 压力 / 100;
myVar["传感器类型"] = "BME280";
myVar["节点名称"] = nodeName;
myVar["Temperature"] = serialized(String(temp, 2)); // 序列化需要转换浮点值
myVar["pres"] = serialized(String(pres, 2));// 序列化需要转换flot值
#ifdef ENABLE_LOG
Serial.println(JSON.stringify(myVar)); //stringify 将数组转换为字符串
#万一
返回 JSON.stringify(myVar);
#万一
#ifdef DHT22
temp = dht.readTemperature();
嗡嗡声 = dht.readHumidity();
myVar["传感器类型"] = "DHT22";
myVar["节点名称"] = nodeName;
myVar["Temperature"] = 序列化(String(temp));
myVar["湿度"] = 序列化(String(hum));
#ifdef ENABLE_LOG
Serial.println(JSON.stringify(myVar));
#万一
返回 JSON.stringify(myVar);
#万一
#ifdef DS18B20
ds18b20.requestTemperatures();
temp = ds18b20.getTempCByIndex(0);
myVar["传感器类型"] = "DS18B20";
myVar["节点名称"] = nodeName;
myVar["Temperature"] = 序列化(String(temp));
#ifdef ENABLE_LOG
Serial.println(JSON.stringify(myVar));
#万一
返回 JSON.stringify(myVar);
#万一
}

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分