在過去的幾年中,物聯網 (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
面包板
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);
#萬一
}
評論
查看更多