diff --git a/lib/MyESP/MyESP.cpp b/lib/MyESP/MyESP.cpp index c4df91fe3..9a50c21a1 100644 --- a/lib/MyESP/MyESP.cpp +++ b/lib/MyESP/MyESP.cpp @@ -43,6 +43,7 @@ MyESP::MyESP() { _helpProjectCmds_count = 0; _use_serial = false; + _heartbeat = false; _mqtt_host = NULL; _mqtt_password = NULL; _mqtt_username = NULL; @@ -137,6 +138,26 @@ bool MyESP::getUseSerial() { return (_use_serial); } +// heartbeat +bool MyESP::getHeartbeat() { + return (_heartbeat); +} + +// init heap ram +uint32_t MyESP::getInitialFreeHeap() { + static uint32_t _heap = 0; + + if (0 == _heap) { + _heap = ESP.getFreeHeap(); + } + + return _heap; +} + +uint32_t MyESP::getUsedHeap() { + return getInitialFreeHeap() - ESP.getFreeHeap(); +} + // called when WiFi is connected, and used to start OTA, MQTT void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) { if ((code == MESSAGE_CONNECTED)) { @@ -507,7 +528,11 @@ void MyESP::_consoleShowHelp() { } else { myDebug_P(PSTR("* Hostname: %s (%s)"), _getESPhostname().c_str(), WiFi.localIP().toString().c_str()); myDebug_P(PSTR("* WiFi SSID: %s (signal %d%%)"), WiFi.SSID().c_str(), getWifiQuality()); - myDebug_P(PSTR("* MQTT %s"), mqttClient.connected() ? "connected" : "disconnected"); + if (isMQTTConnected()) { + myDebug_P(PSTR("* MQTT connected (heartbeat %s)"), getHeartbeat() ? "enabled" : "disabled"); + } else { + myDebug_P(PSTR("* MQTT disconnected")); + } } myDebug_P(PSTR("*")); @@ -598,6 +623,7 @@ void MyESP::_printSetCommands() { myDebug_P(PSTR("")); // newline myDebug_P(PSTR(" serial=%s"), (_use_serial) ? "on" : "off"); + myDebug_P(PSTR(" heartbeat=%s"), (_heartbeat) ? "on" : "off"); // print any custom settings (_fs_settings_callback)(MYESP_FSACTION_LIST, 0, NULL, NULL); @@ -700,6 +726,23 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) ok = false; } } + + } else if (strcmp(setting, "heartbeat") == 0) { + ok = true; + _heartbeat = false; + if (value) { + if (strcmp(value, "on") == 0) { + _heartbeat = true; + ok = true; + myDebug_P(PSTR("Heartbeat on")); + } else if (strcmp(value, "off") == 0) { + _heartbeat = false; + ok = true; + myDebug_P(PSTR("Heartbeat off")); + } else { + ok = false; + } + } } else { // finally check for any custom commands ok = (_fs_settings_callback)(MYESP_FSACTION_SET, wc, setting, value); @@ -917,7 +960,6 @@ void MyESP::_setCustomResetReason(uint8_t reason) { _setSystemResetReason(reason); } -// Treat memory as dirty on cold boot, hardware wdt reset and rst pin bool MyESP::_rtcmemStatus() { bool readable; @@ -961,9 +1003,6 @@ void MyESP::_deferredReset(unsigned long delaytime, unsigned char reason) { // 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 // If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable -// setting _systemOK = false; -// -// An unstable system will only have serial access, WiFi in AP mode and OTA void MyESP::_setSystemCheck(bool stable) { uint8_t value = 0; @@ -1019,9 +1058,10 @@ void MyESP::showSystemStats() { } // uptime - uint32_t t = _getUptime(); // seconds + uint32_t t = _getUptime(); // seconds + uint32_t d = t / 86400L; - uint32_t h = (t / 3600L) % 60; + uint32_t h = ((t % 86400L) / 3600L) % 60; uint32_t rem = t % 3600L; uint8_t m = rem / 60; uint8_t s = rem % 60; @@ -1099,10 +1139,58 @@ void MyESP::showSystemStats() { myDebug_P(PSTR(" [MEM] Firmware size: %d"), ESP.getSketchSize()); myDebug_P(PSTR(" [MEM] Max OTA size: %d"), (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); myDebug_P(PSTR(" [MEM] OTA Reserved: %d"), 4 * SPI_FLASH_SEC_SIZE); - myDebug_P(PSTR(" [MEM] Free Heap: %d"), ESP.getFreeHeap()); + + uint32_t total_memory = getInitialFreeHeap(); + uint32_t free_memory = ESP.getFreeHeap(); + + myDebug(" [MEM] Free Heap: %d bytes initially | %d bytes used (%2u%%) | %d bytes free (%2u%%)", + total_memory, + total_memory - free_memory, + 100 * (total_memory - free_memory) / total_memory, + free_memory, + 100 * free_memory / total_memory); + myDebug_P(PSTR("")); } +/* + * Send heartbeat via MQTT with all system data + */ +void MyESP::_heartbeatCheck(bool force = false) { + static uint32_t last_heartbeat = 0; + + if ((millis() - last_heartbeat > HEARTBEAT_INTERVAL) || force) { + last_heartbeat = millis(); + + if (!isMQTTConnected() || !(_heartbeat)) { + return; + } + + uint32_t total_memory = getInitialFreeHeap(); + uint32_t free_memory = ESP.getFreeHeap(); + uint8_t mem_available = 100 * free_memory / total_memory; // as a % + + char payload[300] = {0}; + char s[10]; + strlcpy(payload, "version=", sizeof(payload)); + strlcat(payload, _app_version, sizeof(payload)); // version + strlcat(payload, ", IP=", sizeof(payload)); + strlcat(payload, WiFi.localIP().toString().c_str(), sizeof(payload)); // IP address + strlcat(payload, ", rssid=", sizeof(payload)); + strlcat(payload, itoa(getWifiQuality(), s, 10), sizeof(payload)); // rssi % + strlcat(payload, "%, load=", sizeof(payload)); + strlcat(payload, ltoa(getSystemLoadAverage(), s, 10), sizeof(payload)); // load + strlcat(payload, "%, uptime=", sizeof(payload)); + strlcat(payload, ltoa(_getUptime(), s, 10), sizeof(payload)); // uptime in secs + strlcat(payload, "secs, freemem=", sizeof(payload)); + strlcat(payload, itoa(mem_available, s, 10), sizeof(payload)); // free mem as a % + strlcat(payload, "%", sizeof(payload)); + + // send to MQTT + myESP.mqttPublish(MQTT_TOPIC_HEARTBEAT, payload); + } +} + // handler for Telnet void MyESP::_telnetHandle() { SerialAndTelnet.handle(); @@ -1396,6 +1484,8 @@ bool MyESP::_fs_loadConfig() { _use_serial = (bool)json["use_serial"]; + _heartbeat = (bool)json["heartbeat"]; + // 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); @@ -1424,6 +1514,7 @@ bool MyESP::fs_saveConfig() { json["mqtt_username"] = _mqtt_username; json["mqtt_password"] = _mqtt_password; json["use_serial"] = _use_serial; + json["heartbeat"] = _heartbeat; // callback for saving custom settings (void)(_fs_callback)(MYESP_FSACTION_SAVE, json); @@ -1475,19 +1566,19 @@ void MyESP::_fs_setup() { // _fs_printConfig(); // enable for debugging } -uint16_t MyESP::getSystemLoadAverage() { +uint32_t MyESP::getSystemLoadAverage() { return _load_average; } // calculate load average void MyESP::_calculateLoad() { - static unsigned long last_loadcheck = 0; - static unsigned long load_counter_temp = 0; + static uint32_t last_loadcheck = 0; + static uint32_t load_counter_temp = 0; load_counter_temp++; if (millis() - last_loadcheck > LOADAVG_INTERVAL) { - static unsigned long load_counter = 0; - static unsigned long load_counter_max = 1; + static uint32_t load_counter = 0; + static uint32_t load_counter_max = 1; load_counter = load_counter_temp; load_counter_temp = 0; @@ -1694,9 +1785,11 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char * _app_name = strdup(app_name); _app_version = strdup(app_version); + getInitialFreeHeap(); // get initial free mem + _rtcmemSetup(); _telnet_setup(); // Telnet setup, called first to set Serial - _eeprom_setup(); // set up eeprom for storing crash data, if compiled with -DCRASH + _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 @@ -1706,6 +1799,7 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char * SerialAndTelnet.flush(); _setSystemCheck(false); // reset system check + _heartbeatCheck(true); // force heartbeat } /* @@ -1714,11 +1808,10 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char * void MyESP::loop() { _calculateLoad(); _systemCheckLoop(); + _heartbeatCheck(); _telnetHandle(); - - jw.loop(); // WiFi - + jw.loop(); // WiFi ArduinoOTA.handle(); // OTA _mqttConnect(); // MQTT diff --git a/lib/MyESP/MyESP.h b/lib/MyESP/MyESP.h index e067f3c49..733d1e0c5 100644 --- a/lib/MyESP/MyESP.h +++ b/lib/MyESP/MyESP.h @@ -9,7 +9,7 @@ #ifndef MyEMS_h #define MyEMS_h -#define MYESP_VERSION "1.1.14" +#define MYESP_VERSION "1.1.16" #include #include @@ -18,7 +18,6 @@ #include #include // https://github.com/xoseperez/justwifi #include // modified from https://github.com/yasheena/telnetspy -#include #include extern "C" { @@ -50,8 +49,9 @@ extern struct rst_info resetInfo; #define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection #define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt #define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most -#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT message +#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic #define MQTT_TOPIC_START "start" +#define MQTT_TOPIC_HEARTBEAT "heartbeat" #define MQTT_TOPIC_START_PAYLOAD "start" #define MQTT_TOPIC_RESTART "restart" @@ -153,8 +153,9 @@ struct RtcmemData { static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); -#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis -#define SYSTEM_CHECK_MAX 5 // After this many crashes on boot +#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute) +#define SYSTEM_CHECK_MAX 5 // After this many crashes on boot +#define HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins) typedef struct { bool set; // is it a set command @@ -230,14 +231,14 @@ class MyESP { void crashInfo(); // general - void end(); - void loop(); - void begin(const char * app_hostname, const char * app_name, const char * app_version); - void setBoottime(const char * boottime); - void resetESP(); - uint16_t getSystemLoadAverage(); - int getWifiQuality(); - void showSystemStats(); + void end(); + void loop(); + void begin(const char * app_hostname, const char * app_name, const char * app_version); + void setBoottime(const char * boottime); + void resetESP(); + int getWifiQuality(); + void showSystemStats(); + bool getHeartbeat(); // rtcmem and reset reason bool rtcmemStatus(); @@ -322,6 +323,7 @@ class MyESP { char * _boottime; bool _suspendOutput; bool _use_serial; + bool _heartbeat; unsigned long _getUptime(); String _buildTime(); @@ -347,10 +349,15 @@ class MyESP { void _systemCheckLoop(); void _setSystemCheck(bool stable); + // load average (0..100) and heap ram + uint32_t getSystemLoadAverage(); + void _calculateLoad(); + uint32_t _load_average; + uint32_t getInitialFreeHeap(); + uint32_t getUsedHeap(); - // load average (0..100) - void _calculateLoad(); - unsigned short int _load_average; + // heartbeat + void _heartbeatCheck(bool force); }; extern MyESP myESP;