Page 1 of 1

The Proper Way to Reconnect to WiFi?

Posted: Thu Dec 07, 2017 1:02 pm
by antonyc
Hi everyone,

What is the proper way to reconnect to WiFi?

Let's say I've connected to my router, then the router is restarted. How should I handle this from within the ESP32?

Thanks, Antony

Re: The Proper Way to Reconnect to WiFi?

Posted: Sat Dec 16, 2017 5:02 pm
by urs_eppenberger
Hello Antony

I have unreliable WiFi and esp32 (and esp8266) based devices. They use MQTT to send their data to a server.
What I wanted to avoid is, that the device hangs or even crashes, when it looses the MQTT connection or even completely loose WiFi.
I wrote a code framework, where in the main loop there are three section.
In the first section, there is the code for the setup of the connections
In the second section, this is only executed, if WiFi and MQTT are up and running
In the third section, this is always executed, regardless of WiFi or MQTT status

Please have a look. Maybe you can use the concept for your own code.

Kind regards,

Urs.

Code: Select all

/* standalone device with MQTT function
 *  
 * The requirements are as follows:
 * - it should run its main tasks regardless of the availability of WiFi/MQTT
 * - it should recover lost WiFi/MQTT without interrupting the main tasks
 * - unless other examples on the net it does not start the WiFi/MQTT connection in the setup() section
 *   since the device should start with its main functions immediately and do the connection setup later
 * 
 * Other features
 * - OTA over WiFi using the Arduino IDE
 * - MQTT over TLS for encrypted data transfer
 * - configuration parameters for device name, SSID, WAP-PW, MQTT broker username and password are stored in Preferences
 * 
 *  Feedback to improve this code is welcome
 *  Urs Eppenberger
 */

#include <WiFiClientSecure.h>                // needed for the WiFi communication
#include <ESPmDNS.h>                         // for FOTA Suport
#include <WiFiUdp.h>                         //    ditto
#include <ArduinoOTA.h>                      //    ditto
#include <MQTTClient.h>                      // MQTT Client from Joël Gaehwiler https://github.com/256dpi/arduino-mqtt   keepalive manually to 15s

const char* Hostname = "sensor1";            // change according your setup : it is used in OTA and as MQTT identifier
String WiFi_SSID = "yourssid";               // change according your setup : SSID and password for the WiFi network
String WiFi_PW = "wifipw";                   //    "
const char* OTA_PW = "otapw";                // change according your setup : password for 'over the air sw update'
String mqtt_broker = "192.168.12.1";         // change according your setup : IP Adress or FQDN of your MQTT broker
String mqtt_user = "mqttuser";               // change according your setup : username and password for authenticated broker access
String mqtt_pw = "mqttpw";                   //    "
String input_topic = "input/sensor1";        // change according your setup : MQTT topic for messages from device to broker
unsigned long waitCount = 0;                 // counter
uint8_t conn_stat = 0;                       // Connection status for WiFi and MQTT:
                                             //
                                             // status |   WiFi   |    MQTT
                                             // -------+----------+------------
                                             //      0 |   down   |    down
                                             //      1 | starting |    down
                                             //      2 |    up    |    down
                                             //      3 |    up    |  starting
                                             //      4 |    up    | finalising
                                             //      5 |    up    |     up

unsigned long lastStatus = 0;                // counter in example code for conn_stat == 5
unsigned long lastTask = 0;                  // counter in example code for conn_stat <> 5

const char* Version = "{\"Version\":\"low_prio_wifi_v2\"}";
const char* Status = "{\"Message\":\"up and running\"}";

WiFiClientSecure TCP;                        // TCP client object, uses SSL/TLS
MQTTClient mqttClient(512);                  // MQTT client object with a buffer size of 512 (depends on your message size)


void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);                                            // config WiFi as client
  MDNS.begin(Hostname);                                           // start MDNS, needed for OTA
  ArduinoOTA.setHostname(Hostname);                               // initialize and start OTA
  ArduinoOTA.setPassword(OTA_PW);                                 //       set OTA password
  ArduinoOTA.onError([](ota_error_t error) {ESP.restart();});     //       restart in case of an error during OTA
  ArduinoOTA.begin();                                             //       at this point OTA is set up
}


