PubSubClient.h Probleme mit callback Stabilität

Hallo!

Im Moment versuche ich meine Daten bzw. Werte über MQTT zu übertragen. In meinem Fall findet alle 15 Minuten das übertragen der AussenTemperatur statt.
Ich benutze dafür die Lib:

PubSubClient.h

Das Senden per client.publish läuft reibungslos. Jedoch das Empfangen bereitet Probleme. Der client abonniert den entsprechenden Channel.

client.subscribe("/ESP8266/aussen/Temp");

Alles läuft perfekt, aber nach 2-3 Stunden können keine Daten mehr empfangen werden. Das Senden geht allerdings weiterhin. Einen Verbindungsabbruch zum MQTT Server schliesse ich somit aus. Ich führe alle 10 Minuten eine Reconnect durch, falls dieser nötig ist.

Frage1: Kann das client.setCallback(callback), also das initialisieren des callback flöten gehen, ohne das die Verbindung zum MQTT Server abbricht?

Frage2: Falls Frage 1 true :slight_smile: ist, wie kann ich das überprüfen?

Ich benutze dafür die Lib:

-->Das sagt nicht viel aus, es gibt 2 gleichnamige Bibliotheken. Eine verwendet char-Arrays, eine verwendet Strings.
Je nachdem, ob Deine Bibliothek von Imroy oder knolleary ist...

Alles läuft perfekt, aber nach 2-3 Stunden können keine Daten mehr empfangen werden. Das Senden geht allerdings weiterhin. Einen Verbindungsabbruch zum MQTT Server schliesse ich somit aus. Ich führe alle 10 Minuten eine Reconnect durch, falls dieser nötig ist.

-->das klingt eher danach, als ob keine Daten mehr auf das Topic geladen werden. Ich empfehle, den Datenverkehr mit mqttSpy zu verfolgen, ob die Daten wirklich so fließen, wie Du es Dir vorstellst.

Wie genau soll so ein Reconnect aussehen? Normalerweise hat man eine Reconnect-Routine, die immer aufgerufen wird. Sie überprüft die WiFi-Verbindung und stellt sie gegebenenfalls her, das Gleiche mit der MQTT-Verbindung

DerLehmi:
Wie genau soll so ein Reconnect aussehen? Normalerweise hat man eine Reconnect-Routine, die immer aufgerufen wird. Sie überprüft die WiFi-Verbindung und stellt sie gegebenenfalls her, das Gleiche mit der MQTT-Verbindung

Zumindest beim WiFi reconnected der ESP8266 von allein.
Das habe ich mal ausführlich getestet, indem ich einen in eine Metalldose eingesperrt habe (nur einer, der für externe Antenne umgelötet war und ohne diese betrieben wurde, war zum Abbruch der Verbindung zu bewegen, da ich ja das Kabel mit rein ziehen musste). Sobald ich die Dose geöffnet habe, war die Verbindung wieder da.
Auch nach 30 Minuten im Käfig.

Gruß Tommy

DerLehmi:
-->Das sagt nicht viel aus, es gibt 2 gleichnamige Bibliotheken. Eine verwendet char-Arrays, eine verwendet Strings.

Ich nutze die mit String.

DerLehmi:
Wie genau soll so ein Reconnect aussehen?

So sieht es zumindest bei mir aus.

void MQTTconnect() {
  String clientId;
  for (int counter = 0; counter < 5; counter ++) {
    if (!client.connect(clientId.c_str())) {
      client.setServer(mqttserver, 1883);
      client.setCallback(callback);
      clientId = "WZESP32";
      delay(1000);

   } else {
 
      client.subscribe("/ESP8266/aussen/Temp");
      client.subscribe("/ESP8266/aussen/Luft");
      break;
    }
  }

  if (!client.connect(clientId.c_str())) {
    delay(2000);
    ESP.restart();
  }
}

Läuft alle 10 Minuten mit dem WLANconnect Check durch.

Tommy56:
Zumindest beim WiFi reconnected der ESP8266 von allein.
Das habe ich mal ausführlich getestet, indem ich einen in eine Metalldose eingesperrt habe (nur einer, der für externe Antenne umgelötet war und ohne diese betrieben wurde, war zum Abbruch der Verbindung zu bewegen, da ich ja das Kabel mit rein ziehen musste). Sobald ich die Dose geöffnet habe, war die Verbindung wieder da.
Auch nach 30 Minuten im Käfig.

