knolleary / pubsubclient

A client library for the Arduino Ethernet Shield that provides support for MQTT.
http://pubsubclient.knolleary.net/
MIT License
3.78k stars 1.46k forks source link

Callback function stop working after some time on ESP8266 at least. #922

Open Pigi-102 opened 2 years ago

Pigi-102 commented 2 years ago

Hello, I have a very simple sketch that sends messages to a MQTT broker and sometimes receive some. It usually works for a couple days but eventually stops receiving messages, while the sending continues to work flawless. Callback messages are really few. Basically is to start my AC at 6.30 AM and shut at 7.00 AM so I expect to receive two or three messages at 6.30 and one at 7.00, on weekdays. The publish phase instead is every 30 seconds all days.

This is my callback function:

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  strTopic = String((char*)topic);
  uint8 need_send=0;
  byte* p = (byte*)malloc(length);

  memcpy(p,payload,length);
   p[length] = '\0';

  if (strTopic == mode_topic) {    // Passo di qui solo se c'e' un qualunque cambio di stato "power"
     need_send=1;                  // Quindi need send.
     ModeSt_Rec = String((char*)p);
     if ( ModeSt_Rec == "off" )      { ac.setPower(false); }  // Tutti i parametri restano come sono. Spengo l'unita'
     if ( ModeSt_Rec == "cool" )     { ac.on(); ac.setMode(kCoolixCool); }
     if ( ModeSt_Rec == "dry" )      { ac.on(); ac.setMode(kCoolixDry);  }
     if ( ModeSt_Rec == "auto" )     { ac.on(); ac.setMode(kCoolixAuto); }
     if ( ModeSt_Rec == "heat" )     { ac.on(); ac.setMode(kCoolixHeat); }
     if ( ModeSt_Rec == "fan_only" ) { ac.on(); ac.setMode(kCoolixFan);  }
     client.publish(modest_topic, ModeSt_Rec.c_str());
  }

  if(strTopic == fan_topic) {    
     FanSt_Rec = String((char*)p);
     if ( ModeSt_Rec == "auto" ) {  
        ac.setFan(kCoolixFanAuto0);
        FanSt_Rec == "auto" ;
     } else {  // Se in auto il mode allora auto0
        if ( FanSt_Rec == "auto" )   { ac.setFan(kCoolixFanAuto); }
        if ( FanSt_Rec == "high" )   { ac.setFan(kCoolixFanMax); }
        if ( FanSt_Rec == "medium" ) { ac.setFan(kCoolixFanMed); }
        if ( FanSt_Rec == "low" )    { ac.setFan(kCoolixFanMin); }
     }
     if ( ModeSt_Rec != "off" ) { need_send = 1; }  
     client.publish(fanst_topic, FanSt_Rec.c_str());
  }

  if(strTopic == ttemp_topic) {    
     TempSt_Rec = String((char*)p);
     ac.setTemp(int (TempSt_Rec.toFloat()));
     if ( ModeSt_Rec != "off" ) { need_send = 1; }  
  }

  if(strTopic == swing_topic) { 
     SwingSt_Rec = String((char*)p);
     if ( ModeSt_Rec != "off" ) { need_send = 1; }  
     client.publish(swingst_topic, SwingSt_Rec.c_str());

  }

  if ( need_send == 1 ) {
        if ( ModeSt_Rec == "auto" ) { 
           ac.setFan(kCoolixFanAuto0);
           FanSt_Rec == "auto" ;
        } else {  
          if ( FanSt_Rec == "auto" )   { ac.setFan(kCoolixFanAuto); }
          if ( FanSt_Rec == "high" )   { ac.setFan(kCoolixFanMax); }
          if ( FanSt_Rec == "medium" ) { ac.setFan(kCoolixFanMed); }
          if ( FanSt_Rec == "low" )    { ac.setFan(kCoolixFanMin); }
       } 
       ac.setTemp(int (TempSt_Rec.toFloat()));
       if ( ModeSt_Rec == "off" ) { ac.setPower(false); }  
       if ( ModeSt_Rec == "off" ) { digitalWrite(D8, LOW);  } else  { digitalWrite(D8, HIGH ); }
       ac.send();
       need_send=0; 
    }
  free(p);

}

and int the main loop() I have:

void loop() {
  wdt_reset();
  webota.handle();
  if (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("NotConn");
    connect_WiFi();
  }

  float h = dht.readHumidity();
  float t = dht.readTemperature();

  client.loop();
  delay ( 1000 );
  new_time = millis();

  if ( (new_time - old_time ) >= 30000 ) {   // Send every 30 seconds
     old_time = millis();
     String hs="Hum: "+String((float)h)+" % ";
     String ts="Temp: "+String((float)t)+" C ";

     if (client.publish(temperature_topic, String(t).c_str())) {
       delay(1); 
     }
     else {
       client.connect(clientID, mqtt_username, mqtt_password);
       delay(10); 
       client.publish(temperature_topic, String(t).c_str());
     }

     if (client.publish(humidity_topic, String(h).c_str())) {
       delay(1); 
     }
     else {
          Serial.println("Humidity failed to send. Reconnecting to MQTT Broker and trying again");
          client.connect(clientID, mqtt_username, mqtt_password);
          delay(10); 
          client.publish(humidity_topic, String(h).c_str());
     }
  }
}

In the broker logs there aren't disconnection logged, and sane goes for reconnection to wifi. From the broker I can see that messages are sent ( confirmed either from the logs and from tcpdump ) The sketch simply stop to ( so it seems ) pass in the callback function.

Unfortunally I can't get serial messages as the ESP is far from a PC so I can't debug it this way.

Any idea on what to look ?

Pigi-102 commented 2 years ago

I've track down a bit more the problem, and it could be that in some case the broker "feel" like the client is disconnected and close the old connection. At this point the client eventually reconnect but does not subscribe again to topic ( code fault ). Does the client need to set again also the callback function or it is enough to subscribe to topic again ?

Tiepolino commented 2 years ago

Hi Pigi-102, I had the same issue. I solved it by re-subscribing every time I send out a message via MQTT. I have created a function that I call just before I try to send data over MQTT:

bool mqttCheck() {
  if (mqttClient.connected()) {                                                                       // Already connected ?
    mqttClient.subscribe("your callback channel here");                               // Re-subscribe to the topic
    return true;
  } 
  bool result = mqttClient.connect("your puplish channel here",               // Connect to the MQTT broker with authentication
                            SN_MQTT_USER, SN_MQTT_PASS);
  if (result) mqttClient.subscribe("your callback channel here");                 // If connected subscribe to the CMD topic
  return result;                                                                                             // Return the connection state
}

I hope this solves your issue.

Tiepolino

Pigi-102 commented 2 years ago

Tiepolino, that's basically the same thing I ended to. When in the cycle I see that I'm not connected I do subscribe again. So far it seems to be working but I'll wait some more days before closing. Thanks for your suggestion.

rebuglio commented 2 years ago

I have the same problem: I too have re-subscribed at regular intervals, but it seems like a tricky solution. Do you think it's the library or are they the brokers? (i use HiveMQ)

Tiepolino commented 2 years ago

Then I guess it's the library. I use Mosquitto and have disconnects after some time 36 hours or so.

Op za 28 mei 2022 14:54 schreef Massimo Rebuglio @.***>:

I have the same problem: I too have re-subscribed at regular intervals, but it seems like a tricky solution. Do you think it's the library or are they the brokers? (i use HiveMQ)

— Reply to this email directly, view it on GitHub https://github.com/knolleary/pubsubclient/issues/922#issuecomment-1140256059, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQOK6NWBX74A36CYVNLFCHLVMIJRPANCNFSM5NWXRIUQ . You are receiving this because you commented.Message ID: @.***>

Pigi-102 commented 1 year ago

Hi Pigi-102, I had the same issue. I solved it by re-subscribing every time I send out a message via MQTT. I have created a function that I call just before I try to send data over MQTT:

bool mqttCheck() {
  if (mqttClient.connected()) {                                                                       // Already connected ?
    mqttClient.subscribe("your callback channel here");                               // Re-subscribe to the topic
    return true;
  } 
  bool result = mqttClient.connect("your puplish channel here",               // Connect to the MQTT broker with authentication
                            SN_MQTT_USER, SN_MQTT_PASS);
  if (result) mqttClient.subscribe("your callback channel here");                 // If connected subscribe to the CMD topic
  return result;                                                                                             // Return the connection state
}

I hope this solves your issue.

Tiepolino

Does this still work for you ? I've drill down a bit more the problem and sure is somewht tied to a sychro issue of some kind. I've tried a simplified sketch and in this sketch I simply send a topic and subscribe to another. When I power on the ESP while the mosquitto broker is running, everything seems to work. If the broker is down, when it comes up again the ESP can reconnect to the broker, can publish data on topics, but seems to be unable to subscribe also if I force it. This week I had a couple of power outage here, due to thunderstorm, and in both case the ESPs were faster than RPI to power-on. The code worked fine, also for the wifi reconnect and so on, but the subscribe didn't worked until I have restarted them.

This is my new code ( in loop ):

     // PUBLISH to the MQTT Broker (topic = Temperature, defined at the beginning)
     if (client.publish(temperature_topic, String(t).c_str())) {
        Serial.println("Temperature sent!");
        client.subscribe(switch1_topic);
     }
     // Again, client.publish will return a boolean value depending on whether it succeded or not.
     // If the message failed to send, we will try again, as the connection may have broken.
     else {
       Serial.println("Temperature failed to send. Reconnecting to MQTT Broker and trying again");
       client.connect(clientID, mqtt_username, mqtt_password);
       delay(10); // This delay ensures that client.publish doesn't clash with the client.connect call
       client.publish(temperature_topic, String(t).c_str());
       client.subscribe(switch1_topic);
     }

As you can see I publish and just after it I subscribe again but this does not seems to change nothing. I will try one more suggestion I've read in antoher issue to give a delay between publish and subscribe, just in case the wifi is slow or loose some packet.

What do you think ?

Pigi-102 commented 1 year ago

It seems to be a problem in the callback, or at least on how my code is written for the callback. I did this test: I shutdown the broker, thus simulating the problem and after a while I've restarted again.

After that I took a tcpdump to see what was going on. The tcpdumpshows clearly that the ESP sends the subscription request, and the broker reply correctly with the state of the topic: Screenshot_20220804_215003

In packet 3 the ESP sends out his publish ( temperature ) and get acked by the broker. In packet 5 the ESP sends out the subscribe request ( switch1 ) and get acked by the broker. In packet 9 the broker publish a state change to the ESP for switch1 and get acked by the ESP, but nothing happens ( when is working the ESP should shut down a relay ).

My callback function is this one:

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  strTopic = String((char*)topic);
  if(strTopic == switch1_topic)
    {
    switch1 = String((char*)payload);
    if(switch1 == "ON")  { digitalWrite(HeatingPin, HIGH);  } else  { digitalWrite(HeatingPin, LOW);  }
    }
}

Unfortunally I can't, at least at the moment, get a serial connection to confirm but that's what at the moment seems. The ESP DO subscribe but the callback don't get called ( or get called with some wrong parameters) . Maybe calling the set_callback function before the subscribe could help.

Edit: I did the test by adding the "set_callback" call before the subscribe and it seems to work. I shut down the broker and start it again, that was breaking stuff, and it has not broke this time. The loop code now i this one:

     if (client.publish(temperature_topic, String(t).c_str())) {

// The following three are the added lines trying to fix the problem
        delay(100);
        client.setCallback(callback);
        client.subscribe(switch1_topic); 
     }
     else {    // Client is disconnected. Need to reconnect
       client.connect(clientID, mqtt_username, mqtt_password);
       delay(10); // This delay ensures that client.publish doesn't clash with the client.connect call
       client.publish(temperature_topic, String(t).c_str());

// The following three are the added lines trying to fix the problem
       delay(100);
       client.setCallback(callback);
       client.subscribe(switch1_topic);
     }

I will wait again for more power outage to confirm it but so far, so good.

Tiepolino commented 1 year ago

Hi Pigi-102,

For me this code seems to be working fine. I have no more problems with the ESP failing to get messages from the broker. Good analysis you did... That will have to be quite some work.

Hope you manage to solve the issue.

Greetings, Tiepolino

Op do 4 aug. 2022 om 20:01 schreef Pigi-102 @.***>:

Hi Pigi-102, I had the same issue. I solved it by re-subscribing every time I send out a message via MQTT. I have created a function that I call just before I try to send data over MQTT:

bool mqttCheck() { if (mqttClient.connected()) { // Already connected ? mqttClient.subscribe("your callback channel here"); // Re-subscribe to the topic return true; } bool result = mqttClient.connect("your puplish channel here", // Connect to the MQTT broker with authentication SN_MQTT_USER, SN_MQTT_PASS); if (result) mqttClient.subscribe("your callback channel here"); // If connected subscribe to the CMD topic return result; // Return the connection state }

I hope this solves your issue.

Tiepolino

Does this still work for you ? I've drill down a bit more the problem and sure is somewht tied to a sychro issue of some kind. I've tried a simplified sketch and in this sketch I simply send a topic and subscribe to another. When I power on the ESP while the mosquitto broker is running, everything seems to work. If the broker is down, when it comes up again the ESP can reconnect to the broker, can publish data on topics, but seems to be unable to subscribe also if I force it. This week I had a couple of power outage here, due to thunderstorm, and in both case the ESPs were faster than RPI to power-on. The code worked fine, also for the wifi reconnect and so on, but the subscribe didn't worked until I have restarted them.

This is my new code ( in loop ):

 // PUBLISH to the MQTT Broker (topic = Temperature, defined at the beginning)
 if (client.publish(temperature_topic, String(t).c_str())) {
    Serial.println("Temperature sent!");
    client.subscribe(switch1_topic);
 }
 // Again, client.publish will return a boolean value depending on whether it succeded or not.
 // If the message failed to send, we will try again, as the connection may have broken.
 else {
   Serial.println("Temperature failed to send. Reconnecting to MQTT Broker and trying again");
   client.connect(clientID, mqtt_username, mqtt_password);
   delay(10); // This delay ensures that client.publish doesn't clash with the client.connect call
   client.publish(temperature_topic, String(t).c_str());
   client.subscribe(switch1_topic);
 }

As you can see I publish and just after it I subscribe again but this does not seems to change nothing. I will try one more suggestion I've read in antoher issue to give a delay between publish and subscribe, just in case the wifi is slow or loose some packet.

What do you think ?

— Reply to this email directly, view it on GitHub https://github.com/knolleary/pubsubclient/issues/922#issuecomment-1205594353, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQOK6NU2GIUVABGO6JJGZHDVXQAQPANCNFSM5NWXRIUQ . You are receiving this because you commented.Message ID: @.***>