void loop() {                                                     // with current code runs roughly 400 times per second
// start of non-blocking connection setup section
  if ((WiFi.status() != WL_CONNECTED) && (conn_stat != 1)) { conn_stat = 0; }
  if ((WiFi.status() == WL_CONNECTED) && !mqttClient.connected() && (conn_stat != 3))  { conn_stat = 2; }
  if ((WiFi.status() == WL_CONNECTED) && mqttClient.connected() && (conn_stat != 5)) { conn_stat = 4;}
  switch (conn_stat) {
    case 0:                                                       // MQTT and WiFi down: start WiFi
      Serial.println("MQTT and WiFi down: start WiFi");
      WiFi.begin(WiFi_SSID.c_str(), WiFi_PW.c_str());
      conn_stat = 1;
      break;
    case 1:                                                       // WiFi starting, do nothing here
      Serial.println("WiFi starting, wait : "+ String(waitCount));
      waitCount++;
      break;
    case 2:                                                       // WiFi up, MQTT down: start MQTT
      Serial.println("WiFi up, MQTT down: start MQTT");
      mqttClient.begin(mqtt_broker.c_str(), 8883, TCP);           //   config MQTT Server, use port 8883 for secure connection
      mqttClient.connect(Hostname, mqtt_user.c_str(), mqtt_pw.c_str());
      conn_stat = 3;
      waitCount = 0;
      break;
    case 3:                                                       // WiFi up, MQTT starting, do nothing here
      Serial.println("WiFi up, MQTT starting, wait : "+ String(waitCount));
      waitCount++;
      break;
    case 4:                                                       // WiFi up, MQTT up: finish MQTT configuration
      Serial.println("WiFi up, MQTT up: finish MQTT configuration");
      //mqttClient.subscribe(output_topic);
      mqttClient.publish(input_topic, Version);
      conn_stat = 5;                    
      break;
  }
// end of non-blocking connection setup section

// start section with tasks where WiFi/MQTT is required
  if (conn_stat == 5) {
    if (millis() - lastStatus > 10000) {                            // Start send status every 10 sec (just as an example)
      Serial.println(Status);
      mqttClient.publish(input_topic, Status);                      //      send status to broker
      mqttClient.loop();                                            //      give control to MQTT to send message to broker
      lastStatus = millis();                                        //      remember time of last sent status message
    }
    ArduinoOTA.handle();                                            // internal household function for OTA
    mqttClient.loop();                                              // internal household function for MQTT
  } 
// end of section for tasks where WiFi/MQTT are required

// start section for tasks which should run regardless of WiFi/MQTT
  if (millis() - lastTask > 1000) {                                 // Print message every second (just as an example)
    Serial.println("print this every second");
    lastTask = millis();
  }
  delay(100);
// end of section for tasks which should run regardless of WiFi/MQTT
}

Re: The Proper Way to Reconnect to WiFi?

Posted: Tue Jan 28, 2020 3:58 pm
by abqmichaelj
This is a very sound algorithm. I successfully converted it to using PubSubClient and it works well there as well.

Re: The Proper Way to Reconnect to WiFi?

Posted: Mon Apr 20, 2020 9:47 pm
by Okneib
Would you mind sharing your converted code for pubsubclient? I'm new to this and using also pubsubclient.

Regards,
Oscar

Re: The Proper Way to Reconnect to WiFi?

Posted: Tue Jun 16, 2020 8:48 am
by LagomBra
To reliably (re)connect WLAN and MQTT services after power or router outage, I use this state machine function void connectToWLANAndMQTT(). It works well on my end. I am using a Metro Mini with an external AirLift FeatherWing. Here is a complete example that publishes dummy data to a test feed.

Code: Select all

#include <SPI.h>

#include <WiFiNINA.h> // Adafruit's WiFiNINA fork, use version 1.4.0
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

// Variables that remain constant
#define SPIWIFI SPI // SPI port
#define SPIWIFI_SS 10 // AirLift ESP32 chip select pin
#define ESP32_RESET 4 // AirLift ESP32 reset pin
#define SPIWIFI_ACK 3 // AirLift ESP32 ready pin
#define ESP32_GPIO0 -1 // AirLift ESP32 pin not used

#define WLAN_SSID "#"
#define WLAN_PASS "#"
//#define WLAN_SSID "Smartphone" // Alternative router
//#define WLAN_PASS "qvd75ogz97gouo7l" // Alternative router
#define AIO_SERVER "io.adafruit.com" // MQTT broker/server host
#define AIO_SERVERPORT 8883 // Secure port, 1883 insecure port
#define AIO_USERNAME "#"
#define AIO_KEY "#"

const int intervalWLAN = 11000; // WLAN (re-)connection interval 11s, depends on router
const int intervalMQTT = 3000; // MQTT (re-)connection interval 3s

enum : byte {
  WLAN_DOWN_MQTT_DOWN,
  WLAN_STARTING_MQTT_DOWN,
  WLAN_UP_MQTT_DOWN,
  WLAN_UP_MQTT_STARTING,
  WLAN_UP_MQTT_UP
} connectionState;

// Variables that can change
unsigned long timeNowWLAN = 0; // Timestamp that updates each loop() iteration
unsigned long timeNowMQTT = 0; // Timestamp that updates each loop() iteration

bool AIOconnected = false; // Flag to enable publish and subscribe

// Instances an object from the WiFiNINA library to connect and
// transfer data with SSL/TLS support
WiFiSSLClient client;

// Instances a client object from the MQTT_Client library with the
// WLAN client, MQTT server, port and login credentials
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);

// Instance publishing objects from the MQTT_Client library; a feed
// is an Adafruit IO specific MQTT topic
Adafruit_MQTT_Publish mytestfeed = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/Mytestfeed");

void setup()
{
  // Serial monitor printing is only needed for debugging
  Serial.begin(9600);
  while (!Serial); Serial.println();

  // Override default pins with the AirLift's breakout board pins
  WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESET, ESP32_GPIO0, &SPIWIFI);
}

void loop()
{
  // A call to this function (re)connects to the WLAN router and MQTT broker
  connectToWLANAndMQTT();

  if (AIOconnected)
  {
    // Dummy data for testing AIO feed and dashboard
    mytestfeed.publish(random(1024));
    Serial.println("Data published to AIO");

    // To avoid hitting AIO rate limit; later better publish with millis() timer
    // to not block the program
    delay(5000);
  }
}

