Reliable WiFi and MQTT connection and reconnection?

LagomBra
Posts: 4
Joined: Fri Jun 12, 2020 7:17 pm

Reliable WiFi and MQTT connection and reconnection?

Postby LagomBra » Fri Jun 12, 2020 7:47 pm

I modified Urs Eppenberger's state machine code from this post https://esp32.com/viewtopic.php?f=19&t=3851 for the Adafruit IO WiFi and MQTT libraries, nothing special. I am using an Adafruit Metro Mini with an Adafruit AirLift FeatherWing ESP32. Two problems.

1. When connected and powered from USB, the WiFi connection is never established the first time around, but only the second time, after pressing the Metro Mini's reset button, or when powering everything solely from a wall mounted PSU without USB connection. Why is this?

2. When the program runs and a WiFi connection is established, I only ever get the serial printing output from case 0 and case 4. Also when publishing no data arrives at AIO, so despite ending on case 4, I don't seem to have MQTT connection. Why is that?

21:05:23.815 -> (Re)start WLAN connection (serial print from case 0)
21:05:34.075 -> WLAN and MQTT connected (serial print from case 4)

I am using millis() to wait 15 seconds because after a router reset or power off, it takes some time until not only the router's WLAN connection, but also its Internet connection is resumed.

Any hints very much appreciated!

Code: Select all

void connectToWLANAndMQTT()
{
  if ((WiFi.status() != WL_CONNECTED) && (stateConnection != 1)) {stateConnection = 0;}
  if ((WiFi.status() == WL_CONNECTED) && (mqtt.connected() != 0) && (stateConnection != 3)) {stateConnection = 2;}
  if ((WiFi.status() == WL_CONNECTED) && (mqtt.connected() == 0) && (stateConnection != 5)) {stateConnection = 4;}

  switch (stateConnection)
  {
    case 0:
      if (millis() - timeNowWLAN >= intervalWLAN)
      {
        timeNowWLAN = millis();
        Serial.println("(Re)start WLAN connection");
        WiFi.disconnect();
        WiFi.begin(WLAN_SSID, WLAN_PASS);
        stateConnection = 1;
      }
      break;

    case 1:
      Serial.println("Wait for WLAN connection");
      break;

    case 2:
      if (millis() - timeNowMQTT >= intervalMQTT)
      {
        timeNowMQTT = millis();
        Serial.println("WLAN connected. Start MQTT connection");
        mqtt.disconnect();
        mqtt.connect();
        stateConnection = 3;
      }
      break;

    case 3:
      Serial.println("WLAN connected. Wait for MQTT connection");
      break;

    case 4:
      WiFi.setLEDs(255, 0, 0); // Green
      Serial.println("WLAN and MQTT connected");
      stateConnection = 5;
      break;
  }
}

idahowalker
Posts: 166
Joined: Wed Aug 01, 2018 12:06 pm

Re: Reliable WiFi and MQTT connection and reconnection?

Postby idahowalker » Sat Jun 13, 2020 2:10 pm

I, also, experience WiFi disconnects. The disconnects happen about 2 to 6 hours. I am still trying to figure out a good reconnect solution.

From what I've been reading the WiFi disconnect has been an issue for sometime.

To determine if the MQTT connection and path and topics are correct, I ran Node-Red as a troubleshooting tool. Once established to Node-Red, I was able to configure the ESP32 thingies for a MQTT connection.

I tried a disconnect/reconnect scheme once every 6 seconds, that causes, after 2 to 4 hours, the ESP32 to reset after showing a core dump fault.

I am currently trying to use the void MQTTkeepalive( void *pvParameters ) function as a reconnect thingiy, awaiting the results for that experiment.

Code: Select all

#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "esp_sleep.h"
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1351.h>
#include "esp32-hal-psram.h"
#include "esp_himem.h"
//
//
hw_timer_t * timer = NULL;
EventGroupHandle_t eg;
#define evtDoDisplay ( 1 << 1 )
#define evtCollectHistory ( 1 << 2 )
#define evtParseMQTT ( 1 << 5 )
#define evtDoTheBME280Thing ( 1 << 8 )
#define evtDoTheHumidityThing ( 1 << 9 )
#define evtGroupBits ( evtCollectHistory | evtDoTheBME280Thing | evtDoTheHumidityThing )
//
Adafruit_SSD1351 tft = Adafruit_SSD1351( 128, 128, GPIO_NUM_27, GPIO_NUM_12, GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_26 );
////
Adafruit_BME280 bme( GPIO_NUM_5 ); // hardware SPI
////
WiFiClient wifiClient;
PubSubClient MQTTclient( mqtt_server, mqtt_port, wifiClient );
////
// memory pointers for variables stored in himem, WROVER PSRAM.
/*
   ePtr = envirtonemtal pointers to float values
   [0] = inside temperature
   [1] = inside humidity
   {2} = inside pressure
   [3] = outside temperature
   [4] = outside humidity
   [5] = outside pressure
   [6] = outside AQ index
*/
float *ePtr;
float *oPressureHistoryPtr;
float *oIAQ_HistoryPtr;
float *oHumidityHistoryPtr;
float *oTemperaturePtr;
////
char *strPayloadPtr;
char *str_eTopicPtr;
////
int *ColorPalettePtr;
/*
   int data pointer, used to store globals
   [0] int DataCells = 50
   [1] int arrayCellPtr = 0;
   [2] int humidity set point
   [3] humidity control enable
*/
int *IntDataPtr;
////
// RTC_DATA_ATTR int bootCount = 0; // assign a memory location in RTC FAST RAM, an experiment remnent
////
TaskHandle_t hDoTheBME280Thing = NULL;
////
SemaphoreHandle_t sema_HistoryCompleted;
SemaphoreHandle_t sema_MQTT_Parser;;
SemaphoreHandle_t sema_TheHumidityThing;
////
volatile int iDoTheBME280Thing = 0;
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  iDoTheBME280Thing++;
  if ( iDoTheBME280Thing == 60000 )
  {
    xEventGroupSetBitsFromISR( eg, evtGroupBits, &xHigherPriorityTaskWoken );
    iDoTheBME280Thing = 0;
  }
}
////
void setup()
{
  pinMode( GPIO_NUM_0, OUTPUT );
  digitalWrite( GPIO_NUM_0, HIGH );
  pinMode( GPIO_NUM_2, OUTPUT );
  digitalWrite( GPIO_NUM_2, LOW );
  // bootCount++;
  /* Use 4th timer of 4.
    1 tick 1/(80MHZ/80) = 1us set divider 80 and count up.
    Attach onTimer function to timer
    Set alarm to call timer ISR, every 1000uS and repeat / reset ISR (true) after each alarm
    Start an timer alarm
  */
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 1000, true);
  timerAlarmEnable(timer);
  // messageTemp.reserve( 300 );
  // memory allocations for items to be stored in PSRAM
  log_i("before Free PSRAM: %d", ESP.getFreePsram());
  IntDataPtr = (int*)ps_calloc( 15, sizeof(int) );
  IntDataPtr[0] = 50; //const int DataCells = 50;
  oPressureHistoryPtr = (float*)ps_calloc( IntDataPtr[0], sizeof(float) );// oPressureHistoryPtr = (float*)ps_calloc( DataCells, sizeof(float) );
  oIAQ_HistoryPtr = (float*)ps_calloc( IntDataPtr[0], sizeof(float) );//oIAQ_HistoryPtr = (float*)ps_calloc( DataCells, sizeof(float) );
  oHumidityHistoryPtr = (float*)ps_calloc( IntDataPtr[0], sizeof(float) );
  oTemperaturePtr = (float*)ps_calloc( IntDataPtr[0], sizeof(float) );
  ePtr =  (float*)ps_calloc( 15, sizeof(float) );
  strPayloadPtr = (char *)ps_calloc(300, sizeof(char) );
  str_eTopicPtr = (char *)ps_calloc(300, sizeof(char) );
  ColorPalettePtr  = (int*)ps_calloc( 10, sizeof(int) );
  log_i("Total heap: %d", ESP.getHeapSize());
  log_i("Free heap: %d", ESP.getFreeHeap());
  log_i("Total PSRAM: %d", ESP.getPsramSize());
  log_i("Free PSRAM: %d", ESP.getFreePsram());
  // popuate color palette for display use
  ColorPalettePtr[0] = 0x0000; //BLACK
  ColorPalettePtr[1] = 0x001F; //BLUE
  ColorPalettePtr[2] = 0xF800; //RED
  ColorPalettePtr[3] = 0x07E0; //GREEN
  ColorPalettePtr[4] = 0x07FF; //CYAN
  ColorPalettePtr[5] = 0xF81F; //MAGENTA
  ColorPalettePtr[6] = 0xFFE0; //YELLOW
  ColorPalettePtr[7] = 0xFFFF; //WHITE
  ColorPalettePtr[8] = 0xFFFFD8; //LIGHTYELLOW
  ColorPalettePtr[9] = 0xFF8040; //BROWN
  // 
  ePtr[2] = 50; // set a default humidity level
  ePtr[3] = 0; // set humidity control to off
  //
  eg = xEventGroupCreate();
  SPI.begin();
  bme.begin();
  while (!bme.begin())
  {
    log_i( "Waiting on response from BME280");
    vTaskDelay( 1000 );
  }
  tft.begin();
  tft.fillScreen(0x0000);
  connectToWiFi();
  connectToMQTT();
  sema_MQTT_Parser = xSemaphoreCreateBinary();
  sema_HistoryCompleted = xSemaphoreCreateBinary();
  sema_TheHumidityThing = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_HistoryCompleted );
  xSemaphoreGive( sema_TheHumidityThing );
  ////
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000, NULL, 4, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( fCollectHistory, "fCollectHistory", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 10000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fUpdateDisplay, "fUpdateDisplay", 50000, NULL, 5, NULL, 1 );
  xTaskCreatePinnedToCore( DoTheBME280Thing, "DoTheBME280Thing", 7000, NULL, 5, &hDoTheBME280Thing, 1 );
  ////
} //setup()
////
////
// send a signal out to relay to energize or de-energize humidifier based on set point
void fDoTheHumidityThing( void * parameter )
{
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoTheHumidityThing, pdTRUE, pdTRUE, portMAX_DELAY );
    xSemaphoreTake( sema_TheHumidityThing, portMAX_DELAY );
    if ( IntDataPtr[3] == 1 )
    {
      // 1 over the set point
      if ( IntDataPtr[2] + 1 > (int)ePtr[2] )
      {
      digitalWrite( GPIO_NUM_2, LOW );  
      }
      // 1 under the set point
      if ( (IntDataPtr[2] - 1) < (int)ePtr[2] )
      {
        // energize humidifier
        digitalWrite( GPIO_NUM_2, HIGH );
      }
    } else {
      //de energize humidifier
      digitalWrite( GPIO_NUM_2, LOW );
    }
    xSemaphoreGive( sema_TheHumidityThing );
  }
  vTaskDelete( NULL );
} //void fCollectHistory( void * parameter )
////
/*
   Collect history information
   task triggered by hardware timer once a minute
   stores history into PSRAM
   Using a semaphore to protect the PSRAM from multiple tasks access the same PSRM locations
*/
void fCollectHistory( void * parameter )
{
  int StorageTriggerCount = 59;
  for (;;)
  {
    xEventGroupWaitBits (eg, evtCollectHistory, pdTRUE, pdTRUE, portMAX_DELAY );
    //log_i( "history triggered" );
    StorageTriggerCount++; //triggered by the timer isr once a minute count 60 minutes
    if ( StorageTriggerCount == 60 )
    {
      xSemaphoreTake( sema_HistoryCompleted, portMAX_DELAY );
      // log_i( " store the history" );
      oPressureHistoryPtr[IntDataPtr[1]] = ePtr[5];
      oIAQ_HistoryPtr[IntDataPtr[1]] = ePtr[6];
      oHumidityHistoryPtr[IntDataPtr[1]] = ePtr[4];
      oTemperaturePtr[IntDataPtr[1]] = ePtr[3];
      IntDataPtr[1]++;
      xSemaphoreGive( sema_HistoryCompleted );
      log_i( "pointer %d stored %f", IntDataPtr[1] - 1, oPressureHistoryPtr[IntDataPtr[1] - 1] );
      if ( IntDataPtr[1] == IntDataPtr[0] )
      {
        IntDataPtr[1] = 0;
      }
      StorageTriggerCount = 0;
    }
    log_i( ">>>>>>>>>>>>ARRAY CELL POINTER %d storagetriggercount %d", IntDataPtr[1] - 1, StorageTriggerCount );
    // log_i( " high watermark %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
} //void fCollectHistory( void * parameter )
////
// Using a semaphore to protect the PSRAM from multiple tasks access the same PSRM locations
////
void fUpdateDisplay( void * parameter )
{
  // Color definitions
  // http://www.barth-dev.de/online/rgb565-color-picker/
  /*
    ColorPalettePtr[0] = 0x0000; //BLACK
    ColorPalettePtr[1] = 0x001F; //BLUE
    ColorPalettePtr[2] = 0xF800; //RED
    ColorPalettePtr[3] = 0x07E0; //GREEN
    ColorPalettePtr[4] = 0x07FF; //CYAN
    ColorPalettePtr[5] = 0xF81F; //MAGENTA
    ColorPalettePtr[6] = 0xFFE0; //YELLOW
    ColorPalettePtr[7] = 0xFFFF; //WHITE
    ColorPalettePtr[8] = 0xFFFFD8; //LIGHTYELLOW
    ColorPalettePtr[9] = 0xFF8040; //BROWN
  */
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoDisplay, pdTRUE, pdTRUE, portMAX_DELAY ); //
    tft.fillScreen( ColorPalettePtr[0] );
    tft.setTextColor( ColorPalettePtr[7] );
    tft.setCursor( 0, 0 );
    tft.print( "Inside"  );
    tft.setTextColor( ColorPalettePtr[3] );
    tft.setCursor( 0, 15 );
    tft.print( "Temp: " + String(ePtr[0]) + "C " + String((ePtr[0] * 9 / 5) + 32) + "F"  );
    tft.setCursor( 0, 25 );
    tft.print( "Humidity " + String(ePtr[2]) + "%" );
    //
    tft.setTextColor( ColorPalettePtr[7] );
    tft.setCursor( 0,  40 );
    tft.print( "Outside" );
    tft.setTextColor( ColorPalettePtr[6] );
    tft.setCursor( 0, 55 );
    tft.print( "Temperature: " + String(ePtr[3]) + "F" );
    tft.setTextColor( ColorPalettePtr[2] );
    tft.setCursor( 0, 65 );
    tft.print( "Humidity " + String(ePtr[4]) + "%" );
    tft.setCursor( 0, 75 );
    tft.setTextColor( ColorPalettePtr[1] );
    tft.print( "Pres. " + String(ePtr[5]) + "mmHg" );
    tft.setCursor( 0, 86 );
    //set the color of the value to be displayed
    if ( ePtr[6] < 51.0f )
    {
      tft.setTextColor( ColorPalettePtr[3] );
    }
    if ( (ePtr[6] >= 50.0f) && (ePtr[6] <= 100.0f) )
    {
      tft.setTextColor( ColorPalettePtr[6] );
    }
    if ( (ePtr[6] >= 100.0f) && (ePtr[6] <= 150.0f) )
    {
      tft.setTextColor( ColorPalettePtr[9] );
    }
    if ( (ePtr[6] >= 150.0f) && (ePtr[6] <= 200.0f) )
    {
      tft.setTextColor( ColorPalettePtr[2] );
    }
    if ( (ePtr[6] >= 200.00f) && (ePtr[6] <= 300.0f) )
    {
      tft.setTextColor( ColorPalettePtr[5] );
    }
    if ( (ePtr[6] > 300.0f) )
    {
      tft.setTextColor( ColorPalettePtr[7] );
    }
    tft.print( "AQ Index " + String(ePtr[6]) );
    tft.setTextColor( ColorPalettePtr[1] ); //set graph line color
    int rowRef = 110;
    int hRef = int( oPressureHistoryPtr[0] );
    int nextPoint = 2;
    int nextCol = 0;
    xSemaphoreTake( sema_HistoryCompleted, portMAX_DELAY );
    for (int i = 0; i < IntDataPtr[0]; i++)
    {
      int hDisplacement = hRef - int( oPressureHistoryPtr[i] ); // cell height displacement from base line
      tft.setCursor( nextCol , (rowRef + hDisplacement) );
      tft.print( "_" );
      nextCol += nextPoint;
    }
    tft.setCursor( (IntDataPtr[1] * nextPoint), (rowRef + 3) );
    tft.print( "I" );
    xSemaphoreGive( sema_HistoryCompleted );
    //     log_i( "fUpdateDisplay MEMORY WATERMARK %d", uxTaskGetStackHighWaterMark(NULL) );
    // xEventGroupSetBits( eg, evtConnect ); // trigger task
  }
  vTaskDelete( NULL );
} // void fUpdateDisplay( void * parameter )
////
// Do not try to send MQTT if MQTT Broker is not online
////
void DoTheBME280Thing( void *pvParameters )
{
  //log_i( "start bme680" );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoTheBME280Thing, pdTRUE, pdTRUE, portMAX_DELAY ); //
    ePtr[0] = bme.readTemperature();
    ePtr[1] = bme.readPressure() / 133.3223684f; // mmHg
    ePtr[2] = bme.readHumidity();
    if ( MQTTclient.connected() )
    {
      MQTTclient.publish( topicInsideTemp, String(ePtr[0]).c_str() );
      vTaskDelay( 2 ); // gives the Raspberry Pi 4 time to receive the message and process
      MQTTclient.publish( topicInsideHumidity, String(ePtr[1]).c_str() );
      vTaskDelay( 2 ); // no delay and RPi is still processing previous message
      MQTTclient.publish( topicInsidePressure, String(ePtr[2]).c_str() );
      //log_i( "Signal strength %d", WiFi.RSSI() );
      //log_i( " high watermark %d",  uxTaskGetStackHighWaterMark( NULL ) );
    }
  }
  vTaskDelete ( NULL );
}
////
/*
    Impostant to not set vtaskDelay to less then 10. Errors begin to develop with the MQTT and network connection.
*/
void MQTTkeepalive( void *pvParameters )
{
  for (;;)
  {
    if ( wifiClient.connected() )
    {
      MQTTclient.loop();
      vTaskDelay( 17 );
    } else {
      log_i( "MQTT keep alive found wifi disconnected" );
      connectToWiFi();
      connectToMQTT();
    }
  }
  vTaskDelete ( NULL );
}
/////
//void onConnectionEstablished()
//{
//}
////
void connectToMQTT()
{
  if ( !(MQTTclient.connect( clientID, mqtt_username, mqtt_password)) )
  {
    log_i("Connection to MQTT Broker failed...");
  }
  // log_i("MQTT Connected");
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( mqtt_topic ); //seems to need to want some topic to subscribe to but does not care in the callback
}
//
void connectToWiFi()
{
  if ( WiFi.status() != WL_CONNECTED )
  {
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 2000 );
    while ( WiFi.status() != WL_CONNECTED )
    {
      vTaskDelay( 2000 );
      log_i(" waiting on wifi connection" );
    }
    WiFi.onEvent( WiFiEvent );
  }
}
////
void fparseMQTT( void *pvParameters )
{
  xSemaphoreGive ( sema_MQTT_Parser );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtParseMQTT, pdTRUE, pdTRUE, portMAX_DELAY ); //
    xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY );
    if ( (String)str_eTopicPtr == topicOutsideTemperature )
    {
      ePtr[3] = String(strPayloadPtr).toFloat();
    }
    if ( (String)str_eTopicPtr == topicOutsideHumidity )
    {
      ePtr[4] = String(strPayloadPtr).toFloat();
    }
    if ( (String)str_eTopicPtr == topicAQIndex )
    {
      ePtr[6] = String(strPayloadPtr).toFloat();
    }
    if ( String(str_eTopicPtr) == topicStateLED )
    {
      if ( int(strPayloadPtr) )
      {
        digitalWrite( GPIO_NUM_0, LOW );
      } else {
        digitalWrite( GPIO_NUM_0, HIGH );
      }
    }
    if ( String(str_eTopicPtr) == topicOutsidePressure )
    {
      ePtr[5] = String(strPayloadPtr).toFloat();
      xEventGroupSetBits( eg, evtDoDisplay ); // trigger tasks
    }
    if ( (String)str_eTopicPtr == topic_hum_enable )
    {
      xSemaphoreTake( sema_TheHumidityThing, portMAX_DELAY );
      IntDataPtr[3] = String(strPayloadPtr).toInt();
      xSemaphoreGive( sema_TheHumidityThing );
    }
    if ( (String)str_eTopicPtr == topic_hum_setpoint )
    {
      xSemaphoreTake( sema_TheHumidityThing, portMAX_DELAY );
      IntDataPtr[2] = String(strPayloadPtr).toInt();
      xSemaphoreGive( sema_TheHumidityThing );
    }
    strPayloadPtr[0] = '\0';;
    strPayloadPtr[1] = '\0';;
    str_eTopicPtr[0] = '\0';
    xSemaphoreGive( sema_MQTT_Parser );
  }
} // void fparseMQTT( void *pvParameters )
////
// Important to get as much code out of the callback for realible operations.
////
void mqttCallback(char* topic, byte* payload, unsigned int length)
{
  xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY);
  str_eTopicPtr = topic;
  strPayloadPtr = (char *)payload;
  xSemaphoreGive ( sema_MQTT_Parser );
  xEventGroupSetBits( eg, evtParseMQTT ); // trigger tasks
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
// great troubleshooting tool when uncommented
////
void WiFiEvent(WiFiEvent_t event)
{
  // log_i( "[WiFi-event] event: %d\n", event );
  //  switch (event) {
  //    case SYSTEM_EVENT_WIFI_READY:
  //      log_i("WiFi interface ready");
  //      break;
  //    case SYSTEM_EVENT_SCAN_DONE:
  //      log_i("Completed scan for access points");
  //      break;
  //    case SYSTEM_EVENT_STA_START:
  //      log_i("WiFi client started");
  //      break;
  //    case SYSTEM_EVENT_STA_STOP:
  //      log_i("WiFi clients stopped");
  //      break;
  //    case SYSTEM_EVENT_STA_CONNECTED:
  //      log_i("Connected to access point");
  //      break;
  //    case SYSTEM_EVENT_STA_DISCONNECTED:
  //      log_i("Disconnected from WiFi access point");
  //      break;
  //    case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:
  //      log_i("Authentication mode of access point has changed");
  //      break;
  //    case SYSTEM_EVENT_STA_GOT_IP:
  //      log_i ("Obtained IP address: %s",  WiFi.localIP() );
  //      break;
  //    case SYSTEM_EVENT_STA_LOST_IP:
  //      log_i("Lost IP address and IP address is reset to 0");
  //      //      vTaskDelay( 5000 );
  //      //      ESP.restart();
  //      break;
  //    case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
  //      log_i("WiFi Protected Setup (WPS): succeeded in enrollee mode");
  //      break;
  //    case SYSTEM_EVENT_STA_WPS_ER_FAILED:
  //      log_i("WiFi Protected Setup (WPS): failed in enrollee mode");
  //      //      ESP.restart();
  //      break;
  //    case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
  //      log_i("WiFi Protected Setup (WPS): timeout in enrollee mode");
  //      break;
  //    case SYSTEM_EVENT_STA_WPS_ER_PIN:
  //      log_i("WiFi Protected Setup (WPS): pin code in enrollee mode");
  //      break;
  //    case SYSTEM_EVENT_AP_START:
  //      log_i("WiFi access point started");
  //      break;
  //    case SYSTEM_EVENT_AP_STOP:
  //      log_i("WiFi access point  stopped");
  //      //      WiFi.mode(WIFI_OFF);
  //      //      esp_sleep_enable_timer_wakeup( 1000000 * 2 ); // 1 second times how many seconds wanted
  //      //      esp_deep_sleep_start();
  //      break;
  //    case SYSTEM_EVENT_AP_STACONNECTED:
  //      log_i("Client connected");
  //      break;
  //    case SYSTEM_EVENT_AP_STADISCONNECTED:
  //      log_i("Client disconnected");
  //      //      WiFi.mode(WIFI_OFF);
  //      //      esp_sleep_enable_timer_wakeup( 1000000 * 2 ); // 1 second times how many seconds wanted
  //      //      esp_deep_sleep_start();
  //      break;
  //    case SYSTEM_EVENT_AP_STAIPASSIGNED:
  //      log_i("Assigned IP address to client");
  //      break;
  //    case SYSTEM_EVENT_AP_PROBEREQRECVED:
  //      log_i("Received probe request");
  //      break;
  //    case SYSTEM_EVENT_GOT_IP6:
  //      log_i("IPv6 is preferred");
  //      break;
  //    case SYSTEM_EVENT_ETH_START:
  //      log_i("Ethernet started");
  //      break;
  //    case SYSTEM_EVENT_ETH_STOP:
  //      log_i("Ethernet stopped");
  //      break;
  //    case SYSTEM_EVENT_ETH_CONNECTED:
  //      log_i("Ethernet connected");
  //      break;
  //    case SYSTEM_EVENT_ETH_DISCONNECTED:
  //      log_i("Ethernet disconnected");
  //      break;
  //    case SYSTEM_EVENT_ETH_GOT_IP:
  //      log_i("Obtained IP address");
  //      break;
  //    case 128:
  //      log_i("err 128");
  //        WiFi.mode(WIFI_OFF);
  //        esp_sleep_enable_timer_wakeup( 1000000 * 2 ); // 1 second times how many seconds wanted
  //        esp_deep_sleep_start();
  //      break;
  //    case 104:
  //      log_i("err 104");
  //      WiFi.mode(WIFI_OFF);
  //      esp_sleep_enable_timer_wakeup( 1000000 * 2 ); // 1 second times how many seconds wanted
  //      esp_deep_sleep_start();
  //      break;
  //    default: break;
  //  }
}
////
void loop() { }
Last edited by idahowalker on Mon Jun 15, 2020 3:00 pm, edited 1 time in total.

LagomBra
Posts: 4
Joined: Fri Jun 12, 2020 7:17 pm

Re: Reliable WiFi and MQTT connection and reconnection?

Postby LagomBra » Sat Jun 13, 2020 3:45 pm

As I read in several places, the ESP32 and various brands/models of routers cause all sorts of issues. I used to have disconnects a few times per day or week and finally found out that it was TP-Link powerline devices a neighbour used to expand his WiFi network. Once removed, no more disconnects, in my case.

Anyhow, I want to achieve failsafe reconnection for all three conditions - set-up loses power briefly, WiFi down, Internet down. I reckon a state machine is the best approach.

case 0: WLAN DOWN (set-up <-> router), MQTT DOWN (router <-> Internet/AIO)
=> WiFi.disconnect(); WiFi.begin(); every 15 seconds

case 1: wait for WLAN UP only then proceed at case 2

case 2: WLAN UP (set-up <-> router), MQTT DOWN (router <-> Internet/AIO)
=> mqtt.connect(); every 5 seconds

case 3: wait for MQTT UP only then proceed at case 4

case 4: WLAN UP (set-up <-> router), MQTT UP (router <-> Internet/AIO)
=> set a flag to true to enable publishing in loop()

idahowalker
Posts: 166
Joined: Wed Aug 01, 2018 12:06 pm

Re: Reliable WiFi and MQTT connection and reconnection?

Postby idahowalker » Sun Jun 14, 2020 2:33 pm

Well cool, that code, posted above, the ESP32 stayed connected for 24 hours. A first. Seems the MQTT callback does not like to run code.

idahowalker
Posts: 166
Joined: Wed Aug 01, 2018 12:06 pm

Re: Reliable WiFi and MQTT connection and reconnection?

Postby idahowalker » Tue Jun 23, 2020 6:54 pm

I did 2 things, a few days ago, that increased the MQTT connection reliability.

Code: Select all

void MQTTkeepalive( void *pvParameters )
{
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds
  for (;;)
  {
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      //log_i( "staying alive " );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); //
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    vTaskDelay( 4900 );
  }
  vTaskDelete ( NULL );
}
I added the line MQTTclient.setKeepAlive( 90 );, which should be added before a MQTT connection is made and I am using a Semaphore to suspend the keep alive during publishing to the MQTT Broker.

LagomBra
Posts: 4
Joined: Fri Jun 12, 2020 7:17 pm

Re: Reliable WiFi and MQTT connection and reconnection?

Postby LagomBra » Tue Jun 23, 2020 7:29 pm

I'm using the Adafruit MQTT library, so MQTTclient.setKeepAlive is not available. I also don't find a "semaphore". But I'm happy as it is, as the again modified "Urs-code" is reliable, when either power or Internet drops out. Always reconnects, so I can now leave my set-up alone and drive away ; )

Code: Select all

void connectToWLANAndMQTT()
{
  // The first time the function runs, the WLAN router and MQTT broker
  // are disconnected; therefore set the state machine's initial state
  static byte stateConnection = WLAN_DOWN_MQTT_DOWN;

  // Next, check if the previously successful WLAN router and MQTT broker
  // connection was dropped
  if (WiFi.status() == WL_DISCONNECTED && stateConnection == 4)
  {
    // If so, disable publishing
    AIOconnected = false;

    Serial.println("Restart WLAN connection");

    // And reset the state machine to its initial state (restart)
    stateConnection = WLAN_DOWN_MQTT_DOWN;
  }

  // Based on the state machine's state, at each loop() execution,
  // execute only the corresponding code (switch statement)
  switch (stateConnection)
  {
    // If there is no WLAN router connection
    case WLAN_DOWN_MQTT_DOWN:
      if (WiFi.status() != WL_CONNECTED)
      {
        Serial.println("Start WLAN connection");

        WiFi.setLEDs(0, 255, 0); // Red

        // Start the connection
        WiFi.begin(WLAN_SSID, WLAN_PASS);

        // Set the timer
        timeNowWLAN = millis();

        // And advance the state machine to the next state
        stateConnection = WLAN_STARTING_MQTT_DOWN;
      }
      
      // The break keyword means that no further case code will be
      // evaluated. In other words, the code immediately returns
      // to loop(), because after the last case statement, there
      // is no other code in this function to jump to
      break;

    // If the WLAN router connection was started
    case WLAN_STARTING_MQTT_DOWN:

      // And it is time
      if (millis() - timeNowWLAN >= intervalWLAN)
      {
        Serial.println("Wait for WLAN connection");

        // And if the WLAN router connection is established
        if (WiFi.status() == WL_CONNECTED)
        {
          // Print various device and network details
          printWLANStatus();

          // And advance the state machine to the next state
          stateConnection = WLAN_UP_MQTT_DOWN;
        }
        else
        {
          // Otherwise, if the WLAN router connection was not established
          Serial.println("Retry WLAN connection");

          // Clear the connection for the next attempt
          WiFi.end();
          WiFi.disconnect();

          // And reset the state machine to its initial state (restart)
          stateConnection = WLAN_DOWN_MQTT_DOWN;
        }
      }
      break;

    // If the WLAN router connection was established
    case WLAN_UP_MQTT_DOWN:

      // And if no MQTT broker connection was established yet
      if ((WiFi.status() == WL_CONNECTED) && !mqtt.connected())
      {
        Serial.println("WLAN connected. Start MQTT connection");

        WiFi.setLEDs(255, 0, 0); // Green

        // Set the timer
        timeNowMQTT = millis();

        // And advance the state machine to the next state
        stateConnection = WLAN_UP_MQTT_STARTING;
      }
      break;
      
    // If the MQTT broker connection was started
    case WLAN_UP_MQTT_STARTING:

      // And it is time
      if (millis() - timeNowMQTT >= intervalMQTT)
      {
        Serial.println("WLAN connected. Wait for MQTT connection");

        // And if the MQTT broker connection is established
        if (mqtt.connect() == 0)
        {
          // Advance the state machine
          stateConnection = WLAN_UP_MQTT_UP;
        }
        else
        {
          // Otherwise if the MQTT broker connection could not be established
          Serial.println("Retry MQTT connection");

          // Reset the state machine to its previous state (go back)
          stateConnection = WLAN_UP_MQTT_DOWN;
        }
      }
      break;

    // If both the WLAN router and MQTT broker connections were established
    case 4:
      Serial.println("WLAN and MQTT connected");
      
      WiFi.setLEDs(0, 0, 0); // Off

      // Toggle flag to enable publishing and subscribing
      AIOconnected = true;
      break;
  }
}

Who is online

Users browsing this forum: No registered users and 66 guests