support for web server and reset firmware option

This commit is contained in:
Paul
2019-06-24 23:40:01 +02:00
parent 4b28a1d9b5
commit bdf18b091d
8 changed files with 492 additions and 119 deletions

View File

@@ -1,7 +1,7 @@
/*
* MyESP - my ESP helper class to handle WiFi, MQTT and Telnet
*
* Paul Derbyshire - December 2018
* Paul Derbyshire - first revision: December 2018
*
* Ideas borrowed from Espurna https://github.com/xoseperez/espurna
*/
@@ -14,9 +14,10 @@ EEPROM_Rotate EEPROMr;
union system_rtcmem_t {
struct {
uint8_t stability_counter;
uint8_t reset_reason;
uint16_t _reserved_;
uint8_t stability_counter;
uint8_t reset_reason;
uint8_t boot_status;
uint8_t _reserved_;
} parts;
uint32_t value;
};
@@ -41,10 +42,12 @@ MyESP::MyESP() {
_fs_callback = NULL;
_fs_settings_callback = NULL;
_web_callback = NULL;
_helpProjectCmds = NULL;
_helpProjectCmds_count = 0;
_use_serial = false;
_serial = false;
_heartbeat = false;
_mqtt_host = NULL;
_mqtt_password = NULL;
@@ -61,6 +64,8 @@ MyESP::MyESP() {
_mqtt_last_connection = 0;
_mqtt_connecting = false;
_firstInstall = false;
_wifi_password = NULL;
_wifi_ssid = NULL;
_wifi_callback = NULL;
@@ -137,7 +142,7 @@ void MyESP::myDebug_P(PGM_P format_P, ...) {
// use Serial?
bool MyESP::getUseSerial() {
return (_use_serial);
return (_serial);
}
// heartbeat
@@ -191,7 +196,7 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
_wifi_connected = true;
// finally if we don't want Serial anymore, turn it off
if (!_use_serial) {
if (!_serial) {
myDebug_P(PSTR("Disabling serial port communication."));
SerialAndTelnet.flush(); // flush so all buffer is printed to serial
SerialAndTelnet.setSerial(NULL);
@@ -213,14 +218,6 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
myDebug_P(PSTR("[WIFI] IP %s"), WiFi.softAPIP().toString().c_str());
myDebug_P(PSTR("[WIFI] MAC %s"), WiFi.softAPmacAddress().c_str());
// we could be in panic mode so enable Serial again
if (!_use_serial) {
SerialAndTelnet.setSerial(&Serial);
_use_serial = true;
}
myDebug_P(PSTR("Enabling serial port output"));
// call any final custom settings
if (_wifi_callback) {
_wifi_callback();
@@ -326,7 +323,7 @@ void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
// Restart the device
if (strcmp(topic, MQTT_TOPIC_RESTART) == 0) {
myDebug_P(PSTR("[MQTT] Received restart command"), message);
myESP.resetESP();
resetESP();
return;
}
@@ -334,7 +331,7 @@ void MyESP::_mqttOnMessage(char * topic, char * payload, size_t len) {
// for example with HA it sends the system time from the server
if (strcmp(topic, MQTT_TOPIC_START) == 0) {
myDebug_P(PSTR("[MQTT] Received boottime: %s"), message);
myESP.setBoottime(message);
setBoottime(message);
return;
}
@@ -487,7 +484,7 @@ void MyESP::_ota_setup() {
ArduinoOTA.onStart([this]() { _OTACallback(); });
ArduinoOTA.onEnd([this]() {
myDebug_P(PSTR("[OTA] Done, restarting..."));
_deferredReset(100, CUSTOM_RESET_OTA);
_deferredReset(500, CUSTOM_RESET_OTA);
});
ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) {
@@ -551,13 +548,7 @@ void MyESP::_telnetConnected() {
uint32_t crash_time;
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time != 0) && (crash_time != 0xFFFFFFFF)) {
crashDump();
/*
// clear crash data
crash_time = 0xFFFFFFFF;
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
*/
myDebug_P(PSTR("[SYSTEM] There is stack data available from the last system crash. Use 'crash dump' to view and 'crash clear' to reset"));
}
#endif
@@ -692,7 +683,7 @@ void MyESP::_printSetCommands() {
}
myDebug_P(PSTR("")); // newline
myDebug_P(PSTR(" serial=%s"), (_use_serial) ? "on" : "off");
myDebug_P(PSTR(" serial=%s"), (_serial) ? "on" : "off");
myDebug_P(PSTR(" heartbeat=%s"), (_heartbeat) ? "on" : "off");
// print any custom settings
@@ -704,7 +695,7 @@ void MyESP::_printSetCommands() {
// reset / restart
void MyESP::resetESP() {
myDebug_P(PSTR("* Reboot ESP..."));
_deferredReset(100, CUSTOM_RESET_TERMINAL);
_deferredReset(500, CUSTOM_RESET_TERMINAL);
end();
#if defined(ARDUINO_ARCH_ESP32)
ESP.restart();
@@ -781,16 +772,16 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
ok = true;
} else if (strcmp(setting, "serial") == 0) {
ok = true;
_use_serial = false;
ok = true;
_serial = false;
if (value) {
if (strcmp(value, "on") == 0) {
_use_serial = true;
ok = true;
_serial = true;
ok = true;
myDebug_P(PSTR("Reboot ESP to activate Serial mode."));
} else if (strcmp(value, "off") == 0) {
_use_serial = false;
ok = true;
_serial = false;
ok = true;
myDebug_P(PSTR("Reboot ESP to deactivate Serial mode."));
} else {
ok = false;
@@ -842,10 +833,10 @@ void MyESP::setUseSerial(bool toggle) {
if (toggle) {
SerialAndTelnet.setSerial(&Serial);
_use_serial = true;
_serial = true;
} else {
SerialAndTelnet.setSerial(NULL);
_use_serial = false;
_serial = false;
}
}
@@ -987,12 +978,26 @@ unsigned long MyESP::_getUptime() {
return uptime_seconds;
}
// reason code
// init RTC mem
void MyESP::_rtcmemInit() {
memset((uint32_t *)RTCMEM_ADDR, 0, sizeof(uint32_t) * RTCMEM_BLOCKS);
Rtcmem->magic = RTCMEM_MAGIC;
}
uint8_t MyESP::getSystemBootStatus() {
system_rtcmem_t data;
data.value = Rtcmem->sys;
return data.parts.boot_status;
}
void MyESP::_setSystemBootStatus(uint8_t status) {
system_rtcmem_t data;
data.value = Rtcmem->sys;
data.parts.boot_status = status;
Rtcmem->sys = data.value;
// myDebug("*** setting boot status to %d", data.parts.boot_status);
}
uint8_t MyESP::_getSystemStabilityCounter() {
system_rtcmem_t data;
data.value = Rtcmem->sys;
@@ -1019,6 +1024,7 @@ void MyESP::_setSystemResetReason(uint8_t reason) {
Rtcmem->sys = data.value;
}
// system_get_rst_info() result is cached by the Core init for internal use
uint32_t MyESP::getSystemResetReason() {
return resetInfo.reason;
}
@@ -1034,14 +1040,26 @@ void MyESP::_setCustomResetReason(uint8_t reason) {
_setSystemResetReason(reason);
}
// returns false if not set and needs to be intialized
// returns false if not set and needs to be intialized, causing all rtcmem data to be wiped
bool MyESP::_rtcmemStatus() {
bool readable;
switch (getSystemResetReason()) {
case REASON_EXT_SYS_RST:
case REASON_WDT_RST:
case REASON_DEFAULT_RST:
uint32_t reason = getSystemResetReason();
// the last reset could have been caused by manually pressing the reset button
// so before wiping, capture the boot sequence
if (reason == REASON_EXT_SYS_RST) { // external system reset
if (getSystemBootStatus() == MYESP_BOOTSTATUS_BOOTING) {
_setSystemBootStatus(MYESP_BOOTSTATUS_RESETNEEDED);
} else {
_setSystemBootStatus(MYESP_BOOTSTATUS_POWERON);
}
}
switch (reason) {
//case REASON_EXT_SYS_RST: // external system reset
case REASON_WDT_RST: // hardware watch dog reset
case REASON_DEFAULT_RST: // normal startup by power on
readable = false;
break;
default:
@@ -1071,12 +1089,13 @@ uint8_t MyESP::_getCustomResetReason() {
}
void MyESP::_deferredReset(unsigned long delaytime, uint8_t reason) {
delay(delaytime);
_setSystemBootStatus(MYESP_BOOTSTATUS_POWERON);
_setCustomResetReason(reason);
delay(delaytime);
}
// Call this method on boot with start=true to increase the crash counter
// Call it again once the system is stable to decrease the counter
// Call this method on boot with stable=true to reset the crash counter
// Each call increments the counter
// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable
void MyESP::_setSystemCheck(bool stable) {
uint8_t value = 0;
@@ -1095,17 +1114,25 @@ void MyESP::_setSystemCheck(bool stable) {
if (++value > SYSTEM_CHECK_MAX) {
_systemStable = false;
value = 0; // system is unstable
myDebug_P(PSTR("[SYSTEM] Warning, system UNSTABLE\n"));
myDebug_P(PSTR("[SYSTEM] Warning, system UNSTABLE. Serial mode is enabled."));
// enable Serial again
if (!_serial) {
SerialAndTelnet.setSerial(&Serial);
_serial = true;
}
}
}
_setSystemStabilityCounter(value);
}
bool MyESP::getSystemCheck() {
// return if system is stable (false=bad)
bool MyESP::_getSystemCheck() {
return _systemStable;
}
// periodically check if system is stable
void MyESP::_systemCheckLoop() {
static bool checked = false;
if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
@@ -1144,7 +1171,7 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [APP] System Load: %d%%"), getSystemLoadAverage());
if (!getSystemCheck()) {
if (!_getSystemCheck()) {
myDebug_P(PSTR(" [SYSTEM] Device is in SAFE MODE"));
}
@@ -1158,6 +1185,12 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [WIFI] WiFi MAC: %s"), WiFi.macAddress().c_str());
if (isMQTTConnected()) {
myDebug_P(PSTR(" [MQTT] connected (heartbeat %s)"), getHeartbeat() ? "enabled" : "disabled");
} else {
myDebug_P(PSTR(" [MQTT] disconnected"));
}
#ifdef CRASH
char output_str[80] = {0};
char buffer[16] = {0};
@@ -1195,7 +1228,7 @@ void MyESP::showSystemStats() {
}
myDebug_P(PSTR(" [SYSTEM] Restart count: %d"), _getSystemStabilityCounter());
myDebug_P(PSTR(" [SYSTEM] rtcmem status:%u blocks:%u addr:0x%p"), _rtcmemStatus(), RtcmemSize, Rtcmem);
myDebug_P(PSTR(" [SYSTEM] rtcmem status: blocks:%u addr:0x%p"), RtcmemSize, Rtcmem);
for (uint8_t block = 0; block < RtcmemSize; ++block) {
myDebug_P(PSTR(" [SYSTEM] rtcmem %02u: %u"), block, reinterpret_cast<volatile uint32_t *>(RTCMEM_ADDR)[block]);
}
@@ -1289,7 +1322,7 @@ void MyESP::_telnetHandle() {
if (charsRead > 0) {
charsRead = 0; // is static, so have to reset
_suspendOutput = false;
if (_use_serial) {
if (_serial) {
SerialAndTelnet.serialPrint('\n'); // force newline if in Serial
}
_telnetCommand(_command);
@@ -1378,14 +1411,14 @@ void MyESP::_mqttConnect() {
// Setup everything we need
void MyESP::setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback) {
// Check SSID too long or missing
if (!wifi_ssid || *wifi_ssid == 0x00 || strlen(wifi_ssid) > 31) {
if (!wifi_ssid || *wifi_ssid == 0x00 || strlen(wifi_ssid) > MAX_STR_LEN) {
_wifi_ssid = NULL;
} else {
_wifi_ssid = strdup(wifi_ssid);
}
// Check PASS too long
if (!wifi_password || *wifi_ssid == 0x00 || strlen(wifi_password) > 31) {
if (!wifi_password || *wifi_ssid == 0x00 || strlen(wifi_password) > MAX_STR_LEN) {
_wifi_password = NULL;
} else {
_wifi_password = strdup(wifi_password);
@@ -1501,15 +1534,21 @@ void MyESP::_fs_printConfig() {
// format File System
void MyESP::_fs_eraseConfig() {
myDebug_P(PSTR("[FS] Erasing settings, please wait a few seconds. ESP will "
myDebug_P(PSTR("[FS] Erasing all settings, please wait a few seconds. ESP will "
"automatically restart when finished."));
if (SPIFFS.format()) {
if (SPIFFS.remove(MYEMS_CONFIG_FILE)) {
delay(1000); // wait 1 second
resetESP();
SerialAndTelnet.flush();
resetESP(); // hard reset
}
}
// custom callback for web info
void MyESP::setWeb(web_callback_f callback_web) {
_web_callback = callback_web;
}
void MyESP::setSettings(fs_callback_f callback_fs, fs_settings_callback_f callback_settings_fs) {
_fs_callback = callback_fs;
_fs_settings_callback = callback_settings_fs;
@@ -1525,8 +1564,6 @@ bool MyESP::_fs_loadConfig() {
return false;
} else if (size == 0) {
myDebug_P(PSTR("[FS] Failed to open config file"));
// file does not exist, so assume its the first install. Set serial to on
_use_serial = true;
return false;
}
@@ -1558,10 +1595,13 @@ bool MyESP::_fs_loadConfig() {
value = json["mqtt_password"];
_mqtt_password = (value) ? strdup(value) : NULL;
_use_serial = (bool)json["use_serial"]; // defaults to off
_serial = (bool)json["serial"]; // defaults to off
// _serial = true; // uncomment for debugging everything to serial
_heartbeat = (bool)json["heartbeat"]; // defaults to off
// callback for loading custom settings
// ok is false if there's a problem loading a custom setting (e.g. does not exist)
bool ok = (_fs_callback)(MYESP_FSACTION_LOAD, json);
@@ -1589,7 +1629,7 @@ bool MyESP::fs_saveConfig() {
json["mqtt_host"] = _mqtt_host;
json["mqtt_username"] = _mqtt_username;
json["mqtt_password"] = _mqtt_password;
json["use_serial"] = _use_serial;
json["serial"] = _serial;
json["heartbeat"] = _heartbeat;
// callback for saving custom settings
@@ -1632,10 +1672,23 @@ void MyESP::_fs_setup() {
return;
}
// if its flagged as a first install, re-create the initial config file and quit function
if (_firstInstall) {
myDebug_P(PSTR("[FS] Re-creating config file for initial install"));
fs_saveConfig();
return;
}
// load the config file. if it doesn't exist (function returns false) create it
if (!_fs_loadConfig()) {
//myDebug_P(PSTR("[FS] Re-creating config file"));
fs_saveConfig();
_firstInstall = true; // flag as a first install
}
// assume if the wifi ssid is empty, its a fresh install too
if ((_wifi_ssid == NULL)) {
_firstInstall = true; // flag as a first install
}
// _fs_printConfig(); // enable for debugging
@@ -1671,14 +1724,6 @@ bool MyESP::isMQTTConnected() {
}
// return true if wifi is connected
// WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
// WL_IDLE_STATUS = 0,
// WL_NO_SSID_AVAIL = 1,
// WL_SCAN_COMPLETED = 2,
// WL_CONNECTED = 3,
// WL_CONNECT_FAILED = 4,
// WL_CONNECTION_LOST = 5,
// WL_DISCONNECTED = 6
bool MyESP::isWifiConnected() {
return (_wifi_connected);
}
@@ -1855,6 +1900,9 @@ void MyESP::crashDump() {
myDebug_P(PSTR("\nTo clean this dump use the command: %scrash clear%s\n"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
}
/*
* Force some crashes to test if stack collection and debugging works
*/
void MyESP::crashTest(uint8_t t) {
if (t == 1) {
myDebug_P(PSTR("[CRASH] Attempting to divide by zero ..."));
@@ -1906,20 +1954,188 @@ void MyESP::crashInfo() {
}
#endif
// default home web page
void MyESP::_webRootPage() {
char s[1000] = {0};
strlcpy(s, webCommonPage_start, sizeof(s));
strlcat(s, "<h1>", sizeof(s));
strlcat(s, _app_name, sizeof(s));
strlcat(s, " version ", sizeof(s));
strlcat(s, _app_version, sizeof(s));
strlcat(s, "</h1>", sizeof(s));
strlcat(s, "<p><b>System stats:</b><br>", sizeof(s));
strlcat(s, isMQTTConnected() ? " MQTT is connected\n" : " MQTT is disconnected\n", sizeof(s));
strlcat(s, "<br>", sizeof(s));
// uptime
char buffer[200];
uint32_t t = _getUptime(); // seconds
uint32_t d = t / 86400L;
uint32_t h = ((t % 86400L) / 3600L) % 60;
uint32_t rem = t % 3600L;
uint8_t m = rem / 60;
uint8_t sec = rem % 60;
sprintf(buffer, " System uptime: %d days %d hours %d minutes %d seconds<br>", d, h, m, sec);
strlcat(s, buffer, sizeof(s));
// memory
uint32_t total_memory = _getInitialFreeHeap();
uint32_t free_memory = ESP.getFreeHeap();
sprintf(buffer, " Memory: %d bytes free (%2u%%)<br>", free_memory, 100 * free_memory / total_memory);
strlcat(s, buffer, sizeof(s));
strlcat(s, "<p>", sizeof(s));
if (_web_callback) {
char custom[MYESP_MAXCHARBUFFER];
(_web_callback)(custom);
strlcat(s, custom, sizeof(s));
}
strlcat(s, "</p><br>", sizeof(s));
// check why we're here
if ((_firstInstall) || (_wifi_ssid == NULL)) {
strlcat(s, "<p>Looks like a first install! Go <a href=/reset>here</a> to connect the System to your network.</p>", sizeof(s));
} else {
strlcat(s, "<p>Go <a href=/reset>here</a> to connect the System to your wireless network.</p>", sizeof(s));
}
strlcat(s, webCommonPage_end, sizeof(s));
webServer.sendHeader("Content-Length", String(strlen(s)));
webServer.send(200, "text/html", s);
}
// Creates a webpage that allows the user to change the SSID and Password from the browser
void MyESP::_webResetPage() {
char s[1000] = {0};
strlcpy(s, webCommonPage_start, sizeof(s));
strlcat(s, "<h1>", sizeof(s));
strlcat(s, _app_name, sizeof(s));
strlcat(s, " version ", sizeof(s));
strlcat(s, _app_version, sizeof(s));
strlcat(s, "</h1>", sizeof(s));
// Check to see if we've been sent any arguments and instantly return if not
if (webServer.args() == 0) {
strlcat(s, "<p>", sizeof(s));
if (_wifi_ssid != NULL) {
strlcat(s, "Current wifi SSID is ", sizeof(s));
strlcat(s, _wifi_ssid, sizeof(s));
strlcat(s, ".<br>", sizeof(s));
}
strlcat(s, "<br>Please enter your new wifi credentials below.</p>", sizeof(s));
strlcat(s, webResetPage_form, sizeof(s));
strlcat(s, webCommonPage_end, sizeof(s));
webServer.sendHeader("Content-Length", String(strlen(s)));
webServer.send(200, "text/html", s);
} else {
// Create a string containing all the arguments
// Check to see if there are new values (also doubles to check the length of the new value is long enough)
if (webServer.arg("newssid").length() < MAX_STR_LEN) {
if (webServer.arg("newssid").length() == 0) {
_wifi_ssid = NULL;
} else {
_wifi_ssid = strdup(webServer.arg("newssid").c_str());
}
}
if (webServer.arg("newpassword").length() < MAX_STR_LEN) {
if (webServer.arg("newpassword").length() == 0) {
_wifi_password = NULL;
} else {
_wifi_password = strdup(webServer.arg("newpassword").c_str());
}
}
// Store the new settings
fs_saveConfig();
// Reply with a web page to indicate success or failure
strlcat(s, webResetPage_post, sizeof(s));
strlcat(s, webCommonPage_end, sizeof(s));
webServer.sendHeader("Content-Length", String(strlen(s)));
webServer.send(200, "text/html", s);
delay(500);
resetESP();
}
}
// reset all settings
void MyESP::_webResetAllPage() {
char s[1000] = {0};
strlcpy(s, webCommonPage_start, sizeof(s));
strlcat(s, "<h1>", sizeof(s));
strlcat(s, _app_name, sizeof(s));
strlcat(s, " version ", sizeof(s));
strlcat(s, _app_version, sizeof(s));
strlcat(s, "</h1>", sizeof(s));
// Check to see if we've been sent any arguments and instantly return if not
if (webServer.args() == 0) {
strlcat(s,
"<p>Are you absolutely sure you want to erase all settings?<br>Typing 'yes' will restart the System and you'll need to reconnect to the wifi "
"Access Point called ems-esp.</p>",
sizeof(s));
strlcat(s, webResetAllPage_form, sizeof(s));
strlcat(s, webCommonPage_end, sizeof(s));
webServer.sendHeader("Content-Length", String(strlen(s)));
webServer.send(200, "text/html", s);
} else {
// delete all settings
if (webServer.arg("confirm") == "yes") {
_fs_eraseConfig();
delay(1000); // wait 1 sec
resetESP();
}
}
}
// set up web server
void MyESP::_webserver_setup() {
webServer.on("/", [this]() { _webRootPage(); });
webServer.on("/reset", [this]() { _webResetPage(); });
webServer.on("/resetall", [this]() { _webResetAllPage(); });
webServer.begin();
}
// setup MyESP
void MyESP::begin(const char * app_hostname, const char * app_name, const char * app_version) {
_app_hostname = strdup(app_hostname);
_app_name = strdup(app_name);
_app_version = strdup(app_version);
_getInitialFreeHeap(); // get initial free mem
// set up onboard LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
_getInitialFreeHeap(); // get initial free mem
_telnet_setup(); // Telnet setup, called first to set Serial
_rtcmemSetup(); // rtc internal mem setup
if (getSystemBootStatus() == MYESP_BOOTSTATUS_RESETNEEDED) {
myDebug_P(PSTR("** resetting all settings"));
_firstInstall = true; // flag as an initial install so the config file will be recreated
}
_rtcmemSetup(); // rtc internal mem setup
_telnet_setup(); // Telnet setup, called first to set Serial
_eeprom_setup(); // set up EEPROM for storing crash data, if compiled with -DCRASH
_fs_setup(); // SPIFFS setup, do this first to get values
_wifi_setup(); // WIFI setup
_ota_setup(); // init OTA
_fs_setup(); // SPIFFS setup, do this first to get values
_wifi_setup(); // WIFI setup
_ota_setup(); // init OTA
_webserver_setup(); // init web server
// print a welcome message
myDebug_P(PSTR("\n* %s version %s"), _app_name, _app_version);
@@ -1929,6 +2145,36 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char *
_heartbeatCheck(true); // force heartbeat
}
// bootup sequence
// quickly flash LED until we get a Wifi connection, or AP established
// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << EMSESP_Status.led_gpio)); // 4 is on, 8 is off
void MyESP::_bootupSequence() {
uint8_t boot_status = getSystemBootStatus();
if ((boot_status == MYESP_BOOTSTATUS_BOOTED) || (millis() <= MYESP_BOOTUP_DELAY)) {
return; // already booted, or still starting up
}
// only kick in after a few seconds
if (boot_status == MYESP_BOOTSTATUS_POWERON) {
_setSystemBootStatus(MYESP_BOOTSTATUS_BOOTING);
}
static uint32_t last_bootupflash = 0;
// flash LED quickly
if ((millis() - last_bootupflash > MYESP_BOOTUP_FLASHDELAY)) {
last_bootupflash = millis();
int state = digitalRead(LED_BUILTIN);
digitalWrite(LED_BUILTIN, !state);
}
if (isWifiConnected()) {
_setSystemBootStatus(MYESP_BOOTSTATUS_BOOTED); // completed, reset flag
digitalWrite(LED_BUILTIN, LOW); // turn off LED
}
}
/*
* Loop. This is called as often as possible and it handles wifi, telnet, mqtt etc
*/
@@ -1936,8 +2182,14 @@ void MyESP::loop() {
_calculateLoad();
_systemCheckLoop();
_heartbeatCheck();
_bootupSequence();
webServer.handleClient(); // web server client requests
// if we're in AP mode, use the web server, otherwise switch to telnet
if (!isAPmode()) {
_telnetHandle();
}
_telnetHandle();
jw.loop(); // WiFi
ArduinoOTA.handle(); // OTA
_mqttConnect(); // MQTT

View File

@@ -9,20 +9,23 @@
#ifndef MyEMS_h
#define MyEMS_h
#define MYESP_VERSION "1.1.17"
#define MYESP_VERSION "1.1.18"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#ifdef CRASH
#include <EEPROM_Rotate.h>
#endif
extern "C" {
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#include "user_interface.h"
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
extern struct rst_info resetInfo;
}
@@ -38,10 +41,10 @@ extern struct rst_info resetInfo;
#define MYEMS_CONFIG_FILE "/config.json"
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms)
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) = 30 seconds
// WIFI
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds)
#define WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
// MQTT
@@ -143,12 +146,11 @@ PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, cus
#define RTCMEM_OFFSET 32u
#define RTCMEM_ADDR (RTCMEM_ADDR_BASE + (RTCMEM_OFFSET * 4u))
#define RTCMEM_BLOCKS 96u
#define RTCMEM_MAGIC 0x45535075
#define RTCMEM_MAGIC 0x45535076
struct RtcmemData {
uint32_t magic; // RTCMEM_MAGIC
uint32_t sys; // system reset reason (1-4)
uint32_t energy; // store energy count
uint32_t magic; // RTCMEM_MAGIC
uint32_t sys; // system details
};
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
@@ -165,6 +167,13 @@ typedef struct {
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION;
typedef enum {
MYESP_BOOTSTATUS_POWERON = 0,
MYESP_BOOTSTATUS_BOOTED = 1,
MYESP_BOOTSTATUS_BOOTING = 2,
MYESP_BOOTSTATUS_RESETNEEDED = 3
} MYESP_BOOTSTATUS; // boot messages
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f;
typedef std::function<void()> ota_callback_f;
@@ -172,6 +181,7 @@ typedef std::function<void(uint8_t, const char *)>
typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
typedef std::function<void(char *)> web_callback_f;
// calculates size of an 2d array at compile time
template <typename T, size_t N>
@@ -181,12 +191,46 @@ constexpr size_t ArraySize(T (&)[N]) {
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// web min and max length of wifi ssid and password
#define MAX_STR_LEN 16
#define MYESP_BOOTUP_FLASHDELAY 50 // flash duration for LED at bootup sequence
#define MYESP_BOOTUP_DELAY 2000 // time before we open the window to reset. This is to stop resetting values when uploading firmware via USB
// max size of char buffer for storing web page
#define MYESP_MAXCHARBUFFER 800
// Holds the admin webpage in the program memory
const char webCommonPage_start[] = "<html>"
"<head>"
"<style>input {font-size: 1.2em; width: 100%; max-width: 350px; display: block; margin: 5px auto; }"
"body {background-color: #FFA500;font: normal 18px Verdana, Arial, sans-serif;} </style>"
"<meta http-equiv='refresh' content='5'>"
"</head><body>";
const char webCommonPage_end[] = "</body></html>";
const char webResetPage_form[] = "<form id='form' action='/reset' method='post'>"
"<input name='newssid' type='text' maxlength='16' placeholder='SSID'>"
"<input name='newpassword' id='password1' type='password' maxlength='16' placeholder='Password'>"
"<input type='submit' value='Save and reboot'>"
"</form>";
const char webResetPage_post[] = "<p>New wifi credentials set. System is now rebooting. Please wait a few seconds and then reconnect via telnet or browser to its new IP given address.</p>";
const char webResetAllPage_form[] = "<form id='resetform' action='/resetall' method='post'>"
"<input name='confirm' type='text' minlength='3' maxlength='16' placeholder='yes'>"
"<input type='submit' value='Reset All'>"
"</form>";
// class definition
class MyESP {
public:
MyESP();
~MyESP();
ESP8266WebServer webServer; // Web server on port 80
// wifi
void setWIFICallback(void (*callback)());
void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback);
@@ -224,6 +268,9 @@ class MyESP {
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback);
bool fs_saveConfig();
// Web
void setWeb(web_callback_f callback_web);
// Crash
void crashClear();
void crashDump();
@@ -241,6 +288,7 @@ class MyESP {
bool getHeartbeat();
uint32_t getSystemLoadAverage();
uint32_t getSystemResetReason();
uint8_t getSystemBootStatus();
private:
// mqtt
@@ -269,7 +317,6 @@ class MyESP {
bool _rtcmem_status;
// wifi
DNSServer dnsServer; // For Access Point (AP) support
void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup();
wifi_callback_f _wifi_callback;
@@ -314,35 +361,45 @@ class MyESP {
fs_settings_callback_f _fs_settings_callback;
void _printSetCommands();
// web
web_callback_f _web_callback;
// general
char * _app_hostname;
char * _app_name;
char * _app_version;
char * _boottime;
bool _suspendOutput;
bool _use_serial;
bool _serial;
bool _heartbeat;
unsigned long _getUptime();
String _buildTime();
bool _firstInstall;
// reset reason and rtcmem
bool _rtcmemStatus();
void _rtcmemInit();
void _rtcmemSetup();
void _deferredReset(unsigned long delay, uint8_t reason);
bool _rtcmemStatus();
bool _getRtcmemStatus();
void _rtcmemInit();
void _rtcmemSetup();
void _deferredReset(unsigned long delay, uint8_t reason);
uint8_t _getSystemStabilityCounter();
void _setSystemStabilityCounter(uint8_t counter);
void _setSystemResetReason(uint8_t reason);
uint8_t _getCustomResetReason();
void _setCustomResetReason(uint8_t reason);
bool _systemStable;
// rtcmem and reset reason
bool _getRtcmemStatus();
uint8_t _getSystemResetReason();
bool getSystemCheck();
void _systemCheckLoop();
void _setSystemCheck(bool stable);
void _setSystemBootStatus(uint8_t status);
bool _systemStable;
void _bootupSequence();
bool _getSystemCheck();
void _systemCheckLoop();
void _setSystemCheck(bool stable);
// load average (0..100) and heap ram
void _calculateLoad();
@@ -352,6 +409,12 @@ class MyESP {
// heartbeat
void _heartbeatCheck(bool force);
// webserver
void _webserver_setup();
void _webRootPage();
void _webResetPage();
void _webResetAllPage();
};
extern MyESP myESP;