void connectToWLANAndMQTT()
{

  static byte connectionState = WLAN_DOWN_MQTT_DOWN;

  switch (connectionState)
  {
    case WLAN_DOWN_MQTT_DOWN:
      if (WiFi.status() != WL_CONNECTED)
      {
        Serial.println("(Re)start WLAN connection");
        WiFi.setLEDs(0, 255, 0); // Red = no connection to anything
        WiFi.begin(WLAN_SSID, WLAN_PASS);
        timeNowWLAN = millis();
        connectionState = WLAN_STARTING_MQTT_DOWN;
      }
      break;

    case WLAN_STARTING_MQTT_DOWN:
      if (millis() - timeNowWLAN >= intervalWLAN)
      {
        Serial.println("Wait for WLAN connection");
        if (WiFi.status() == WL_CONNECTED)
        {
          connectionState = WLAN_UP_MQTT_DOWN;
        }
        else
        {
          Serial.println("Retry WLAN connection");
          WiFi.disconnect();
          connectionState = WLAN_DOWN_MQTT_DOWN;
        }
      }
      break;

    case WLAN_UP_MQTT_DOWN:
      if ((WiFi.status() == WL_CONNECTED) && !mqtt.connected())
      {
        Serial.println("WLAN connected. Start MQTT connection");
        WiFi.setLEDs(160, 255, 0); // Yellow = connection to router but not MQTT/AIO (= Internet)
        printWLANStatus();
        timeNowMQTT = millis();
        connectionState = WLAN_UP_MQTT_STARTING;
      }
      break;

    case WLAN_UP_MQTT_STARTING:
      if (millis() - timeNowMQTT >= intervalMQTT)
      {
        Serial.println("WLAN connected. Wait for MQTT connection");
        if (mqtt.connect() == 0)
        {
          connectionState = WLAN_UP_MQTT_UP;
        }
        else
        {
          Serial.println("Retry MQTT connection");
          connectionState = WLAN_UP_MQTT_DOWN;
        }
      }
      break;

    case 4:
      Serial.println("WLAN and MQTT connected");
      WiFi.setLEDs(255, 0, 0); // Green = connection to router and MQTT/AIO (= Internet)
      AIOconnected = true;
      break;
  }
}

