diff --git a/main/config_WebContent.h b/main/config_WebContent.h index 4c6ad58..a535c2d 100644 --- a/main/config_WebContent.h +++ b/main/config_WebContent.h @@ -62,8 +62,8 @@ #else # define configure_6 #endif -#if BLEDecryptor -# define configure_7 "

" +#ifdef ZgatewayBT +# define configure_7 "

" #else # define configure_7 #endif @@ -112,7 +112,16 @@ const char config_mqtt_body[] = body_header "
MQTT Parameters

MQTT Server

MQTT Port

MQTT Username


MQTT Secure Connection

Gateway Name

MQTT Base Topic


" body_footer_config_menu; #endif #ifndef ESPWifiManualSetup -const char config_gateway_body[] = body_header "
Gateway Configuration

Gateway Password (8 characters min)


" body_footer_config_menu; +const char config_gateway_body[] = body_header + "
Gateway Configuration" + "
" + "

Gateway Password (8 characters min)
" + "

" +# ifdef ZmqttDiscovery + "

" +# endif + "
" + "
" body_footer_config_menu; #endif const char config_logging_body[] = body_header "
OpenMQTTGateway Logging

Log Level


BLE Key Pairs

MacAddress:AESKey with space separator


" body_footer_config_menu; +#ifdef ZgatewayBT +// BLE config page split into parts to fit within WEB_TEMPLATE_BUFFER_MAX_SIZE -// Client side javascript validation of the Default AES Key is hex, and the custom keys are in the correct format. This pushes the theengs-bridge-v11 way of the file size, so reducing it now and leaving it commented out -//const char ble_script[] = "function bkv(){let e=document.getElementById('bk'),t=document.getElementById('bke'),l=e.value.trim();0===l.length||/^[A-Fa-f0-9]{32}$/.test(l)?(t.textContent='',e.style.color=''):(t.textContent='Invalid key',e.style.color='red')}function kpv(){let e=document.getElementById('kp'),t=document.getElementById('kpe'),l=e.value.split(/ /),n=/^[A-Fa-f0-9]{12}:[A-Fa-f0-9]{32}$/,o=l.map((e,t)=>({line:e,index:t})).filter(e=>!n.test(e.line));if(0===e.value.trim().length||0===o.length)t.textContent='',e.style.color='';else{let i=o.map(e=>e.index+1).join(', ');t.textContent=`Invalid format on line(s): ${i}`,e.style.color='red'}}"; +// Part 1: Header + Scan settings (body_header %s=modules, %s=gateway_name, then: %s=enabled, %s=adaptivescan, %lu=interval, %lu=intervalacts, %lu=scanduration, %s=forcepscn, %s=bleconnect, %d=minrssi) +const char config_ble_body_scan[] = body_header + "
Configure BLE" + "
" + "

Scan Settings

" + "

" + "

" + "

Scan Interval (ms)

" + "

Active Scan Interval (ms)

" + "

Scan Duration (ms)

" + "

" + "

" + "

Min RSSI

"; + +// Part 2: Publish settings (%s=onlysensors, %s=randommacs, %s=pubadvdata, %s=pubuuid4topic, %s=filterConnectable, %s=ignoreWBlist) +const char config_ble_body_publish[] = + "

Publish Settings

" + "

" + "

" + "

" + "

" + "

" + "

"; + +// Part 3: Presence settings (%s=hasspresence, %s=presuseuuid, %s=prestopic, %lu=presenceawaytimer, %lu=movingtimer) +const char config_ble_body_presence[] = + "

Presence Settings

" + "

" + "

" + "

Presence Topic

" + "

Away Timer (ms)

" + "

Moving Timer (ms)

"; + +// Part 4: External decoder settings (%s=extDecoderEnable, %s=extDecoderTopic, %lu=intervalcnct) +const char config_ble_body_decoder[] = + "

External Decoder

" + "

" + "

Ext Decoder Topic

" + "

Connect Interval (ms)

"; + +# if BLEDecryptor +// Part 5: Encryption settings (%s=ble_aes, %s=ble_aes_keys) +const char config_ble_body_encrypt[] = + "

Encryption

" + "

BLE AES Default Key (32 char hex)

" + "" + "

BLE Key Pairs

" + "

MacAddress:AESKey with space separator

" + "

"; +# endif + +// Part 6: Save button + footer +const char config_ble_body_footer[] = + "
" + "
" body_footer_config_menu; const char ble_script[] = ""; #endif diff --git a/main/webUI.cpp b/main/webUI.cpp index b301116..443fb61 100644 --- a/main/webUI.cpp +++ b/main/webUI.cpp @@ -64,6 +64,7 @@ extern void eraseConfig(); extern unsigned long uptime(); extern void ESPRestart(byte reason); extern void XtoSYS(const char* topicOri, JsonObject& SYSdata); +extern void SYSConfig_save(); extern String stateMeasures(); extern void MQTTHttpsFWUpdate(const char* topicOri, JsonObject& HttpsFwUpdateData); extern void receivingDATA(const char* topicOri, const char* datacallback); @@ -862,44 +863,61 @@ void handleMQ() { 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(); + bool pwUpdate = false; + bool sysUpdate = false; 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 (server.hasArg("save")) { + if (server.hasArg("gp") && server.arg("gp").length() > 0 && strcmp(ota_pass, server.arg("gp").c_str())) { + strncpy(ota_pass, server.arg("gp").c_str(), parameters_size); + pwUpdate = true; + } +# ifdef ZmqttDiscovery + if (SYSConfig.discovery != server.hasArg("dc")) { + SYSConfig.discovery = server.hasArg("dc"); + sysUpdate = true; + } +# endif } } - if (update) { - THEENGS_LOG_WARNING(F("[WebUI] Save Password and Restart" CR)); + if (pwUpdate || sysUpdate) { + THEENGS_LOG_NOTICE(F("[WebUI] Save gateway config and restart" CR)); + + if (sysUpdate) { + SYSConfig_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) + " - Save Password and Restart").c_str()); + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save 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"); + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save 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)); + if (pwUpdate) { + StaticJsonDocument jsonBuffer; + JsonObject WEBtoSYS = jsonBuffer.to(); + WEBtoSYS["gw_pass"] = ota_pass; + delay(2000); + String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset); + XtoSYS((char*)topic.c_str(), WEBtoSYS); + } else { + delay(2000); + ESPRestart(5); + } + return; } char jsonChar[100]; @@ -911,7 +929,12 @@ void handleCG() { 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); + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_gateway_body, jsonChar, gateway_name +# ifdef ZmqttDiscovery + , + (SYSConfig.discovery ? "checked" : "") +# endif + ); response += String(buffer); snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); @@ -955,74 +978,122 @@ void handleLO() { server.send(200, "text/html", response); } -# if BLEDecryptor +# ifdef ZgatewayBT +# include "config_BT.h" +extern void BTConfig_fromJson(JsonObject& BTdata, bool startup); +extern String stateBTMeasures(bool start); +extern BTConfig_s BTConfig; + /** * @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; - } + // Save BLE config in its own scope so the JSON buffer is freed before stateBTMeasures + // runs (via BTConfig_fromJson), avoiding stack overflow from nested StaticJsonDocument allocations + { + StaticJsonDocument jsonBuffer; + JsonObject WEBtoBT = jsonBuffer.to(); - // 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); - } + // Scan settings + WEBtoBT["enabled"] = server.hasArg("en"); + WEBtoBT["adaptivescan"] = server.hasArg("as"); + if (server.hasArg("bi")) { + WEBtoBT["interval"] = server.arg("bi").toInt(); + } + if (server.hasArg("ai")) { + WEBtoBT["intervalacts"] = server.arg("ai").toInt(); + } + if (server.hasArg("sd")) { + WEBtoBT["scanduration"] = server.arg("sd").toInt(); + } + WEBtoBT["forcepscn"] = server.hasArg("fp"); + WEBtoBT["bleconnect"] = server.hasArg("bc"); + if (server.hasArg("mr")) { + WEBtoBT["minrssi"] = server.arg("mr").toInt(); } - 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)); + // Publish settings + WEBtoBT["onlysensors"] = server.hasArg("os"); + WEBtoBT["randommacs"] = server.hasArg("rm"); + WEBtoBT["pubadvdata"] = server.hasArg("pa"); + WEBtoBT["pubuuid4topic"] = server.hasArg("ut"); + WEBtoBT["filterConnectable"] = server.hasArg("fc"); + WEBtoBT["ignoreWBlist"] = server.hasArg("iw"); + + // Presence settings + WEBtoBT["hasspresence"] = server.hasArg("hp"); + WEBtoBT["presuseuuid"] = server.hasArg("pu"); + if (server.hasArg("pt")) { + WEBtoBT["prestopic"] = server.arg("pt"); + } + if (server.hasArg("at")) { + WEBtoBT["presenceawaytimer"] = server.arg("at").toInt(); + } + if (server.hasArg("mo")) { + WEBtoBT["movingtimer"] = server.arg("mo").toInt(); + } + + // External decoder + WEBtoBT["extDecoderEnable"] = server.hasArg("ed"); + if (server.hasArg("et")) { + WEBtoBT["extDecoderTopic"] = server.arg("et"); + } + if (server.hasArg("ci")) { + WEBtoBT["intervalcnct"] = server.arg("ci").toInt(); + } + + THEENGS_LOG_NOTICE(F("[WebUI] Save BLE config" CR)); + WEBtoBT["save"] = true; + BTConfig_fromJson(WEBtoBT, false); + } // jsonBuffer freed here before stateBTMeasures internal allocations complete + +# if BLEDecryptor + // Encryption settings (via SYS topic) - separate scope + { + StaticJsonDocument sysBuffer; + JsonObject WEBtoSYS = sysBuffer.to(); + bool sysUpdate = false; + + if (server.hasArg("bk")) { + WEBtoSYS["ble_aes"] = server.arg("bk"); + sysUpdate = true; + } + 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); + } + } + sysUpdate = true; + } + + if (sysUpdate) { + THEENGS_LOG_NOTICE(F("[WebUI] Save BLE encryption config" CR)); + String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset); + XtoSYS((char*)topic.c_str(), WEBtoSYS); + } } +# endif } } - // 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); @@ -1033,9 +1104,69 @@ void handleBL() { 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()); + + // Scan settings + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body_scan, + jsonChar, gateway_name, + (BTConfig.enabled ? "checked" : ""), + (BTConfig.adaptiveScan ? "checked" : ""), + BTConfig.BLEinterval, + BTConfig.intervalActiveScan, + BTConfig.scanDuration, + (BTConfig.forcePassiveScan ? "checked" : ""), + (BTConfig.bleConnect ? "checked" : ""), + BTConfig.minRssi); response += String(buffer); + + // Publish settings + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body_publish, + (BTConfig.pubOnlySensors ? "checked" : ""), + (BTConfig.pubRandomMACs ? "checked" : ""), + (BTConfig.pubAdvData ? "checked" : ""), + (BTConfig.pubBeaconUuidForTopic ? "checked" : ""), + (BTConfig.filterConnectable ? "checked" : ""), + (BTConfig.ignoreWBlist ? "checked" : "")); + response += String(buffer); + + // Presence settings + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body_presence, + (BTConfig.presenceEnable ? "checked" : ""), + (BTConfig.presenceUseBeaconUuid ? "checked" : ""), + BTConfig.presenceTopic.c_str(), + BTConfig.presenceAwayTimer, + BTConfig.movingTimer); + response += String(buffer); + + // External decoder settings + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body_decoder, + (BTConfig.extDecoderEnable ? "checked" : ""), + BTConfig.extDecoderTopic.c_str(), + BTConfig.intervalConnect); + response += String(buffer); + +# if BLEDecryptor + // Encryption settings + std::string aeskeysstring; + JsonObject root = ble_aes_keys.as(); + for (JsonPair kv : root) { + aeskeysstring += kv.key().c_str(); + aeskeysstring += ":"; + aeskeysstring += kv.value().as(); + aeskeysstring += " "; + } + if (!aeskeysstring.empty()) { + aeskeysstring.pop_back(); + } + + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body_encrypt, + ble_aes, aeskeysstring.c_str()); + response += String(buffer); +# endif + + // Footer + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, "%s", config_ble_body_footer); + response += String(buffer); + snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION); response += String(buffer); server.send(200, "text/html", response); @@ -1757,7 +1888,7 @@ void WebUISetup() { server.on("/wi", HTTP_POST, handleWI); // Configure Wifi server.on("/mq", HTTP_POST, handleMQ); // Configure MQTT # ifndef ESPWifiManualSetup - server.on("/cg", HTTP_POST, handleCG); // Configure gateway" + server.on("/cg", handleCG); // Configure gateway # endif server.on("/wu", handleWU); // Configure WebUI # ifdef ZgatewayLORA @@ -1771,8 +1902,8 @@ void WebUISetup() { # endif server.on("/lo", handleLO); // Configure Logging -# if BLEDecryptor - server.on("/bl", HTTP_POST, handleBL); // Configure BLE +# ifdef ZgatewayBT + server.on("/bl", handleBL); // Configure BLE # endif server.on("/rt", handleRT); // Reset configuration ( Erase and Restart )