ESP32 TCP Kommunikation

Warum?
Lesen wird gestartet mit der ersten Verfügbarkeit eines Zeichens.
Es wird sich der Zeitpunkt gemerkt.
Jetzt gibt es zwei Bedingungen: Entweder ein "TimeOut" oder eine "EndeKennung". Das geht komplett blockadefrei.
Oder was meinst Du?

Hallo ,

Sorry so richtig hab ich deinen Ansatz noch nicht verstanden.

Ich glaub ich muss da noch mal ran.
Gruß Heinz

Das zwingende client.stop() stört mich.
Mach langsam - ich werd da heute nicht mehr dran rühren. Ich bau zu morgen meine Idee in ein paar Zeilen.

Kurze Frage: Gibt es einen bestimmten Grund, den client regelmäßig mit client.stop() zu entkoppeln?

Ein Charme von TCP-Verbindungen besteht doch eigentlich darin, dass man die Verbindung einmal aufbaut und dann solange bestehen lässt, bis sie tatsächlich nicht mehr gebraucht wird ... Anderenfalls würde sich (für kurze Nachrichten, nicht für File-Übertragungen) UDP anbieten; dann müsste man zwar - wenn es gewünscht ist! - selbst eine Empfangsbestätigung und ggf. Wiederholungen nach einem Timeout programmieren, aber das wäre ja auch kein Hexenwerk...

Wenn ich den Sketch in Post No. 17 richtig interpretiere, passiert im Regelfall in der loop() alle 5 s nur ein Öffnen und anschließend sofortiges Schliessen der Verbindung, wenn nämlich keine Daten vom Server zum Lesen im (eigenen) TCP-Stack vorliegen.

Wenn nichts dagegen spricht, würde ich mal versuchen, die Verbindung zu öffnen und so lange zu lesen, bis etwas ankommt. Vereinfacht etwa so:


void setup() {
// ...
  WiFiClient client;
  if (!client.connect(host, 54164)) {
    Serial.println("connection failed");
  }
  else {
    client.print("helloworld");
  }
// ...
}

void loop() {

// ...
  
  
   if (client.available())
    {
      char c = client.read();
      Serial.print(c);
    }
  
// ... 

}

Das Innenleben der loop() würde man sicher noch ergänzen (erneuter Verbindunsgaufbau, wenn diese abbricht; Sammeln der Character, bis eine Zeile abgeschlossen ist; weitere gewünschte Funktionen) ...

Wenn Du die Verbindung offen lässt, muss es immer die gleiche Verbindung sein.
Bei enem Verbindungsabbruch ist unklar, wie der ESP darauf dann reagiert (Da müsstest Du das Innenleben studieren, um das zu ermitteln). Wenn die 4 möglichen Verbindungen aufgebraucht sind, ist Schicht im Schacht.

Deshalb dürfte das Schließen der Client-Verbindung der sicherere Weg sein.

Gruß Tommy

void loop()
{
  WiFiClient client;  // Muss das wirklich bei jedem loop()-Durchlauf neu gebaut werden?
  if (!client.connect(host, 54164))
  {
    Serial.println("connection failed");
  }
  else
  {
    Serial.println("connection succesfull");
    client.print("helloworld");
    //    while (client.available())
    if (client.available())
    {
      tik = millis();
      char c = client.read();
      Serial.print(c);
      antwort = true;
    }
  }
  if ((millis() - tik > timeOut) || (antwort == true))
  {
    client.stop();
    antwort = false,
  }
}

antwort darf natürlicherst dann true werden, wenn die wirklich vollständig da ist - ich will hier nur andeuten, das es zweier Variablen braucht.

Hallo,
@my_xy_projekt , Sorry ich hab mir das aus #27 kurz angesehen , aber noch nicht probiert. hab da selber was brobiert.
Ich hab da heute festgestellt das es eigentlich nicht wirklich einen timeout blockade gibt wenn kein response erfolgt. Ist anscheinend bereits was eingebaut.
if (client.connected())
wird nach etwa 50 ms false wenn nichts mehr erfolgt. Allerdings wäre es sicher gut wenn in dem Fall dennoch ein client.stop() erfolgt und die Verbindung normal abgebaut wird. Ich hab auch mal versucht die Verbindung im Setup aufzubauen und dann stehen zu lassen , irgendwie geht das auch ?? :innocent: hab das aber nicht weiter verfolgt.
Ich habe den Ansatz verfolgt senden und empfangen zu trennen. Damit ist letztlich auch die Verwendung flexibler. Beim empfangen kann man dann mit

