網狀網路?

zh.wikipedia.org

網狀網路(Mesh Network)在wiki上有了很詳細的介紹,其特點可以簡單描述為:

  • 所有節點都可與拓撲中所有節點進行連線而形成一個「區域網路」
  • 所有節點可以透過多次跳躍進行數據通信
  • 即使在拓撲中有節點無法服務或過於忙碌,網路還是可以正常運作

對於esp8266來說,網狀網路的意義是什麼呢?官方有個簡單的介紹:

隨著物聯網的發展,物聯網節點規模迅速擴張,但路由器可供直接接入的節點數有限(通常少於 32 個)。針對此問題,樂鑫開發了 ESP-MESH 協議。在 Mesh 網路中,節點之間可以組成網路並轉發數據包。這樣不需要改變路由即可實現大量節點連接到網路。

在網狀網路中,每個節點都是一個AP(AccessPopint),可以被其他節點連接,也可以連接一個其他的節點;ESP8266單個節點最多支持4個連接,ESP32可以支持到10個。

1、既然這麼厲害,來看看示例代碼的效果

ESP8266示例: HelloMesh

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMesh.h>

unsigned int request_i = 0;
unsigned int response_i = 0;

String manageRequest(String request);

/* Create the mesh node object */
ESP8266WiFiMesh mesh_node = ESP8266WiFiMesh(ESP.getChipId(), manageRequest);

/**
* Callback for when other nodes send you data
*
* @request The string received from another node in the mesh
* @returns The string to send back to the other node
*/
String manageRequest(String request)
{
/* Print out received message */
Serial.print("received: ");
Serial.println(request);

/* return a string to send back */
char response[60];
sprintf(response, "Hello world response #%d from Mesh_Node%d.", response_i++, ESP.getChipId());
return response;
}

void setup()
{
Serial.begin(115200);
delay(10);

Serial.println();
Serial.println();
Serial.println("Setting up mesh node...");

/* Initialise the mesh node */
mesh_node.begin();
}

void loop()
{
/* Accept any incoming connections */
mesh_node.acceptRequest();

/* Scan for other nodes and send them a message */
char request[60];
sprintf(request, "Hello world request #%d from Mesh_Node%d.", request_i++, ESP.getChipId());
mesh_node.attemptScan(request);
delay(1000);
}

把上面的示例分別寫入兩台ESP8266設備,打開串口監視器可以看到設備先是各自設置好mesh網路,然後嘗試連接,連接上了之後分別列印出"Hello world request #1 from Mesh_Node 12312312.",這樣就已經成功建立了一個Mesh網路並相互通信了。

2、在這個基礎上進行修改,實現從在A發出控制信息到B,B接收後做出相應的控制響應

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMesh.h>

unsigned int request_i = 0;
unsigned int response_i = 0;

#define LED_PIN = D2 // 定義LED燈輸出引腳
#define SW_PIN = D0 // 定義開關控制輸入引腳

String manageRequest(String request);
ESP8266WiFiMesh mesh_node = ESP8266WiFiMesh(ESP.getChipId(), manageRequest);

String manageRequest(String request)
{
Serial.print("received: ");
Serial.println(request);

char response[60];
sprintf(response, "Hello world response #%d from Mesh_Node%d.", response_i++, ESP.getChipId());
if (request == "ON") { // 收到的消息是"ON"
digitalWrite(LED_PIN, HIGH); // 打開LED
delay(500); // 延遲500毫秒
}
digitalWrite(LED_PIN, LOW); // 關閉LED
return response;
}

void setup()
{
Serial.begin(115200);
delay(10);

pinMode(LED_PIN, OUTPUT);
pinMode(SW_PIN, INPUT);

Serial.println();
Serial.println();
Serial.println("Setting up mesh node...");

mesh_node.begin();
}

void loop()
{
mesh_node.acceptRequest();

int ledStatus = digitalRead(SW_PIN); // 讀取開關的狀態
if (ledStatus == 1) {
char request[2];
sprintf(request, "ON");
mesh_node.attemptScan(request); // 發送打開LED燈的消息:"ON"
} else {
char request[60];
sprintf(request, "Hello world request #%d from Mesh_Node%d.", request_i++, ESP.getChipId());
mesh_node.attemptScan(request);
}
delay(1000);
}

上述程序分別寫入到兩個ESP8266設備(設備A/設別B);設備A的D0上連接一個控制開關,設備B的D2上連接一個輸入LED燈。成功建立連接後,在A上打開開關(digitalRead(SW_PIN) == 1),發送打開的消息給B,B成功收到後打開D2的led燈。

如果把開關常開的話,B設備表現為間隔若干秒後LED燈打開。預期應該是每次打開LED燈的間隔都是一樣的了;基於這個情況,記錄了連續十次LED打開的時間間隔(手動計時,誤差±1s):

可以看到#1和#9的時間是明顯大於其他的。出現這樣的情況主要有以下兩個原因:

  • 消息並不是每條都能準確被收到
  • 設備之間需要調整時鐘信號進行數據傳輸

3、藉助painlessMesh實現控制LED燈

painlessMesh(gitlab / github)是一個包,用來創建一個基於ESP8266/ESP32的簡單網狀網路。

The goal is to allow the programmer to work with a mesh network without having to worry about how the network is structured or managed.