Gruß Tommy

Das kann gut sein solange deine WLAN Verbindung durch den Router oder sonstige Störungen von AUSSEN getrennt wird. Sollte allerdings ein internes Problem auftauchen (z.B. WLAN Modul abgestürzt) findet keine neue Verbindung statt.

Deshalb noch alle 10 Minuten

void WIFIconnect() {
  for (int counter = 0; counter < 5; counter++) {
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, password);
      delay (1000);

    } else {

      Serial.print("WLAN verbunden - IP:  ");
      Serial.println(WiFi.localIP());
      break;
    }
  }

if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    tft.drawBitmap(85, 0, wlanpic, 16, 16, RED);
    delay(1000);
    ESP.restart();
  }
}

Steffmaster:
Alles läuft perfekt, aber nach 2-3 Stunden können keine Daten mehr empfangen werden. Das Senden geht allerdings weiterhin. Einen Verbindungsabbruch zum MQTT Server schliesse ich somit aus. Ich führe alle 10 Minuten eine Reconnect durch, falls dieser nötig ist.

Kannst du mal deine Callback-Methode zeigen? Fehler, die erst nach einiger Zeit auftreten, könnten auf ein Speicherproblem hindeuten. Der ESP hat zwar viel RAM, aber auch nicht unendlich.

Mach doch mal am Anfang deiner Callback-Methode ein Ausgabe auf die serielle Schnittstelle. Dann weißt du zumindest, ob die Methode nach 2-3 Stunden noch aufgerufen wird. Falls ja, hängt es mit deiner Verarbeitung der Daten zusammen. Falls nicht, liegt der Fehler wohl an der Verbindung, o.Ä.

Ich habe das Problem jetzt erstmal gelöst, indem ich alle halbe Stunde ein

client.setCallback(callback);

ausführe.

Ich kann mir zwar nicht vorstellen, dass das so muß, aber es funktioniert so erstmal.

LightuC:
Kannst du mal deine Callback-Methode zeigen? Fehler, die erst nach einiger Zeit auftreten, könnten auf ein Speicherproblem hindeuten. Der ESP hat zwar viel RAM, aber auch nicht unendlich.

void callback(char* topic, byte * payload, unsigned int length) {

  String strTopic = String((char*)topic);

  if (DisplayMode == DSP_STD) {
    if (strTopic == "/ESP8266/aussen/Temp") {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float f = s.toFloat();
        ClientTemp = f;

        if (f < 10 && f >= 0) {
          tft.fillRect(4, 93, 10, 15, ST7735_BLACK);
          tft.setCursor(16, 93);
          tft.setTextSize(2);
          tft.print(f, 2);

        } else if (f < 0 && f > -10) {
          tft.setCursor(4, 93);
          tft.setTextSize(2);
          tft.print(f, 2);

        } else if (f <= -10) {
          tft.setTextSize(2);
          tft.setCursor(4, 93);
          tft.print(f, 1);

        } else {
          tft.setCursor(4, 93);
          tft.setTextSize(2);
          tft.print(f, 2);
        }
        tft.setTextSize(1);
        tft.print("C");
      }
    }
    else if (strTopic == "/ESP8266/aussen/Luft") {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float l = s.toFloat();

        tft.setCursor(3, 118);
        tft.setTextSize(1);
        tft.print(l);
        tft.print("%");
      }
    }
  }
  callb_str = String(ClientTemp);
  callb_str.toCharArray(callb, callb_str.length() + 1);
  client.publish("/callb/temp/WZ", callb);
}

Mit

callb_str = String(ClientTemp);
callb_str.toCharArray(callb, callb_str.length() + 1);
client.publish("/callb/temp/WZ", callb);

sehe ich auch, wann die Callback Methode nicht mehr aufgerufen wird. Mein Server sendet die Daten allerdings korrekt. Das sehe ich an einer Console, die auf dem MQTT Server mitläuft.

Einen Speicherüberlauf würde ich erstmal ausschließen. Der ESP läuft ja ansonsten perfekt weiter.
Scheinbar geht irgendwie die Callbackverbindung flöten.

Wie verbindet ihr euch denn mit MQTT? Bzw. prüft ihr auf Connect?

Hier ist Dein Problem:

String strTopic = String((char*)topic);

Die Klasse String solltest Du nicht verwenden, sondern mit char-Arrays arbeiten.
Du musst dann halt anstelle von == die Funktion strcmp verwenden.

Achtung! Die gibt 0 zurück, wenn beide gleich sind.

Gruß Tommy

Tommy56:
Hier ist Dein Problem:

String strTopic = String((char*)topic);

Die Klasse String solltest Du nicht verwenden, sondern mit char-Arrays arbeiten.
Du musst dann halt anstelle von == die Funktion strcmp verwenden.

Achtung! Die gibt 0 zurück, wenn beide gleich sind.

Gruß Tommy

Dankeschön. Das werde ich ändern. Hast du vlt. noch etwas Hintergrundwissen, warum dieses mein Problem ist? Ich hatte in sämtlichen Beispielen nur diese String Klassen Variante gefunden.

Noch kurz die geänderte Callback Methode

void callback(char* topic, byte * payload, unsigned int length) {

  if (DisplayMode == DSP_STD) {
    if (strcmp(topic, "/ESP8266/aussen/Temp") == 0) {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float f = s.toFloat();
        ClientTemp = f;

        if (f < 10 && f >= 0) {
          tft.fillRect(4, 93, 10, 15, ST7735_BLACK);
          tft.setCursor(16, 93);
          tft.setTextSize(2);
          tft.print(f, 2);

        } else if (f < 0 && f > -10) {
          tft.setCursor(4, 93);
          tft.setTextSize(2);
          tft.print(f, 2);

        } else if (f <= -10) {
          tft.setTextSize(2);
          tft.setCursor(4, 93);
          tft.print(f, 1);

        } else {
          tft.setCursor(4, 93);
          tft.setTextSize(2);
          tft.print(f, 2);
        }
        tft.setTextSize(1);
        tft.print("C");
      }
    }

    else if (strcmp(topic, "/ESP8266/aussen/Luft") == 0) {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float l = s.toFloat();

        tft.setCursor(3, 118);
        tft.setTextSize(1);
        tft.print(l);
        tft.print("%");
      }
    }
  }
  callb_str = String(ClientTemp);
  callb_str.toCharArray(callb, callb_str.length() + 1);
  client.publish("/callb/temp/WZ", callb);
}

Heute Morgen leider dasselbe Ergebnis. Nach 3 Stunden 10 Minuten kein Callback mehr.

Mit der halbstündigen Überprüfung und ggf. neu setzten des callback ging es.

Du hast ja auch noch mehr String-Umwandlungen drin. Die solltest Du auch entfernen. Im Endeffekt sollte in Deinem gesamten Sketch das Wort String nicht mehr vorkommen.

Gruß Tommy

Edit: Auch auf einem ESP8266 wird irgendwann der RAM zu Ende gehen, gerade bei diesen Umwandlungen.

Um zu überprüfen ob die Methode aufgerufen wird, kannst du nicht eine MQTT Nachricht an den Broker posten. Bei Speicherproblemen kannst du deine Nachricht nämlich gar nicht erst erzeugen. Deshalb bitte einmal:

void callback(char* topic, byte * payload, unsigned int length) 
{
    Serial.println("callback called");
    // ...
}