void printWLANStatus()
{
  // Print the ESP32's MAC address
  byte mac[6];
  WiFi.macAddress(mac);
  Serial.print("MAC address: ");

  // A call to this function fetches the MAC address
  printMacAddress(mac);

  // Print the SSID of the WLAN network connected to
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // Print the ESP32's IP address assigned by the router
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // Print the router's subnet mask, usually 255.255.255.0
  Serial.print("Subnet mask: ");
  Serial.println((IPAddress)WiFi.subnetMask());

  // Print the rounter's IP address
  Serial.print("Gateway IP: ");
  Serial.println((IPAddress)WiFi.gatewayIP());

  // Print the WLAN router's signal strength received
  long rssi = WiFi.RSSI();
  Serial.print("Signal strength (RSSI): ");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void printMacAddress(byte mac[]) {
  for (int i = 5; i >= 0; i--) {
    if (mac[i] < 16) {
      Serial.print("0");
    }
    Serial.print(mac[i], HEX);
    if (i > 0) {
      Serial.print(":");
    }
  }
  Serial.println();
}

Re: The Proper Way to Reconnect to WiFi?

Posted: Tue Jun 16, 2020 2:16 pm
by idahowalker
I use the MQTT keep alive to detect a lack of network connection with MQTT.

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 evtDoTheSunLampThing ( 1 << 10 )
#define evtGroupBits ( evtCollectHistory | evtDoTheBME280Thing | evtDoTheHumidityThing | evtDoTheSunLampThing )
//
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
   IntDataPtr[0] int DataCells = 50
   IntDataPtr[1] int arrayCellPtr = 0;
   IntDataPtr[2] int humidity set point
   IntDataPtr[3] humidity control enable
   IntDataPtr[4] = sunlamp on
   IntDataPtr[5] = sun lamp enable
*/
int *IntDataPtr;
////
// RTC_DATA_ATTR int bootCount = 0; // assign a memory location in RTC FAST RAM, an experiment remnant
////
SemaphoreHandle_t sema_HistoryCompleted;
SemaphoreHandle_t sema_MQTT_Parser;;
SemaphoreHandle_t sema_TheHumidityThing;
SemaphoreHandle_t sema_DoTheSunLampTHing;
////
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 );
  pinMode( GPIO_NUM_15, OUTPUT );
  digitalWrite( GPIO_NUM_15, LOW );
  /* 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();
  sema_DoTheSunLampTHing = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_DoTheSunLampTHing );
  xSemaphoreGive( sema_HistoryCompleted );
  xSemaphoreGive( sema_TheHumidityThing );
  ////
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000, NULL, 5, 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, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fDoTheHumidityThing, "fDoTheHumidityThing", 3000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fDoTheSunLampThing, "fDoTheSunLampThing", 3000, NULL, 3, NULL, 1 );
  ////
} //setup()
////
void fDoTheSunLampThing( void * parameter )
{
  // IntDataPtr[4] = sunlamp on manual mode, automatic mode off for manual mode to work
  // IntDataPtr[5] = sun lamp enable automatic mode
  //digitalWrite( GPIO_NUM_15, LOW );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoTheSunLampThing, pdTRUE, pdTRUE, portMAX_DELAY );
    vTaskDelay( 2 );
    xSemaphoreTake( sema_DoTheSunLampTHing, portMAX_DELAY );
    if ( (IntDataPtr[4] == 1) && (IntDataPtr[5] == 0) )
    {
      digitalWrite( GPIO_NUM_15, HIGH );
    } else {
      digitalWrite( GPIO_NUM_15, LOW );
    }
    xSemaphoreGive( sema_DoTheSunLampTHing );
    log_i( "fDoTheSunLampThing high watermark %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
} //void fCollectHistory( void * parameter )
////
// 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 );
    vTaskDelay( 1 );
    xSemaphoreTake( sema_TheHumidityThing, portMAX_DELAY );
    if ( IntDataPtr[3] == 1 )
    {
      if ( IntDataPtr[2] + 1 < (int)ePtr[2] )
      {
        digitalWrite( GPIO_NUM_2, LOW );
      }
      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 );
    log_i( "fDoTheHumidityThing high watermark %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  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 MQTTclient 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 );
    }
    if ( (String)str_eTopicPtr == topic_SunLampOn )
    {
      xSemaphoreTake( sema_DoTheSunLampTHing, portMAX_DELAY );
      IntDataPtr[4] = String(strPayloadPtr).toInt();
      xSemaphoreGive( sema_DoTheSunLampTHing );
    }
    if ( (String)str_eTopicPtr == topic_SunLampEnable )
    {
      xSemaphoreTake( sema_DoTheSunLampTHing, portMAX_DELAY );
      IntDataPtr[5] = String(strPayloadPtr).toInt();
      xSemaphoreGive( sema_DoTheSunLampTHing );
    }
    //
    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() { }
Detecting the status of the MQTT client (wifiClient.connected()) was the trick that worked for me.
The connectToWiFi(); called, if client is disconnected, will not try a WiFi reconnect if the WiFi is still connected by checking the status of connection with if ( WiFi.status() != WL_CONNECTED ). I found that if the issue is not a WiFi connection, just blindly trying to reconnect to the WiFi causes issues.

Most of the disconnect errors I get is error 128, which only needs a MQTT reconnect. I found running the MQTT keep alive loop faster then 10mS, also, produces errors. I found a good range for MQTT keep alive is between 10 and 20 mS.

Re: The Proper Way to Reconnect to WiFi?

Posted: Sun Jul 26, 2020 4:00 am
by MikeBanPho1
This is a response to Urs' contribution,

I have thankfully used Urs' idea as published on https://esp32.com/viewtopic.php?t=3851 and made some modifications as below.
I could not find a source for <WiFiUdp.h> and that may have been the reason for some errors, -not sure about that- but initially I could not get things running on a Raspberry3.

I use ESP32 for
  • controlling water level in a 1000 L tank
  • switching off some units when the house is battery/inverter-powered, and for
  • auto-switching outdoor lights.
The outdoor lights - or better all lights in the house- are controlled by 433MHz relays in flip flop setup, so nearly all lights in the house can be switched on and off by sending a pulse via smartphone, pc or wall mounted sender-buttons. The 433MHz control-center is a (24V battery-system powered) raspberry 3B+. If that would be offline, (that's a real exception, since running 24/7. Frequent power outs in a rural area of Thailand made me setting it up this way) we can still turn the lights on/off without network.
Reason for using esp32 here: the lights do not disclose their status after sending a pulse.
To be able to receive the status I included <PubSubClient.h> in the sketch for ESP32.
I encountered several C++ variable-problems with the switch structure when compiling, so I changed it to <if{ } else if{}>. It is almost the same structure.
I encountered further errors when trying to do OTA as in Urs' example, so I changed my sketch to the web-version. I used the code of the example-file in Arduino-IDE, that worked out of the box. I just had to modify the html-dimensions, that was all.
My MQTT Broker is an intern instance on the forementioned Raspberry. Every information stays in the network, no reason to use a third-party broker. But btw I also can access the Raspberry over internet using true.ddns (a free ddns-service of True Thailand, it requires port-forwarding, works like a charm). DDNS is in use by a second raspberry which is securing a well with PIR & camera on our remote farmland. The code is almost the same (replace the broker address with the ddns.address and change the portnumber) so I did not add the pertinent files here.
To switch the lights on or off, I use the sum of status daylight and status optocoupler. In my case this sum should be 1. Either in the night 0 +1 or 1 +0 during the day. The ESP32 checks daylight in every cycle.
In conn_stat 5 there is a 30000 ms routine for publishing. As soon as the daylight changes the cycletime is temporarily set to 1 sec and is being reset to 30000 as soon as the reaction has been concluded by the change of the optocoupler-signal.
I found that sending two payloads in one message is better than two individual messages to the broker. Especially if both values are required for one and the same action this simplfies the python-code.
On that raspberry a python script reacts on status-changes. That is a developing story; attached please find the last python status
A third Raspberry is combined. This Raspberry (B) was set up years earlier to control the outdoor light in the garden and it works flawlessly, so it was more than logical to integrate it by publishing its status via MQTT.
The ESP's are powered with 3.3V miniature adapters. The outputs of ESP are not powerful enough to control solid-state relays (switching off the 5.5 kW water-heater and the laundry-machine), so for the bathroom the ESP output is connected to a relay.

I hope this is helpful to somebody. I am a tech-freak, was involved inrobotics before retirement, but I am not a professional, happy to receive comments.

Cheers

Mike

THE SKETCH

Code: Select all

#include <WiFi.h>                                                // Enables the ESP32 to connect to the local network (via WiFi)
#include <WiFiClient.h>                                       //
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <PubSubClient.h>
/* standalone device with MQTT function
   found on https://esp32.com/viewtopic.php?t=3851
   The requirements are as follows:
   - it should run its main tasks regardless of the availability of WiFi/MQTT
   - it should recover lost WiFi/MQTT without interrupting the main tasks
   - unless other examples on the net it does not start the WiFi/MQTT connection in the setup() section
     since the device should start with its main functions immediately and do the connection setup later
 * ******************************************************************************************************
*/
const char* host = "esp32-100";
const char* ssid = "********";
const char* password = "********";
int payload = 0;
unsigned long waitCount = 0;                                     // counter
unsigned long WiFiCount = 0;                                     // counter
uint8_t lastSensor = 2;
uint8_t lastOptoc = 2;
uint8_t conn_stat = 0;                                           // Connection status for WiFi and MQTT:
//                                                               //
//                                                               // status |   WiFi   |    MQTT
//                                                               // -------+----------+------------
//                                                               //      0 |   down   |    down
//                                                               //      1 | starting |    down
//                                                               //      2 |    up    |    down
//                                                               //      3 |    up    |  starting
//                                                               //      4 |    up    | finalising
//                                                               //      5 |    up    |     up
unsigned long lastStatus = 0;                                    // counter in example code for conn_stat == 5
unsigned long lastTask = 0;                                      // counter in example code for conn_stat <> 5
unsigned long emptyloopCount = 0;
unsigned long loopCount = 0;
unsigned long prevms = 0;
unsigned long interval = 0;
unsigned long cycle = 30000;
unsigned long currentMillis = 0;
unsigned long modific_time = 0;
String mesg_1 = "";
String mesg_2 = "";
String mesg = "";
int ledState = LOW;
int d = 0;
int e = 0;
int f = 0;
int o = 0;
int p = 0;
int q = 0;
const char* clientID = "client_livingroom";                            // MQTT client ID
const char* daylight_topic = "home/livingroom/daylight";
const char* optocoupler_topic = "home/livingroom/optocop";
const char* mqtt_server = "192.168.1.110";                        // IP of the MQTT broker
const int LED = 2;                                                                // the onboard blue LED
const char* Version = "{\"Version\":\"low_prio_wifi_v2\"}";
const char* Status = "{\"Message\":\"up and running\"}";
const char* mqtt_username = "mikebkk";                            // MQTT username
const char* mqtt_password = "DdhGmsk1";                        // MQTT password
const int sensorPin = 4;                                                       // the number of the sensor pin
const int optocPin = 22;                                                       // the number of the optocoupler pin
const int ledPin = 23;
boolean statuschange = 0;
WebServer server(80);
/* Style */
String style =
  "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
  "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
  "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
  "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
  "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
  ".btn{background:#3498db;color:#fff;cursor:pointer}</style>";
