Komunikation zwischen mehreren Esp32 im Lan (Werte ändern)

Hallo,

ich habe eine Frage bezüglich Kommunikation zwischen mehreren ESP32,
der Einfachheit halber nenne ich sie mal A als Hauptakteur, und b1-b8 als Nebendarsteller.

Zielsetzung (Beispielhaft): Jeder mit einem RFID-Tag bekommt einmal am Tag
an einer von mehreren Ausgabestelle an unterschiedlichen Standorten (b1-b8) einen Keks.
Um dieses sicherzustellen, wird der RFI-Tag dort gelesen, und wenn noch keine Keks ausgegeben wurde,
geht eine grüne Lampe an. Der Ausgebende gibt den Keks aus, und drückt eine Taste zur Bestätigung,
Damit weiß das System das and die Person Heute schon ein Keks ausgegeben wurde.
Und beim nächsten Versuch an einer der Ausgabestellen gibt's halt kein grünes Licht für nen Keks.

A und b1-b8 werden sich in einem isoliertem VLAN befinden.
Zum Verwalten der Berechtigungen dient eine CSV-Datei. ( laufende Nr.(3) ; UID(14) ; Status(1) )
mit 999 Zeilen

Was ich bisher habe;
A ist ein ESP32-P4 mit SD Kartenslot, einem GPS Empfänger und einer RTC. Er dient auch als NTP Server für alle.
Sowie einem 2004 LCD und 5 Wege Tastenmodul zur Konfiguration und Statusanzeige.

b1-b8 sind ebenfalls je ein ESP32-P4, mit PN532 RFID-Leser, eine Leuchte z.B.:WS2812, und einem Taster,
sowie ein 128x64 Oled-Display und ein 5 Wege Tastenmodul zur Konfiguration und Fehleranzeige.

Was bisher läuft:

A :
NTP-Server läuft, Web-Server läuft, die SD Karte und somit die CSV wird zum Download angeboten.
Das werde ich noch ändern, so das die SD Karte eingesteckt wird, und die Datei auf Knopfdruck in das Littlefs auf A kopiert wird,
und von dort aus bereit steht. Dürfte stabiler sein.

b1:
Wenn er eingeschaltet wird, überprüft er ob A erreichbar ist, holt sich die Zeit, und lädt die CSV in sein Littlefs.
(Ich wollte vor dem Download noch eine Versionsüberprüfung machen, ob der Download notwendig ist,
aber bei 22MB Littlefs und einer Dateigröße von wenigen zig kB ist das hinfällig.)
Dann parst er die CSV in ein Char-Array. (Die Datei im Littlefs stellt sicher das es auch bei fehlender Verbindung geht)
Wenn jetzt ein RFID-Tag gelesen wird, schaut er im Array nach ob die UID vorhanden ist.
Wenn ja schaut er nach dem Status, wenn der z.B.:1 ist, macht er die Grüne Lampe für 5sec. an
und wartet auf einen Tastendruck. Wird die Taste gedrückt, wird der Status im Array auf Null gesetzt,
und wenn nicht, halt Lampe wieder aus und warten. Das ConfigMenü ist fertig.

Das heißt für einzelne Standorte funktioniert es,
Viel Text, für eine Frage. Ich hoffe aber das es für das Verständnis Hilfreich ist.

Und jetzt meine Frage,
Ich möchte das die Überprüfung für alle Standorte funktioniert, soll heißen, wenn ich an b1 nen Keks geholt habe,
soll ich auch an b2-b8 keinen mehr bekommen.

Meine Idee, ich würde die CSV auf A auch in eine Array einlesen, und b1-b8 sagen sie sollen erst bei A Fragen
wie der Status für die UID im Array ist, wenn 1 dann Keks ausgeben und A sagen das der Status auf 0 Gesetz wird.
Und im eigenen Array auch auf 0 setzten. Das Array auf b1-b8 dient dann zur Absicherung bei Netzwerkproblemen,
und wird nur bei nicht Erreichbarkeit von A abgefragt.