簡單看下幾個常用的api:

  • init(): 初始化mesh網路(網路名稱/密碼/埠等信息)
  • onReceive(): 當設備接收到消息的時候執行該回調
  • onNewConnection(): 當設備有新的節點接入的時候執行該回調
  • onNodeTimeAdjusted(): 把當前設備的時間跟mesh網路的時間進行對比校正
  • sendBroadcast(): 發送廣播消息
  • sendSingle(): 對指定節點發送消息
  • update(): 在每次loop的時候都需要更新當前節點的mesh網路狀態等信息,沒有這個是無法正常運作的

明白了主要的api的作用,再來看給出的示例代碼就很簡單了

控制端設備A:

#include "painlessMesh.h"

#define MESH_PREFIX "ESPMESH" // 設置當前MESH網路的名稱
#define MESH_PASSWORD "mypassword" // 密碼
#define MESH_PORT 5555 // 埠

painlessMesh mesh;

void receivedCallback( uint32_t from, String &msg );

void sendMsg(int lesStatus) {
// 以json的形式進行包裝數據
// 為什麼用json:
// https://gitlab.com/painlessMesh/painlessMesh#json-based
DynamicJsonBuffer jsonBuffer;
JsonObject& msg = jsonBuffer.createObject();
msg["msgId"] = millis();
msg["topic"] = "ledStatus";
msg["status"] = lesStatus;
msg["nodeId"] = mesh.getNodeId();

String str;
msg.printTo(str); // 把json轉成string
mesh.sendBroadcast(str); // 發送廣播消息

msg.printTo(Serial);
Serial.printf("
");
}

void setup() {
Serial.begin(115200);
mesh.setDebugMsgTypes( ERROR | CONNECTION | S_TIME ); // 設置需要展示的debug信息(列印到串口監視器)
mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6 ); // 初始化mesh網路

mesh.onNewConnection([](size_t nodeId) { // 新節點接入的時候
Serial.printf("New Connection %u
", nodeId); // 列印節點唯一id
});

mesh.onDroppedConnection([](size_t nodeId) { // 節點連接丟失的時候
Serial.printf("Dropped Connection %u
", nodeId); // 列印丟失的節點id
});

mesh.onReceive(&receivedCallback); // 收到消息的時候,執行receivedCallback()
}

void loop() {
int lesStatus = digitalRead(D0); // 獲取開關狀態
if (lesStatus == 1) { // 狀態是1
sendMsg(lesStatus); // 把狀態消息發送出去
} else {
Serial.println("status 0");
}
mesh.update(); // 更新mesh網路
delay(1000);
}

void receivedCallback( uint32_t from, String &msg ) { // 收到消息的時候,執行該回調方法,列印收到的消息
Serial.printf("control server: Received from %u msg=%s
", from, msg.c_str());
}

被控制端設備B:

#include "painlessMesh.h"

#define MESH_PREFIX "ESPMESH" // 名稱/密碼/埠一致才能連接到同一個mesh網路
#define MESH_PASSWORD "mypassword"
#define MESH_PORT 5555

void receivedCallback( uint32_t from, String &msg );

painlessMesh mesh;

void setup() {
Serial.begin(115200);
pinMode(D2, OUTPUT);

mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION );

mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
mesh.onReceive(&receivedCallback);
}

void loop() {
mesh.update();
}

void receivedCallback( uint32_t from, String &msg ) { // 被控制設備接收到消息,執行該回調
Serial.printf("echoNode: Received from %u msg=%s
", from, msg.c_str());
mesh.sendSingle(from, msg);

// 重新把string轉化成json進行解析
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(msg);
if (root.containsKey("topic")) {
if (String("ledStatus").equals(root["topic"].as<String>())) {
digitalWrite(D2, root["status"].as<uint32_t>()); // 打開D2引腳控制的led
delay(500);
}
}
digitalWrite(D2, LOW);
}

開關用了一個循跡感測器,原理是一樣的

兩段程序分別寫入到兩個ESP8266設備(設備A/設別B);效果同[2]中的一樣,在設備A控制設備B的LED燈。打開串口監視器,會列印了很多的調試信息,簡單分析之後,同樣可以得出[2]中說的兩點信息:

  • 消息並不是每條都能準確被收到
    • 設備A連續發送了多條信息,設備B要麼沒有響應(丟失)要麼是延遲響應(傳輸/接收延時)
  • 設備之間需要調整時鐘信號進行數據傳輸
    • onNodeTimeAdjusted()這個方法可以列印出來校正的時間誤差

TIPS:

  • 消息的傳輸是不能完全信賴的,需要機制來保證,因為:
    • 消息會丟失
    • 消息會因為阻塞被丟棄
  • 如何保證消息被成功傳輸?
    • 用較高的頻率重複發送同一個消息,即使有一些丟失,但是也能有一些被成功傳輸
    • 接收端發送確認接收的信息給發送端,如果沒有收到確認接收的消息就繼續發送
      • 接收端發送確認接收的信息也需要考慮這個消息能否被發送端接收

例子視頻

視頻封面

4、EasyMesh

這個庫跟painlessMesh很像,但是作者沒有維護了,不建議使用。但是作者做了一個很酷的demoToy,不過很複雜-_-||

Examples demoToy is currently the only example. It is kind of complex, uses a web server, web sockets, and neopixel animations, so it is not really a great entry level example. That said, it does some pretty cools stuff… here is a video of the demo.

-- easyMesh

為了demoToy,作者寫了一個easyWebServer + easyWebSocket。能翻牆的可以在YouTube上看,或者下面??

視頻封面

源碼地址:

CNBlackJ/iot-lesson?

github.com
圖標

推薦閱讀:
相关文章