if (client.connected())
if (client.available())

abfragen ob eine Verbindung besteht, damit blokiert die funktion dann nicht. Was fehlt ist eigendlich eine Abfrage im receive um die IP des Servers abzufragen. Macht dann Sinn wenn mehrere Server vorhanden sind.

Der Sketch läuft so getestet mit dem Paketsender. Unter Files/Settings lässt sich da ein response einstellen.
Heinz


#include <ESP8266WiFi.h>

const char* ssid = "xxx";
const char* password = "yyy";
const char* host = "192.168.178.33";
uint16_t hostPort = 8088;
uint32_t altzeit;

WiFiClient client; // globale client Instanz erstellen

void setup() {

  Serial.begin(115200);
  Serial.println("Connect Wlan");
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");
  Serial.print("local IP:");
  Serial.println(WiFi.localIP());

}

void loop() {

  if (millis() - altzeit >= 5000) {
    altzeit = millis();
    TCP_send();
  }

  if ( TCP_recive()) {
    Serial.println("response Fehler");
  }
}// ----------ende loop --------

void TCP_send() {

  Serial.printf("connecting to %s %d \n", host, hostPort);
  if (!client.connect(host, hostPort)) {
    Serial.println("connection failed");
  }
  else {
    Serial.println("sending data to server");
    client.printf("Hello an Server");
  }
}
bool  TCP_recive() {
  char buffer[50]; buffer[0] = ('\0');
  int i = 0;
  static uint32_t altzeit;
  static bool merker;

  if (client.connected()) {  // besteht eine Verbindung
    if (!merker) {
      altzeit = millis();
      merker = true;
    }
    if (client.available()) {
      Serial.printf("Received %d bytes\n", client.available());
      while (client.available() && i < sizeof(buffer) - 2 ) {
        char s = client.read();
        buffer[i] = s;
        i++;
      }
      buffer[i] = char('\0');
      client.stop();
      merker = false;
      Serial.println(buffer);
    }
  }

  if (millis() - altzeit > 100 && merker == true) {
    merker = false;
    client.stop();
    return 1; // mit Fehler
  }

  return 0;// ohne Fehler
}

Wahrscheinlich ist die Wartezeit auf die Rückantwort vom Server einfach nur zu kurz ... Am besten das Beispiel WifiClient der Wifi-Lib anschauen und den Ansatz mal testweise umsetzen. Der relevante Auszug sieht dort so aus:

// ...   
// This will send the request to the server
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Connection: close\r\n\r\n");
    unsigned long timeout = millis();
    while (client.available() == 0) {
        if (millis() - timeout > 5000) {
            Serial.println(">>> Client Timeout !");
            client.stop();
            return;
        }
    }

    // Read all the lines of the reply from server and print them to Serial
    while(client.available()) {
        String line = client.readStringUntil('\r');
        Serial.print(line);
    }

    Serial.println();
    Serial.println("closing connection");
// ...

Hallo,
Das war mein erster Ansatz , dabei habe ich das Problem gehabt das nach dem senden direkt die Antwort abgefragt würde. Wenn der Server nicht schnell genug geantwortet hat war bei der Abfrage der while Schleife noch nichts da , Bedingung für die Schleife nicht erfüllt, und die Verbindung wurde geschlossen.

Aber schau mal weiter oben da hatte ich das dem TO bereits angemerkt. Siehe #19

Heinz

Hallo,
@my_xy_projekt ich hab mit das noch mal angesehen , aber eigentlich nur verstanden das Du die Verbindung nach einer maxzeit schließen willst, oder wenn alles drin ist. Das ist ja auch richtig.

Es gibt doch folgende Teilaufgaben wenn was gesendet werden soll

  1. Verbindung aufbauen (einmalig)
  2. Daten versenden einmalig
  3. ständig Antwort abfragen solange die Verbindung steht ( ohne Blockade) und auslesen
  4. Verbindung schließen " client.stop() " entweder wenn alle Daten ausgelesen wurden ,nach maxzeit oder wenn die Verbindung abgebrochen wurde "!client.connected()"

Man könnte das jetzt auch alles in eine function bauen und als parameter , ein Flag zum einmaligen senden , die sendedaten, response daten und einen status als returnwert.

Eventuell hat ja jemand noch einen eleganteren Vorschlag. Ich finde eine TCP Verbindung für zwei Arduino oder ESP sind gut geeignet um ein paar Daten sicher zu versenden. Für den ESP ist ESPNOW vermutlich noch optimaler. ( hab da aber bisher nur mal kurz ein Beispiel probiert )
UDP ist sicher schneller da der Overhead viel kleiner ist. Ich hab das aber mal versucht, dabei ist mir aber immer schon mal was verloren gegangen. Also muss man was mit Quittung machen und gegebenfalls noch mal senden. Bei TCP ist mit bisher noch nicht im Nirwana verschwunden, oder ich habs nicht bemerkt.

Gruß Heinz

Und für @the_jo_user Habe heute mal Folgendes gemacht:
a) Einen kleinen TCP-Server mit Lazarus für Win10 geschrieben, der die empfangene Message mit Präfix "Echo : " zurückschickt.
b) den u.a. Code für ESP32 aus einem Beispiel für eine Port 80-Abfrage abgeleitet (ziemlich umgeschrieben :wink: )

Läuft bei mir problemlos; ich kann den Server disconnecten und erneut starten oder auch den ESP32 dazu bringen, seinerseits die Verbindung abzubrechen. Die Anwendungen finden sich von alleine wieder ... (jedenfalls soweit und oft, wie ich es getestet habe). Wäre nun interessant, ob der so umgesetzte Ablauf auch mit der node-Red-Anwendung funktioniert ...