//****************************************************************************      Login page
String loginIndex =
  "<form name='loginForm'>"
  "<table width='100%' bgcolor='A09F9F' align='center'>"
  "<tr>"
  "<td colspan=2>"
  "<center><font size=4><b>ESP32 Login Page</b></font></center>"
  "<br>"
  "<center><font size=4><b>Living-room</b></font></center>"
  "<br>"
  "</td>"
  "<br>"
  "<br>"
  "</tr>"
  "<tr>"
  "<td>Username:</td>"
  "<td><input type='text' size=25 name='userid'><br></td>"
  "</tr>"
  "<br>"
  "<br>"
  "<tr>"
  "<td>Password:</td>"
  "<td><input type='Password' size=25 name='pwd'><br></td>"
  "<br>"
  "<br>"
  "</tr>"
  "<tr>"
  "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
  "</tr>"
  "</table>"
  "</form>"
  "<script>"
  "function check(form)"
  "{"
  "if(form.userid.value=='admin' && form.pwd.value=='DdhGmsk0')"
  "{"
  "window.open('/serverIndex')"
  "}"
  "else"
  "{"
  " alert('Error Password or Username')/*displays error message*/"
  "}"
  "}"
  "</script>" + style;
//************************************************************************     Server Index Page
const char* serverIndex =
  "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
  "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
  "<input type='file' name='update'>"
  "<input type='submit' value='Update'>"
  "</form>"
  "<div id='prg'>progress: 0%</div>"
  "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
  "},"
  "error: function (a, b, c) {"
  "}"
  "});"  // end ajax
  "});"    // end form
  "</script>";
//****************************************************************    setup function
void setup(void) {
  pinMode(LED, OUTPUT);                                                  // the onboard blue LED
  pinMode(sensorPin, INPUT);
  pinMode(optocPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  WiFi.begin(ssid, password);                                               // Connect to WiFi network
  Serial.println("");
  while (WiFi.status() != WL_CONNECTED) {                      // Wait for connection
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  if (!MDNS.begin(host)) {                                                      //http://esp32-100.local                use mdns for host name resolution
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");                                  //return index page which is stored in serverIndex
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });

  server.on("/update", HTTP_POST, []() {                                       // handling uploading firmware file
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {                                  //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) {                                                   //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}
WiFiClient wifiClient;
PubSubClient client(mqtt_server, 1883, wifiClient);                // 1883 is the listener port for the Broker
// keepAlive, timeout changed to 75 in PubSubClient.h
void connect_MQTT() {                                                            // Custom function to connect to the MQTT broker via WiFi:
  Serial.print("Connecting to ");
  Serial.println(ssid);
  Serial.println("WiFi is connected");                                         // Debugging - Output the IP Address of the ESP32
  Serial.print("IP address of ESP32: ");
  Serial.println(WiFi.localIP());
  Serial.print("going to connect clientID; "); Serial.print(clientID);
  Serial.print(", user: "); Serial.print(mqtt_username);
  Serial.print(". password: "); Serial.println(mqtt_password);
  delay(100);
  boolean rc = client.connect(clientID, mqtt_username, mqtt_password);
  Serial.print("returned answer on connect: ");
  Serial.print(String(rc));
  if (rc == 1) {
    Serial.println(" (True)");
    int s = client.state();
    Serial.print("Connected to MQTT Broker returned ");               // client.connect returns a boolean value to let us know if the connection was successful.
    Serial.println("client.state() = " + String(s));
  } else {
    Serial.println(" (False)");
    Serial.println("Connection to MQTT Broker failed...");
  }
}
void loop(void) {                                                                              // with current code runs roughly 400 times per second
  server.handleClient();
  delay(1);
  // start of non-blocking connection setup section
  if ((WiFi.status() != WL_CONNECTED) && (conn_stat != 1)) {
    conn_stat = 0;
    Serial.print("1st criterium, "); Serial.print("connection status =  "); Serial.println(String(conn_stat));
  }
  if ((WiFi.status() == WL_CONNECTED) && !client.connected() && (conn_stat != 3))  {
    conn_stat = 2;
    Serial.print("2nd criterium, "); Serial.print("connection status =  "); Serial.println(String(conn_stat));
  }
  if ((WiFi.status() == WL_CONNECTED) && client.connected() && (conn_stat != 5)) {
    conn_stat = 4;
    Serial.print("3rd criterium, "); Serial.print("connection status =  "); Serial.println(String(conn_stat));
  }
  if (conn_stat == 0) {                                                                  // MQTT and WiFi down: start WiFi
    Serial.println("MQTT and WiFi down: start WiFi");
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);                                                   // Connect to the WiFi
    conn_stat = 1;
  } else if (conn_stat == 1) {                                                         // WiFi starting, do nothing here
    Serial.println("WiFi starting, wait : " + String(waitCount));
    if (client.connected()) {
      Serial.println("Connected is True: " + String(client.connected()));
    } else {
      Serial.println("Connected is False: " + String(client.connected()));
    }
  } else if (conn_stat == 2) {                                                      // WiFi up, MQTT down: start MQTT                                                       // WiFi up, MQTT down: start MQTT
    Serial.println("WiFi up, MQTT down: start MQTT");
    connect_MQTT();
    conn_stat = 3;
    waitCount = 0;
  } else if (conn_stat == 3) {                                                       // WiFi up, MQTT starting, do nothing here
    Serial.println("WiFi up, MQTT starting, wait : " + String(waitCount));
    if (client.connected()) {
      Serial.println("Connected is True: " + String(client.connected()));
    } else {
      Serial.println("Connected is False: " + String(client.connected()));
      connect_MQTT();
    }
    waitCount++;
  } else if (conn_stat == 4) {                                                       // WiFi up, MQTT up: finish MQTT configuration
    Serial.println("WiFi up, MQTT up: finish MQTT configuration");

    emptyloopCount = 0;
    delay(500);
    conn_stat = 5;
    /*
     *************************         end of non-blocking connection setup section           ***********************************

     *************************     start section with tasks where WiFi/MQTT is required     **********************************/
  } else if (conn_stat == 5) {                                                     // normal Start send status every 30 sec (just as an example)
    //Serial.println("from section with tasks where WiFi/MQTT is required: ");
    d = digitalRead(sensorPin);
    e = 0;
    f = 0;
    if (lastSensor != d) {
      statuschange = 1;
    }
    if (statuschange == 1) {
      cycle = 1500;                                                                // look at the status every second after a change
      interval = 200;           // blinking freq 200 ms
    } else {
      cycle = 30000;                                                                // look at the status every second after a change
      interval = 1000;           // blinking freq 200 ms
    }
    e = digitalRead(sensorPin);
    delay(10);
    f = digitalRead(sensorPin);
    delay(10);
    if ((d == e)  && (d == f)) {
      Serial.print("Daylight: "); Serial.println(String(d));
      mesg_1 = String(d);
      Serial.print("Daylight: "); Serial.println(String(d));
      Serial.println("Current status daylight sensor: " + String(d));
      Serial.println("Current statuscode " + mesg_1);                     // MQTT can only transmit strings
      Serial.print("home/livingroom/light-sensor, "); Serial.println(String(mesg_1));

    } else {
      // difference between two recordings // do new cycle
      Serial.println(String(d) + String(e) + String(f));
      Serial.println("no hit");
    }
    //      remember time of last sent status message
    o = digitalRead(optocPin);
    p = -1;
    q = -1;
    if (lastOptoc != o) {
      statuschange = 1;
    }
    p = digitalRead(optocPin);
    delay(10);
    q = digitalRead(optocPin);
    delay(10);
    if ((o == p)  && (o == q)) {
      Serial.print("Optocoupler: "); Serial.println(o);
      Serial.println("Current status optocoupler " + String(o));
      String toprint = "clientID: " + String(clientID);
      Serial.println(toprint);
      mesg_2 = String(o);
    } else {
      // difference between two recordings // do new cycle
      Serial.println(String(o) + String(p) + String(q));
    }
    mesg = mesg_1;
    mesg.concat(mesg_2);
    Serial.println("Result of concatenated messages: " + mesg);
    Serial.print("millis() = " + String(millis()));
    Serial.print(", lastStatus = " + String(lastStatus));
    Serial.print(", difference = " + String(millis() -lastStatus));
    Serial.println(", Interval = " + String(interval));
    String Telegram = daylight_topic; 
    Telegram += ", ";
    Telegram.concat(mesg);
    Serial.println("Test for Telegram = " + Telegram); 
    if (millis() - lastStatus > cycle) {
      boolean rq = client.publish(daylight_topic, String(mesg).c_str());
      if (rq == 1) {                                                                       //      send powerstatus to broker
        Serial.println("##########################################################################");
        Serial.print("Statusmessage: "); Serial.println(Status); 
        //Serial.print(Telegram); Serial.println(" sent!");
        Serial.println("##########################################################################");
      } else {
        Serial.println("con_stat: " + String(conn_stat));
        Serial.print("Daylight-sensor failed to send. ");
        Serial.println("Reconnecting to MQTT Broker and trying again");                    // If the message failed to send, we will try again, as the connection may have broken.
        Serial.print("error : status should refresh automatically");
      }
      client.loop();                                            //      give control to MQTT to send message to broker
      lastStatus = millis();
      //delay(100);
    }
    if (d + o == 1) {
      statuschange = 0;
    }
    Serial.print("millis() = " + String(millis()));
    Serial.print(", lastStatus = " + String(lastStatus));
    Serial.print(", difference = " + String(millis() -lastStatus));
    Serial.println(", Interval = " + String(interval));
    lastSensor = d;
    lastOptoc = o;
    mesg = ""; mesg_1 = ""; mesg_2 = "";
    d = 0; e = 0; f = 0;
    o = 0; p = 0; q = 0;
    client.loop();                                              // internal household function for MQTT

    /*
    *************************         end of section for tasks where WiFi/MQTT are required          ********************************

    *************************     start section for tasks which should run regardless of WiFi/MQTT   **********************************/

    // count the loops in disconnected status
    if (WiFi.status() != WL_CONNECTED) {
      loopCount++;
      if (loopCount > 50) {
        Serial.println("going to restart, because loopCount = " + String(loopCount) );
        ESP.restart();
      }
    }
    currentMillis = millis();
    if (currentMillis - prevms >= interval) {
      prevms = currentMillis;                      // save the last time you blinked the LED
      ledState = not(ledState);                    // if the LED is off turn it on and vice-versa:
      digitalWrite(LED,  ledState);                // set the LED with the ledState of the variable:
      //delay(interval/2);
    }
    // end of section for tasks which should run regardless of WiFi/MQTT
  }
}

PYTHON

Code: Select all

from __future__ import division 
import paho.mqtt.client as mqtt
import RPi.GPIO as GPIO
import time
import subprocess
import sys
import os
from subprocess import call
from datetime import datetime
import pytz
import pymsgbox

MQTT_ADDRESS = '192.168.1.110'
MQTT_USER = '********'
MQTT_PASSWORD = '********'
MQTT_TOPIC = 'home/+/+'
tz_BKK = pytz.timezone('Asia/Bangkok')
oldident = ''
ItsNight = -2
Night = -2
PowerInfo = -2
datetime_BKK = datetime.now(tz_BKK)
timenow = str(datetime_BKK.strftime("%H:%M:%S"))
PowerInfo= 0
count = 0
text = ""
logfile = "/var/log/lighting.log"                          # define a logfile
debug = 1
loops = 0

class init():    
  global initialized
  global loops
  
  try:
    def __init__(self, init_done, laps):
      self.done = init_done
      self.laps = laps
    def ret_init(self):
      initialized = self.done
      loops = self.laps
      print("Message from Class init: value = " + str(self.done))
      print("Message from Class init: loops = " + str(loops))
      print("Message from Class init: loop = " + str(self.laps))
  except Exception as e:
    print(e)
    
def write_logfile(entry):

  count = 0 
  global logfile
  
  with open(logfile, 'r') as f:
    for line in f:
      count += 1                                           # count the lines in the logfile
      if (count > 60):                                     # make sure the logfile is not growing endlessly
        with open(logfile, 'r') as f_in:
          data =f_in.read().splitlines(True)
        with open(logfile, 'w') as f_out:
          f_out.writelines(data[1:])

  f = open(logfile,"a+")
  f.write(entry)
  f.close()

        
def on_connect(client, userdata, flags, rc):
  """ The callback for when the client receives a CONNACK response from the server."""
  print('Connected with result code ' + str(rc))
  client.subscribe(MQTT_TOPIC)

def on_message(client, userdata, msg):
  """The callback for when a PUBLISH message is received from the server."""
  global ItsNight
  global PowerInfo
  global Night
  global oldident
  global timenow
  global datetime_BKK
  global loops
  
  text = ""
  datetime_BKK = datetime.now(tz_BKK)
  timenow = str(datetime_BKK.strftime("%H:%M:%S")) 
  if (loops > 0):                                                                        # loops enables a delay to overcome a fuzzy daylight status 
    loops = loops - 1                                                                # as long as loops > 0 no signal is being sent 
  try:
    timenow = str(datetime_BKK.strftime("%H:%M:%S"))  
    ident=msg.topic[5:11]
    # uncomment next line for debugging
    # print(ident)
    if (ident == 'entree'): 
      print('____________________  ENTREE  _____________________\n')
      obj = msg.topic[12:len(msg.topic)]
      # entree is not active yet, it can do the same function as living room
      # currently just manually controlled
      print('ident 1 = ' + ident + ', topic: '  + obj + ', msg '  + msg.payload  + "\n")
      if (int(msg.payload) == 1):
        print(timenow + " light is off ")
      else:
        print(timenow + " light is on")
    elif (ident == 'garden'):
      print('_____________________  GARDEN  _____________________\n')
      print('topic  = ' + msg.topic + ', payload = ' + msg.payload)
      if (int(msg.payload) == 1):
        print(timenow + " It is dark\n")
      else:
        print(timenow + " Daylight in the garden\n")
      if (int(msg.payload) == 1):
        Night = 1
        #if (ItsNight == 0):
         # ItsNight += 1   # concat not possible
      else:
        ItsNight = 0
        Night = 0
      print('Lightsensor Westside: ' + str(Night))
      print('global ItsNight ' + str(ItsNight))
      print('\n')
    elif (ident == 'bathro'):
      print('____________________  BATHROOM  ____________________\n')
      print('topic  = ' + msg.topic + ', payload = ' + msg.payload)
      obj = msg.topic[14:len(msg.topic)]
      # print(obj)
      Inverter = int(msg.payload[0:1:1])
      Waterlevel = int(msg.payload[1:2:1]);
      print("Inverter = " + str(Inverter) + " , Waterlevel = " + str(Waterlevel))
      if (Inverter == 0):
        print(timenow + " running on the grid\n")
      else:
        print(timenow + " running on battery, water-heater \nand laundry-machine interrupted\n") 
      if (Waterlevel == 0):
        print(timenow + " running out of water\n")
      else:
        print(timenow + " enough water\n") 
    elif (ident == 'living'): 
      obj = msg.topic[16:len(msg.topic)]
      # controlling light in the pergola. It is on all night.
      PowerInfo = int(msg.payload[1:2:1]);
      ItsNight = int(msg.payload[0:1:1])  
      print("number of loops " + str(loops))
      if (1 == 1):
        print('___________________  LIVING-ROOM  __________________\n')
        print('topic  = ' + msg.topic + ', payload = ' + msg.payload)
        if ((ItsNight == 1) & (PowerInfo == 0)) | ((ItsNight == 0) & (PowerInfo == 1)):               
          loops = 0
          if (ItsNight == 1):  
            text = timenow + ", No action required, ItsNight = " + str(ItsNight) 
            text += " and light is on (optoc): " + str(PowerInfo)  + "\n"
          else:
            text = timenow + ", No action required, Daylight (ItsNight = " + str(ItsNight) 
            text += ") and light is off (optoc): " + str(PowerInfo)  + "\n"
        elif ((ItsNight == 0) & (PowerInfo == 0)) | ((ItsNight == 1) & (PowerInfo == 1)):          
          if (loops == 0):
            loops = 3
            subprocess.call(["/home/mikebkk/433Utils/RPi_utils/mycontrol1", "8"])
            
            write_logfile(text)
            
          if (ItsNight == 1): 
            text = timenow + ", action required, night & lamp off\n"
          else:
            text = timenow + ", action required, daylight & lamp on\n"
        else:
          pymsgbox.alert('Something went wrong!', 'Check this first')
        
        print(text)
        
      # in case the fire command fails it will be repeated until success 
      if (ident != 'living'): 
        print('topic  = ' + msg.topic + ', payload = ' + msg.payload +"\n")
    else:
      print('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')  
      print('ident ' + ident + ' no action yet')
      print(timenow)
      print(obj)
      print('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')  
    oldident = ident
  except Exception as e:
    print(e)

def main():
  #mqtt_client = mqtt.Client()
  mqtt_client = mqtt.Client("", True, mqtt.MQTTv31)
  mqtt_client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
  mqtt_client.on_connect = on_connect
  mqtt_client.on_message = on_message

  mqtt_client.connect(MQTT_ADDRESS, 1883)
  print(MQTT_ADDRESS)
  # uncomment to verify 
  # print(str(mqtt_client) + ", user : " +  str(MQTT_USER) + ", passwd: " +  str(MQTT_PASSWORD))

  mqtt_client.loop_forever()

if __name__ == '__main__':
  print('MQTT to InfluxDB bridge')
  main()

Re: The Proper Way to Reconnect to WiFi?

Posted: Mon Jan 25, 2021 10:05 am
by tuxtown
urs_eppenberger wrote:
Sat Dec 16, 2017 5:02 pm

I wrote a code framework, where in the main loop there are three section.
In the first section, there is the code for the setup of the connections
In the second section, this is only executed, if WiFi and MQTT are up and running
In the third section, this is always executed, regardless of WiFi or MQTT status

Please have a look. Maybe you can use the concept for your own code.
Hello, nice concept. I implemented it for PubSubClient. One question;

To reconnect to wifi, you use WiFi.begin() again. Seems to be working, however I also found information on the net about reconnection should be automatically. That is, the WiFi lib is handling that already. Anyone knows the details / pros and cons?

Besides that, are there any downsides on check WiFi.status() every loop() cycle? Some examples introduce delays for this.