Hieromon / AutoConnect

An Arduino library for ESP8266/ESP32 WLAN configuration at runtime with the Web interface
https://hieromon.github.io/AutoConnect/
MIT License
902 stars 188 forks source link

Auto reconnect after router power off #118

Closed Adrian8x closed 4 years ago

Adrian8x commented 5 years ago

Hello Hieromon,

I'm running in some problem's with ESP32 using AutoConnect. I can access the captive portal, the WiFi ssid and password are saved OK but I have trouble when my router power is disconnected or when there is a blackout. The ESP doesn't reconnect automatically to the last saved credentials when the power come back, if I not reset the ESP32 manually. The ESP is powered with batteries so it is not restarted with the router when a blackout occur. There is a specific configuration for that?

I use Arduino 1.8.9 and AutoConnect 0.9.10 with ESP32 DEVKIT v.1

Thank you!

Hieromon commented 5 years ago

@Adrian8x, It is worthwhile to consider the mechanism by which the module detects the situation you encounter and recovers itself.

ESP-IDF says: https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/wifi.html?highlight=reconnect#wifi-event-sta-disconnected

But, there are two things we need to be aware of to deal with this phenomenon.

  1. By ESP32 core, No SYSTEM_EVENT_STA_DISCONNECTED event occurs if the WiFi data link lost due to a power failure of the router, etc.
  2. ESP32 core will reconnect if it is just a WiFi disconnect.

Even if a power failure of the router occurs, ESP32 will reconnect since the router will recover the WiFi radio signal after the power of the router restored. If you have a cell phone that supports WiFi dithering, you can use it as a simple router and check the WiFi recovery sequence using the WiFiClientEvents.ino contained in WiFiClientEvents example of the ESP32 core library.

However, as you have experienced, if the WiFi data link does not recover even if the router recovers, you need to reconnect intentionally with a sketch. I have a suggestion that the module detects the WiFi disconnection event itself.

Polling the WiFi status, reset the module.

wl_status_t   WIFIStatus;
unsigned long StablePeriod;

void checkStateChange() {
  wl_status_t st = WiFi.status();
  if (WIFIStatus != st && st != WL_NO_SSID_AVAIL) {
    Serial.printf("Status changed:%d->%d, %ld\n", WIFIStatus, WiFi.status(), StablePeriod);
    WIFIStatus = WiFi.status();
    StablePeriod = millis();
  }
  else if (WIFIStatus == WL_DISCONNECTED) {
    // If disconnected state maintains while 60 seconds,
    // wait 3 minutes to reset the module

    if (millis() - StablePeriod > (60 * 1000)) {
      Serial.println("WiFi disconnected, enter deep sleep.");
      esp_sleep_enable_timer_wakeup(3 * 60 * 1000 * 1000);
      esp_deep_sleep_start();
    }
  }
}

void setup() {
  delay(1000);
  Serial.begin(115200);
  portal.begin();

  WIFIStatus = WiFi.status(); // set status to the connected
  StablePeriod = millis();
}

void loop() {
  checkStateChange();
  portal.handleClient();
}

If you can try this solution, please let me know the results. I think the problem and solution you have experienced is beneficial to other users and would like to reflect the results in the documentation if you have solved the problem.

Adrian8x commented 5 years ago

I get this response with your suggested code.

09:10:48.862 -> Status changed:3->6, 2493 09:11:48.865 -> WiFi disconnected, enter deep sleep. 09:14:46.671 -> ets Jun 8 2016 00:22:57 09:14:46.671 -> 09:14:46.671 -> rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) 09:14:46.671 -> configsip: 0, SPIWP:0xee 09:14:46.671 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 09:14:46.671 -> mode:DIO, clock div:1 09:14:46.671 -> load:0x3fff0018,len:4 09:14:46.705 -> load:0x3fff001c,len:1100 09:14:46.705 -> load:0x40078000,len:9232 09:14:46.705 -> load:0x40080400,len:6400 09:14:46.705 -> entry 0x400806a8 09:14:47.996 -> 09:14:48.030 -> [AC] SoftAP configure 192.168.244.1, 192.168.244.1, 255.255.255.0 09:14:48.030 -> [AC] Element not registered 09:14:48.030 -> [AC] caption<8> of /timezone created 09:14:48.064 -> [AC] noname placed on /timezone 09:14:48.064 -> [AC] caption<8> of /timezone loaded 09:14:48.064 -> [AC] Element not registered 09:14:48.064 -> [AC] timezone<6> of /timezone created 09:14:48.064 -> [AC] noname placed on /timezone 09:14:48.064 -> [AC] timezone<6> of /timezone loaded 09:14:48.064 -> [AC] Element not registered 09:14:48.064 -> [AC] newline<2> of /timezone created 09:14:48.064 -> [AC] noname placed on /timezone 09:14:48.064 -> [AC] newline<2> of /timezone loaded 09:14:48.064 -> [AC] Element not registered 09:14:48.098 -> [AC] start<7> of /timezone created 09:14:48.098 -> [AC] noname placed on /timezone 09:14:48.098 -> [AC] start<7> of /timezone loaded 09:14:48.098 -> [AC] /timezone on hands 09:14:48.199 -> [AC] WiFi.config(IP=0.0.0.0, Gateway=0.0.0.0, Subnetmask=0.0.0.0, DNS1=0.0.0.0, DNS2=0.0.0.0) 09:14:48.199 -> [AC] WiFi.begin() 09:14:48.199 -> [AC] Connecting....established IP:192.168.43.128 09:14:49.421 -> [AC] http server started 09:14:49.421 -> WiFi connected: 192.168.43.128

and still remain disconnected but I saw that after 3 minutes is reconnected, off coarse with your code with wakeup from deepsleep. I don't know why the ESP enter in deep sleep when the connection is lost.

For debug I use my cellphone with wifi hotspot.

The entire code is here:

`

if defined(ARDUINO_ARCH_ESP8266)

include

include

elif defined(ARDUINO_ARCH_ESP32)

include

include

endif

include

include

wl_status_t WIFIStatus; unsigned long StablePeriod;

static const char AUX_TIMEZONE[] PROGMEM = R"( { "title": "TimeZone", "uri": "/timezone", "menu": true, "element": [ { "name": "caption", "type": "ACText", "value": "Sets the time zone to get the current local time.", "style": "font-family:Arial;font-weight:bold;text-align:center;margin-bottom:10px;color:DarkSlateBlue" }, { "name": "timezone", "type": "ACSelect", "label": "Select TZ name", "option": [], "selected": 10 }, { "name": "newline", "type": "ACElement", "value": "
" }, { "name": "start", "type": "ACSubmit", "value": "OK", "uri": "/start" } ] } )";

typedef struct { const char zone; const char ntpServer; int8_t tzoff; } Timezone_t;

static const Timezone_t TZ[] = { { "Europe/London", "europe.pool.ntp.org", 0 }, { "Europe/Berlin", "europe.pool.ntp.org", 1 }, { "Europe/Helsinki", "europe.pool.ntp.org", 2 }, { "Europe/Moscow", "europe.pool.ntp.org", 3 }, { "Asia/Dubai", "asia.pool.ntp.org", 4 }, { "Asia/Karachi", "asia.pool.ntp.org", 5 }, { "Asia/Dhaka", "asia.pool.ntp.org", 6 }, { "Asia/Jakarta", "asia.pool.ntp.org", 7 }, { "Asia/Manila", "asia.pool.ntp.org", 8 }, { "Asia/Tokyo", "asia.pool.ntp.org", 9 }, { "Australia/Brisbane", "oceania.pool.ntp.org", 10 }, { "Pacific/Noumea", "oceania.pool.ntp.org", 11 }, { "Pacific/Auckland", "oceania.pool.ntp.org", 12 }, { "Atlantic/Azores", "europe.pool.ntp.org", -1 }, { "America/Noronha", "south-america.pool.ntp.org", -2 }, { "America/Araguaina", "south-america.pool.ntp.org", -3 }, { "America/Blanc-Sablon", "north-america.pool.ntp.org", -4}, { "America/New_York", "north-america.pool.ntp.org", -5 }, { "America/Chicago", "north-america.pool.ntp.org", -6 }, { "America/Denver", "north-america.pool.ntp.org", -7 }, { "America/Los_Angeles", "north-america.pool.ntp.org", -8 }, { "America/Anchorage", "north-america.pool.ntp.org", -9 }, { "Pacific/Honolulu", "north-america.pool.ntp.org", -10 }, { "Pacific/Samoa", "oceania.pool.ntp.org", -11 } };

if defined(ARDUINO_ARCH_ESP8266)

ESP8266WebServer Server;

elif defined(ARDUINO_ARCH_ESP32)

WebServer Server;

endif

AutoConnect Portal(Server); AutoConnectConfig Config; // Enable autoReconnect supported on v0.9.4 AutoConnectAux Timezone;

void rootPage() { String content = "" "" "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" "" "" "<h2 align=\"center\" style=\"color:blue;margin:20px;\">Hello, world" "<h3 align=\"center\" style=\"color:gray;margin:10px;\">{{DateTime}}" "<p style=\"text-align:center;\">Reload the page to update the time.

" "

<p style=\"padding-top:15px;text-align:center\">" AUTOCONNECT_LINK(COG_24) "

" "" ""; static const char wd[7] = { "Sun","Mon","Tue","Wed","Thr","Fri","Sat" }; struct tm tm; time_t t; char dateTime[26];

t = time(NULL); tm = localtime(&t); sprintf(dateTime, "%04d/%02d/%02d(%s) %02d:%02d:%02d.", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, wd[tm->tm_wday], tm->tm_hour, tm->tm_min, tm->tm_sec); content.replace("{{DateTime}}", String(dateTime)); Server.send(200, "text/html", content); }

void startPage() { // Retrieve the value of AutoConnectElement with arg function of WebServer class. // Values are accessible with the element name. String tz = Server.arg("timezone");

for (uint8_t n = 0; n < sizeof(TZ) / sizeof(Timezone_t); n++) { String tzName = String(TZ[n].zone); if (tz.equalsIgnoreCase(tzName)) { configTime(TZ[n].tzoff * 3600, 0, TZ[n].ntpServer); Serial.println("Time zone: " + tz); Serial.println("ntp server: " + String(TZ[n].ntpServer)); break; } }

// The /start page just constitutes timezone, // it redirects to the root page without the content response. Server.sendHeader("Location", String("http://") + Server.client().localIP().toString() + String("/")); Server.send(302, "text/plain", ""); Server.client().flush(); Server.client().stop(); }

void setup() { delay(1000); Serial.begin(115200); Serial.println();

// Enable saved past credential by autoReconnect option, // even once it is disconnected. Config.autoReconnect = true; Portal.config(Config);

// Load aux. page Timezone.load(AUX_TIMEZONE); // Retrieve the select element that holds the time zone code and // register the zone mnemonic in advance. AutoConnectSelect& tz = Timezone["timezone"].as(); for (uint8_t n = 0; n < sizeof(TZ) / sizeof(Timezone_t); n++) { tz.add(String(TZ[n].zone)); }

Portal.join({ Timezone }); // Register aux. page

// Behavior a root path of ESP8266WebServer. Server.on("/", rootPage); Server.on("/start", startPage); // Set NTP server trigger handler

// Establish a connection with an autoReconnect option. if (Portal.begin()) { Serial.println("WiFi connected: " + WiFi.localIP().toString()); }

WIFIStatus = WiFi.status(); // set status to the connected StablePeriod = millis();

}

void loop() { checkStateChange(); Portal.handleClient(); }

void checkStateChange() { wl_status_t st = WiFi.status(); if (WIFIStatus != st && st != WL_NO_SSID_AVAIL) { Serial.printf("Status changed:%d->%d, %ld\n", WIFIStatus, WiFi.status(), StablePeriod); WIFIStatus = WiFi.status(); StablePeriod = millis(); } else if (WIFIStatus == WL_DISCONNECTED) { // If disconnected state maintains while 60 seconds, // wait 3 minutes to reset the module

if (millis() - StablePeriod > (60 * 1000)) {
  Serial.println("WiFi disconnected, enter deep sleep.");
  esp_sleep_enable_timer_wakeup(3 * 60 * 1000 * 1000);
  esp_deep_sleep_start();
}

} }`

image

Adrian8x commented 5 years ago

Now I have another problem. If the wifi data link is lost for more than 3 minutes, the ESP enter in AP and remain in this mode even if i enable again the WIFI.

