Project4 - Esp32 verbindung mit Meshnetzwerk ============================================ Projektaufbau -------------- Eines der letzten Projekte behandelte das Thema MQTT und das Broker Netzwerk, welches ein gerne genutztes Protokoll für IoT Gerätekommunikation ist. Dieses mal soll es um eine weitere Vernetzungsmöglichkeit für IoT Geräte gehen - **Ein Mesh Netzwerk**. Um das Mesh-Netzwerk zu testen, werden vier ESP32 verwendet. Es gibt eine Collector Node, die eine LED-Diode mit dem IO-Port 02 verbunden hat. Diese Node erwartet ein Ein- und Ausschaltsignal für diese Diode sowie Temperaturwerte, die dann im seriellen Output geloggt werden. Zum Senden des Ein- und Ausschaltsignals und der Temperatur werden jeweils zwei weitere Nodes verwendet. Zur Verdeutlichung des Mesh-Netzwerks sind die beiden Nodes, die Daten senden, weiter entfernt, und die vierte Node dient zur Überbrückung der Distanz. Meshnetzwerk ------------- Ein Meshnetzwerk ist ein Netzwerk, in dem alle Geräte bzw. Knoten direkt oder indirekt miteinander kommunizieren können – ohne einen zentralen Router. Jeder Knoten kann: - Daten empfangen und senden - Nachrichten an andere Knoten weiterleiten (Relay-Funktion) - Neue Verbindungen automatisch aufbauen, wenn Knoten sich verbinden oder trennen .. image:: ./img/project4/mesh-network.png :alt: Mesh Netzwerk Verwendete Techniken --------------------- Painless Mesh ^^^^^^^^^^^^^^ **Überblick** ``painlessMesh`` ist eine Arduino-kompatible Bibliothek zur einfachen Erstellung von dynamischen Mesh-Netzwerken mit ESP8266- und ESP32-Mikrocontrollern. Die Bibliothek verwaltet automatisch: - die Verbindungsstruktur zwischen Knoten - Routing von Nachrichten - Wiederverbindungen bei Netzänderungen **Grundfunktionen** - Netzwerkaufbau ohne zentralen Server oder Gateway - Automatische Adressierung und Weiterleitung von Nachrichten - Unterstützung für Broadcast- und direkte Kommunikation - Kompatibel mit ESP8266 und ESP32 - Unterstützung für Task-Scheduling über Scheduler **Nutzung** .. code-block:: cpp #include painlessMesh mesh; void setup() { mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION); // Optional: Debug-Ausgaben aktivieren mesh.init("NetzwerkName", "Passwort", 5555, WIFI_AP_STA, 6); // Initialisierung mesh.onReceive([](uint32_t from, String &msg) { Serial.println("Nachricht empfangen: " + msg); //Ausgabe von empfangenen Nachrichten }); } void loop() { mesh.update(); // Muss regelmäßig aufgerufen werden } **Nutzung mit Scheduler** Mit einem Scheduler-Objekt können wiederkehrende Aufgaben eingeplant werden: .. code-block:: cpp Scheduler userScheduler; // Scheduler-Objekt für benutzerdefinierte periodische Tasks Task blinkTask(1000, TASK_FOREVER, []() { // Definition eines Tasks, der alle 1000 ms (1 Sekunde) ausgeführt wird digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Onboard-LED an- und ausschalten }); void setup() { mesh.init("NetzwerkName", "Passwort", &userScheduler, 5555, WIFI_AP_STA, 6); // Initialisierung mit scheduler userScheduler.addTask(blinkTask); // Fügt den blinkTask dem Scheduler hinzu blinkTask.enable(); // Aktiviert den Task, sodass er automatisch im Hintergrund läuft } **Vorteile** - Keine zentrale Steuerung notwendig (keine Single Points of Failure) - Skalierbar durch automatische Topologieanpassung - Ideal für mobile oder instabile Funkverbindungen - Geeignet für dezentrale IoT-Netze **Einschränkungen** - Keine Ende-zu-Ende-Verschlüsselung eingebaut - Keine Authentifizierung einzelner Nodes - Begrenzte Reichweite und Bandbreite durch WiFi-Basis Arduino IDE ^^^^^^^^^^^^ Die Arduino IDE ist eine plattformübergreifende Entwicklungsumgebung zur Programmierung von Mikrocontrollern. Mit ihr kann auch der ESP32 programmiert, konfiguriert und überwacht werden. Sie bietet Funktionen zum Editieren, Kompilieren, Hochladen von Code sowie zur seriellen Kommunikation mit dem Gerät. Zur verwendung von ESP32 muss die Boardunterstützung zuerst über den Boardverwalter hinzugefügt werden. Nach dem Verbinden per USB Kabel muss auch noch der jeweilige USB Port in der IDE ausgewählt werden. Das schreiben, kompilieren und hochladen des Codes ist ebenfalls sehr einfach: #. Schreibe den Code in eine .ino-Datei in der IDE. #. Klicke auf das ``Hochladen``-Symbol (Pfeil nach rechts). #. Die IDE kompiliert den den Code und lädt ihn auf den ESP32. #. Beim hochladen des Codes muss die ``BOOT``-Taste des ESP32 gedrückt gehalten werden. Sobald der ESP32 den Code ausführt kann man sich die seriellen Logs direkt in der IDE direkt anzeigen lassen. Hierzu muss man lediglich den Serial Montor unter Tools öffnen und die Baudrate vor dem verbinden richtig einstellen. .. image:: ./img/project4/ArduinoIDE.png :alt: Arduino IDE Knoten ------- Im Folgenden werden die einzelnen Knoten mit ihrer jeweiligen Funktion erläutert. Node_Bridge ^^^^^^^^^^^ **Funktion:** Diese Node dient als Brücke zwischen der collector Node und den beiden Sender Nodes. **Code:** Starten die serielle Verbindung zum Computer mit 115200 Baud: .. code-block:: cpp Serial.begin(115200); Folgender Code dient zur Ausgabe von Texten im seriellen Monitor: .. code-block:: cpp mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION); Zeigt bestimmte Debug-Nachrichten: - ERROR: Fehler - STARTUP: Startinformationen - CONNECTION: Verbindungsstatus Starten und Verbinden mit Hilfe der PainlessMesh Bibliothek: .. code-block:: cpp mesh.init("espMesh", "password123", 5555, WIFI_AP_STA, 6); verwendete Parameter: - "espMesh": Name (SSID) des Mesh-Netzwerks – muss bei allen Knoten gleich sein - "password123": Passwort des Netzwerks - 5555: Port, über den kommuniziert wird (bei allen Knoten gleich) - WIFI_AP_STA: WLAN-Modus (Access Point + Station) für Mesh-Funktion - 6: WLAN-Kanal – muss bei allen Knoten gleich sein Bei erfolgreicher Verbindung weiterer Nodes erscheinen folgende Nachrichten in den Logs: .. code-block:: bash CONNECTION: eventScanDoneHandler: ARDUINO_EVENT_WIFI_SCAN_DONE CONNECTION: scanComplete(): Scan finished CONNECTION: scanComplete():--> Cleared old APs. CONNECTION: scanComplete(): num = 26 CONNECTION: found : espMesh, -57dBm CONNECTION: found : espMesh, -57dBm CONNECTION: Found 2 nodes CONNECTION: connectToAP(): No unknown nodes found scan rate set to normal Hier sieht man wie viele Nodes Verbunden sind und deren Verbindungsstärke. Node_Collector ^^^^^^^^^^^^^^^ **Funktion:** Dieser Knoten empfängt Nachrichten von anderen Geräten und: - Schaltet eine verbundene LED an oder aus (erwartete Signale: LED_ON / LED_OFF) - Gibt eine Meldung aus, wenn Temperaturdaten empfangen werden - Logged jede Nachricht mit Zeitstempel **Code:** Erstellung der Zeit-Angabe: .. code-block:: cpp void receivedCallback(uint32_t from, String &msg) { String timestamp = String(hour()) + ":" + String(minute()) + ":" + String(second()); logMessages += "[" + timestamp + "] From " + String(from) + ": " + msg + "\n"; } LED-Reaktion: .. code-block:: cpp if (msg == "LED_ON") { digitalWrite(LED_PIN, HIGH); } else if (msg == "LED_OFF") { digitalWrite(LED_PIN, LOW); } Temperaturerkennung: .. code-block:: cpp else if (msg.indexOf("Temp") != -1) { Serial.println("warm:"); } Zeitstempel setzen für das Logging: .. code-block:: cpp setTime(12, 0, 0, 1, 1, 2025); Auch hier die Mesh-Initialisierung (mit userScheduler): .. code-block:: cpp mesh.init("espMesh", "password123", &userScheduler, 5555, WIFI_AP_STA, 6); Timeoutwarnung mit Scheduler: .. code-block:: cpp Task checkTimeoutTask(10000, TASK_FOREVER, []() { if (millis() - lastMessageTime > 15000) { Serial.println("WARNUNG: Keine Nachricht seit 15 Sekunden! Bitte Verbindung zu Sendern prüfen."); } }); Variablen: - `#define LED_PIN 2` - Deklaration der Pin, über die die LED gesteuert wird - `String logMessages = "";` - String zur Ausgabe der Logs - `bool ledState = false;` - LED zu beginn auf "OFF" Hier ist ein Beispiel der Log Meldungen aus dem Serial Monitor: .. code-block:: bash LOG: [12:0:25] From 4234133741: {"temp": 20.80} LOG: [12:0:25] From 4234133741: {"temp": 29.50} LOG: [12:0:25] From 4234133741: {"temp": 24.60} LOG: [12:0:26] From 3520580029: LED_ON LOG: [12:0:26] From 4234133741: {"temp": 21.20} LOG: [12:0:27] From 4234133741: {"temp": 29.60} LOG: [12:0:28] From 4234133741: {"temp": 23.20} LOG: [12:0:29] From 4234133741: {"temp": 27.10} LOG: [12:0:30] From 4234133741: {"temp": 29.60} LOG: [12:0:31] From 3520580029: LED_OFF Node_Led_Signal ^^^^^^^^^^^^^^^^ **Funktion:** Dieser Knoten sendet alle 5 Sekunden eine Nachricht basierend auf dem internen Zustand: - "LED_ON" wenn die LED ausgeschaltet war - "LED_OFF" wenn die LED an war **Code:** Das Senden der Nachrichten passiert über einen Task (repetitive Funktion): .. code-block:: cpp Task sendMessageTask(SEND_INTERVAL, TASK_FOREVER, []() { ledState = !ledState; String msg = ledState ? "LED_ON" : "LED_OFF"; mesh.sendBroadcast(msg); }); Die Messagetask wird anhand von Scheduler aktualisiert, hierfür muss man den Task aktivieren: .. code-block:: cpp userScheduler.addTask(sendMessageTask); sendMessageTask.enable(); Empfangsbestätigung der Nachricht: .. code-block:: cpp mesh.onReceive([](uint32_t from, String &msg) { Serial.println("Received something: " + msg); }); Variablen: `bool ledState = false;` - Intern gespeicherter Zustand der LED Node_Temperature ^^^^^^^^^^^^^^^^^ Dieser Knoten simuliert eine Temperaturmessung und sendet sie jede Sekunde als Nachricht: .. code-block:: cpp float fakeTemp = random(200, 300) / 10.0; String msg = "{\"temp\": " + String(fakeTemp) + "}"; mesh.sendBroadcast(msg); Auch hier wird ein Task verwendet, damit der Code unabhängig von loop() regelmäßig läuft. .. code-block:: cpp userScheduler.addTask(taskSendTemp); taskSendTemp.enable(); Vergleich Mesh und MQTT-Broker ------------------------------- **Struktur** - Mesh-Netzwerk: Dezentral, jeder ist Knoten - MQTT mit zentralem Broker: Zentraler Server (Broker) **Kommunikation** - Mesh-Netzwerk: Direkt oder über Relais-Knoten - MQTT mit zentralem Broker: Über definierte Topics via Broker **Abhängigkeit** - Mesh-Netzwerk: Kein zentraler Punkt - MQTT mit zentralem Broker: Broker als zentraler Punkt notwendig **Robustheit** - Mesh-Netzwerk: Hoch (Selbstheilung möglich) - MQTT mit zentralem Broker: Geringer bei Ausfall des Broker **Einrichtung** - Mesh-Netzwerk: Bibliothek + einfache Initialisierung - MQTT mit zentralem Broker: Komplexer (hostapd, DHCP, Broker) **Beobachtung / Logging** - Mesh-Netzwerk: Lokale Logs auf Geräten - MQTT mit zentralem Broker: Zentrale Protokollierung über Broker **Beispielhafte Anwendung** - Mesh-Netzwerk: LED-Steuerung, Temperaturwarnung - MQTT mit zentralem Broker: Schaltbefehle über Weboberfläche Meshnetzwerk-Test ------------------ Ziel des Tests ^^^^^^^^^^^^^^ Überprüfung der Reichweite und Funktionalität der automatischen Nachrichtenweiterleitung im Mesh-Netzwerk mithilfe von ``painlessMesh``. Testaufbau ^^^^^^^^^^ Drei ESP32-Boards wurden wie folgt verwendet: - **Gerät A:** Sender-Node - **Gerät B:** Empfänger-Node - **Gerät C:** Diente als *Brücke* (Relay-Knoten), also eine Repeater-Funktion im Mesh Ablauf ^^^^^^ #. **Startkonfiguration:** Alle drei Geräte befinden sich in Funkreichweite zueinander. Gerät A sendet Temperaturdaten. Gerät B empfängt und loggt die Daten korrekt. #. **Entfernungstest:** Gerät A wurde schrittweise vom restlichen Aufbau entfernt. Sobald Gerät A **außer Reichweite** von Gerät B war, festgestellt durch Timeoutwarnung, kam es zu **Nachrichtenausfällen** – die Temperaturdaten wurden nicht mehr empfangen. #. **Mesh-Wiederherstellung:** Gerät C wurde nun **zwischen A und B** positioniert – auf halber Strecke, sodass **beide Geräte wieder Reichweite zu Gerät C** hatten. Sofort nach der neuen Positionierung wurde das Netzwerk **dynamisch reorganisiert**. Die Nachrichten von A konnten **über Gerät C an B** weitergeleitet werden. Die Logs auf Gerät B zeigten erneut die Temperaturdaten von Gerät A – das Mesh funktionierte wieder zuverlässig. Beobachtungen ^^^^^^^^^^^^^ - **Ohne Relay-Gerät:** keine Verbindung zwischen A und B bei zu großer Entfernung - **Mit Relay-Gerät:** Netzwerk funktionierte wie gewünscht – automatische Weiterleitung durch dynamische Mesh-Struktur - Die Umorganisation dauerte **nur wenige Sekunden** – dies zeigt die **Selbstheilungsfähigkeit** des Meshnetzwerks Schlussfolgerung ^^^^^^^^^^^^^^^^ Der Test demonstrierte anschaulich die Stärke eines Meshnetzwerks: - **Flexible Topologie** - **Keine Notwendigkeit fester Infrastruktur** - **Dynamische Anpassung** an die Umgebung und Funkreichweite Die praktische Umsetzung zeigte, dass ``painlessMesh`` auch unter realen Bedingungen mit Entfernung und Hindernissen zuverlässige Kommunikation sicherstellen kann – vorausgesetzt, ein Relay-Gerät ist innerhalb der erforderlichen Reichweite vorhanden. Fazit ------ **painlessMesh** vereinfacht das Erstellen stabiler **Meshnetzwerke** mit ESP32. Die Kombination aus Scheduler, onReceive() und sendBroadcast() erlaubt: - Automatische Nachrichtenverarbeitung - Wiederholte Aufgaben ohne die Haupt-Loop zu blockieren Die Knoten können verschiedenste Rollen übernehmen: Sender, Empfänger, Relais, Logger usw. Quellen ------- - [C++](https://www.geeksforgeeks.org/c-plus-plus/) - [PainlessMesh](https://gitlab.com/painlessMesh/painlessMesh) - [ArduinoIDE](https://www.arduino.cc/en/software/) - [ESP32](https://www.az-delivery.de/products/esp32-developmentboard)