From ecfa569c9328ee7011a13e3de525978d12a17da8 Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 25 May 2026 01:27:28 +0200 Subject: [PATCH 01/16] [AccessControl] Fix checking for allowed IP range --- platformio_core_defs.ini | 2 +- src/ESPEasy/net/Globals/NWPlugins.cpp | 54 +++++++++++++++------------ src/ESPEasy/net/_NW001_WiFi_STA.cpp | 5 ++- src/ESPEasy/net/_NW002_WiFi_AP.cpp | 2 +- src/src/ESPEasyCore/ESPEasy_Log.cpp | 10 +++++ src/src/Helpers/PeriodicalActions.cpp | 7 +++- src/src/WebServer/AccessControl.cpp | 14 +------ 7 files changed, 55 insertions(+), 39 deletions(-) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 402149ca5..1b5cedfef 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -191,7 +191,7 @@ extra_scripts = ${esp82xx_common.extra_scripts} ;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2904-2115-5.5/framework-arduinoespressif32-release_v5.5-f2a3fa2b.tar.xz platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc152 -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1905-1208-5.5/framework-arduinoespressif32-release_v5.5-f3cdb9d0.tar.xz +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2305-1355-5.5/framework-arduinoespressif32-release_v5.5-f3cdb9d0.tar.xz custom_remove_include = true diff --git a/src/ESPEasy/net/Globals/NWPlugins.cpp b/src/ESPEasy/net/Globals/NWPlugins.cpp index 10709b356..2577b2d8b 100644 --- a/src/ESPEasy/net/Globals/NWPlugins.cpp +++ b/src/ESPEasy/net/Globals/NWPlugins.cpp @@ -4,24 +4,24 @@ #include "../../../src/DataStructs/TimingStats.h" #include "../../../src/DataTypes/ESPEasy_plugin_functions.h" #ifdef ESP32 -#include "../../../src/Globals/SecuritySettings.h" +# include "../../../src/Globals/SecuritySettings.h" #endif #include "../../../src/Globals/Settings.h" #include "../Helpers/_NWPlugin_init.h" #ifdef ESP32 -#include "../Helpers/NWAccessControl.h" +# include "../Helpers/NWAccessControl.h" #endif #include "../../../src/Globals/ESPEasy_Scheduler.h" +#include "../../../src/Helpers/Networking.h" #include "../_NWPlugin_Helper.h" #ifdef ESP8266 -#include "../wifi/ESPEasyWifi.h" +# include "../wifi/ESPEasyWifi.h" #endif #include "../ESPEasyNetwork.h" - namespace ESPEasy { namespace net { @@ -324,11 +324,11 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) // calls to specific network which need to be enabled before calling case NWPlugin::Function::NWPLUGIN_INIT: -// case NWPlugin::Function::NWPLUGIN_CONNECT_SUCCESS: -// case NWPlugin::Function::NWPLUGIN_CONNECT_FAIL: + // case NWPlugin::Function::NWPLUGIN_CONNECT_SUCCESS: + // case NWPlugin::Function::NWPLUGIN_CONNECT_FAIL: case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_ACTIVE: case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_CONNECTED: -// case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_EXTENDED: + // case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_EXTENDED: case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_HW_ADDRESS: case NWPlugin::Function::NWPLUGIN_WEBFORM_SHOW_IP: #ifdef ESP32 @@ -356,7 +356,7 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD: case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: -// case NWPlugin::Function::NWPLUGIN_DRIVER_TEMPLATE: + // case NWPlugin::Function::NWPLUGIN_DRIVER_TEMPLATE: { const networkIndex_t networkIndex = event->NetworkIndex; bool success = false; @@ -468,20 +468,26 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: { - IPAddress client_ip; - client_ip.fromString(str); + if (!Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { + IPAddress client_ip; + client_ip.fromString(str); - if ((SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) && - !Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { - success = NWPlugin::IP_in_subnet(client_ip, event->networkInterface); - } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { - const IPAddress low(SecuritySettings.AllowedIPrangeLow); - const IPAddress high(SecuritySettings.AllowedIPrangeHigh); - success = NWPlugin::ipInRange(client_ip, low, high) && - NWPlugin::IP_in_subnet(low, event->networkInterface) && - NWPlugin::IP_in_subnet(high, event->networkInterface); - } else { - success = true; + if (SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) { + success = NWPlugin::IP_in_subnet(client_ip, event->networkInterface); + } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { + const IPAddress low(SecuritySettings.AllowedIPrangeLow); + const IPAddress high(SecuritySettings.AllowedIPrangeHigh); + + if (IPAddressSet(low) && IPAddressSet(high)) + { + success = + NWPlugin::ipInRange(client_ip, low, high) && + NWPlugin::IP_in_subnet(low, event->networkInterface) && + NWPlugin::IP_in_subnet(high, event->networkInterface); + } else { success = true; } + } else { + success = true; + } } break; } @@ -493,7 +499,8 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) } #endif // ifdef ESP32 } -//#ifdef ESP32 + + // #ifdef ESP32 if (Function == NWPlugin::Function::NWPLUGIN_EXIT) { // Cache.clearNetworkSettings(networkIndex); @@ -505,7 +512,8 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) } clearNWPluginData(event->NetworkIndex); } -//#endif // ifdef ESP32 + + // #endif // ifdef ESP32 } return success; } diff --git a/src/ESPEasy/net/_NW001_WiFi_STA.cpp b/src/ESPEasy/net/_NW001_WiFi_STA.cpp index 099166a38..65b56e94b 100644 --- a/src/ESPEasy/net/_NW001_WiFi_STA.cpp +++ b/src/ESPEasy/net/_NW001_WiFi_STA.cpp @@ -304,7 +304,10 @@ bool NWPlugin_001(NWPlugin::Function function, EventStruct *event, String& strin } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { const IPAddress low(SecuritySettings.AllowedIPrangeLow); const IPAddress high(SecuritySettings.AllowedIPrangeHigh); - success = NWPlugin::ipInRange(client_ip, low, high); + success = + !IPAddressSet(low) || + !IPAddressSet(high) || + NWPlugin::ipInRange(client_ip, low, high); } else { success = true; } diff --git a/src/ESPEasy/net/_NW002_WiFi_AP.cpp b/src/ESPEasy/net/_NW002_WiFi_AP.cpp index 8d3745926..fb5248192 100644 --- a/src/ESPEasy/net/_NW002_WiFi_AP.cpp +++ b/src/ESPEasy/net/_NW002_WiFi_AP.cpp @@ -219,6 +219,7 @@ bool NWPlugin_002(NWPlugin::Function function, EventStruct *event, String& strin } break; } +# endif // ifdef ESP8266 case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: { @@ -240,7 +241,6 @@ bool NWPlugin_002(NWPlugin::Function function, EventStruct *event, String& strin } break; } -# endif // ifdef ESP8266 case NWPlugin::Function::NWPLUGIN_WEBFORM_SAVE: { diff --git a/src/src/ESPEasyCore/ESPEasy_Log.cpp b/src/src/ESPEasyCore/ESPEasy_Log.cpp index d86e364ae..6e4931f63 100644 --- a/src/src/ESPEasyCore/ESPEasy_Log.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Log.cpp @@ -273,6 +273,16 @@ void addToLogMove(uint8_t logLevel, String&& str) void processLogs(bool serialOnly) { + #ifdef ESP32 + + if (xPortInIsrContext()) { + // When called from an ISR, you should not send out logs. + // Allocating memory from within an ISR is a big no-no. + // Also long-time blocking like sending logs (especially to a syslog server) + // is also really not a good idea from an ISR call. + return; + } + #endif // ifdef ESP32 process_serialWriteBuffer(); Logging.loop(serialOnly); #if FEATURE_SYSLOG diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 8fd354c5e..97447c08a 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -411,7 +411,12 @@ void updateMQTTclient_connected() { const bool actual_MQTTclient_connected = ESPEasy::net::NetworkConnected(true) && MQTTclient.connected(); if (MQTTclient_connected != actual_MQTTclient_connected) { MQTTclient_connected = actual_MQTTclient_connected; - if (!actual_MQTTclient_connected) mqtt.stop(); // Make sure PubSubClient isn't trying to do a graceful disconnect + if (!actual_MQTTclient_connected) { + // Make sure PubSubClient isn't trying to do a graceful disconnect + // FIXME TD-er: This seems to cause a crash on ESP32-xx, though no idea how to fix. + // See also: https://github.com/espressif/arduino-esp32/issues/12517 + mqtt.stop(); + } MQTTclient_connected_stats.set(actual_MQTTclient_connected); if (!MQTTclient_connected) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { diff --git a/src/src/WebServer/AccessControl.cpp b/src/src/WebServer/AccessControl.cpp index 6dd5c821f..2a1553f9d 100644 --- a/src/src/WebServer/AccessControl.cpp +++ b/src/src/WebServer/AccessControl.cpp @@ -33,24 +33,14 @@ bool clientIPallowed() // return true; #endif const IPAddress remoteIP = web_server.client().remoteIP(); - if (remoteIP == IPAddress(0, 0, 0, 0) - #if ESP_IDF_VERSION_MAJOR>=5 - || remoteIP.type() == IPv6 - #else - || !remoteIP.isV4() - #endif - ) { + if (!IPAddressSet(remoteIP)) + { // FIXME TD-er: Must see what's going on here, why the client doesn't send remote IP for some reason return true; } if (ESPEasy::net::ipInAllowedSubnet(remoteIP)) { return true; } - - if ( ESPEasy::net::wifi::WifiIsAP(WiFi.getMode())) { - // @TD-er Fixme: Should match subnet of SoftAP. - return true; - } String response = concat(F("IP blocked: "), formatIP(remoteIP)); web_server.send(403, F("text/html"), response); From 1646c4e953c59e655877163884597b37aed495d8 Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 25 May 2026 23:24:22 +0200 Subject: [PATCH 02/16] [ESP8266] Fix ESP8266 builds missing include --- src/ESPEasy/net/_NW001_WiFi_STA.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ESPEasy/net/_NW001_WiFi_STA.cpp b/src/ESPEasy/net/_NW001_WiFi_STA.cpp index 65b56e94b..916ac03fc 100644 --- a/src/ESPEasy/net/_NW001_WiFi_STA.cpp +++ b/src/ESPEasy/net/_NW001_WiFi_STA.cpp @@ -13,6 +13,7 @@ # include "../../src/DataStructs/ESPEasy_EventStruct.h" # include "../../src/Globals/SecuritySettings.h" # include "../../src/Globals/Settings.h" +# include "../../src/Helpers/Networking.h" # include "../../src/Helpers/StringConverter.h" # include "../../src/Helpers/StringGenerator_WiFi.h" # include "../../src/WebServer/ESPEasy_WebServer.h" @@ -27,6 +28,7 @@ # include "../net/ESPEasyNetwork.h" # endif + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE # include "../../src/Helpers/ESPEasy_UnitOfMeasure.h" # endif From ef324e6e5ff82c1d54c7ba0c39313c53ac089279 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 28 May 2026 16:52:01 +0200 Subject: [PATCH 03/16] [WiFi AP] Fix really stupid error setting WiFi AP static IP --- .../NWPluginData_static_runtime_ESP32.cpp | 20 ++++--- .../NW002_data_struct_WiFi_AP.cpp | 60 +++++++++++-------- .../net/wifi/ESPEasyWifi_abstracted.cpp | 14 +++-- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp index 720791ab7..1b6f48255 100644 --- a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp @@ -96,14 +96,18 @@ void NWPluginData_static_runtime::mark_start() if (!_netif) { return; } - if (_useStaticIP) { - _netif->config( - _ip, - _gateway, - _sn, - _dns); - } else { - _netif->config((uint32_t)0, (uint32_t)0, (uint32_t)0); + // AP IP and DHCP server are configured via softAPConfig(). + // Calling config(0,0,0) on the AP netif enables DHCP client and breaks AP DHCP. + if (!_isAP) { + if (_useStaticIP) { + _netif->config( + _ip, + _gateway, + _sn, + _dns); + } else { + _netif->config((uint32_t)0, (uint32_t)0, (uint32_t)0); + } } const String hostname = strformat( diff --git a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp index da534666d..00e8d794a 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp @@ -2,12 +2,10 @@ #ifdef USES_NW002 -# ifdef ESP32 -# include "../../../src/Globals/Settings.h" -# endif - # include "../wifi/ESPEasyWifi.h" +# include "../Globals/NetworkState.h" +# include "../../../src/Globals/Settings.h" # include "../../../src/Helpers/Networking.h" # ifdef ESP32 @@ -105,8 +103,13 @@ bool NW002_data_struct_WiFi_AP::init(EventStruct *event) nw002_enable_NAPT = Settings.WiFi_AP_enable_NAPT(); # endif - ESPEasy::net::wifi::setAPinternal(true); + if (!ESPEasy::net::wifi::setAPinternal(true)) { + return false; + } # ifdef ESP32 + if (NW_PLUGIN_INTERFACE.hasIP()) { + stats_and_cache.mark_got_IP(); + } NW002_update_NAPT(); # endif # if FEATURE_MDNS @@ -146,28 +149,25 @@ NWPluginData_static_runtime * NW002_data_struct_WiFi_AP::getNWPluginData_static_ bool NW002_data_struct_WiFi_AP::getStaticIPAddress(IPAddressType addressType, IPAddress& ip) const { - // TODO TD-er: Implement for AP - - /* - IPAddress res; - - switch (addressType) - { - case IPAddressType::IP: res = IPAddress(Settings.IP); - break; - case IPAddressType::Gateway: res = IPAddress(Settings.Gateway); - break; - case IPAddressType::Subnetmask: res = IPAddress(Settings.Subnet); - break; - case IPAddressType::DNS: res = IPAddress(Settings.DNS); - break; - } - - if (IPAddressSet(res)) { - ip = res; + switch (addressType) + { + case IPAddressType::IP: + ip = apIP; return true; - } - */ + case IPAddressType::Gateway: + ip = apIP; + return true; + case IPAddressType::Subnetmask: + ip = IPAddress(DEFAULT_AP_SUBNET); + return true; + case IPAddressType::DNS: + if (Settings.ApCaptivePortal()) { + ip = apIP; + } else { + ip = IPAddress(DEFAULT_AP_DNS); + } + return true; + } return false; } @@ -264,6 +264,10 @@ void NW002_data_struct_WiFi_AP::onEvent(arduino_event_id_t event, { case ARDUINO_EVENT_WIFI_AP_START: stats_and_cache.mark_start(); + + if (NW_PLUGIN_INTERFACE.hasIP()) { + stats_and_cache.mark_got_IP(); + } break; case ARDUINO_EVENT_WIFI_AP_STOP: stats_and_cache.mark_stop(); @@ -283,6 +287,10 @@ void NW002_data_struct_WiFi_AP::onEvent(arduino_event_id_t event, break; case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: addLog(LOG_LEVEL_INFO, F("AP_STAIPASSIGNED")); + + if (!stats_and_cache.hasIP() && NW_PLUGIN_INTERFACE.hasIP()) { + stats_and_cache.mark_got_IP(); + } break; case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: addLog(LOG_LEVEL_INFO, F("AP_PROBEREQRECVED")); diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp index af6bf3151..81f0bb10d 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp +++ b/src/ESPEasy/net/wifi/ESPEasyWifi_abstracted.cpp @@ -82,6 +82,13 @@ bool doSetAPinternal(bool enable) String softAPSSID = NetworkCreateRFCCompliantHostname(); String pwd = SecuritySettings.WifiAPKey; IPAddress subnet(DEFAULT_AP_SUBNET); + + // AP mode (and AP netif on ESP32) must be enabled before softAPConfig, + // otherwise DHCP server setup fails when STA is active but not connected. + if (!doSetAP(true)) { + return false; + } + # ifdef ESP32 IPAddress dhcp_lease_start = (uint32_t)0; @@ -138,10 +145,6 @@ bool doSetAPinternal(bool enable) channel = WiFi.channel(); } -#ifdef ESP32 - doSetAP(true); -#endif - if (WiFi.softAP(softAPSSID.c_str(), pwd.c_str(), channel)) { auto data = getWiFi_AP_NWPluginData_static_runtime(); @@ -204,6 +207,7 @@ bool doSetAPinternal(bool enable) # endif // if FEATURE_DNS_SERVER # endif // ifdef ESP32 } + return true; } else { # if FEATURE_DNS_SERVER @@ -218,7 +222,7 @@ bool doSetAPinternal(bool enable) doSetAP(false); } - return false; + return true; } void doSetConnectionSpeed() { From 052780e5959c1157a0c61f31a7d0ec6195b74e75 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 2 Jun 2026 22:11:23 +0200 Subject: [PATCH 04/16] [SysVars] List variables numerically sorted --- src/src/WebServer/SysVarPage.cpp | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/src/WebServer/SysVarPage.cpp b/src/src/WebServer/SysVarPage.cpp index 3ff9b41f4..39f1027bf 100644 --- a/src/src/WebServer/SysVarPage.cpp +++ b/src/src/WebServer/SysVarPage.cpp @@ -52,6 +52,16 @@ void handle_sysvars() { html_table_header(F("URL encoded"), F("RTDReference/SystemVariable.html"), 0); addTableSeparator(F("Custom Variables"), 3, 3); + # if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + auto numAlphaSort = [](const String& a, const String& b) { + const int32_t ai = a.toInt(); + + if (!ai) { return false; } // Alphanum after num + return ai < b.toInt(); // Numerical order + }; + std::vector customStringSort; + + # endif // if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES if (customFloatVar.empty()) { html_TR_TD(); @@ -59,14 +69,33 @@ void handle_sysvars() { html_TD(); html_TD(); } else { + # if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + + for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) { + customStringSort.push_back(it->first); + } + std::sort(customStringSort.begin(), customStringSort.end(), numAlphaSort); + + for (auto& it : customStringSort) { + NumericalType detectedType; + bool isv_ = true; + + if (getNumerical(it, NumericalType::HexadecimalUInt, detectedType).length() > 0) { + isv_ = detectedType != NumericalType::Integer; + } + addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it.c_str()), false); + } + # else // if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) { NumericalType detectedType; bool isv_ = true; + if (getNumerical(it->first, NumericalType::HexadecimalUInt, detectedType).length() > 0) { isv_ = detectedType != NumericalType::Integer; } addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it->first.c_str()), false); } + # endif // if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES } # if FEATURE_STRING_VARIABLES @@ -78,8 +107,15 @@ void handle_sysvars() { html_TD(); html_TD(); } else { + customStringSort.clear(); + for (auto it = customStringVar.begin(); it != customStringVar.end(); ++it) { - addSysVar_html(strformat(F("[str#%s]"), it->first.c_str()), false); + customStringSort.push_back(it->first); + } + std::sort(customStringSort.begin(), customStringSort.end(), numAlphaSort); + + for (auto& it : customStringSort) { + addSysVar_html(strformat(F("[str#%s]"), it.c_str()), false); } } # endif // if FEATURE_STRING_VARIABLES From 926dc99d65be8c99150bfab924a1cd52e0b4bf4a Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 3 Jun 2026 14:44:16 +0200 Subject: [PATCH 05/16] [ESP-IDF] Update Arduino/ESP-IDF to latest version --- platformio_core_defs.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 1b5cedfef..11a8e282e 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -191,7 +191,7 @@ extra_scripts = ${esp82xx_common.extra_scripts} ;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2904-2115-5.5/framework-arduinoespressif32-release_v5.5-f2a3fa2b.tar.xz platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc152 -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2305-1355-5.5/framework-arduinoespressif32-release_v5.5-f3cdb9d0.tar.xz +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2805-1324-5.5/framework-arduinoespressif32-release_v5.5-f3cdb9d0.tar.xz custom_remove_include = true From a3228f02723c4ca1771e36f2e05cc605f52d1735 Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 3 Jun 2026 14:44:56 +0200 Subject: [PATCH 06/16] [WebFlasher] Update webflasher to 12.2.4 --- tools/pio/generate_web_flasher_manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pio/generate_web_flasher_manifest.py b/tools/pio/generate_web_flasher_manifest.py index 0fe34d049..aaa7deac6 100644 --- a/tools/pio/generate_web_flasher_manifest.py +++ b/tools/pio/generate_web_flasher_manifest.py @@ -378,7 +378,7 @@ def generate_manifest_files(bin_folder, output_prefix): ' \n', ' \n', ' \n', ' \n', From 99e6205bc513a5537fe0515a3e2211dcca03eba3 Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 3 Jun 2026 14:46:00 +0200 Subject: [PATCH 07/16] [Network] Fix route setting priority (ESP32-only) --- src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp index 185e1a4c9..d9a2eb8cc 100644 --- a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp @@ -122,6 +122,7 @@ void NWPluginData_static_runtime::clear(networkIndex_t networkIndex) _eventInterfaceName.toUpperCase(); } } + _routePrio = Settings.getRoutePrio_for_network(_networkIndex); #endif // ifdef ESP32 _connectionFailures = 0; From 7cc89ccd5d0885efc7063eed064ef2b1b0a16e6d Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 3 Jun 2026 15:26:06 +0200 Subject: [PATCH 08/16] [Network] Fix properly clearing runtime data when starting nw interface --- .../NWPluginData_static_runtime.cpp | 51 +++++++++++++------ .../NWPluginData_static_runtime_ESP32.cpp | 2 - .../NW001_data_struct_WiFi_STA.cpp | 1 + .../NW002_data_struct_WiFi_AP.cpp | 1 + .../NW003_data_struct_ETH_RMII.cpp | 1 + .../NW004_data_struct_ETH_SPI.cpp | 3 +- .../NW005_data_struct_PPP_modem.cpp | 2 + 7 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp index d9a2eb8cc..402970962 100644 --- a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp @@ -15,8 +15,9 @@ #if FEATURE_MDNS -//#include "../../../src/Helpers/MDNS_Helper.h" -#endif + +// #include "../../../src/Helpers/MDNS_Helper.h" +#endif // if FEATURE_MDNS namespace ESPEasy { @@ -103,7 +104,9 @@ void NWPluginData_static_runtime::clear(networkIndex_t networkIndex) _gotIPStats.reset(); #if FEATURE_USE_IPV6 _gotIP6Stats.reset(); -#endif + + // TODO TD-er: Must also clear _gotIP6Events +#endif // if FEATURE_USE_IPV6 _operationalStats.reset(); #if FEATURE_NETWORK_TRAFFIC_COUNT @@ -121,13 +124,20 @@ void NWPluginData_static_runtime::clear(networkIndex_t networkIndex) _eventInterfaceName = _netif->desc(); _eventInterfaceName.toUpperCase(); } + _enableIPv6 = Settings.EnableIPv6() && Settings.getNetworkEnabled_IPv6(_networkIndex); } _routePrio = Settings.getRoutePrio_for_network(_networkIndex); + + _dns_cache[0] = INADDR_NONE; + _dns_cache[1] = INADDR_NONE; #endif // ifdef ESP32 _connectionFailures = 0; - // FIXME TD-er: Should also clear dns cache and/or static IP? + _ip = INADDR_NONE; + _gateway = INADDR_NONE; + _sn = INADDR_NONE; + _dns = INADDR_NONE; } void NWPluginData_static_runtime::processEvent_and_clear() @@ -198,8 +208,9 @@ void NWPluginData_static_runtime::processEvents() # endif // ifndef BUILD_NO_DEBUG #endif // ifdef ESP8266 #if FEATURE_MDNS -// update_mDNS(); -#endif + + // update_mDNS(); +#endif // if FEATURE_MDNS } #if FEATURE_USE_IPV6 @@ -226,9 +237,10 @@ void NWPluginData_static_runtime::processEvents() } } } -#if FEATURE_MDNS -// update_mDNS(); -#endif +# if FEATURE_MDNS + + // update_mDNS(); +# endif // if FEATURE_MDNS } #endif // if FEATURE_USE_IPV6 @@ -305,13 +317,21 @@ void NWPluginData_static_runtime::processEvents() } } -void NWPluginData_static_runtime::setStaticIP(const IPAddress & ip, const IPAddress & gateway, const IPAddress & subnetmask, const IPAddress & dns) +void NWPluginData_static_runtime::setStaticIP(const IPAddress& ip, const IPAddress& gateway, const IPAddress& subnetmask, + const IPAddress& dns) { _useStaticIP = IPAddressSet(ip) && IPAddressSet(gateway) && IPAddressSet(subnetmask); - _ip = ip; - _gateway = gateway; - _sn = subnetmask; - _dns = dns; + _ip = ip; + _gateway = gateway; + _sn = subnetmask; + _dns = dns; +#ifdef ESP32 + + if (_useStaticIP) { + _dns_cache[0] = dns; + _dns_cache[1] = INADDR_NONE; + } +#endif // ifdef ESP32 } String NWPluginData_static_runtime::statusToString() const @@ -324,7 +344,8 @@ String NWPluginData_static_runtime::statusToString() const if (hasIP()) { log += F("IP "); - if (_useStaticIP) log += F("(static) "); + + if (_useStaticIP) { log += F("(static) "); } } if (operational()) { diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp index 1b6f48255..606cf0987 100644 --- a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime_ESP32.cpp @@ -241,9 +241,7 @@ void NWPluginData_static_runtime::mark_begin_establish_connection() _enableIPv6 = false; } # endif // if FEATURE_USE_IPV6 -# ifdef ESP32 _routePrio = Settings.getRoutePrio_for_network(_networkIndex); -# endif } void NWPluginData_static_runtime::mark_connected() diff --git a/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp index d4e32efc2..6e008dabf 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp @@ -101,6 +101,7 @@ bool NW001_data_struct_WiFi_STA::init(EventStruct *event) auto runtime_data = getNWPluginData_static_runtime(); if (runtime_data) { + runtime_data->clear(event->NetworkIndex); IPAddress ip, gateway, sn, dns; getStaticIPAddresses(ip, gateway, sn, dns); runtime_data->setStaticIP(ip, gateway, sn, dns); diff --git a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp index 00e8d794a..4e90a643d 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp @@ -93,6 +93,7 @@ bool NW002_data_struct_WiFi_AP::init(EventStruct *event) auto runtime_data = getNWPluginData_static_runtime(); if (runtime_data) { + runtime_data->clear(event->NetworkIndex); IPAddress ip, gateway, sn, dns; getStaticIPAddresses(ip, gateway, sn, dns); runtime_data->setStaticIP(ip, gateway, sn, dns); diff --git a/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp index 72345e5ce..9da072b75 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp @@ -350,6 +350,7 @@ bool NW003_data_struct_ETH_RMII::init(EventStruct *event) { auto runtime_data = getNWPluginData_static_runtime(); if (runtime_data) { + runtime_data->clear(event->NetworkIndex); IPAddress ip, gateway, sn, dns; getStaticIPAddresses(ip, gateway, sn, dns); runtime_data->setStaticIP(ip, gateway, sn, dns); diff --git a/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp index f86f57f05..a712039a3 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp @@ -293,9 +293,10 @@ bool NW004_data_struct_ETH_SPI::webform_getPort(KeyValueWriter *writer) { return bool NW004_data_struct_ETH_SPI::init(EventStruct *event) { _load(); - { + { auto runtime_data = getNWPluginData_static_runtime(); if (runtime_data) { + runtime_data->clear(event->NetworkIndex); IPAddress ip, gateway, sn, dns; getStaticIPAddresses(ip, gateway, sn, dns); runtime_data->setStaticIP(ip, gateway, sn, dns); diff --git a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp index bcb963a51..87e7793b7 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp @@ -980,6 +980,8 @@ bool NW005_data_struct_PPP_modem::init(EventStruct *event) _modem_task_data.initializing = false; _modem_task_data.dtrPin = _kvs->getValueAsInt_or_default(NW005_KEY_PIN_DTR, -1); + stats_and_cache.clear(event->NetworkIndex); + stats_and_cache.mark_begin_establish_connection(); xTaskCreatePinnedToCore( From 85e4cc283dcbd54c0674f5d7f4430f19d60d7b9c Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 3 Jun 2026 21:52:28 +0200 Subject: [PATCH 09/16] [SysVars] Sort alphabetic values too (though they come in already sorted) --- src/src/WebServer/SysVarPage.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/src/WebServer/SysVarPage.cpp b/src/src/WebServer/SysVarPage.cpp index 39f1027bf..e932cd40a 100644 --- a/src/src/WebServer/SysVarPage.cpp +++ b/src/src/WebServer/SysVarPage.cpp @@ -55,9 +55,12 @@ void handle_sysvars() { # if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES auto numAlphaSort = [](const String& a, const String& b) { const int32_t ai = a.toInt(); + const int32_t bi = b.toInt(); + + if (!ai && !bi) { return a < b; } // a..z if (!ai) { return false; } // Alphanum after num - return ai < b.toInt(); // Numerical order + return ai < bi; // Numerical order }; std::vector customStringSort; From a954b0224fd703613ebf6e1aef8af145e819d124 Mon Sep 17 00:00:00 2001 From: TD-er Date: Fri, 5 Jun 2026 14:52:59 +0200 Subject: [PATCH 10/16] [Arduino 3.3.9] Update to latest espressif32/Arduino 3.3.9 --- platformio_core_defs.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 11a8e282e..2c1720e05 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -191,7 +191,8 @@ extra_scripts = ${esp82xx_common.extra_scripts} ;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2904-2115-5.5/framework-arduinoespressif32-release_v5.5-f2a3fa2b.tar.xz platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc152 -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2805-1324-5.5/framework-arduinoespressif32-release_v5.5-f3cdb9d0.tar.xz +platform_packages = +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2805-1324-5.5/framework-arduinoespressif32-release_v5.5-f3cdb9d0.tar.xz custom_remove_include = true From b95cda55ad8b42a524da555694732996b8cb8a72 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 6 Jun 2026 10:35:28 +0200 Subject: [PATCH 11/16] [Docs] Add link to Supported Features and Peripherals on ESP32-xx --- docs/source/ESPEasy/ESPchips.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/ESPEasy/ESPchips.rst b/docs/source/ESPEasy/ESPchips.rst index f196876ce..0e325609d 100644 --- a/docs/source/ESPEasy/ESPchips.rst +++ b/docs/source/ESPEasy/ESPchips.rst @@ -666,6 +666,8 @@ Sources: * `Espressif Product Selector `_ * `Espressif ESP32-xx modules overview `_ * `Espressif Product overview SoCs `_ +* `Espressif Arduino Libraries: Supported Features and Peripherals `_ + Datasheets ========== @@ -1004,4 +1006,4 @@ ESP32-P4 rev3.x New revision of the ESP32-P4, which is incompatible with the initial P4. -.. note:: Not yet supported (as of March 2026) \ No newline at end of file +.. note:: Not yet supported (as of March 2026) From 1dee7c775d5e48dba309be016ec5111670389be3 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 6 Jun 2026 13:29:35 +0200 Subject: [PATCH 12/16] [Docs] Suggest VSCode plugin TODO Highlight as Todo Tree doesn't work anymore without an external tool --- docs/source/Participate/PlatformIO.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Participate/PlatformIO.rst b/docs/source/Participate/PlatformIO.rst index a73c88d4d..22b5b5d2e 100644 --- a/docs/source/Participate/PlatformIO.rst +++ b/docs/source/Participate/PlatformIO.rst @@ -83,7 +83,7 @@ Optional, but highly recommended: * C/C++ DevTools (by Microsoft) * Bookmarks (by Alessandro Fragnani) * GitLens - Git supercharged (by Gitkraken) -* Todo Tree (by Gruntfuggly) +* TODO Highlight (by Wayou Liu) * All Autocomplete (by Atishay Jain) * Excel Viewer (by GrapeCity) * Esbonio - An extension for editing sphinx projects (by Swyddfa) From 9bfc82bb70e71042ee5db17d06bdf50dc1141ede Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Jun 2026 17:06:06 +0200 Subject: [PATCH 13/16] [AccessControl] Fix checking for allowed IP access of webinterface --- src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp | 20 ++--- src/ESPEasy/net/Globals/NWPlugins.cpp | 33 ++++++-- src/ESPEasy/net/Helpers/NWAccessControl.cpp | 56 +++++-------- src/ESPEasy/net/Helpers/NWAccessControl.h | 3 +- src/ESPEasy/net/_NW001_WiFi_STA.cpp | 32 ++++---- src/ESPEasy/net/_NW002_WiFi_AP.cpp | 9 +- .../DataTypes/ESPEasy_plugin_functions.cpp | 82 ++++++++++++++++--- src/src/DataTypes/ESPEasy_plugin_functions.h | 8 ++ src/src/DataTypes/SettingsType.cpp | 2 +- src/src/DataTypes/SettingsType.h | 2 +- src/src/Helpers/Misc.cpp | 3 +- src/src/WebServer/AccessControl.cpp | 2 +- src/src/WebServer/ESPEasy_WebServer.cpp | 2 +- src/src/WebServer/ESPEasy_WebServer.h | 2 +- src/src/WebServer/SysInfoPage.cpp | 2 +- 15 files changed, 160 insertions(+), 98 deletions(-) diff --git a/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp b/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp index 4b8480f78..d9dcf9ab4 100644 --- a/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp +++ b/src/ESPEasy/net/ESPEasyNetwork_ESP8266.cpp @@ -77,24 +77,16 @@ IPAddress NetworkLocalIP() { return WiFi.localIP(); } IPAddress NetworkID() { - const IPAddress subnet = NetworkSubnetMask(); - IPAddress networkID = ESPEasy::net::NetworkLocalIP(); - - for (uint8_t i = 0; i < 4; ++i) { - networkID[i] &= subnet[i]; - } - return networkID; + return NWPlugin::getNetworkID( + ESPEasy::net::NetworkLocalIP(), + NetworkSubnetMask()); } IPAddress NetworkBroadcast() { - const IPAddress subnet = NetworkSubnetMask(); - IPAddress broadcast = ESPEasy::net::NetworkLocalIP(); - - for (uint8_t i = 0; i < 4; ++i) { - broadcast[i] |= ~subnet[i]; - } - return broadcast; + return NWPlugin::getNetworkBroadcast( + ESPEasy::net::NetworkLocalIP(), + NetworkSubnetMask()); } IPAddress NetworkSubnetMask() { return WiFi.subnetMask(); } diff --git a/src/ESPEasy/net/Globals/NWPlugins.cpp b/src/ESPEasy/net/Globals/NWPlugins.cpp index 2577b2d8b..41e7494c1 100644 --- a/src/ESPEasy/net/Globals/NWPlugins.cpp +++ b/src/ESPEasy/net/Globals/NWPlugins.cpp @@ -472,21 +472,38 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str) IPAddress client_ip; client_ip.fromString(str); + success = true; + if (SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) { success = NWPlugin::IP_in_subnet(client_ip, event->networkInterface); + + const NWPlugin::IP_type ip_type = NWPlugin::get_IP_type(client_ip); + + IPAddress networkID; + IPAddress broadcast; + + if (NWPlugin::get_subnet(ip_type, event->networkInterface, networkID, broadcast)) { + # if FEATURE_USE_IPV6 + const bool includeZone = ip_type == NWPlugin::IP_type::ipv6_link_local; + event->String1 = formatIP(networkID, includeZone); + event->String2 = formatIP(broadcast, includeZone); + # else // if FEATURE_USE_IPV6 + event->String1 = formatIP(networkID); + event->String2 = formatIP(broadcast); + # endif // if FEATURE_USE_IPV6 + } } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { const IPAddress low(SecuritySettings.AllowedIPrangeLow); const IPAddress high(SecuritySettings.AllowedIPrangeHigh); - if (IPAddressSet(low) && IPAddressSet(high)) + if (IPAddressSet(low) || IPAddressSet(high)) { - success = - NWPlugin::ipInRange(client_ip, low, high) && - NWPlugin::IP_in_subnet(low, event->networkInterface) && - NWPlugin::IP_in_subnet(high, event->networkInterface); - } else { success = true; } - } else { - success = true; + // Use || as for example 0.0.0.0 ... 123.0.0.0 might also be a possible range. + // Though I have no idea for what practical use case... + success = NWPlugin::ipInRange(client_ip, low, high); + event->String1 = formatIP(low); + event->String2 = formatIP(high); + } } } break; diff --git a/src/ESPEasy/net/Helpers/NWAccessControl.cpp b/src/ESPEasy/net/Helpers/NWAccessControl.cpp index ed0d8b9f0..7b17144bc 100644 --- a/src/ESPEasy/net/Helpers/NWAccessControl.cpp +++ b/src/ESPEasy/net/Helpers/NWAccessControl.cpp @@ -30,43 +30,31 @@ bool ipInAllowedSubnet(const IPAddress& ip) } String describeAllowedIPrange() { - String reply; - - switch (SecuritySettings.IPblockLevel) - { - case ALL_ALLOWED: - reply += F("All Allowed"); - break; - default: - { - IPAddress low, high; - getIPallowedRange(low, high); - reply += formatIP(low); - reply += F(" - "); - reply += formatIP(high); - } - } - return reply; + return describeAllowedIPrange(ESPEasy::net::NetworkLocalIP()); } -bool getIPallowedRange(IPAddress& low, IPAddress& high) -{ - switch (SecuritySettings.IPblockLevel) - { - case LOCAL_SUBNET_ALLOWED: - low = NetworkID(); - high = NetworkBroadcast(); - return true; - case ONLY_IP_RANGE_ALLOWED: - low = IPAddress(SecuritySettings.AllowedIPrangeLow); - high = IPAddress(SecuritySettings.AllowedIPrangeHigh); - break; - default: - low = IPAddress(0, 0, 0, 0); - high = IPAddress(255, 255, 255, 255); - return false; +String describeAllowedIPrange(const IPAddress& ip) { + if (SecuritySettings.IPblockLevel == ALL_ALLOWED) { + return F("All Allowed"); } - return true; + String allowedRange; + String ip_str = ip.toString(); + + for (networkIndex_t x = 0; x < NETWORK_MAX; x++) { + EventStruct tempEvent; + tempEvent.NetworkIndex = x; + + if (NWPluginCall(NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED, &tempEvent, ip_str)) { + if (!tempEvent.String1.isEmpty() && !tempEvent.String2.isEmpty()) { + + if (!allowedRange.isEmpty()) { allowedRange += F(", "); } + allowedRange += tempEvent.String1; + allowedRange += F(" - "); + allowedRange += tempEvent.String2; + } + } + } + return allowedRange; } } // namespace net diff --git a/src/ESPEasy/net/Helpers/NWAccessControl.h b/src/ESPEasy/net/Helpers/NWAccessControl.h index 035ce428d..7855229c7 100644 --- a/src/ESPEasy/net/Helpers/NWAccessControl.h +++ b/src/ESPEasy/net/Helpers/NWAccessControl.h @@ -15,8 +15,7 @@ namespace net { bool ipInAllowedSubnet(const IPAddress& ip); String describeAllowedIPrange(); - -bool getIPallowedRange(IPAddress& low, IPAddress& high); +String describeAllowedIPrange(const IPAddress& ip); } // namespace net diff --git a/src/ESPEasy/net/_NW001_WiFi_STA.cpp b/src/ESPEasy/net/_NW001_WiFi_STA.cpp index 916ac03fc..388ac3d93 100644 --- a/src/ESPEasy/net/_NW001_WiFi_STA.cpp +++ b/src/ESPEasy/net/_NW001_WiFi_STA.cpp @@ -297,21 +297,25 @@ bool NWPlugin_001(NWPlugin::Function function, EventStruct *event, String& strin case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED: { - IPAddress client_ip; - client_ip.fromString(string); - - if ((SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) && - !Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { - success = NWPlugin::ipInRange(client_ip, NetworkID(), NetworkBroadcast()); - } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { - const IPAddress low(SecuritySettings.AllowedIPrangeLow); - const IPAddress high(SecuritySettings.AllowedIPrangeHigh); - success = - !IPAddressSet(low) || - !IPAddressSet(high) || - NWPlugin::ipInRange(client_ip, low, high); - } else { + if (!Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) { + IPAddress client_ip; + client_ip.fromString(string); success = true; + if (SecuritySettings.IPblockLevel != ALL_ALLOWED) { + IPAddress low, high; + if (SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) { + low = NetworkID(); + high = NetworkBroadcast(); + } else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) { + low = IPAddress(SecuritySettings.AllowedIPrangeLow); + high = IPAddress(SecuritySettings.AllowedIPrangeHigh); + } + if (IPAddressSet(low) || IPAddressSet(high)) { + success = NWPlugin::ipInRange(client_ip, low, high); + event->String1 = formatIP(low); + event->String2 = formatIP(high); + } + } } break; } diff --git a/src/ESPEasy/net/_NW002_WiFi_AP.cpp b/src/ESPEasy/net/_NW002_WiFi_AP.cpp index fb5248192..4c1778d7e 100644 --- a/src/ESPEasy/net/_NW002_WiFi_AP.cpp +++ b/src/ESPEasy/net/_NW002_WiFi_AP.cpp @@ -230,14 +230,7 @@ bool NWPlugin_002(NWPlugin::Function function, EventStruct *event, String& strin // FIXME TD-er: Do we allow to set the subnetmask for AP to anything else? const IPAddress subnet(255, 255, 255, 0); - const IPAddress localIP = WiFi.softAPIP(); - bool success = true; - - for (uint8_t i = 0; success && i < 4; ++i) { - if ((localIP[i] & subnet[i]) != (client_ip[i] & subnet[i])) { - success = false; - } - } + success = NWPlugin::IP_in_subnet(WiFi.softAPIP(), client_ip, subnet); } break; } diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.cpp b/src/src/DataTypes/ESPEasy_plugin_functions.cpp index 55f381d35..68e24a6d3 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.cpp +++ b/src/src/DataTypes/ESPEasy_plugin_functions.cpp @@ -185,21 +185,22 @@ bool NWPlugin::get_subnet(NWPlugin::IP_type ip_type, NetworkInterface*networkInt broadcast = networkInterface->broadcastIP(); return true; } -#endif + +#endif // ifdef ESP32 bool NWPlugin::ipLessEqual(const IPAddress& ip, const IPAddress& high) { // FIXME TD-er: Must check whether both are of same type and check full range IPv6 int nrOctets = 4; - # if FEATURE_USE_IPV6 + #if FEATURE_USE_IPV6 if (ip.type() != high.type()) { return false; } if (ip.type() == IPv6) { nrOctets = 16; } - # endif // if FEATURE_USE_IPV6 + #endif // if FEATURE_USE_IPV6 for (int i = 0; i < nrOctets; ++i) { if (ip[i] != high[i]) { @@ -211,11 +212,69 @@ bool NWPlugin::ipLessEqual(const IPAddress& ip, const IPAddress& high) return true; } -bool NWPlugin::ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high) { - return ipLessEqual(low, ip) && ipLessEqual(ip, high); +bool NWPlugin::ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high) { + return ipLessEqual(low, ip) && ipLessEqual(ip, high); +} + +bool NWPlugin::IP_in_subnet(const IPAddress& localIP, + const IPAddress& client_ip, + const IPAddress& subnet) +{ + #if FEATURE_USE_IPV6 + if ((localIP.type() != client_ip.type()) || + (localIP.type() != subnet.type())) { return false; } + #endif // if FEATURE_USE_IPV6 + + return ipInRange( + client_ip, + getNetworkID(localIP, subnet), + getNetworkBroadcast(localIP, subnet)); +} + +IPAddress NWPlugin::getNetworkID(const IPAddress& localIP, const IPAddress& subnet) +{ + IPAddress networkID = localIP; + + int nrOctets = 4; + + #if FEATURE_USE_IPV6 + + if (localIP.type() != subnet.type()) { return IPAddress(); } + + if (localIP.type() == IPv6) { + nrOctets = 16; + } + #endif // if FEATURE_USE_IPV6 + + for (uint8_t i = 0; i < nrOctets; ++i) { + networkID[i] &= subnet[i]; + } + return networkID; +} + +IPAddress NWPlugin::getNetworkBroadcast(const IPAddress& localIP, const IPAddress& subnet) +{ + IPAddress broadcast = localIP; + + int nrOctets = 4; + + #if FEATURE_USE_IPV6 + + if (localIP.type() != subnet.type()) { return IPAddress(); } + + if (localIP.type() == IPv6) { + nrOctets = 16; + } + #endif // if FEATURE_USE_IPV6 + + for (uint8_t i = 0; i < nrOctets; ++i) { + broadcast[i] |= ~subnet[i]; + } + return broadcast; } #ifdef ESP32 + bool NWPlugin::IP_in_subnet(const IPAddress & ip, NetworkInterface *networkInterface) { @@ -223,17 +282,18 @@ bool NWPlugin::IP_in_subnet(const IPAddress & ip, IPAddress networkID; IPAddress broadcast; - if (!get_subnet(ip_type, networkInterface, networkID, broadcast)) { - return false; + if (!get_subnet(ip_type, networkInterface, networkID, broadcast)) { + return false; } -#if FEATURE_USE_IPV6 +# if FEATURE_USE_IPV6 + if (ip_type == NWPlugin::IP_type::ipv6_link_local) { // Must match zone, or else it will always match. - return (ip.zone() == networkID.zone()); + if (ip.zone() != networkID.zone()) return false; } -#endif +# endif // if FEATURE_USE_IPV6 - return ipLessEqual(networkID, ip) && ipLessEqual(ip, broadcast); + return ipInRange(ip, networkID, broadcast); } NWPlugin::IP_type NWPlugin::get_IP_type(const IPAddress& ip) diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index 7090c90ec..4d84bafc8 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -255,6 +255,14 @@ public: const IPAddress& low, const IPAddress& high); + static bool IP_in_subnet(const IPAddress& localIP, + const IPAddress& client_ip, + const IPAddress& subnet); + + static IPAddress getNetworkID(const IPAddress& localIP, const IPAddress& subnet); + + static IPAddress getNetworkBroadcast(const IPAddress& localIP, const IPAddress& subnet); + #ifdef ESP32 static bool IP_in_subnet(const IPAddress & ip, NetworkInterface *networkInterface); diff --git a/src/src/DataTypes/SettingsType.cpp b/src/src/DataTypes/SettingsType.cpp index fde993e3e..f0d3afb93 100644 --- a/src/src/DataTypes/SettingsType.cpp +++ b/src/src/DataTypes/SettingsType.cpp @@ -212,7 +212,7 @@ int SettingsType::getFileSize(Enum settingsType) { return max_file_pos; } -#ifndef BUILD_NO_DEBUG +#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) unsigned int SettingsType::getSVGcolor(Enum settingsType) { switch (settingsType) { case Enum::BasicSettings_Type: diff --git a/src/src/DataTypes/SettingsType.h b/src/src/DataTypes/SettingsType.h index b5f2c43cb..ea92f610c 100644 --- a/src/src/DataTypes/SettingsType.h +++ b/src/src/DataTypes/SettingsType.h @@ -54,7 +54,7 @@ public: static int getMaxFilePos(Enum settingsType); static int getFileSize(Enum settingsType); -#ifndef BUILD_NO_DEBUG +#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) static unsigned int getSVGcolor(Enum settingsType); #endif diff --git a/src/src/Helpers/Misc.cpp b/src/src/Helpers/Misc.cpp index f7c42a343..b8afad98d 100644 --- a/src/src/Helpers/Misc.cpp +++ b/src/src/Helpers/Misc.cpp @@ -615,7 +615,7 @@ void logMemUsageAfter(const __FlashStringHelper *function, int value) { // The recorded used memory is not an exact value, as background (or interrupt) tasks may also allocate or free heap memory. static int last_freemem = ESP.getFreeHeap(); const int freemem_end = ESP.getFreeHeap(); - +#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { String log; @@ -639,6 +639,7 @@ void logMemUsageAfter(const __FlashStringHelper *function, int value) { addLogMove(LOG_LEVEL_DEBUG, log); } } +#endif last_freemem = freemem_end; } diff --git a/src/src/WebServer/AccessControl.cpp b/src/src/WebServer/AccessControl.cpp index 2a1553f9d..188cce1d9 100644 --- a/src/src/WebServer/AccessControl.cpp +++ b/src/src/WebServer/AccessControl.cpp @@ -46,7 +46,7 @@ bool clientIPallowed() if (loglevelActiveFor(LOG_LEVEL_ERROR)) { response += F(" Allowed: "); - response += ESPEasy::net::describeAllowedIPrange(); + response += ESPEasy::net::describeAllowedIPrange(remoteIP); addLogMove(LOG_LEVEL_ERROR, response); } return false; diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index c2630004a..d5b591e58 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -1091,7 +1091,7 @@ void getWiFi_RSSI_icon(int rssi, int width_pixels) addHtml(F("\n")); } -#if FEATURE_CHART_STORAGE_LAYOUT +#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) void getConfig_dat_file_layout() { const int shiftY = 2; float yOffset = shiftY; diff --git a/src/src/WebServer/ESPEasy_WebServer.h b/src/src/WebServer/ESPEasy_WebServer.h index 35a83016a..787876d75 100644 --- a/src/src/WebServer/ESPEasy_WebServer.h +++ b/src/src/WebServer/ESPEasy_WebServer.h @@ -193,7 +193,7 @@ void write_SVG_image_header(int width, void getWiFi_RSSI_icon(int rssi, int width_pixels); -#if FEATURE_CHART_STORAGE_LAYOUT +#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) void getConfig_dat_file_layout(); void getStorageTableSVG(SettingsType::Enum settingsType); diff --git a/src/src/WebServer/SysInfoPage.cpp b/src/src/WebServer/SysInfoPage.cpp index cd33e2d12..2d256bf75 100644 --- a/src/src/WebServer/SysInfoPage.cpp +++ b/src/src/WebServer/SysInfoPage.cpp @@ -668,7 +668,7 @@ void handle_sysinfo_Storage() { } # endif // ifndef LIMIT_BUILD_SIZE -# if FEATURE_CHART_STORAGE_LAYOUT +#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) if (showSettingsFileLayout) { addTableSeparator(F("Settings Files"), 2, 3); From 7a1fc1ed194446c541b4a41a0706d07bee57a96b Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Jun 2026 19:50:00 +0200 Subject: [PATCH 14/16] [Build] Fix custom build missing an include for Metrics.cpp --- src/src/WebServer/Metrics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src/WebServer/Metrics.cpp b/src/src/WebServer/Metrics.cpp index c1fd1e115..e83769f07 100644 --- a/src/src/WebServer/Metrics.cpp +++ b/src/src/WebServer/Metrics.cpp @@ -5,6 +5,8 @@ #ifdef WEBSERVER_METRICS +#include "../../ESPEasy/net/ESPEasyNetwork.h" + # if FEATURE_NETWORK_STATS # ifdef ESP8266 # define MAX_NR_NETWORKS_IN_TABLE 2 From 673f5bbdc3d7e0570d8922d36290a7ee3f8723a1 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Jun 2026 22:18:23 +0200 Subject: [PATCH 15/16] [Build] Fix sort custom vars on ESP8266 ESP8266 needs all return paths of the lambda function to be exactly the same type. Thus the `operator<` of `String` needs to be cast to a bool as it would otherwise be an `unsigned char` . --- src/src/WebServer/SysVarPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/WebServer/SysVarPage.cpp b/src/src/WebServer/SysVarPage.cpp index e932cd40a..0eedeb4a9 100644 --- a/src/src/WebServer/SysVarPage.cpp +++ b/src/src/WebServer/SysVarPage.cpp @@ -57,7 +57,7 @@ void handle_sysvars() { const int32_t ai = a.toInt(); const int32_t bi = b.toInt(); - if (!ai && !bi) { return a < b; } // a..z + if (!ai && !bi) { return !!(a < b); } // a..z , need to cast to bool for ESP8266 if (!ai) { return false; } // Alphanum after num return ai < bi; // Numerical order From 30ca5c63707b4989eb6d29e1423c7feb17af98c4 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 7 Jun 2026 22:39:40 +0200 Subject: [PATCH 16/16] [SysVarSort] Only sort system variables on ESP32 due to build size --- .gitignore | 2 ++ src/src/WebServer/SysVarPage.cpp | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f4a4487ca..a195b861d 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,5 @@ sdkconfig.* src/Custom_.h compile_commands.json + +*.Identifier diff --git a/src/src/WebServer/SysVarPage.cpp b/src/src/WebServer/SysVarPage.cpp index 0eedeb4a9..a9a3c0eaa 100644 --- a/src/src/WebServer/SysVarPage.cpp +++ b/src/src/WebServer/SysVarPage.cpp @@ -18,6 +18,11 @@ # include "../Helpers/SystemVariables.h" +#if defined(ESP32) && (!defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES) +#define FEATURE_CUSTOM_STRING_SORT 1 +#else +#define FEATURE_CUSTOM_STRING_SORT 0 +#endif // ******************************************************************************** // Web Interface sysvars showing all system vars and their value. // ******************************************************************************** @@ -52,7 +57,7 @@ void handle_sysvars() { html_table_header(F("URL encoded"), F("RTDReference/SystemVariable.html"), 0); addTableSeparator(F("Custom Variables"), 3, 3); - # if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + #if FEATURE_CUSTOM_STRING_SORT auto numAlphaSort = [](const String& a, const String& b) { const int32_t ai = a.toInt(); const int32_t bi = b.toInt(); @@ -64,7 +69,7 @@ void handle_sysvars() { }; std::vector customStringSort; - # endif // if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + # endif // FEATURE_CUSTOM_STRING_SORT if (customFloatVar.empty()) { html_TR_TD(); @@ -72,7 +77,7 @@ void handle_sysvars() { html_TD(); html_TD(); } else { - # if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + #if FEATURE_CUSTOM_STRING_SORT for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) { customStringSort.push_back(it->first); @@ -88,7 +93,7 @@ void handle_sysvars() { } addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it.c_str()), false); } - # else // if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + # else // FEATURE_CUSTOM_STRING_SORT for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) { NumericalType detectedType; bool isv_ = true; @@ -98,7 +103,7 @@ void handle_sysvars() { } addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it->first.c_str()), false); } - # endif // if !defined(LIMIT_BUILD_SIZE) || FEATURE_STRING_VARIABLES + # endif // FEATURE_CUSTOM_STRING_SORT } # if FEATURE_STRING_VARIABLES