image

Hieromon commented 5 years ago

still remain disconnected but I saw that after 3 minutes is reconnected, off coarse with your code with wakeup from deepsleep. I don't know why the ESP enter in deep sleep when the connection is lost.

My understanding that you hope to reset the module after the router's restart, is it right? So, I suggested that would meet your wishes. Don't you need to wait for the router to restart? It waits for the router to the ready. The 3 minutes is the time I decided. Please change it according to your situation.

Another method is to issue the WiFi.begin() after waiting for the router to restart.

Hieromon commented 5 years ago

If the wifi data link is lost for more than 3 minutes, the ESP enter in AP and remain in this mode even if i enable again the WIFI.

Try AutoConnectConfig::autoReconnect.

Adrian8x commented 5 years ago

I use Autoconnect for a weather station. All what I want are the following:

If I press a button, the system will enter in AP, config mode. When the connection is made, the system enter in station mode. If the router is disconnected, the system doesn't enter in AP back, it have to automatically reconnect to the router when the connection is back (the power down time is unknown...it may be 1 minute or one hour...) but in a few moment as my PC reconnect to my hotspot when I re-enable it .

Sorry for my english. I hope you understand what I say.

Hieromon commented 5 years ago

The detection of WiFi disconnection can be achieved with my proposed logic. But, I have a question before I answer.

If I press a button, the system will enter in AP, config mode. When the connection is made, the system enter in station mode.

How doing you achieve this using AutoConnect? The measures for reconnection differ depending on its method.

Adrian8x commented 5 years ago

void setup() { Config.autoRise = false; Config.immediateStart = true; Portal.config(Config); }

void loop() { if (digitalRead(2) == LOW) { while (digitalRead(2) == LOW) yield(); Portal.begin(); } Portal.handleClient(); }

Adrian8x commented 5 years ago

I wanna make a connection like WPS from router to protect the system. Only the user with physical device should be able to enter in config mode (with a timeout of 1 minute) if he touch a button on device.

This work in progress.

Hieromon commented 5 years ago

Please try this one. I expect you to read the intent of the code below.

void checkStateChange() {
  wl_status_t st = WiFi.status();
  if (WIFIStatus != st && st != WL_NO_SSID_AVAIL) {
    Serial.printf("Status changed:%d->%d, %ld\n", WIFIStatus, WiFi.status(), StablePeriod);
    WIFIStatus = WiFi.status();
    StablePeriod = millis();
  }
  else if (WIFIStatus == WL_DISCONNECTED) {
    // If disconnected state maintains while 60 seconds,
    // wait 3 minutes to reset the module

    if (millis() - StablePeriod > (60 * 1000)) {
      Serial.println("WiFi disconnected, enter deep sleep.");
      esp_sleep_enable_timer_wakeup(3 * 60 * 1000 * 1000);
      esp_deep_sleep_start();
    }
  }
}

void setup() {
  Config.autoRise = false;
  Portal.config(Config);
  Portal.begin();
  WIFIStatus = WiFi.status();
  StablePeriod = millis();
}

void loop() {
  if (digitalRead(2) == LOW) {
    while (digitalRead(2) == LOW)
      yield();
    Config.immediateStart = true;
    Portal.config(Config);
    Portal.begin();
  }
  checkStateChange();
  Portal.handleClient();
}

Wait about 30 seconds after detecting the first WL_DISCONNECED. Check WiFistatus again and if it is WL_DISCONNECTED (If it is a simple communication failure, it will be reconnected on the ESP32 core side.), it will go into the deepsleep once. The waiting time is 1 minute that enough. The reason for using deep sleep for standby processing is to reduce battery consumption.

Hieromon commented 5 years ago

Supplement, Portal.begin() in setup() may be more appropriate to wait for connection with WiFi.begin(). It depends on your usage.

void setup() {
  WiFi.mode(WIFI_STA);
  delay(100);
  WiFi.begin();
  unsigned long tm = millis();
  while (WiFi.status != WL_CONNECTED) {
    if (millis() - tm > 30000)
      break;
    delay(300);
  }
  WIFIStatus = WiFi.status();
  StablePeriod = millis();
}
Hieromon commented 4 years ago

Close once due to no activity record for one month. Reopen when new reports are posted.