MQTT pubsubclient. Häufige reconnection und Blockierungen

Moin,

ich benutze folgenden Code auf einem nodeMCU ESP8266:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "XXXX";
const char* password = "XXXXXXXXXXXXXX";
const char* mqtt_server = "XXX.XXX.XXX.XXX";
char timestamp[14];
double wert;

WiFiClient espClient;
PubSubClient client(espClient);
long lastReconnectAttempt = 0;
long lastMsg = 0;
char msg[50];
int value = 0;

void setup() {
  Serial.begin(74880);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  lastReconnectAttempt = 0;
}

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

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.print(" Stringlaenge: ");
  Serial.print(length);
  Serial.print(" ");

  char* pos = strstr((char*)payload, "timestamp=");
  if (pos != NULL) {
    memset(timestamp, NULL, sizeof(timestamp));
    memcpy(timestamp, pos + 10, 13);
  }
  Serial.print(timestamp);

  pos = strstr((char*)payload, "value=");
  if (pos != NULL)
    wert = atof(pos + 6);
  Serial.print(" ");
  Serial.print(wert, 15);
  Serial.println();
}

boolean reconnect() {
  Serial.print("Attempting MQTT connection...");
  // Attempt to connect
  if (client.connect("ESP8266Client")) {
    Serial.println("connected");
    client.subscribe("volkszaehler/Wasser/Verbrauch");
  } else {
    Serial.print("failed, rc=");
    Serial.println(client.state());
  }
  return client.connected();
}

void loop() {
  if (!client.connected()) {
    long now = millis();
    if (now - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = now;
      // Attempt to reconnect
      if (reconnect()) {
        lastReconnectAttempt = 0;
      }
    }
  } else {
    // Client connected

    client.loop();
  }
}

Ich habe den Code aus den Beispielen "mqtt_esp8266" und "mqtt_reconnect_nonblocking" zusammengesetzt.
Grundsätzlich funktioniert der Code.
Allerdings bemerke ich ein seltsamen Verhalten.

Alle paar Sekunden (z. B. 5s, 10s, 6s, 7s, 2s usw.) kommen zwischen 2-50 Nachrichten "Attempting MQTT connection...connected" und dann passiert nichts mehr für weitere z.B. 5s, 10s, 6s, 7s , 2s.

1.) Was ist der Grund?
2.) Wie kann ich das debuggen?

Alle paar hundert bis einige tausend Anläufe für eine neue Verbindung gibt's eine Fehlermeldung:
"Attempting MQTT connection...failed, rc=-2"
oder
"Attempting MQTT connection...failed, rc=-4"

3.) Was bedeutet rc=-2 und rc=-4?

Liebe Grüße,
Chris

zunächst: Dein now ist long, ideal wäre unsigned long

Laut API bedeutet -2 keine Netzwerkverbindung und -4 keine Serverantwort während einer Anfrage. Klingt für mich nach einer instabilen WLAN-Verbindung.
Was mir noch auffällt: in Deiner reconnect()-Routine kontrollierst Du gar nicht, ob es eine WLAN-Verbindung gibt...

Moin Lehmi,

okay, now ist jetzt unsigned long.

Die reconnect() Routine habe ich aus einem Beispiel mit Ethernet Anbindung. Logisch, das die natürlich nicht prüft, ob ein WLAN läuft.

So war sie original:

boolean reconnect() {
  Serial.print("Attempting MQTT connection...");
  // Attempt to connect
  if (client.connect("ESP8266Client")) {
    Serial.println("connected");
    client.subscribe("volkszaehler/Wasser/Verbrauch");
  } else {
    Serial.print("failed, rc=");
    Serial.print(client.state());
  }
  return client.connected();
}

Wie müsste die denn sein, damit erst geprüft wird ob es eine WLAN Verbindung gibt? So?:

boolean reconnect() {
  if (isConnected(30)) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      client.subscribe("volkszaehler/Wasser/Verbrauch");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
    }
    return client.connected();
  }
}

Und was baut man da sinnvollerweise noch ein, oder ist das so komplett?

.....warum hab ich eigentlich nicht die API gefunden?!

Lieben Gruß,
Chris

Zunächst erstmal der Test auf eine WLAN-Verbindung:
if(espClient.status() != WL_CONNECTED)
-->Du musst den WLAN-Clienten, nicht den MQTT-Clienten prüfen

Bei mir sieht der WLAN-Part so aus:
(bei Dir statt WiFi dann espClient)

if(WiFi.status() != WL_CONNECTED){
    while (WiFi.status() != WL_CONNECTED){
      WiFi.begin(ssid, password);
      uint8_t timeout = 8;
      while (timeout && (WiFi.status() != WL_CONNECTED)) {
        timeout--;
        delay(1000);
      }
    }

Mein MQTT-Client-Teil sieht so aus:

if(!mqttClient.connected()){
    mqttClient.set_callback(callback);
    while(!mqttClient.connected()){
      mqttClient.connect(nodeName);
      uint8_t timeout = 8;
      while (timeout && (!mqttClient.connected())){
        timeout--;
        delay(1000);
      }
      if(mqttClient.connected()){
        //Subscribe-Teil hier
      }
    }
  }

-->folgende Unterschiede zu Deinem Teil:
-ich gebe den Clienten Zeit, eine Verbindung aufzubauen
-nach einem timeout wird die callback-Routine bei mir neu zugewiesen (manchmal kann hier ein Fehler auftreten, darum die neue Zuweisung)
-bei mir werden WLAN und MQTT in einer reconnect()-Routine abgehandelt, diese muss einfach nur in die loop gelegt werden. Bei Dir wird in der loop erst geprüft, ob der MQTT-Client verbunden ist, und erst wenn dies nicht der Fall ist, wird die reconnect()-Funktion aufgerufen, um die Verbindung herzustellen

Hallo Lehmi,

ohje, ich befürchte ich habe da nicht ganz den Durchblick, bzw. durchaus auch den Überblick verloren.
Ich arbeite an einem Sketch, was mittlerweile über 35.000 Zeichen hat.
Das ganze wächst nun schon seit 14 Monaten und es sind inzwischen 12 Libraries eingebunden.
Das ganze aus verschiedenen Quellen mit verschiedenen Programmierstilen und sicher auch kein einheitliches Konzept. Sagen wir mal so, ich bin froh, das es überhaupt läuft.
Ein "connect" oder "connected" kommt da an vielen Stellen vor, nicht nur an einer.
Außerdem läuft eine Uhrzeit mit Sekundenanzeige auf ein Display, da kann ich nichts mit Delay über vielleicht maximal 200ms einbauen, weil ja sonst die Sekundenanzeige stehenbleibt.

Jetzt wäre herauszufinden, wie ich deinen Vorschlag an welcher Stelle im Sketch einbauen kann.

Im Anhang ist der leicht anonymisierte Sketch.

Lieben Gruß und vielen Dank,
Chris

Display.ino (33.8 KB)

Das delay() kommt nur zum Einsatz, wenn eine Verbindung fehlgeschlagen ist. Das Programm soll wohl normal weiterlaufen, auch bei abgebrochener Verbindung? Dann müssten Schleife und delay mittels millis() aus der Welt geschafft werden.

ohje, ich befürchte ich habe da nicht ganz den Durchblick, bzw. durchaus auch den Überblick verloren.
Ich arbeite an einem Sketch, was mittlerweile über 35.000 Zeichen hat.

-->Zusammenkopieren ist sowieso Mist... eigentlich müsste der Sketch derart modular aufgebaut sein, dass Du ohne Probleme die Verbindungsroutinen vom Rest sondiert betrachten kannst....
Deine Vorlage sah jedenfalls entsprechend wandelbar aus...