In deine Code einfügen und wenn keine Nachrichten mehr verarbeitet werden auf die serielle Schnittstelle gucken . (Achtung: Wenn du die serielle Schnittstelle öffnest, dann wird der Arduino resettet. Ich weiß aber nicht, wie das bei den ESP's ist)

Die For-Schleife in deiner Callback Funktion macht meines Erachtens wenig Sinn. Sagen wir, du empfängst "123.456" als Payload. Dann wird der folgende Code 7 mal durchlaufen, bei längeren Payloads sogar noch öfter. Lass die For-Schleife weg.

        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float f = s.toFloat();
        ClientTemp = f;

        if (f < 10 && f >= 0) {
          tft.fillRect(4, 93, 10, 15, ST7735_BLACK);
          tft.setCursor(16, 93);
          tft.setTextSize(2);
          tft.print(f, 2);

        } else if (f < 0 && f > -10) {
          tft.setCursor(4, 93);
          tft.setTextSize(2);
          tft.print(f, 2);

        } else if (f <= -10) {
          tft.setTextSize(2);
          tft.setCursor(4, 93);
          tft.print(f, 1);

        } else {
          tft.setCursor(4, 93);
          tft.setTextSize(2);
          tft.print(f, 2);
        }
        tft.setTextSize(1);
        tft.print("C");

Außerdem, was macht denn der GUI-Code in deinem Callback? Der sollte nur dafür zuständig sein die MQTT-Nachricht entgegen zu nehmen und nicht auch noch dafür die GUI upzudaten. Und vor allem sollte der Empfang von MQTT Nachrichten nicht davon abhängen, in welchem DisplayMode man ist. Wenn es gar nicht anders geht und die GUI unbedingt von dort aus geupdatet werden muss, dann bitte den GUI-Code in eine eigene Funktion und diese dann von da aus aufrufen.

void callback(char* topic, byte * payload, unsigned int length) {

  if (DisplayMode == DSP_STD) {
    if (strcmp(topic, "/ESP8266/aussen/Temp") == 0) {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float f = s.toFloat();
        ClientTemp = f;

        guiUpdateTemperature(ClientTemp);
    }
  }
    else if (strcmp(topic, "/ESP8266/aussen/Luft") == 0) {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);

        payload[length] = '\0';
        String s = String((char*)payload);
        float l = s.toFloat();

        guiUpdateHumidity(l);
      }
    }
  }
  callb_str = String(ClientTemp);
  callb_str.toCharArray(callb, callb_str.length() + 1);
  client.publish("/callb/temp/WZ", callb);
}

LightuC:
Die For-Schleife in deiner Callback Funktion macht meines Erachtens wenig Sinn. Sagen wir, du empfängst "123.456" als Payload. Dann wird der folgende Code 7 mal durchlaufen, bei längeren Payloads sogar noch öfter. Lass die For-Schleife weg.

Ich habe eine for Schleife in meinem MQTTconnect. Diese wird max. 5 mal druchlaufen falls keine Verbindung zum Broker besteht.
In meiner Callback Methode habe ich keine Schleifen.

LightuC:
Um zu überprüfen ob die Methode aufgerufen wird, kannst du nicht eine MQTT Nachricht an den Broker posten. Bei Speicherproblemen kannst du deine Nachricht nämlich gar nicht erst erzeugen. Deshalb bitte einmal:

Mein Sketch sendet ja nicht nur die "Bestätigungsnachricht" aus der Callback Methode. Ich übertrage z.B. noch alle 2 Minuten die aktuelle Raumtemperatur, den Sollwert, Luftfeuchte usw. an meinen FHEM Server. Hätte ich ein allgemeines Brockerproblem, würden doch diese Nachrichten auch nicht gesendet, oder irre ich mich? Die kommen aber.

LightuC:
... dann bitte den GUI-Code in eine eigene Funktion und diese dann von da aus aufrufen.

Das kann ich machen.

Tommy56:
Edit: Auch auf einem ESP8266 wird irgendwann der RAM zu Ende gehen, gerade bei diesen Umwandlungen.

Zugegeben bin ich noch recht neu in der Arduino/ESP Programmierung. Ich verstehe gerade das mit dem String nicht.
String deklariert doch eine Variable, die nur Text enthält. Wird dieser Text beim nächsten befüllen hintendran gehängt? Oder belegt jeder String, den ich in diese Variable schreibe, einen neuen Speicherbereich? Wie soll bzw. warum bekommt man Speicherprobleme?

Ich erhalte doch von meiner Callback Methode nur einen String. Den muß ich in einen Float wandeln.
Wäre das so besser?

float f = String((char*)payload).toFloat();

Mal eine etwas vereinfachte Erklärung:

Der Speicherbereich für Strings wird dynamisch zur Laufzeit belegt. Dadurch kann es vorkommen, dass viele verteilte Stückchen belegt sind und dazwischen nicht mehr genug Platz für größere Bereiche ist und dann geht nix mehr. Das nennt man Fragmentieren des Speichers.

Gruß Tommy

Tommy56:
Mal eine etwas vereinfachte Erklärung:

Der Speicherbereich für Strings wird dynamisch zur Laufzeit belegt. Dadurch kann es vorkommen, dass viele verteilte Stückchen belegt sind und dazwischen nicht mehr genug Platz für größere Bereiche ist und dann geht nix mehr. Das nennt man Fragmentieren des Speichers.

Gruß Tommy

Ok, danke für diese Erklärung. Wäre es so besser?

float f = String((char*)payload).toFloat();