Lazarus-GUI auf PC (nur zur Info):
image

  • Port 4100, der Hostname spielt hier keine Rolle, da als Server im Einsatz
  • Im Textfenster Ausgaben der empfangenen Zeitstempel (#-Zeichen auf Server-Seite eingefügt)
  • Taste Send erlaubt eigene Texte aus der davor stehenden Editkomponente zu versenden
  • Taste Host aktiviert das Annehmen von Client-Anfragen
  • Taste Disconnect, beendet alle Client-Verbindungen (das Lazarus-Progr. könnte mehrere parallel).
  • Taste Send QUIT sendet den String "QUIT" zum Client, der daraufhin selbst die Verbindung trennt

Hier der Sketch, wie er auf dem ESP32 läuft:

/*
 *  This sketch sends data via TCP to a server specified by host-IP and port 
 *  in a local WiFi Network
 *  
 */

#include <WiFi.h>

const char* ssid     = "..............";
const char* password = "..............";
const char* host     = "192.168.1.169";    // TCP-Server IP
const int httpPort   = 4100;			   // TCP-Server Port

WiFiClient client;

void ConnectToWiFi(){
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println();
    Serial.print("WiFi connected. IP address: ");
    Serial.println(WiFi.localIP());
}

boolean ConnectToServer(){
    Serial.println("connecting to "+String(host));
    if (!client.connect(host, httpPort)) {
        Serial.println("connection failed");
        return false;
    } else {
      return true;
      }
  }

void  DoTransmission(){
    Serial.println("Start Transmission");
    String echo = "";
    while (!echo.endsWith("QUIT")){
      unsigned long timeout = millis();
      // The value of timeout is sent to the Server and printed to Serial
      client.print(String(timeout));      
      client.print(char(13));
      Serial.print("Send : "); 
      Serial.println(String(timeout));
      // In case that there is no message returned by the Server within 5 s 
      // The client stops and the routine returns to loop()for a new connection
      while (client.available() == 0) {
          if (millis() - timeout > 5000) {
              Serial.println("Error: Client Timeout!");
              client.stop();
              return;
          }
      }
      // If an answer has arrived, the characters are read into a string
      // which ends when no more data are available OR if the character '#' 
      // has been sent (can be changed to a character of choice)
      // The received message is then printed to Serial
      char c = ' ';
      echo = "";
      while (client.available() && c >= ' ') {
            c = client.read();
            if (c >= ' ' && c != '#') echo += c;
        }
       Serial.println(echo);
    } // This repeats until either the connection has been lost 
      // or the Server has sent a message with "QUIT" at the end
    client.stop();  
    Serial.println();
    Serial.println("closing connection");
 }

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

void loop() {
    if (WiFi.status() != WL_CONNECTED) ConnectToWiFi;
    if (!ConnectToServer()) return;
    DoTransmission();
}

Den in #33 dargestellten Sketch konnte ich heute auch noch erfolgreich gegen einen TCPServer-Flow in nodeRed auf meinem Raspi 4 testen. Das Re-Connect auf dem ESP32 könnte man noch mit einem delay etwas "beruhigen", da es sonst kontinuierlich läuft, wenn die Verbindung abbricht. Es geht aber ja erst einmal ums Prinzip, und das scheint so zu funktionieren.

Ohne auf Details einzugehen, sieht das auf dem Raspi wie folgt aus (nicht schön, aber es tut's):
image

Hallo und nochmals vielen Dank für eure Unterstützung!

Mein ESP läuft inzwischen Problemlos. Die Beiträge von Rentner und ec2021 war hier wirklich gold wert für mein Verständnis, vielen dank dafür. Ich finde es wahnsinnig interessant, dass ich nun Informationen zwischen 2 Geräten austauschen kann.

Am Raspberry sitze ich nun seit einiger Zeit und bekomme das TCP nicht zum laufen (nicht einmal mit der Paketsender-Software). Ich denke es ist Zeit für mich, vom Raspberry erstmal abstand zu nehmen. Für mich ist das alles rein Hobby und ich habe mir das meiste selbst beigebracht ohne ein konkretes oder größeres Projekt, sondern eher am Spaß an der Sache. Da ich nun schon so viel Zeit in den Raspberry investiert habe und auch einige Tutorials im internet nicht geholfen haben, ist das für mich gerade etwas frustrierend und ich denke ich werde ihn erstmal ruhen lassen und vielleicht über die Feiertage noch ein Versuch machen.

Ich sehe das Projekt dennoch als Erfolg, da der Kernpunk, das TCP auf dem ESP, nun läuft und darüber bin ich sehr glücklich.
Vielen dank für eure Unterstützung.

Ist natürlich Deine Entscheidung; aber willst Du wirklich schon aufgeben :wink: Bei mir ist das Ganze nunmehr auch nur noch Hobby, früher diente es dazu im Unternehmen Gespräche mit Spezialisten besser führen zu können (gesundes Halbwissen).

Du musst natürlich selbst entscheiden, wie Du Deine Zeit einteilst, aber falls Du Dich doch wieder an den Raspi heranmachen solltest, drei kurze Tipps:

  • Setze eine SD-Karte neu mit dem Komplettsystem auf und teste damit; ich hatte mit einer OS-Version auch Probleme und habe dann einfach mal eine andere getestet, mit der ich mit node Red sofort zum Ziel kam!
  • Alternativ setze ein Raspbian als VM auf dem PC auf und verbinde die VM per Ethernet-Bridge (bei Windows) mit dem lokalen Netzwerk. Auch das funktionierte bei mir auf Anhieb.
  • Falls Du Dich mit Lazarus/Freepascal oder Python auskennst, kannst Du damit ebenfalls eine Gegenstelle auf dem Pi schreiben und testen, wenn node Red sich störrisch verhält.

Ich könnte mir vorstellen, dass bereits die erste Möglichkeit Abhilfe schafft ...

Anyway, weiterhin viel Spaß damit!

Mal ganz offTopic und aus reinem eigenem machen.
Ich habe im Oktober 2 Tage gebraucht ein Raspian mit einem apache, einem mySql-Server, einem php und phpmyadmin so zum laufen zu bekommen, das es tatsächlich läuft.

Das fängt schon damit an, das Pakete fehlen, veraltet sind oder gar durch andere Versionen ersetzt wurden. Und alles manuell nachgebaut werden muss...
Rechte müssen vergeben werden und Passwörter geändert.

Das war schon vor einigen Jahren der Grund, warum ich damit nicht warm wurde.
Nichts gegen das Konzept.
Aber Einsteiger- oder InHousefreundlich ist anders.

[/offtopic] :wink:

Ebenfalls offTopic

Zusammenfassung

Oha, das klingt echt übel. :wink: Solche Probleme kenne ich allerdings auch, z.B. beim Aufsetzen von opencv für Python. Aber würde es auch Spaß machen, wenn's immer einfach wäre, ...?!?

1 Like

Okay, ich denke dass der Raspberry noch eine Chance verdient hat und dass ich ihn mir nach der Weihnachtszeit noch einmal vorknöpfe. Es stimmt schon, dass das Tüfteln am meisten Spaß macht :smiley:

Du siehst ja, Du bist nicht alleine, sowohl im Leid wie auch in der Freude an der Sache ... :wink: