/* Theengs OpenMQTTGateway - We Unite Sensors in One Open-Source Interface Act as a gateway between your 433mhz, infrared IR, BLE, LoRa signal and one interface like an MQTT broker Send and receiving command by MQTT Copyright: (c)Florian ROBERT This file is part of OpenMQTTGateway. OpenMQTTGateway 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. OpenMQTTGateway 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 this program. If not, see . */ #include "User_config.h" #if defined(ZwebUI) && defined(ESP32) # include # include // Docs for this are here - https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer # include # include "TheengsCommon.h" # include "config_WebContent.h" # include "config_WebUI.h" # if defined(ZgatewayCloud) # include "config_Cloud.h" # endif # if defined(ZdisplaySSD1306) # include "config_SSD1306.h" # endif extern String stateRFMeasures(); uint32_t requestToken = 0; QueueHandle_t webUIQueue; WebServer server(80); webUIQueueMessage* currentWebUIMessage; extern JsonArray modules; extern char discovery_prefix[]; extern String latestVersion; bool WebUIConfig_save(); void sendRestartPage(); bool WebUIConfig_load(); void WebUIConfig_init(); void addLog(const uint8_t* buffer, size_t size); /*------------------- External functions ----------------------*/ extern void eraseConfig(); extern unsigned long uptime(); extern void ESPRestart(byte reason); extern void XtoSYS(const char* topicOri, JsonObject& SYSdata); extern String stateMeasures(); extern void MQTTHttpsFWUpdate(const char* topicOri, JsonObject& HttpsFwUpdateData); extern void receivingDATA(const char* topicOri, const char* datacallback); /*------------------- Web Console Globals ----------------------*/ # define ROW_LENGTH 1024 const uint16_t LOG_BUFFER_SIZE = 6096; uint32_t log_buffer_pointer; void* log_buffer_mutex; char log_buffer[LOG_BUFFER_SIZE]; // Log buffer in HEAP const uint16_t MAX_LOGSZ = LOG_BUFFER_SIZE - 96; const uint16_t TOPSZ = 151; // Max number of characters in topic string uint8_t masterlog_level; // Master log level used to override set log level bool reset_web_log_flag = false; // Reset web console log const char* www_username = WEBUI_LOGIN; String authFailResponse = "Authentication Failed"; bool webUISecure = WEBUI_AUTH; boolean displayMetric = DISPLAY_METRIC; /*********************************************************************************************\ * ESP32 AutoMutex \*********************************************************************************************/ ////////////////////////////////////////// // automutex. // create a mute in your driver with: // void *mutex = nullptr; // // then protect any function with // TasAutoMutex m(&mutex, "somename"); // - mutex is automatically initialised if not already intialised. // - it will be automagically released when the function is over. // - the same thread can take multiple times (recursive). // - advanced options m.give() and m.take() allow you fine control within a function. // - if take=false at creat, it will not be initially taken. // - name is used in serial log of mutex deadlock. // - maxWait in ticks is how long it will wait before failing in a deadlock scenario (and then emitting on serial) class TasAutoMutex { SemaphoreHandle_t mutex; bool taken; int maxWait; const char* name; public: TasAutoMutex(SemaphoreHandle_t* mutex, const char* name = "", int maxWait = 40, bool take = true); ~TasAutoMutex(); void give(); void take(); static void init(SemaphoreHandle_t* ptr); }; ////////////////////////////////////////// TasAutoMutex::TasAutoMutex(SemaphoreHandle_t* mutex, const char* name, int maxWait, bool take) { if (mutex) { if (!(*mutex)) { TasAutoMutex::init(mutex); } this->mutex = *mutex; this->maxWait = maxWait; this->name = name; if (take) { this->taken = xSemaphoreTakeRecursive(this->mutex, this->maxWait); // if (!this->taken){ // Serial.printf("\r\nMutexfail %s\r\n", this->name); // } } } else { this->mutex = (SemaphoreHandle_t) nullptr; } } TasAutoMutex::~TasAutoMutex() { if (this->mutex) { if (this->taken) { xSemaphoreGiveRecursive(this->mutex); this->taken = false; } } } void TasAutoMutex::init(SemaphoreHandle_t* ptr) { SemaphoreHandle_t mutex = xSemaphoreCreateRecursiveMutex(); (*ptr) = mutex; // needed, else for ESP8266 as we will initialis more than once in logging // (*ptr) = (void *) 1; } void TasAutoMutex::give() { if (this->mutex) { if (this->taken) { xSemaphoreGiveRecursive(this->mutex); this->taken = false; } } } void TasAutoMutex::take() { if (this->mutex) { if (!this->taken) { this->taken = xSemaphoreTakeRecursive(this->mutex, this->maxWait); // if (!this->taken){ // Serial.printf("\r\nMutexfail %s\r\n", this->name); // } } } } // Get span until single character in string size_t strchrspn(const char* str1, int character) { size_t ret = 0; char* start = (char*)str1; char* end = strchr(str1, character); if (end) ret = end - start; return ret; } int WifiGetRssiAsQuality(int rssi) { int quality = 0; if (rssi <= -100) { quality = 0; } else if (rssi >= -50) { quality = 100; } else { quality = 2 * (rssi + 100); } return quality; } char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack) { // Returns empty string if not found // Returns text of found char* write = destination; const char* read = haystack; index++; while (index--) { size_t size = destination_size - 1; write = destination; char ch = '.'; while ((ch != '\0') && (ch != '|')) { ch = pgm_read_byte(read++); if (size && (ch != '|')) { *write++ = ch; size--; } } if (0 == ch) { if (index) { write = destination; } break; } } *write = '\0'; return destination; } const char kUnescapeCode[] = "&><\"\'\\"; const char kEscapeCode[] PROGMEM = "&|>|<|"|'|\"; String HtmlEscape(const String unescaped) { char escaped[10]; size_t ulen = unescaped.length(); String result; result.reserve(ulen); // pre-reserve the required space to avoid mutiple reallocations for (size_t i = 0; i < ulen; i++) { char c = unescaped[i]; char* p = strchr(kUnescapeCode, c); if (p != nullptr) { result += GetTextIndexed(escaped, sizeof(escaped), p - kUnescapeCode, kEscapeCode); } else { result += c; } } return result; } void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_payload = nullptr, const char* log_data_retained = nullptr) { // Store log_data in buffer // To lower heap usage log_data_payload may contain the payload data from MqttPublishPayload() // and log_data_retained may contain optional retained message from MqttPublishPayload() # ifdef ESP32 // this takes the mutex, and will be release when the class is destroyed - // i.e. when the functon leaves You CAN call mutex.give() to leave early. TasAutoMutex mutex((SemaphoreHandle_t*)&log_buffer_mutex); # endif // ESP32 char empty[2] = {0}; if (!log_data_payload) { log_data_payload = empty; } if (!log_data_retained) { log_data_retained = empty; } if (!log_buffer) { return; } // Leave now if there is no buffer available // Delimited, zero-terminated buffer of log lines. // Each entry has this format: [index][loglevel][log data]['\1'] // Truncate log messages longer than MAX_LOGSZ which is the log buffer size minus 64 spare uint32_t log_data_len = strlen(log_data) + strlen(log_data_payload) + strlen(log_data_retained); char too_long[TOPSZ]; if (log_data_len > MAX_LOGSZ) { snprintf_P(too_long, sizeof(too_long) - 20, PSTR("%s%s"), log_data, log_data_payload); // 20 = strlen("... 123456 truncated") snprintf_P(too_long, sizeof(too_long), PSTR("%s... %d truncated"), too_long, log_data_len); log_data = too_long; log_data_payload = empty; log_data_retained = empty; } log_buffer_pointer &= 0xFF; if (!log_buffer_pointer) { log_buffer_pointer++; // Index 0 is not allowed as it is the end of char string } while (log_buffer_pointer == log_buffer[0] || // If log already holds the next index, remove it strlen(log_buffer) + strlen(log_data) + strlen(log_data_payload) + strlen(log_data_retained) + 4 > LOG_BUFFER_SIZE) // 4 = log_buffer_pointer + '\1' + '\0' { char* it = log_buffer; it++; // Skip log_buffer_pointer it += strchrspn(it, '\1'); // Skip log line it++; // Skip delimiting "\1" memmove(log_buffer, it, LOG_BUFFER_SIZE - (it - log_buffer)); // Move buffer forward to remove oldest log line } snprintf_P(log_buffer, LOG_BUFFER_SIZE, PSTR("%s%c%c%s%s%s%s\1"), log_buffer, log_buffer_pointer++, '0' + loglevel, "", log_data, log_data_payload, log_data_retained); log_buffer_pointer &= 0xFF; if (!log_buffer_pointer) { log_buffer_pointer++; // Index 0 is not allowed as it is the end of char string } } bool GetLog(uint32_t req_loglevel, uint32_t* index_p, char** entry_pp, size_t* len_p) { if (!log_buffer) { return false; } // Leave now if there is no buffer available if (uptime() < 3) { return false; } // Allow time to setup correct log level uint32_t index = *index_p; if (!req_loglevel || (index == log_buffer_pointer)) { return false; } # ifdef ESP32 // this takes the mutex, and will be release when the class is destroyed - // i.e. when the functon leaves You CAN call mutex.give() to leave early. TasAutoMutex mutex((SemaphoreHandle_t*)&log_buffer_mutex); # endif // ESP32 if (!index) { // Dump all index = log_buffer[0]; } do { size_t len = 0; uint32_t loglevel = 0; char* entry_p = log_buffer; do { uint32_t cur_idx = *entry_p; entry_p++; size_t tmp = strchrspn(entry_p, '\1'); tmp++; // Skip terminating '\1' if (cur_idx == index) { // Found the requested entry loglevel = *entry_p - '0'; entry_p++; // Skip loglevel len = tmp - 1; break; } entry_p += tmp; } while (entry_p < log_buffer + LOG_BUFFER_SIZE && *entry_p != '\0'); index++; if (index > 255) { index = 1; } // Skip 0 as it is not allowed *index_p = index; if ((len > 0) && (loglevel <= req_loglevel) && (masterlog_level <= req_loglevel)) { *entry_pp = entry_p; *len_p = len; return true; } delay(0); } while (index != log_buffer_pointer); return false; } bool NeedLogRefresh(uint32_t req_loglevel, uint32_t index) { if (!log_buffer) { return false; } // Leave now if there is no buffer available # ifdef ESP32 // this takes the mutex, and will be release when the class is destroyed - // i.e. when the functon leaves You CAN call mutex.give() to leave early. TasAutoMutex mutex((SemaphoreHandle_t*)&log_buffer_mutex); # endif // ESP32 // Skip initial buffer fill if (strlen(log_buffer) < LOG_BUFFER_SIZE / 2) { return false; } char* line; size_t len; if (!GetLog(req_loglevel, &index, &line, &len)) { return false; } return ((line - log_buffer) < LOG_BUFFER_SIZE / 4); } /*------------------- Local functions ----------------------*/ # ifdef WEBUI_DEVELOPMENT //format bytes String formatBytes(size_t bytes) { if (bytes < 1024) { return String(bytes) + "B"; } else if (bytes < (1024 * 1024)) { return String(bytes / 1024.0) + "KB"; } else if (bytes < (1024 * 1024 * 1024)) { return String(bytes / 1024.0 / 1024.0) + "MB"; } else { return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; } } bool exists(String path) { bool yes = false; File file = FILESYSTEM.open(path, "r"); if (!file.isDirectory()) { yes = true; } file.close(); return yes; } # endif /** * @brief / - Page * */ void handleRoot() { WEBUI_TRACE_LOG(F("handleRoot: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } if (server.hasArg("m")) { if (currentWebUIMessage) { server.send(200, "application/json", "{t}{s}" + String(currentWebUIMessage->title) + "{e}{s}" + String(currentWebUIMessage->line1) + "{e}{s}" + String(currentWebUIMessage->line2) + "{e}{s}" + String(currentWebUIMessage->line3) + "{e}{s}" + String(currentWebUIMessage->line4) + "{e}"); } else { server.send(200, "application/json", "{t}{s}Uptime:{m}" + String(uptime()) + "{e}"); } } else if (server.hasArg("rst")) { // TODO: This should redirect to the RST page THEENGS_LOG_WARNING(F("[WebUI] Restart" CR)); char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Restart").c_str()); String response = String(buffer); response += String(restart_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Restart"); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); delay(2000); // Wait for web page to be sent before ESPRestart(5); } else { // WEBUI_TRACE_LOG(F("Arguments %s" CR), message); server.send(200, "text/plain", "00:14:36.767 RSL: RESULT = {\"Topic\":\"topic\"}"); } } else { char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Main Menu").c_str()); String response = String(buffer); response += String(root_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, root_body, jsonChar, gateway_name); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } } /** * @brief /CN - Configuration Page * */ void handleCN() { WEBUI_SECURE WEBUI_TRACE_LOG(F("handleCN: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleCN Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } } else { char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configuration").c_str()); String response = String(buffer); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_body, jsonChar, gateway_name); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } } /** * @brief /WU - Configuration Page * T: handleWU: uri: /wu, args: 3, method: 1 * T: handleWU Arg: 0, dm=on - displayMetric * T: handleWU Arg: 1, sw=on - webUISecure * T: handleWU Arg: 2, save= */ void handleWU() { WEBUI_TRACE_LOG(F("handleWU: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleWU Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } bool update = false; if (displayMetric != server.hasArg("dm")) { update = true; } displayMetric = server.hasArg("dm"); if (webUISecure != server.hasArg("sw")) { update = true; } webUISecure = server.hasArg("sw"); if (server.hasArg("save") && update) { WebUIConfig_save(); } } char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WebUI").c_str()); String response = String(buffer); response += String(script); response += String(style); int logLevel = Log.getLevel(); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_webui_body, jsonChar, gateway_name, (displayMetric ? "checked" : ""), (webUISecure ? "checked" : "")); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } /** * @brief /WI - Configure WiFi Page * T: handleWI: uri: /wi, args: 4, method: 1 * T: handleWI Arg: 0, s1=SSID * T: handleWI Arg: 1, p1=xxxxxx * T: handleWI Arg: 3, save= */ void handleWI() { WEBUI_TRACE_LOG(F("handleWI: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE String WiFiScan = ""; if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleWI Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } if (server.hasArg("scan")) { bool limitScannedNetworks = true; int n = WiFi.scanNetworks(); WEBUI_TRACE_LOG(F("handleWI scan: found %d" CR), n); if (0 == n) { // WSContentSend_P(PSTR(D_NO_NETWORKS_FOUND)); // limitScannedNetworks = false; // in order to show D_SCAN_FOR_WIFI_NETWORKS } else { //sort networks int indices[n]; for (uint32_t i = 0; i < n; i++) { indices[i] = i; } // RSSI SORT for (uint32_t i = 0; i < n; i++) { for (uint32_t j = i + 1; j < n; j++) { if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { std::swap(indices[i], indices[j]); } } } uint32_t networksToShow = n; if ((limitScannedNetworks) && (networksToShow > MAX_WIFI_NETWORKS_TO_SHOW)) { networksToShow = MAX_WIFI_NETWORKS_TO_SHOW; } for (uint32_t i = 0; i < n; i++) { if (-1 == indices[i]) { continue; } String cssid = WiFi.SSID(indices[i]); uint32_t cschn = WiFi.channel(indices[i]); for (uint32_t j = i + 1; j < n; j++) { if ((cssid == WiFi.SSID(indices[j])) && (cschn == WiFi.channel(indices[j]))) { WEBUI_TRACE_LOG(F("handleWI scan: duplicate %s" CR), WiFi.SSID(indices[j]).c_str()); indices[j] = -1; // set dup aps to index -1 } } } //display networks in page for (uint32_t i = 0; i < networksToShow; i++) { if (-1 == indices[i]) { continue; } // skip dups int32_t rssi = WiFi.RSSI(indices[i]); WEBUI_TRACE_LOG(F("D_LOG_WIFI D_SSID %s, D_BSSID %s, D_CHANNEL %d, D_RSSI %d" CR), WiFi.SSID(indices[i]).c_str(), WiFi.BSSIDstr(indices[i]).c_str(), WiFi.channel(indices[i]), rssi); int quality = WifiGetRssiAsQuality(rssi); String ssid_copy = WiFi.SSID(indices[i]); if (!ssid_copy.length()) { ssid_copy = F("no_name"); } WiFiScan += "
" + HtmlEscape(ssid_copy) + " (" + WiFi.channel(indices[i]) + ") " + quality + "% (" + rssi + " dBm)
"; } } WEBUI_TRACE_LOG(F("handleWI scan: results %s" CR), WiFiScan.c_str()); char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WiFi").c_str()); String response = String(buffer); response += String(wifi_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_wifi_body, jsonChar, gateway_name, WiFiScan.c_str(), WiFi.SSID().c_str()); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); return; } else if (server.hasArg("save")) { StaticJsonDocument WEBtoSYSBuffer; JsonObject WEBtoSYS = WEBtoSYSBuffer.to(); bool update = false; if (server.hasArg("s1")) { WEBtoSYS["wifi_ssid"] = server.arg("s1"); if (strncmp((char*)WiFi.SSID().c_str(), server.arg("s1").c_str(), parameters_size)) { update = true; } } if (server.hasArg("p1")) { WEBtoSYS["wifi_pass"] = server.arg("p1"); if (strncmp((char*)WiFi.psk().c_str(), server.arg("p1").c_str(), parameters_size)) { update = true; } } if (update) { String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset); THEENGS_LOG_WARNING(F("[WebUI] Save WiFi and Restart" CR)); char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save WiFi and Restart").c_str()); String response = String(buffer); response += String(restart_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save WiFi and Restart"); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); delay(2000); // Wait for web page to be sent before XtoSYS((char*)topic.c_str(), WEBtoSYS); return; } else { THEENGS_LOG_WARNING(F("[WebUI] No changes" CR)); } } } char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WiFi").c_str()); String response = String(buffer); response += String(wifi_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_wifi_body, jsonChar, gateway_name, WiFiScan.c_str(), WiFi.SSID().c_str()); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } /** * @brief /MQ - Configure MQTT Page * T: handleMQ: uri: /mq, args: 8, method: 1 * T: handleMQ Arg: 0, mh=192.168.1.11 * T: handleMQ Arg: 1, ml=1883 * T: handleMQ Arg: 2, mu=1234 * T: handleMQ Arg: 3, mp= * T: handleMQ Arg: 4, sc=on * T: handleMQ Arg: 5, h= * T: handleMQ Arg: 6, mt=home/ * T: handleMQ Arg: 7 dp=homeassistant (#ifdef ZmqttDiscovery) * T: handleMQ Arg: 8, save= */ void handleMQ() { WEBUI_TRACE_LOG(F("handleMQ: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleMQ Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } if (server.hasArg("save")) { StaticJsonDocument WEBtoSYSBuffer; JsonObject WEBtoSYS = WEBtoSYSBuffer.to(); bool update = false; # if !MQTT_BROKER_MODE if (server.hasArg("mh")) { WEBtoSYS["mqtt_server"] = server.arg("mh"); if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, server.arg("mh").c_str(), parameters_size)) { update = true; } } if (server.hasArg("ml")) { WEBtoSYS["mqtt_port"] = server.arg("ml"); if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, server.arg("ml").c_str(), 6)) { update = true; } } if (server.hasArg("mu")) { WEBtoSYS["mqtt_user"] = server.arg("mu"); if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, server.arg("mu").c_str(), parameters_size)) { update = true; } } if (server.hasArg("mp")) { WEBtoSYS["mqtt_pass"] = server.arg("mp"); if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_pass, server.arg("mp").c_str(), parameters_size)) { update = true; } } // SC - Secure Connection argument is only present when true if (cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure != server.hasArg("sc")) { update = true; } WEBtoSYS["mqtt_secure"] = server.hasArg("sc"); if (!update) { THEENGS_LOG_WARNING(F("[WebUI] clearing" CR)); for (JsonObject::iterator it = WEBtoSYS.begin(); it != WEBtoSYS.end(); ++it) { WEBtoSYS.remove(it); } } # endif if (server.hasArg("h")) { WEBtoSYS["gateway_name"] = server.arg("h"); if (strncmp(gateway_name, server.arg("h").c_str(), parameters_size)) { update = true; } } if (server.hasArg("mt")) { WEBtoSYS["mqtt_topic"] = server.arg("mt"); if (strncmp(mqtt_topic, server.arg("mt").c_str(), parameters_size)) { update = true; } } # ifdef ZmqttDiscovery if (server.hasArg("dp")) { WEBtoSYS["discovery_prefix"] = server.arg("dp"); if (strncmp(discovery_prefix, server.arg("dp").c_str(), parameters_size)) { update = true; } } # endif # ifndef ESPWifiManualSetup if (update) { THEENGS_LOG_WARNING(F("[WebUI] Save MQTT and Reconnect" CR)); WEBtoSYS["cnt_index"] = CNT_DEFAULT_INDEX; WEBtoSYS["save_cnt"] = true; char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save MQTT and Reconnect").c_str()); String response = String(buffer); response += String(restart_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save MQTT and Reconnect"); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); delay(2000); // Wait for web page to be sent before String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset); XtoSYS((char*)topic.c_str(), WEBtoSYS); return; } else { THEENGS_LOG_WARNING(F("[WebUI] No changes" CR)); } # endif } } char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure MQTT").c_str()); String response = String(buffer); response += String(script); response += String(style); // mqtt server (mh), mqtt port (ml), mqtt username (mu), mqtt password (mp), secure connection (sc), server certificate (msc), mqtt topic (mt), discovery prefix (dp) (last one only #ifdef ZmqttDiscovery) # if MQTT_BROKER_MODE # ifdef ZmqttDiscovery snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, "", "1883", "", "", gateway_name, mqtt_topic, discovery_prefix); # else snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, "", "1883", "", "", gateway_name, mqtt_topic); # endif # else # ifdef ZmqttDiscovery snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, (cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure ? "checked" : ""), gateway_name, mqtt_topic, discovery_prefix); # else snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, (cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure ? "checked" : ""), gateway_name, mqtt_topic); # endif # endif response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } # ifndef ESPWifiManualSetup /** * @brief /CG - Configure gateway Page * T: handleCG: uri: /gw, args: 2, method: 1 * T: handleCG Arg: 0, gp=1234 * T: handleCG Arg: 1, save= */ void handleCG() { WEBUI_TRACE_LOG(F("handleCG: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE bool update = false; StaticJsonDocument jsonBuffer; JsonObject WEBtoSYS = jsonBuffer.to(); if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleCG Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } if (server.hasArg("save") && server.hasArg("gp") && strcmp(ota_pass, server.arg("gp").c_str())) { strncpy(ota_pass, server.arg("gp").c_str(), parameters_size); WEBtoSYS["gw_pass"] = ota_pass; update = true; } } if (update) { THEENGS_LOG_WARNING(F("[WebUI] Save Password and Restart" CR)); char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save Password and Restart").c_str()); String response = String(buffer); response += String(restart_script); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save Password and Restart"); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); delay(2000); // Wait for web page to be sent before String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset); XtoSYS((char*)topic.c_str(), WEBtoSYS); } else { THEENGS_LOG_WARNING(F("[WebUI] No changes" CR)); } char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure gateway").c_str()); String response = String(buffer); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_gateway_body, jsonChar, gateway_name, ota_pass); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } # endif /** * @brief /LO - Configure Logging Page * T: handleLO: uri: /lo, args: 2, method: 1 * T: handleLO Arg: 0, lo=5 * T: handleLO Arg: 1, save= */ void handleLO() { WEBUI_TRACE_LOG(F("handleLO: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleLO Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } if (server.hasArg("save") && server.hasArg("lo") && server.arg("lo").toInt() != Log.getLevel()) { Log.fatal(F("[WebUI] Log level changed to: %d" CR), server.arg("lo").toInt()); Log.setLevel(server.arg("lo").toInt()); } } char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure Logging").c_str()); String response = String(buffer); response += String(script); response += String(style); int logLevel = Log.getLevel(); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_logging_body, jsonChar, gateway_name, (logLevel == 0 ? "selected" : ""), (logLevel == 1 ? "selected" : ""), (logLevel == 2 ? "selected" : ""), (logLevel == 3 ? "selected" : ""), (logLevel == 4 ? "selected" : ""), (logLevel == 5 ? "selected" : ""), (logLevel == 6 ? "selected" : "")); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } # if BLEDecryptor /** * @brief /BL - Config BLE * T: handleBL: uri: /bl, args: 3, method: 1 * T: handleBL Arg: 0, dn=on - Force Device Name * T: handleBL Arg: 1, bk=on - BLE AES Key * T: handleBL Arg: 2, save= */ void handleBL() { WEBUI_TRACE_LOG(F("handleBL: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE StaticJsonDocument jsonBuffer; JsonObject WEBtoSYS = jsonBuffer.to(); if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleBL Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } bool update = false; if (server.hasArg("save")) { // Default BLE AES Key if (server.hasArg("bk")) { WEBtoSYS["ble_aes"] = server.arg("bk"); update = true; } // Split Custom BLE AES key pair string add to config if (server.hasArg("kp")) { String kp = server.arg("kp"); while (kp.length() > 0) { int kpindex = kp.indexOf(' '); if (kpindex == -1) { if (kp.indexOf(':') == 12) { WEBtoSYS["ble_aes_keys"][kp.substring(0, 12)] = kp.substring(13, 45); } break; } else { if (kp.indexOf(':') == 12) { WEBtoSYS["ble_aes_keys"][kp.substring(0, 12)] = kp.substring(13, 45); } kp = kp.substring(kpindex + 1); } } update = true; } if (update) { THEENGS_LOG_WARNING(F("[BLE] Save Config" CR)); String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset); XtoSYS((char*)topic.c_str(), WEBtoSYS); } else { THEENGS_LOG_WARNING(F("[BLE] No changes" CR)); } } } // Build BLE Key Pair string std::string aeskeysstring; JsonObject root = ble_aes_keys.as(); for (JsonPair kv : root) { aeskeysstring += kv.key().c_str(); aeskeysstring += ":"; aeskeysstring += kv.value().as(); aeskeysstring += " "; } aeskeysstring.pop_back(); char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure BLE").c_str()); String response = String(buffer); response += String(ble_script); response += String(script); response += String(style); int logLevel = Log.getLevel(); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body, jsonChar, gateway_name, ble_aes, aeskeysstring.c_str()); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } # endif # ifdef ZgatewayLORA # include "config_LORA.h" extern void LORAConfig_fromJson(JsonObject& LORAdata); extern String stateLORAMeasures(); extern LORAConfig_s LORAConfig; /** * @brief /LA - Configure LORA Page * T: handleLA: uri: /la, args: 11, method: 1 * T: handleLA Arg: 0, lf=868100000 * T: handleLA Arg: 1, lt=14 * T: handleLA Arg: 2, ls=12 * T: handleLA Arg: 3, lb=125 * T: handleLA Arg: 4, lc=5 * T: handleLA Arg: 5, ll=8 * T: handleLA Arg: 6, lw=0 * T: handleLA Arg: 7, lr=1 * T: handleLA Arg: 8, li=0 * T: handleLA Arg: 9, ok=0 * T: handleLA Arg: 10, save= */ void handleLA() { WEBUI_TRACE_LOG(F("handleLA: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method()); WEBUI_SECURE if (server.args()) { for (uint8_t i = 0; i < server.args(); i++) { WEBUI_TRACE_LOG(F("handleLA Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str()); } if (server.hasArg("save")) { StaticJsonDocument jsonBuffer; JsonObject WEBtoLORA = jsonBuffer.to(); bool update = false; if (server.hasArg("lf")) { WEBtoLORA["frequency"] = server.arg("lf"); update = true; } if (server.hasArg("lt")) { WEBtoLORA["txpower"] = server.arg("lt"); update = true; } if (server.hasArg("ls")) { WEBtoLORA["spreadingfactor"] = server.arg("ls"); update = true; } if (server.hasArg("lb")) { WEBtoLORA["signalbandwidth"] = server.arg("lb"); update = true; } if (server.hasArg("lc")) { WEBtoLORA["codingrate"] = server.arg("lc"); update = true; } if (server.hasArg("ll")) { WEBtoLORA["preamblelength"] = server.arg("ll"); update = true; } if (server.hasArg("lw")) { WEBtoLORA["syncword"] = server.arg("lw"); update = true; } if (server.hasArg("lr")) { WEBtoLORA["enablecrc"] = server.arg("lr"); update = true; } else { WEBtoLORA["enablecrc"] = false; update = true; } if (server.hasArg("li")) { WEBtoLORA["invertiq"] = server.arg("li"); update = true; } else { WEBtoLORA["invertiq"] = false; update = true; } if (server.hasArg("ok")) { WEBtoLORA["onlyknown"] = server.arg("ok"); update = true; } else { WEBtoLORA["onlyknown"] = false; update = true; } if (update) { THEENGS_LOG_NOTICE(F("[WebUI] Save data" CR)); WEBtoLORA["save"] = true; LORAConfig_fromJson(WEBtoLORA); stateLORAMeasures(); THEENGS_LOG_TRACE(F("[WebUI] LORAConfig end" CR)); } } } char jsonChar[100]; serializeJson(modules, jsonChar, measureJson(modules) + 1); char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE]; snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure LORA").c_str()); String response = String(buffer); response += String(script); response += String(style); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_lora_body, jsonChar, gateway_name, LORAConfig.frequency == 868000000 ? "selected" : "", LORAConfig.frequency == 915000000 ? "selected" : "", LORAConfig.frequency == 433000000 ? "selected" : "", LORAConfig.txPower == 0 ? "selected" : "", LORAConfig.txPower == 1 ? "selected" : "", LORAConfig.txPower == 2 ? "selected" : "", LORAConfig.txPower == 3 ? "selected" : "", LORAConfig.txPower == 4 ? "selected" : "", LORAConfig.txPower == 5 ? "selected" : "", LORAConfig.txPower == 6 ? "selected" : "", LORAConfig.txPower == 7 ? "selected" : "", LORAConfig.txPower == 8 ? "selected" : "", LORAConfig.txPower == 9 ? "selected" : "", LORAConfig.txPower == 10 ? "selected" : "", LORAConfig.txPower == 11 ? "selected" : "", LORAConfig.txPower == 12 ? "selected" : "", LORAConfig.txPower == 13 ? "selected" : "", LORAConfig.txPower == 14 ? "selected" : "", LORAConfig.spreadingFactor == 7 ? "selected" : "", LORAConfig.spreadingFactor == 8 ? "selected" : "", LORAConfig.spreadingFactor == 9 ? "selected" : "", LORAConfig.spreadingFactor == 10 ? "selected" : "", LORAConfig.spreadingFactor == 11 ? "selected" : "", LORAConfig.spreadingFactor == 12 ? "selected" : "", LORAConfig.signalBandwidth == 7800 ? "selected" : "", LORAConfig.signalBandwidth == 10400 ? "selected" : "", LORAConfig.signalBandwidth == 15600 ? "selected" : "", LORAConfig.signalBandwidth == 20800 ? "selected" : "", LORAConfig.signalBandwidth == 31250 ? "selected" : "", LORAConfig.signalBandwidth == 41700 ? "selected" : "", LORAConfig.signalBandwidth == 62500 ? "selected" : "", LORAConfig.signalBandwidth == 125000 ? "selected" : "", LORAConfig.signalBandwidth == 250000 ? "selected" : "", LORAConfig.signalBandwidth == 500000 ? "selected" : "", LORAConfig.codingRateDenominator == 5 ? "selected" : "", LORAConfig.codingRateDenominator == 6 ? "selected" : "", LORAConfig.codingRateDenominator == 7 ? "selected" : "", LORAConfig.codingRateDenominator == 8 ? "selected" : "", LORAConfig.preambleLength, LORAConfig.syncWord, LORAConfig.crc ? "checked" : "", LORAConfig.invertIQ ? "checked" : "", LORAConfig.onlyKnown ? "checked" : ""); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); } # elif defined(ZgatewayRTL_433) || defined(ZgatewayPilight) || defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZactuatorSomfy) # include # include "rf/RFConfiguration.h" std::map activeReceiverOptions = { {0, "Inactive"}, # if defined(ZgatewayPilight) && !defined(ZradioSX127x) {1, "PiLight"}, # endif # if defined(ZgatewayRF) && !defined(ZradioSX127x) {2, "RF"}, # endif # ifdef ZgatewayRTL_433 {3, "RTL_433"}, # endif # if defined(ZgatewayRF2) && !defined(ZradioSX127x) {4, "RF2 (restart required)"} # endif }; extern RFConfiguration iRFConfig; bool isValidReceiver(int receiverId) { // Check if the receiverId exists in the activeReceiverOptions map return activeReceiverOptions.find(receiverId) != activeReceiverOptions.end(); } String generateActiveReceiverOptions(int currentSelection) { String optionsHtml = ""; for (const auto& option : activeReceiverOptions) { optionsHtml += "