Nur wie ? Http, Udp oder... und auch noch einfach ;-)
Meine bisherige Recherche (Viel Lesen, mäßiges Verstehen) brachte die Meinung, Udp einfacher.

Für Anregungen und Vorschläge wäre ich Dankbar.

Gruß
Achim

Fast genau so: A ist der Master der Daten (1.) und ich würde nur dort die Datenhaltung machen. Wenn jetzt nicht 300 UIDs in jeder Sekunde einen Keks haben wollen, sollte sich der Netzwerkverkehr in Grenzen halten.

(2.) wäre ein (aus meiner Sicht) besser zu vermeidender Notnagel und nur erforderlich, wenn es unbedingt nötig ist, dass auch bei temporärer Nichterreichbarkeit von A Kekse ausgegeben werden sollen. Da ist dann aber bei Rück-Synchronisation Vorsicht geboten - b3 weiß dann u.U. nicht, dass auch b7 einen Keks für UID X ausgegeben hat.

Noch so ein kleiner Sync-Haken am Rande: Wenn der neue Tag anbricht, gibt es für einen Moment gar keine Kekse, bis alle zurückgesetzt sind und bestätigt haben?

Hallo,
vielen Dank für die Rückmeldung

300 Abfragen/Sec. werden es sicher nicht. Momentan gehe ich von max. 300 Anfragen in ca 1h aus.
Und das von 5 Standorten, zur Mittagszeit.
Damit mal weg vom Beispiel, ca. 300 Personen haben Anspruch auf ein Kostenfreies Mittagessen,
welches je nach Standort in ca. 1h-2h Ausgegeben wird. Bisher wurde das recht Rudimentär gelöst.
Aufgrund des steigenden Kostendrucks mach es langsam keinen Sinn mehr das so LaLa zu machen.

Damit ergibt sich auch Punkt 2, das es auch bei Netzwerkausfall funktioniert, ist ein muß.
Wenn es ausfällt, ist Rück-Synchronisation nicht wirklich ein Problem, mal ein Essen mehr OK.
100 Leute die sich Nachschlag holen dann schon eher.
Aber ich muss damit rechnen das eventuell dann doch 2 Geräte an einem Standort eingesetzt
werden daher die Anforderung das alle Geräte wissen an wenn eine Ausgabe stattfand.
Sonst hätte schon die jetzige Version gereicht. Aber die Leute bekommen schnell mit, das man an
der zweiten Ausgabe dann doch noch was bekommt.

Und der kleine Hacken sollte kein Problem sein, Die Ausgaben haben ca 3h geöffnet, entweder
die Geräte werden dann ausgeschaltet, oder ich setzte den Status zeitgesteuert zurück.
Am nächstem Tag ist alles wieder wie neu.

Tatsächlich habe ich hauptsächlich ein Problem mit der Umsetzung der Komunikation untereinander.
Also mit welchem Protokoll und wie fragt b bei A an, und sagt ihm dann das die Ausgabe stattfand.
Ohne das was verloren geht (UDP ?)
Wobei UDP einfacher wäre, glaube ich. A reagiert ja schon auf UDP wegen der NTP Funktion.

Naja und mein Status mit Arduino (C++) = Blutiger Anfänger, ist auch nicht förderlich.
Es freut mich, das ich mit meinem bisherigem Konzept nicht völlig auf dem Holzweg bin.

Gruß
Achim

Dann solltest du besser TCP verwenden. Ist def. sicherer (mit Quittung) und auch nicht komplizierter. Hier ein Beispiel.

Du meinst WLAN?

Verglichen mit UDP bin ich bei Dir.

Bei mir im Hinterkopf geistert eine Webseite, bereitgestellt von A, und Kommunikation mit GET/POST rum - wäre das auch eine Lösung oder ist das in Deinem Sinne TCP, weil http darauf aufbaut?

Ich habe es erstmal nur als Kommunikationweg im Vergleich zu UDP gesehen.

Hallo,

ich meine tatsächlich VLAN, (Virtual Local Area Network)
dieses wird durch die IT bereitgestellt. Daher auch der eigene NTP-Server,
weil ich nicht weiß wie isoliert es sein wird.

Ich betrachte das ganze so, als ob alle Geräte nur über ein Switch verbunden wären.
Deshalb hat jeder auch ein Menü für das Netzwerk, mit den notwendigen Punkten.
IP, NTP IP, Server IP oder DHCP usw. Tatsächlich sind sie Kilometerweit voneinander getrennt.

Gruß
Achim

Danke, wieder was gelernt :smiley:

Hallo,

davon ausgehend, das wenn eine Verbindung zu A besteht auch die Arrays gleich sind,
brauche ich praktisch nur eine Anfrage über TCP an A senden, Status von X ?,
A schaut mit int statusWert = aboArray[x][2][0] - '0'; nach,
und schickt mir den statusWert zurück.

b macht wenn Status = 1 die grüne Lampe an, wartet auf Tastendruck,
und wenn gedrückt, schickt er an A, Status von X ändern in 0, dieser ändert den Status mit
aboArray[x][2][0] = '0' + Status;

Wenn ich das jetzt richtig verstanden habe, brauche ich mir bei TCP keine Gedanken
drum machen ob es angekommen ist, (bis auf, verbindung fehlgeschlagen, nehme ich an).

Während ich bei UDP immer alles überprüfen muss, also nen Timeout wenn keine
Antwort kommt Anfrage nochmal senden, beim Ändern schauen ob eine Bestätigung
kommt und wieder nach bestimmter Zeit den Änderungswunsch nochmal senden.

Auch die Auslastung des (der) Kern(e) spielt bei TCP nicht so die Rolle wie bei UDP ?
Ich war schon am schauen, ob ich den UDP teil auf Kern 0 schiebe.
Da hat er Ruhe vor meinen Sachen im Loop und ist näher am Netzwerkteil,
da das ja auch auf Kern 0 läuft. (noch eine Baustelle mit ohne Ahnung)

Habe ich das so richtig verstanden ?

Gruß
Achim

Naja, du musst schon die empfangene Quittung nach Richtigkeit auswerten und darauf entsprechend reagieren.

Hallo,

aber ich bekomme in jedem Fall eine Rückmeldung, Fehler oder geforderten Wert,
hoffe ich. Also z.B. wenn Statuswert sowieso 0, Verbindung schließen.
Wenn Statuswert 1 und die Taste wird gedrückt, im sagen das er es in 0 ändert,
das OK abwarten und dann die Verbindung schließen.
Und noch das OK fürs Schließen überprüfen.

Und wenn mehrere gleichzeitig was wollen gibst es kein durcheinander, wie bei UDP möglich.
So meine Hoffnung.

Wobei mir da gerade auffällt, wenn er auf den Tastendruck wartet, muß ich die Verbindung
schließen? Und zum ändern wieder Aufbauen.
Oder kann ich die Verbindung bestehen lassen, füe die ca. 5 Sek.
Wenn ich das richtig gelesen habe sind für den ESP 32 durch espressif in Arduino 10 Sockets
die Vorgabe, somit dürfte das ja kein problem machen die Verbindung stehen zu lassen.

Das gibt noch mal ein paar Zeilen Code mehr. Ca. 700 bis jetzt.
Von ein paar LED´s und Taster gleich zu sowas, steile Lernkurve....
Ich werde mal ein paar Beispiele anschauen, das Wochenende ist ja schon fast da. :slight_smile:

Gruß
Achim

Hallo,

nur mal so gefragt, wie würde das über http so aussehen. ?

Mein Server (A) muste ich ja einfach und schnell fertigmachen,
damit ich den Client (b1-b8) überhaupt testen und Programieren kann.

Ich habe mir schon ein bisschen für TCP Anfragen angeschaut, aber http läuft ja schon auf A.
Ich verstehe von dem Code noch nicht alles genau, aber ich habe das Gefühl mit ein paar
Zeilen mehr müsste das auf http anfragen bezüglich des Status Wertes in einer Array Zeile,
und der möglichen Änderung dessen so reagieren können wie ich es brauche.
Es wird in dem Netzwerk später keinen Rechner mit Browser geben, das ist praktisch nur für
mich zum testen drin.
Hier mal der Teil des Servers der jetzt b das runterladen von A ermöglicht:

void start_server() {
  // Einfacher File-Explorer
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String fileList = "<h1>Dateien:</h1><ul>";
    File root = SD.open("/");
    File file = root.openNextFile();
    while (file) {
      fileList += "<li><a href='/dl?file=" + String(file.name()) + "'>" + String(file.name()) + "</a></li>";
      file = root.openNextFile();
    }
    request->send(200, "text/html", fileList + "</ul>");
  });

  // Datei-Download
  server.on("/dl", HTTP_GET, [](AsyncWebServerRequest *request) {
    if (request->hasParam("file")) {
      String filename = request->getParam("file")->value();

      // Sicherstellen, dass der Pfad mit "/" beginnt, aber nicht mit "//"
      if (!filename.startsWith("/")) {
        filename = "/" + filename;
      }

      // Prüfen, ob Datei existiert (verhindert Server-Absturz/Fehler 500)
      if (SD.exists(filename)) {
        request->send(SD, filename, "application/octet-stream");
      } else {
        request->send(404, "text/plain", "Datei nicht gefunden auf SD");
      }
    } else {
      request->send(400, "text/plain", "Fehlender Parameter: file");
    }
  });

  server.begin();
  return;
}

Das wird spater nicht mehr von SD Karte sondern Littlefs kommen.

Hättes du ein Beispiel wie es erweitert werden müsste, damit es entsprechend reagiert. ?

Gruß
Achim

Wenn Du im realen Betrieb HTTP nicht brauchst, solltest Du auf den Overhead des Protokolls verzichten oder im Extremfall nur mit text/plain und nicht mit HTML arbeiten.

Gruß Tommy

Hallo,

wie gesagt, das Projekt ist so ein von fast 0 auf 80 Ding, ich weiß was ich machen möchte, nur wie sag ichs meinem Rechner. Also lesen, Beispiele anschauen, verstehen versuchen, anpassen, ausprobieren. Und da war Http ganz vorn. Und es Funktioniert.

Hier mal das was momentan der Client (b) beim Start unter anderem macht.

void s_http_get() {
  if (!eth_connected) return;

  HTTPClient http;

  String url = "http://" + String(hostIp[0]) + "." + String(hostIp[1]) + "." + String(hostIp[2]) + "." + String(hostIp[3]) + "/dl?file=" + dateiName;

  Serial.print("Starte Download von: ");
  Serial.println(url);

  http.begin(url);
  int httpResponseCode = http.GET();

  if (httpResponseCode == HTTP_CODE_OK) {
    File file = LittleFS.open(LOCAL_PATH, "w");
    if (!file) {
      Serial.println("Fehler: Datei konnte im LittleFS nicht geöffnet werden!");
      http.end();
      return;
    }

    Serial.println("Verbindung steht. Schreibe in LittleFS...");

    int geschriebeneBytes = http.writeToStream(&file);

    file.close(); 

    if (geschriebeneBytes >= 0) {
      Serial.print("Erfolg! Datei erfolgreich gespeichert. Größe: ");
      Serial.print(geschriebeneBytes);
      Serial.println(" Bytes.");
      leseCsvInPsram();
    } else {
      Serial.println("Fehler beim Schreiben der Stream-Daten.");
    }

  } else {
    Serial.print("Fehler beim Download. HTTP Code: ");
    Serial.println(httpResponseCode);
  }

  http.end();
}

Damit holt er sicht das CSV, und wenn der Server A sowieso Http hat, dachte ich mir, kann es Sinn machen. Klar ist die Masse der Daten die da hin und her geschickt werden um ein vielfaches höher als die paar Byte die ich benötige.

Irgendwann werde ich mir dann an den Kopf fassen, und mir Denken Warum ?
Bei max. 8 Clients sollte es aber keine Probleme machen hoffe ich.

Mit sowas wie

server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request) {
if (request->hasParam("zeile")) {

oben im Code sollte ich doch den Status aus der zeile in meinem Array zurückbekommen können, und wenn das keine Probleme macht, erst mal gut.

Optimierung kommt mit dem Wissen, für praktische Beispiele bin ich immer Dankbar.
Momentan kommt da halt noch nicht so viel Wissen zusammen.

Wenn ich die Probleme die UDP dabei machen würde, alle abfangen könnte würde ich vom Gefühl her sagen Perfekt. Schnell, kaum Daten die hin und her gehen. Aber so.

Gruß
Achim

Nee, ein wirklich passendes Beispiel habe ich nicht zur Hand.
Meine Idee lief auf sowas ähnliches hinaus wie die APIs von Openweathermap (hier noch auf API 3.0) oder Tankerkönig funktionieren. Sieht am Ende so ähnlich aus wie Dein Konstrukt für die ganze Datei.

In Deinem Fall könnte eine Anfrage von bX sinngemäß so aussehen:

String request = "https://myserverA.mycompany.mytld/essen?uid=XXXMEMBERXXX" 
        // passend mit der UID von der Karte zusammenbauen

        WiFiClient client;
        HTTPClient http;

        // anfragen
        http.begin(request);
        // A schaut in seinen Daten nach und bastelt die Antwort zusammen
        // Antwort aufnehmen
        int httpResponseCode = http.GET();

bX sammelt die Antwort ein:

        if (httpResponseCode > 0)
        {
            responseData = http.getString();
            // Nachsehen ob "0" oder "1"...
            ...
        }
        http.end();

...die die UID und die 0 bzw 1 enthält:

// responseData
{
  "uid": "XXXMEMBERXXX",
  "status": "1"
}

Danach das gleiche Spiel mit dem Ergebnis "hat Taste gedrückt oder nicht".

Ich habe aber nie so einen Server gebaut, der die Antworten generiert (jedenfalls auf dem ESP32; mit PHP auf meinem großen Server ist ein anderes Ding). Müsste sich mit Auswertung des Teilstrings hinter dem Fragezeichen in der Routine, die <url>/essen behandelt, aber erschlagen lassen...

Und wahrscheinlich hat @Tommy56 recht: Mit Kanonen auf Spatzen geschossen.

Gruß Walter

Hallo,

vielen Dank für die Rückmeldung. Ich muss unbedingt aufpassen auf welchen Antwortbutton ich Klicke, der post #15 wollte ich @Tommy56 schicken, aber hab den "normalen" Antwortbutton erwischt.

Egal, ich denke mit Http bekomme ich es am einfachsten hin, erstmal.

Dein Beispiel nimmt die UID als Parameter, ich werde direkt die "Zeile im Array" übergeben, von der ich den Status brauche, da die Arrays immer Identisch sind. Die sind eigentlich nur auf den Client als Fallback wenn Netzwerk tot. Aber so wird die Suche nach der UID auf dem Client ausgeführt, und der Server kann sich in Ruhe um die Antwort auf die Anfragen kümmern. Und wenn das Netzwerk down ist, muß die Suche sowieso auf dem Client laufen.

Die Url zusamenbauen mit passenden Parameter werde ich ich hinbekommen, und für die Antwort bin ich mttlerweile auch auf ein Beispiel gestossen das auch Hilfreich ist.

Ich liebe Kanonen :wink: nein im Ernst, wenn es mit Http geht, Ok. Das Board was ich nehme, hat 32MB Flash und 32MB PS-Ram aber dafür nur LAN, ist auch ein bisschen groß dafür.

Gruß
Achim

Bin gerade über Async WebServer mit dem ESP32 gestolpert, da wird HTTP und ESP-NOW verwendet. Wie immer von Wolle gut erklärt :wink:

Hallo Jochen,

du musst dir erstmal ein sauberes Protokoll ausdenken um

a) alle RFID zu verwalten
b) alle ESP Clients zu verwalten
c) soll jeder Client nach RFID lesen sofort die Daten auf dem ESP Master aktualisieren, also senden
d) oder soll der ESP 'A' immer der Reihe nach all Clients 'Bn' abfragen?
e) soll der Client 'Bn' die RFID lesen, warten bis diese Daten zum 'A' gehen um dann festzustellen Essen geht raus oder nicht?
f) man benötigt einen zentralen Ort wo alle Daten immer aktuell sind, dafür bietet sich der ESP 'A' an.
g) oder mit jeden Datenaustausch zu einem 'Bn' bekommt 'B' den kompletten Datensatz für sich lokal aktualisiert, erzeugt mehr Datenverkehr.

Die Zeit das 'A' der Reihe nach alle 'Bn' abfragt wäre laut meiner Meinung nach vorhanden, man kann nicht von einer Essensausgabe zur Nächsten mit Teller in der Hand in der nächsten Sekunde sein. Selbst wenn die RFID Karte unter der Hand zum Hintermann in der Schlange an der gleichen Ausgabe durchgereicht wird, dauert es eine Weile bis die nächste RFID gelesen wird.

Wenn ich von einem Single Master ausgehe, was einfacher wäre wie Multimaster, dann fragt 'A' einen 'Bn' ab, 'Bn' sendet Daten an 'A', 'A' schaut nach und gibt sein 'OK' oder 'NOK' zurück. Damit nicht unterschiedliche Protokolle verwendet werden müssen, würd ich mir ein passendes für Anfrage von 'A' nach 'Bn', Antwort von 'Bn' zu 'A' und Rückantwort zu 'Bn' ausdenken, was alle Daten enthält.

Ich denke allein damit hast du schon genug zu tun. Und das ist das Schwierigste von allen Aufgaben. Hier ist genügend Raum für Gedankenspiele.

Das Thema wie die Daten letztlich übertragen werden, spielt eine nachgelagerte Rolle, dass ist nur das Medium und das ist unabhängig vom Protokoll jederzeit austauschbar. Entscheidend ist das Protokoll und dessen Ablauf. Das muss alle Überlegungen bestehen.

Ich würde das weniger kompliziert machen.

Die CSV-Datei würde ich auf den Hauptrechner (chef-ESP32) :) lassen.
Und dort jeden Tag um Mitternacht eine neue anlegen. (mit dem Datum als Dateiname)

Der Mitarbeiter scannt seine RFID-Karte und der Code der Karte wird übertragen.
Dann prüft der "Hauptrechner " ob sein Code sich in der CSV-des-Tages befindet, wenn ja = du böse , wenn nein dann "grünes Licht" und die CSV wird um seinen Code erweitert.

Bei 300 Mitarbeitern kann man (damit das schneller geht) die Abfrage auch gleichzeitig im Speicher machen (ist ja nur eine Personal-Nr. = wenn drin = böse) und nur in der CSV Protokollieren (mit Uhrzeit und anfragende Scanner).
Fertig ist.

Irgendwann wird dann man die SD-Karte mit den CSV-Dateien kopiert auf ein richtigen PC und man kann sie auswerten.

Edit: Sollten nur berechtigte RFID-Nr. ein grünes Licht bekommen, so macht du das so.
Und KOPIERST eine Master-CSV-Datei mit den berechtigten Nr. vorher in den Speicher, und setzt eine "true / Flash - Pointer) .

Bei einen Systemcrash (Stromausfall) , wird die Datei neu in den Speicher kopiert, und mit der gespeicherten CSV-Datei abgeglichen .

Wir reden hier über ca. max. 50 Bytes pro Mitarbeiter. Das schafft ein ESP32 locker im Speicher.

Gruß

Pucki