2x ESP8266: Datenübertragung von DHT22-Messwerten über UDP

Hallo,

ich bastel jetzt schon seit über 1 Woche an einer kleinen "Wetterstation". Es gibt einige Beispiele im Netz (und auch hier im Forum - ich glaube von Tommy56), für die allermeisten bin ich aber zu doof und verstehe nicht, was da passiert. Deshalb versuche ich möglichst (für mich) verständliche Beispiele zu finden und diese auf meine Bedürfnisse anzupassen.
Zielsetzung:
Zwei ESP8266, jeweils mit einem DHT-Sensor verbunden. Einer davon soll im Keller, der andere draußen am Haus sitzen. Beide ESP sind mit dem Hausnetzwerk verbunden und auf dem einen der beiden läuft ein html-Server. Auf dieser html-Seite sollen jeweils beide Messwerte (Luftfeuchtigkeit & Temperatur) dieser beiden DHT-Sensoren angezeigt werden.

Was bisher klappt:
Beide ESP sind mit dem Hausnetzwerk verbunden, auf die html-Seite kann ich von einem Endgerät aus zugreifen und ich kann bisher ein char über UDP von einem zum anderen ESP senden, der auch auf der html-Seite angezeigt wird. Also im Prinzip ist mein kleines Projekt zu 99% fertig.

Was ich aber nicht hinbekomme:
Ich weiß nicht, wie ich beide Messwerte von dem einen ESP zum anderen schicken kann. Ich habe es nur hinbekommen, eine feste Zeichenkette zu übertragen. Ich denke, ich müsste es also entweder schaffen zwei Datenpakete zu schicken oder beide Messwerte in ein Datenpaket zu packen. Mit beidem bin ich aber schon überfordert :melting_face:

Kann mir da einer auf die Sprünge helfen?
Hier der Empfänger mit der html-Seite

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include "DHT.h"                                //DHT Bibliothek laden
#define DHTPIN_1 5                              //Der Sensor wird an PIN 2 angeschlossen    
#define DHTTYPE DHT22                           // Es handelt sich um den DHT22 Sensor
DHT dht1(DHTPIN_1, DHTTYPE);                    //Der Sensor wird ab jetzt mit „dth1“ angesprochen
WiFiUDP Udp;

unsigned int localUdpPort = 4210;               // local port to listen on
char daten[10];

float Luftfeuchtigkeit_1;
float Temperatur_1;
float Luftfeuchtigkeit_2;
float Temperatur_2;

unsigned long zeit;                             // Variable um den aktuellen Timestamp zu speichern
unsigned long zeitupdate;                       // Variable um den Timestamp des letzten Update zu speichern

const char* ssid = "geheim";
const char* password = "geheim";
String header;

WiFiServer server(80);

void setup()
{ zeitupdate = millis ();
  WiFi.begin(ssid, password);                   // Versucht mit WLAN Name und Schlüssel zu verbinden
  Serial.begin(9600);
  dht1.begin();                                 //DHT22 Sensor starten
  Udp.begin(localUdpPort);
  server.begin();


  // erste Messungen
  for (int i = 0; i < 50; i++) {
    float Lf_1 = dht1.readHumidity();                     //die Luftfeuchtigkeit auslesen und unter „Luftfeuchtigkeit“ speichern
    Luftfeuchtigkeit_1 = Lf_1 + Luftfeuchtigkeit_1;
    float Temp_1 = dht1.readTemperature();                //die Temperatur auslesen und unter „Temperatur“ speichern
    Temperatur_1 = Temp_1 + Temperatur_1;
  }
  Luftfeuchtigkeit_1 = Luftfeuchtigkeit_1 / 50;
  Temperatur_1 = Temperatur_1 / 50;
}

void messung() {
  zeit = millis ();
  if (zeit - zeitupdate > 5000) {
    Luftfeuchtigkeit_1 = 0;
    Temperatur_1 = 0;
    for (int i = 0; i < 50; i++) {
      float Lf_1 = dht1.readHumidity();                     //die Luftfeuchtigkeit auslesen und unter „Luftfeuchtigkeit“ speichern
      Luftfeuchtigkeit_1 = Lf_1 + Luftfeuchtigkeit_1;
      float Temp_1 = dht1.readTemperature();                //die Temperatur auslesen und unter „Temperatur“ speichern
      Temperatur_1 = Temp_1 + Temperatur_1;
    }
    Luftfeuchtigkeit_1 = Luftfeuchtigkeit_1 / 50;
    Temperatur_1 = Temperatur_1 / 50;
    zeitupdate = zeit;
  }
}


void empfang() {
  int packetSize, len;
  // UDP
  packetSize = Udp.parsePacket();
  // Da ist was da
  if (packetSize) {
    len = Udp.read(daten, 10);
    Luftfeuchtigkeit_2 = atof (daten) / 100;
    Temperatur_2 = atof (daten + 5) / 100;
  }
}

void loop() {
  WiFiClient client = server.available();   // Listen for incoming clients
  messung();
  empfang();


  if (client) {                             // If a new client connects,
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          if (currentLine.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println("Refresh: 4");
            client.println();

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}</style></head>");

            // Web Page Heading
            client.println("<body><h1>Hygrometeranzeige</h1>");
            client.println("<center><table border=0 width=40% height=100><tr><th></th><th>Luftfeuchtigkeit</th><th>Temperatur</th></tr>");
            client.println("<tr><td align=right><h4>Sensor 1</h4></td><td align=center>");
            client.println(Luftfeuchtigkeit_1);
            client.println("%</td><td align=center>");
            client.println(Temperatur_1);
            client.println("&#8451;</td></tr>");

            client.println("<tr><td align=right><h4>Sensor 2</h4></td><td align=center>");
            client.println(Luftfeuchtigkeit_2);                  // Luftfeuchtigkeit_2
            client.println("%</td><td align=center>");
            client.println(Temperatur_2);                   // Temperatur_2
            client.println("&#8451;</td></tr></table></center>");
            client.println("</body></html>");

            // The HTTP response ends with another blank line
            client.println();

            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    header = "";
    client.stop();
  }
}

Hier der Sender:

// Sendet Daten

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "DHT.h"                                //DHT Bibliothek laden
#define DHTPIN_2 5                              //Der Sensor wird an PIN 2 angeschlossen    
#define DHTTYPE DHT22                           // Es handelt sich um den DHT22 Sensor
DHT dht2(DHTPIN_2, DHTTYPE);                    //Der Sensor wird ab jetzt mit „dth1“ angesprochen
float Luftfeuchtigkeit_2;
float Temperatur_2;
int Lf;
int Temp;

char daten[10]= "4150X2250";

unsigned long zeit;                             // Variable um den aktuellen Timestamp zu speichern
unsigned long zeitupdate;                       // Variable um den Timestamp des letzten Update zu speichern

char *ssid = "geheim";
char *password = "geheim";

// hier die Adresse vom Slave eintragen
IPAddress remoteIP(192, 168, 1, 37);
unsigned int remotePort = 4210;  // remote port to listen on


WiFiUDP Udp;
uint32_t aktMillis;
uint32_t prevMillis;

void setup()
{
  Serial.begin(9600);
  Serial.println();
  Serial.println("Start");
  Serial.print("Connecting to "); Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.print("\n connected.");
  Serial.println(WiFi.localIP());

  dht2.begin();
  // erste Messungen
  for (int i = 0; i < 50; i++) {
    float Lf = dht2.readHumidity();                     //die Luftfeuchtigkeit auslesen und unter „Luftfeuchtigkeit“ speichern
    Luftfeuchtigkeit_2 = Luftfeuchtigkeit_2 + Lf;
    float Temp = dht2.readTemperature();                //die Temperatur auslesen und unter „Temperatur“ speichern
    Temperatur_2 = Temperatur_2 + Temp;
  }
  Lf = Luftfeuchtigkeit_2 / 50;
  Temp = Temperatur_2 / 50;
}


void messung() {
  zeit = millis ();
  if (zeit - zeitupdate > 5000) {
    Luftfeuchtigkeit_2 = 0;
    Temperatur_2 = 0;
    for (int i = 0; i < 50; i++) {
      float Lf_2 = dht2.readHumidity();                     //die Luftfeuchtigkeit auslesen und unter „Luftfeuchtigkeit“ speichern
      Luftfeuchtigkeit_2 = Lf_2 + Luftfeuchtigkeit_2;
      float Temp_2 = dht2.readTemperature();                //die Temperatur auslesen und unter „Temperatur“ speichern
      Temperatur_2 = Temp_2 + Temperatur_2;
    }
    Lf = Luftfeuchtigkeit_2 / 50;
    Temp = Temperatur_2 / 50;
    zeitupdate = zeit;
  }
}



void loop() {
  messung();
  aktMillis = millis();
  if (aktMillis - prevMillis >= 5000) {
    prevMillis = aktMillis;
    Udp.beginPacket(remoteIP, remotePort);
    Udp.write(daten,10);
    Udp.endPacket();
  }
}

Viele Grüße,
Faddi

Sind Deine Messwerte so schlecht (streuen so stark) dass Du einen Mittelwert aus 50 Messungen bilden musst?
Du kannst zur Übertragung alles nutzen, z.B. auch den Webserver mit einem HTTP-Request (weil Du ja schon einen Webserver hast) den Wert übertragen oder über UDP oder ...
Für all das gibt es in Deiner Installation gute Beispiele, auch für Deinen Webserver.
Diese Beispiele sind besser geeignet, als irgendwas aus dem Web zusammen Kopiertes.

Wenn Du weitere gute Beispiele suchst, schau Dir die Seite von Fips an.

Gruß Tommy

Vielleicht hilft ja dieses kleine Beispiel:

#include "Streaming.h"

float Luftfeuchtigkeit_2;
float Temperatur_2;
char daten[10]= "4150X2250";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Luftfeuchtigkeit_2 = atof (daten) / 100;
  Temperatur_2 = atof (daten + 5) / 100;
  Serial << Luftfeuchtigkeit_2 << "\n";
  Serial << Temperatur_2 << "\n";

  Test(9.0, 7.4);
  Test(29.22, 18.30);
  Test(29.22, -18.30);
}

void loop() {
  // put your main code here, to run repeatedly:

}

void Test(float feuchte, float temp){
   sprintf(daten,"%04dx%04d", int(feuchte*100),int(temp*100));
   Serial << daten << "\n"; 
}

Die Funktion Test() zeigt, wie man die beiden float-Werte mit führender Null in den Puffer daten überträgt.

Die atof() Funktion wird ja bereits auf Empfängerseite genutzt, um die empfangenen Daten in die beiden Werte rückzurechnen ...

Das muss man dann noch in die loop() des Senders einbauen:

void loop() {
  messung();
  aktMillis = millis();
  if (aktMillis - prevMillis >= 5000) {
    prevMillis = aktMillis;
    Udp.beginPacket(remoteIP, remotePort);
  // Hier die Umrechnung von Feuchte/Temp in daten[ ] einsetzen
   sprintf(daten,"%04dx%04d", int(Luftfeuchtigkeit_2 *100),int(Temperatur_2 *100));
    Udp.write(daten,10);
    Udp.endPacket();
  }
}

Hi,
danke für Eure Antworten. @Tommy56 : Haste natürlich Recht, ein Mittelwert aus 50 Messwerten ist n bissl zu hoch. Ich hatte das anfänglich einfach so reingeschrieben.
Ich hab seit meinem letzten Projekt bestimmt 2 Jahre nicht mehr am Arduino gebastelt, die Beispiele hatte ich schon wieder vollkommen vergessen. Ich hab eben das eine oder andere Überflogen, aber mein Problem ist, dass die mir schon zu hoch sind zum Einstieg.

@ec2021 Ey, das ist ja super - es klappt jetzt!!!
Allerdings verstehe ich nicht, wieso :rofl:
Ich habe zudem etwas rumprobiert und erst mit dieser Zeile beim Sender hat´s dann passend funktioniert:

 sprintf(daten, "%04dx%04d", int(Luftfeuchtigkeit_2 * 10), int(Temperatur_2 * 10));  

Ich muss mir das "sprintfl "mal ansehen, was das ist und was das macht.
Aber soweit erstmal vielen Dank euch beiden!

UDP mit Textdaten (gerade zwischen 2 Prozessoren des gleichen Typs) ist ja schon Frevel (da kannst Du auch gleich http nutzen). Da kann man die float ja gleich in einem struct direkt übertragen. UDP ist für schnelle Übertragung ohne Overhead gedacht, den Du mit der Textumwandlung rein bringst.
Codevorschlag (ungetestet)
Eine gemeinsame Headerdatei transfer.h mit

struct dhtWerte{
  float temp;
  float hum;
}

// Sender:

dhtWerte werte;
// Werte füllen

Udp.beginPacket(remoteIP, remotePort);
Udp.write(&werte,sizeof(werte));
Udp.endPacket();

// Empfänger
dhtWerte werte;
  packetSize = Udp.parsePacket();
  // Da ist was da
  if (packetSize) {
    len = Udp.read(&werte, sizeof(werte));
}

Und da hast Du beide Werte ohne sprintf und atof. Das geht aber nur so einfach, wenn Du auf beiden Seiten den gleichen Prozessor hast.

Gruß Tommy

Im Empfänger-Code steht

    len = Udp.read(daten, 10);
    Luftfeuchtigkeit_2 = atof (daten) / 100;
    Temperatur_2 = atof (daten + 5) / 100;

Und auf der Sendeseite : char daten[10]= "4150X2250";

Der Code wandelt zunächst alle Ziffern, die am Anfang des char-Arrays stehen (in diesem Fall alles bis zum "X", in eine float-Zahl. atof bedeutet "ASCII to floatingpoint".

atof( "4150X2250") ergibt die Zahl 4150, die - durch 100 geteilt - den Wert 41,5 für Feuchte darstellt.

atof( daten[5]) entspricht atof( "2250") und wird zu der Zahl 2250, die ebenfalls durch 100 geteilt den Wert 22,5 für die Temperatur ergibt.

Ich habe daher nur das Gegenstück hierzu codiert, ohne mich um Fragen der Wirtschaftlichkeit zu kümmern, so dass eine schnelle Lösung unter Nutzung des Vorhandenen möglich sein sollte.

sprintf() füllt ein char-array mit einem Formatierungsstring, der Text und Formatzeichen (die mit % beginnen) enthalten kann. Für jeden dort einzufügenden Wert wird eine solcher "Platzhalter" mit Formatangaben in den String eingefügt; die Variablen folgen dann als Argumente nacheinander in der Reihenfolge ihrer Platzhalter.
Siehe auch hier:

https://draeger-it.blog/arduino-programmierung-ausgabe-formatierter-strings-zeichenketten/

P.S.: Wenn es um größere Mengen an Daten und/oder häufige Übertragung geht, hat @Tommy56
sicher recht: Mit der Übertragung der "nackten" Zahlenwerte kann bzw. sollte man optimieren. Dabei muss man bei - darauf bezog sich sein Hinweis - bei unterschiedlichen Prozessoren die Frage von Big oder Little Endian (also die Reihenfolge der höher bzw. niedriger wertigen Bytes) berücksichtigen.

Im vorliegenden Fall von letztlich maximal 9 Byte "Nutzlast" (4 Stellen Feuchte, ggf. negatives Vorzeichen plus 4 Stellen Temperatur) alle 5 Sekunden, ist das m.E. jedoch vernachlässigbar. Überträgt man beide Zahlen als float, benötigt das hier ebenfalls bereits 2 x 4 Byte = 8 Byte ...

Vermutlich, weil ich im Schnellschuss nicht die "richtigen" Variablen erwischt habe:

sprintf(daten,"%04dx%04d", int(Luftfeuchtigkeit_2 *100),int(Temperatur_2 *100));

muss wohl korrekterweise

sprintf(daten,"%04dx%04d", int(Lf*100),int(Temp *100));

heissen, da dies die gemittelten Werte sind .... :wink:

ich gehe noch mal zum Anfang:

a) würde ich auf einem ESP den ESP8266WebServer nutzen.
Da gibt es in der IDE das Beispiel
ESP8266WebServer/Hello Server
am ESP muss man die ganzen HTTP Header Fields nicht selber schreiben. Das macht der ESP8266WebServer von selber.

b) wenn man schon einen (ESP8266)Webserver hat, dann kann man sich UDP sparen und die Daten auch mittels HTTP/TCP an den Webserver schicken. Der andere ESP (ist aus dieser Sicht dann ein Client) braucht dem ESP8266WebServer nur die Parameter posten.

c) Ein Beispiel habe ich hier: https://werner.rothschopf.net/201809_arduino_esp8266_server_client_0.htm, es enthält auch ein Beispiel wie man die Werte auf der Webseite mit dem Fetch API aktualsiert - ohne einem Refresh der ganzen Seite.

Besonders wenn es ohnhin zwei ähnliche Module sind mit je einem DHT, spricht aus meiner Sicht ja gar nichts gegen weitgehend gleichem Code wo der eine ESP dem anderen eben seine Daten zusätzlich sendet. Einen Code warten und mit geänderter Konfiguration auf beide ESPs aufbringen.

Moinsen,

vielen Dank, dass Ihr Euch so viel Mühe macht!
Tatsächlich habe ich sämtliche Seiten, die ihr hier aufführt, im Vorfeld auch schon entdeckt.
Aber gerade auch die Seite von Werner Rothschopf ist mir viel zu kompliziert, da werde ich mit meinen if- und for-Schleifen und "blink without delay" -Kenntnissen heillos überfordert.
Über die möglichen Arten der Übertragungen hatte ich mich auch informiert und dass auch ein "refresh" der html-Seite nicht die feine englische Art ist, ist mir immerhin auch bewusst. Aber was anderes krieg ich halt nicht gebacken. Wenn ich schon nicht in der Lage bin mir sowas selbst aus den Fingern zu ziehen, dann möchte ich wenigstens in der Lage sein, vorhandene Sketche nachzuvollziehen, damit ich sie auf meine Bedürfnisse anpassen kann. Das beinhaltet also auch, dass ich viele Sketche verworfen habe, wie den von Werner Rothschopf, da ich dort einfach nicht durchblicke.
Für mich ist das deutlich zu hoch, was aber nicht heißen soll, dass ich lernfaul wäre. Unter
https://www.wikinger-tommy.de/arduino/tut_zeichenketten.html hab ich z.B. gefunden, wie ich mit Zeichenketten umgehen kann - vorher wusste ich gar nicht, dass es solche Möglichkeiten gibt, bzw. dass ich das überhaupt brauchen würde. Zumindest von dort habe ich mir das Versenden von dem

char daten[10]= "4150X2250";

ausgedacht, auch mit dem "X" als Trennung. Auf der Seite hab ich auch das mit dem "atof" entdeckt. Ich war übrigens schon froh als ich überhaupt verstanden hab, wofür die Abkürzungen überhaupt stehen. Aber wenn man das alles nicht weiß, sind die Zusammenhänge nicht ganz so einfach.

Lange Rede, kurzer Sinn:
Ich bin mit @ec2021 's Lösung sehr zufrieden, weil sie direkt auf meinen Sketch passt.
Soll aber nicht heißen, dass ich mir keine Gedanken zu den anderen Vorschlägen mache und gemacht habe. Nur sind die (noch?) zu unverständlich für mich, so dass ich damit (noch?) nicht arbeiten kann.

Viele Grüße und nochmals Danke an Euch alle!

Die Anmerkungen der weiteren Helfer hier sind grundsätzlich korrekt. Insbesondere, wenn man eine neue Anwendung angeht, sollte man sich diese anschauen.

Ich bin hier davon ausgegangen, dass es Dir darauf ankam, die vorhandenen Anteile lauffähig zu machen. Soweit nichts komplett Abwegiges beschritten wird, bevorzuge ich nachvollziehbare Hinweise auf Vor- und Nachteile.

Viele (wenn auch nicht alle) Wege führen nach Rom...

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.