Wofür soll der extra String gut sein?

float f = atof((char*)payload);

Steffmaster:
Ich habe eine for Schleife in meinem MQTTconnect. Diese wird max. 5 mal druchlaufen falls keine Verbindung zum Broker besteht.
In meiner Callback Methode habe ich keine Schleifen.

void callback(char* topic, byte * payload, unsigned int length) {

  if (DisplayMode == DSP_STD) {
    if (strcmp(topic, "/ESP8266/aussen/Temp") == 0) {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      
      for (int i = 0; i < length; i++) {
        // ...
      }
    }

    else if (strcmp(topic, "/ESP8266/aussen/Luft") == 0) {

      Serial.print("Message arrived [");
      Serial.print(topic);
      Serial.print("] ");
      for (int i = 0; i < length; i++) {
        // ...
    }
  }
}

Da sehe ich zwei For-Schleifen in deiner Callback-Methode.

Steffmaster:
Mein Sketch sendet ja nicht nur die "Bestätigungsnachricht" aus der Callback Methode. Ich übertrage z.B. noch alle 2 Minuten die aktuelle Raumtemperatur, den Sollwert, Luftfeuchte usw. an meinen FHEM Server. Hätte ich ein allgemeines Brockerproblem, würden doch diese Nachrichten auch nicht gesendet, oder irre ich mich? Die kommen aber.

Das hast du vorher nicht erwähnt. Allerdings kann es trotzdem zu Speicherproblemen kommen, das ist ja gerade das blöde an dynamischer Speicherverwaltung. Es hängt von der Ausführungshistorie ab. Zum Beispiel kann es sein, dass du vor dem Senden einen großen Speicherbereich wieder freigegeben hast und während des Sendens dann genügend Speicher zur Verfügung steht. Während des Empfangens kann das aber ganz anders aussehen.

michael_x:
Wofür soll der extra String gut sein?

Leider wußte ich es bisher nicht besser.

LightuC:
Da sehe ich zwei For-Schleifen in deiner Callback-Methode.

Da hast du natürlich Recht Die fallen ja jetzt durch Atof weg. Danke.

Alle Änderungen bringen genau nichts. Es bleibt unverändert. Nach 3 Stunden kein Empfangen mehr möglich, senden geht ohne Probleme.

Steffmaster:
Alle Änderungen bringen genau nichts. Es bleibt unverändert. Nach 3 Stunden kein Empfangen mehr möglich, senden geht ohne Probleme.

Wird deine Callback-Methode denn nun aufgerufen oder nicht? Hast du das mit Serial.println als erste Anweisung in deinem Callback mal ausprobiert?

LightuC:
Wird deine Callback-Methode denn nun aufgerufen oder nicht? Hast du das mit Serial.println als erste Anweisung in deinem Callback mal ausprobiert?

Habe ich. Sie wird aufgerufen und alle Werte werden auch perfekt übernommen. Auf der Console kommt dann irgendwann nichts mehr an. Einen TimeStamp habe ich da jetzt nicht, mein Server aber. Dieses Mal 2:50H nach ESP Boot.

void callback(char* topic, byte * payload, unsigned int length) {

  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.println("] ");
  Serial.println((char*)payload);

  if (strcmp(topic, "/ESP8266/aussen/Temp") == 0) {
    ClientTemp = atof((char*)payload);
    AussenTemp();
  }
  if (strcmp(topic, "/ESP8266/aussen/Luft") == 0) {
    ClientLuft = atof((char*)payload);
    AussenLuft();
  }
  if (strcmp(topic, "/FHEM/Power/Fhem") == 0) {

    Powfhem = atof((char*)payload);
    PowerFhem();
  }
  if (strcmp(topic, "/FHEM/Bad/Window") == 0) {
    WinBad = atof((char*)payload);
    WindowBad();
  }
}

Ich bin mir fast sicher, das es nicht am Sketch liegt. Denn wie ich schon sagte: FÜhre ich ein

      client.setCallback(callback);
      clientId = "ESPWZ";
      client.subscribe("/ESP8266/aussen/Temp");
      client.subscribe("/ESP8266/aussen/Luft");
      client.subscribe("/FHEM/Bad/Window");
      client.subscribe("/FHEM/Power/Fhem");

halle 30 Minuten aus, dann läuft es. Als wenn der Server die abonierten Clients killt.