Skip to content

Instantly share code, notes, and snippets.

@ItKindaWorks
Last active January 29, 2022 17:23
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ItKindaWorks/24fac4d9b0dcc11670a1ffcc947babf7 to your computer and use it in GitHub Desktop.
Save ItKindaWorks/24fac4d9b0dcc11670a1ffcc947babf7 to your computer and use it in GitHub Desktop.
An ESP8266 based Arduino program for connecting to and automatically rebooting a router when it loses an internet connection
/*
configAndStatusAPDemo.ino
Copyright (c) 2017 ItKindaWorks All right reserved.
github.com/ItKindaWorks
This file is part of ESPHelper
ESPHelper is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ESPHelper is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ESPHelper. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ESPHelper.h" //ESPHelper & links to its dependencies found here: https://github.com/ItKindaWorks/ESPHelper
#include "ESPHelperFS.h"
#include "ESPHelperWebConfig.h"
#include <ESP8266Ping.h> //ESP8266Ping found here: https://github.com/dancol90/ESP8266Ping
#include "Metro.h" //Metro library found here: http://playground.arduino.cc/Code/Metro
netInfo config;
ESPHelper myESP;
//setup a server on port 80 (http). We use an external server here because we want more than just a config page
//but also a status page or anything else that we want to display
ESP8266WebServer server(80);
ESPHelperWebConfig configPage(&server, "/config");
//defualt net info for unconfigured devices
netInfo homeNet = { .mqttHost = "YOUR MQTT-IP", //can be blank if not using MQTT
.mqttUser = "YOUR MQTT USERNAME", //can be blank
.mqttPass = "", //can be blank
.mqttPort = 1883, //default port for MQTT is 1883 - only chance if needed.
.ssid = "YOUR SSID",
.pass = "",
.otaPassword = "",
.hostname = "NEW-ESP8266"};
//AP moade setup info
const char* broadcastSSID = "ESP-Hotspot";
const char* broadcastPASS = "";
IPAddress broadcastIP = {192, 168, 1, 1};
const int configBtnPin = D2;
//timeout before triggering the relay to turn off and on
Metro connectTimeout = Metro(30000);
Metro pingTimer = Metro(5000);
bool pingStatus = false;
const char* pingHost = "google.com";
//variables for controlling the router relay
const int relayPin = 5;
Metro relayTimer = Metro(10000);
//cooldown timer to prevent the system from restarting the router over and over before it
//can fully start up
Metro cooldownTimer = Metro(130000);
enum states {CHECKING, RUNNING, COOLDOWN, AP_MODE};
int8_t currentState = CHECKING;
void setup(void){
Serial.begin(115200);
//print some debug
Serial.println("Starting Up - Please Wait...");
delay(100);
pinMode(configBtnPin, INPUT);
pinMode(relayPin, OUTPUT);
digitalWrite(relayPin, LOW);
//startup the wifi and web server (more in the lines below)
startWifi();
//setup the http server and config page (fillConfig will take the netInfo file and use that for
//default values)
configPage.fillConfig(&config);
configPage.begin(config.hostname);
// Actually start the server (again this would be done automatically
//if we were just using the config page and didnt use an external server...)
server.begin();
server.on("/", HTTP_GET, handleStatus);
}
void loop(void){
////// config handling //////
//check to see if the AP mode button has been pressed and go into AP mode if needed
checkForAPMode();
//handle saving a new network config
if(configPage.handle()){
Serial.println("Saving new network config and restarting...");
myESP.saveConfigFile(configPage.getConfig(), "/netConfig.json");
delay(500);
ESP.restart();
}
////// main state machine //////
//get the current status of ESPHelper
int espHelperStatus = myESP.loop();
//if the ping timer has elapsed and we are connected to WiFi then ping the test host
if(currentState == CHECKING && pingTimer.check()){
//update pingstatus with results of ping or false if no wifi
if(espHelperStatus >= WIFI_ONLY){pingStatus = Ping.ping(pingHost);}
else{pingStatus = false;}
//print out results of ping attempt
if(pingStatus){ Serial.println(String("Good ping to " + String(pingHost)));}
else{ Serial.println(String("Could not ping " + String(pingHost)));}
pingTimer.reset();
}
//reset the timer if the pingStatus is true or if we are in AP mode
if(currentState == AP_MODE || pingStatus){
connectTimeout.reset();
}
//if the ESP cannot connect (and based on the above if statement is not in AP broadcast mode)
//then trigger the relay pin and start (reset) the relay timer
if(currentState == CHECKING && connectTimeout.check() ){
Serial.println("Cannot connect to wifi or cannot reach WAN, restarting router...");
digitalWrite(relayPin, HIGH);
relayTimer.reset();
cooldownTimer.reset();
currentState = RUNNING;
}
//if the relay is on and the timer for turning the relay off has elapsed
//then turn the relay off
else if(currentState == RUNNING && relayTimer.check()){
Serial.println("Turning router back on...");
digitalWrite(relayPin, LOW);
currentState = COOLDOWN;
}
//cooldown state timer checker (cooldown to prevent the router from being rebooted while still starting up)
if(currentState == COOLDOWN && cooldownTimer.check()){
Serial.println("Router restart cooldown complete returning to normal state.");
connectTimeout.reset();
currentState = CHECKING;
}
//after each loop give the ESP some time to do other (networking related) functions
delay(50);
}
//ESPHelper & config setup and runtime handler functions
//function that checks for the config button to be pressed and drops the ESP into AP mode for configuring
void checkForAPMode(){
if(digitalRead(configBtnPin) && currentState != AP_MODE){
Serial.println("AP mode button pressed - starting broadcast (AP) mode...");
currentState = AP_MODE;
myESP.broadcastMode(broadcastSSID, broadcastPASS, broadcastIP);
myESP.OTA_setPassword(config.otaPassword);
myESP.OTA_setHostnameWithVersion(config.hostname);
myESP.OTA_enable();
Serial.println("Done.");
}
}
void startWifi(){
loadConfig();
//setup other ESPHelper info and enable OTA updates
myESP.setHopping(false);
myESP.OTA_setPassword(config.otaPassword);
myESP.OTA_setHostnameWithVersion(config.hostname);
myESP.OTA_enable();
Serial.println("Connecting to network");
Serial.println( String("SSID: " + String(myESP.getSSID()) + "\nWiFiPass: " + String(myESP.getPASS()) + " ") );
delay(10);
//connect to wifi before proceeding. (also check for the AP button being pressed and drop into AP mode if it is)
//this will timeout after 20 seconds and just continue the loop regardless
Metro startupTimeout = Metro(20000);
while(myESP.loop() < WIFI_ONLY){
checkForAPMode();
if(currentState == AP_MODE){return;}
if(startupTimeout.check()){break;}
delay(10);
}
if(myESP.loop() >= WIFI_ONLY){
Serial.println("Success!");
Serial.print("IP Address: ");
Serial.println(myESP.getIP());
}
else{
Serial.println("Could not connect to router. Make sure your device is configured \ncorrectly and/or put it into AP mode for configuration");
}
}
//attempt to load a network configuration from the filesystem
void loadConfig(){
//check for a good config file and start ESPHelper with the file stored on the ESP
if(ESPHelperFS::begin()){
Serial.println("Filesystem loaded - Loading Config");
if(ESPHelperFS::validateConfig("/netConfig.json") == GOOD_CONFIG){
Serial.println("Config loaded");
delay(10);
myESP.begin("/netConfig.json");
}
//if no good config can be loaded (no file/corruption/etc.) then
//attempt to generate a new config and restart the module
else{
Serial.println("Could not load config - saving new config from default values and restarting");
delay(10);
ESPHelperFS::createConfig(&homeNet, "/netConfig.json");
ESPHelperFS::end();
ESP.restart();
}
}
//if the filesystem cannot be started, just fail over to the
//built in network config hardcoded in here
else{
Serial.println("Could not load filesystem, proceeding with default config values");
delay(10);
myESP.begin(&homeNet);
}
//load the netInfo from espHelper for use in the config page
config = myESP.getNetInfo();
}
//main config page that allows user to enter in configuration info
void handleStatus() {
server.send(200, "text/html", \
String("<html>\
<header>\
<title>Device Info</title>\
</header>\
<body>\
<p><strong>System Status</strong></br>\
Device Name: " + String(myESP.getHostname()) + "</br>\
Connected SSID: " + String(myESP.getSSID()) + "</br>\
Device IP: " + String(myESP.getIP()) + "</br>\
Uptime (ms): " + String(millis()) + "</p>\
<p> </p>\
<p><strong>Device Variables</strong></p>\
<p>Ping Status: " + String(pingStatus) + "</p>\
<p>States: 0: CHECKING | 1: RUNNING | 2: COOLDOWN | 3: AP_MODE </p>\
<p>Device State: " + String(currentState) + "</p>\
</body>\
</html>"));
}
@gotenham
Copy link

under the section void startWifi(){ the metro timer initialisation Metro startupTimeout = Metro(20000); needs to be moved to the beginning with the other metro timers otherwise the timer never starts and breaks the while(myESP.loop() < WIFI_ONLY) loop because of the if(startupTimeout.check()){break;} before the ESP has had time to connect and get an IP address

@douglasbaiocco
Copy link

Hello. First of all, this project is amazing. Thanks.

I was searching for it for a long time and this is the best approach that I found. Unfortunately, I tried to compile in Arduino IDE 1.8.13 and I get some errors. I tried to correct these errors by compiling many versions of ESP8266 boards (from 2.0 to 2.7). I tried to use Chrono library instead of a Metro too, but no success in any case.

One of the errors is:
c:/users/douglas/appdata/local/arduino15/packages/esp8266/tools/xtensa-lx106-elf-gcc/2.5.0-3-20ed2b9/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: sketch\WIFI_REBOOT.ino.cpp.o: in function _GLOBAL__sub_I__ZN9PingClassC2Ev': C:\Users\Douglas\Documents\Arduino\libraries\ESPHelper-master\src/sharedData.h:135: undefined reference to Metro::Metro(unsigned long)'

What that I doing wrong?

Thanks a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment