diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index 2e458e7da9..0b61f35374 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -135,7 +135,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
- // NOTE: Ethernet configuration takes priority over other use of pins
+ #if defined(ARDUINO_ARCH_ESP32)
+ // AI: deserialize ethernet static IP configuration.
+ JsonArray eth_ip = ethernet[F("eip")];
+ JsonArray eth_gw = ethernet[F("egw")];
+ JsonArray eth_sn = ethernet[F("esn")];
+ if (!eth_ip.isNull()) {
+ for (size_t i = 0; i < 4; i++) {
+ CJSON(ethStaticIP[i], eth_ip[i]);
+ CJSON(ethStaticGW[i], eth_gw[i]);
+ CJSON(ethStaticSN[i], eth_sn[i]);
+ }
+ }
+ // AI: deserialize primary network interface selection.
+ CJSON(ethPrimaryInterface, ethernet[F("epi")]);
+ #endif
initEthernet();
#endif
@@ -935,6 +949,21 @@ void serializeConfig(JsonObject root) {
break;
}
}
+ // AI: serialize ethernet static IP configuration.
+ // Stored as 4-element JSON arrays, one byte per octet, consistent
+ // with the WiFi static IP serialisation pattern in nw.ins[n].ip/gw/sn.
+ // Only written when an ethernet board type is configured.
+ JsonArray eth_ip = ethernet.createNestedArray(F("eip"));
+ JsonArray eth_gw = ethernet.createNestedArray(F("egw"));
+ JsonArray eth_sn = ethernet.createNestedArray(F("esn"));
+ for (size_t i = 0; i < 4; i++) {
+ eth_ip.add(ethStaticIP[i]);
+ eth_gw.add(ethStaticGW[i]);
+ eth_sn.add(ethStaticSN[i]);
+ }
+ // AI: serialize primary network interface selection.
+ // false = WiFi is primary, true = Ethernet is primary.
+ ethernet[F("epi")] = ethPrimaryInterface;
#endif
JsonObject hw = root.createNestedObject(F("hw"));
diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm
index e187f887fb..1b78e19a16 100644
--- a/wled00/data/settings_wifi.htm
+++ b/wled00/data/settings_wifi.htm
@@ -133,12 +133,14 @@
Identity:
`;
}
+ // AI: removed "Also used by Ethernet" note from WiFi static IP label
+ // WiFi and Ethernet now have independent IP configurations
var b = `
Network name (SSID${i==0?", empty to not connect":""}): 0?"required":""}>
${encryptionTypeField}
Network password:
BSSID (optional):
-Static IP (leave at 0.0.0.0 for DHCP)${i==0?" Also used by Ethernet":""}:
+Static IP (leave at 0.0.0.0 for DHCP):
...
Static gateway:
...
@@ -166,6 +168,8 @@
function tE() {
// keep the hidden input with MAC addresses, only toggle visibility of the list UI
gId('rlc').style.display = d.Sf.RE.checked ? 'block' : 'none';
+ // AI: also refresh ethernet IP section visibility on page init
+ toggleEthIP();
}
// reset remotes: initialize empty list (called from xml.cpp)
function rstR() {
@@ -207,6 +211,18 @@
}
}
+ // AI: toggleEthIP() shows or hides the Ethernet static IP configuration
+ // section based on whether an ethernet board type is selected.
+ // Called on page init (via tE) and on ethernet type dropdown change.
+ function toggleEthIP() {
+ const ethSelect = d.Sf.ETH;
+ if (!ethSelect) return;
+ const ethIpSection = gId('ethip');
+ if (!ethIpSection) return;
+ // AI: value "0" means "None" - no ethernet board selected, hide IP config
+ ethIpSection.style.display = (ethSelect.value !== "0") ? 'block' : 'none';
+ }
+
@@ -228,7 +244,8 @@
Wireless network
Ethernet Type
-
+
+
+
Ethernet IP Configuration
+ Static IP (leave at 0.0.0.0 for DHCP):
+ ...
+ Static gateway (leave at 0.0.0.0 for no gateway):
+ ...
+ Static subnet mask:
+ ...
+
+
+
+
+
Primary Network Interface
+ Select the primary network WLED should use for internet and for access to network services like NTP, MQTT, etc.
+ WiFi
+ <-->
+ Ethernet
+
+
+
DNS & mDNS
DNS server address:
@@ -254,7 +291,8 @@
DNS & mDNS
mDNS address (leave empty for no mDNS):
http:// .local
- Client IP: Not connected
+
+ Active IP(s): Not connected
Configure Access Point
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h
index 6201a19192..c7f3bbed0d 100644
--- a/wled00/fcn_declare.h
+++ b/wled00/fcn_declare.h
@@ -301,6 +301,13 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
//network.cpp
bool initEthernet(); // result is informational
+#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
+bool initEthernet();
+// AI: sets lwIP primary network interface based on ethPrimaryInterface
+// by enabling wled to be dual-homed we need to be deterministic about which interface
+// wled uses for outgoing connections to minimise asyncronous routing issues.
+void setPrimaryNetworkInterface();
+#endif
int getSignalQuality(int rssi);
void fillMAC2Str(char *str, const uint8_t *mac);
void fillStr2MAC(uint8_t *mac, const char *str);
diff --git a/wled00/network.cpp b/wled00/network.cpp
index 606fa65d9e..595a1266db 100644
--- a/wled00/network.cpp
+++ b/wled00/network.cpp
@@ -4,6 +4,8 @@
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
+#include "lwip/netif.h" // AI: required for netif_set_default() and netif_list
+#include "lwip/tcpip.h" // AI: required for LOCK_TCPIP_CORE/UNLOCK_TCPIP_CORE
// The following six pins are neither configurable nor
// can they be re-assigned through IOMUX / GPIO matrix.
// See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface
@@ -274,18 +276,93 @@ bool initEthernet()
}
// https://github.com/wled/WLED/issues/5247
- if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) {
- ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress);
+ // AI: apply ethernet static IP configuration using the new dedicated
+ // ethernet IP variables (ethStaticIP, ethStaticGW, ethStaticSN) rather than
+ // sharing the first WiFi network's static IP config as was previously done.
+ // ethStaticIP of 0.0.0.0 means use DHCP for ethernet.
+ // Gateway of 0.0.0.0 is valid — means no default route via ethernet,
+ // lwIP will only install a subnet route for the ethernet interface.
+ if ((uint32_t)ethStaticIP != 0x00000000) {
+ // AI: always pass the configured gateway to ETH.config().
+ // Default route selection between interfaces is handled by netif_set_default()
+ // in setPrimaryNetworkInterface(). Gateway of 0.0.0.0 is explicitly supported
+ // for users who want ethernet as a stub interface with no onward routing.
+ ETH.config(ethStaticIP, ethStaticGW, ethStaticSN, dnsAddress);
+ DEBUG_PRINTF_P(PSTR("initE: Static IP configured. IP=%d.%d.%d.%d GW=%d.%d.%d.%d PNI=%s\n"),
+ ethStaticIP[0], ethStaticIP[1], ethStaticIP[2], ethStaticIP[3],
+ ethStaticGW[0], ethStaticGW[1], ethStaticGW[2], ethStaticGW[3],
+ ethPrimaryInterface ? "ETH" : "WiFi");
} else {
+ // AI: no static IP configured, use DHCP for ethernet
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
+ DEBUG_PRINTLN(F("initE: DHCP configured for ethernet"));
}
successfullyConfiguredEthernet = true;
DEBUG_PRINTLN(F("initE: *** Ethernet successfully configured! ***"));
return true;
}
-#endif
+// AI: setPrimaryNetworkInterface() explicitly sets the lwIP primary
+// network interface based on the user's ethPrimaryInterface selection.
+// Directly tells lwIP which netif to use for outbound traffic, resolving
+// asymmetric routing issues where reply packets were routed out the wrong
+// interface when both WiFi and Ethernet are active simultaneously.
+// Interfaces are identified by name prefix ('en'=ethernet, 'st'=WiFi STA)
+// which works correctly for both static IP and DHCP configurations.
+// Called from multiple network events to ensure it fires after whichever
+// interface comes up last.
+
+// AI: below section was generated by an AI
+void setPrimaryNetworkInterface() {
+ struct netif *netif_iter;
+ struct netif *target = nullptr;
+ struct netif *fallback = nullptr;
+
+ // AI: interface name prefixes in arduino-esp32 IDF V4 (Tasmota platform):
+ // 'en' = ethernet, 'st' = WiFi STA. Validated on IDF 4.4.8.
+ const char *targetName = ethPrimaryInterface ?
+ "en" :
+ "st";
+
+ // AI: acquire lwIP TCP/IP core lock before accessing netif_list
+ // and calling netif_set_default() to avoid thread-safety assertions
+ LOCK_TCPIP_CORE();
+
+ for (netif_iter = netif_list; netif_iter != NULL; netif_iter = netif_iter->next) {
+ if (!netif_is_up(netif_iter) || netif_iter->ip_addr.u_addr.ip4.addr == 0) continue;
+ const bool isPreferred = (netif_iter->name[0] == targetName[0] &&
+ netif_iter->name[1] == targetName[1]);
+ if (isPreferred) {
+ target = netif_iter;
+ break;
+ }
+ if (!fallback) fallback = netif_iter;
+ }
+
+ // AI: if preferred interface unavailable, fall back to any ready interface
+ // prevents outbound traffic being pinned to a dead default netif
+ if (!target && fallback) {
+ target = fallback;
+ DEBUG_PRINTLN(F("setPNI: Preferred interface unavailable, using fallback"));
+ }
+
+ if (target != nullptr) {
+ netif_set_default(target);
+ DEBUG_PRINTF_P(PSTR("setPNI: Primary netif set to %c%c%d (%d.%d.%d.%d)\n"),
+ target->name[0], target->name[1], target->num,
+ ip4_addr1(&target->ip_addr.u_addr.ip4),
+ ip4_addr2(&target->ip_addr.u_addr.ip4),
+ ip4_addr3(&target->ip_addr.u_addr.ip4),
+ ip4_addr4(&target->ip_addr.u_addr.ip4));
+ } else {
+ DEBUG_PRINTLN(F("setPNI: No ready interface found, will retry on next IP event"));
+ }
+
+ UNLOCK_TCPIP_CORE();
+}
+#endif
+// AI: end
//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp
int getSignalQuality(int rssi)
@@ -383,6 +460,7 @@ bool isWiFiConfigured() {
#define ARDUINO_EVENT_WIFI_SCAN_DONE SYSTEM_EVENT_SCAN_DONE
#define ARDUINO_EVENT_ETH_START SYSTEM_EVENT_ETH_START
#define ARDUINO_EVENT_ETH_CONNECTED SYSTEM_EVENT_ETH_CONNECTED
+ #define ARDUINO_EVENT_ETH_GOT_IP SYSTEM_EVENT_ETH_GOT_IP // AI: added for DHCP ethernet IP assignment event
#define ARDUINO_EVENT_ETH_DISCONNECTED SYSTEM_EVENT_ETH_DISCONNECTED
#endif
@@ -431,6 +509,11 @@ void WiFiEvent(WiFiEvent_t event)
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
DEBUG_PRINT(F("WiFi-E: IP address: ")); DEBUG_PRINTLN(Network.localIP());
+ // AI: re-evaluate primary network interface when WiFi gets its IP
+ // handles both static IP and DHCP scenarios for WiFi interface
+ #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
+ setPrimaryNetworkInterface();
+ #endif
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
// followed by IDLE and SCAN_DONE
@@ -481,17 +564,30 @@ void WiFiEvent(WiFiEvent_t event)
case ARDUINO_EVENT_ETH_CONNECTED:
{
DEBUG_PRINTLN(F("ETH-E: Connected"));
- if (!apActive) {
- WiFi.disconnect(true); // disable WiFi entirely
- }
- char hostname[64] = {'\0'}; // any "hostname" within a Fully Qualified Domain Name (FQDN) must not exceed 63 characters
- getWLEDhostname(hostname, sizeof(hostname), true); // create DNS name based on mDNS name if set, or fall back to standard WLED server name
+ // AI: WiFi is intentionally kept active when ethernet connects.
+ // Previously WiFi was disabled here to prevent routing conflicts, but
+ // with dual-interface support, netif_set_default() handles routing
+ // preference between interfaces. Disabling WiFi here would defeat the
+ // purpose of the feature entirely.
+ char hostname[64] = {'\0'};
+ getWLEDhostname(hostname, sizeof(hostname), true);
ETH.setHostname(hostname);
+ // AI: attempt to set default gateway interface on ethernet connect
+ setPrimaryNetworkInterface();
showWelcomePage = false;
break;
}
+ case ARDUINO_EVENT_ETH_GOT_IP:
+ // AI: ethernet DHCP IP assigned — now safe to set default netif
+ // this event is the reliable trigger for DHCP ethernet configuration
+ DEBUG_PRINT(F("ETH-E: Got IP: ")); DEBUG_PRINTLN(ETH.localIP());
+ setPrimaryNetworkInterface();
+ break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
DEBUG_PRINTLN(F("ETH-E: Disconnected"));
+ // AI: re-evaluate primary network interface on ethernet disconnect
+ // ensures fallback to WiFi if ethernet was the primary interface
+ setPrimaryNetworkInterface();
// This doesn't really affect ethernet per se,
// as it's only configured once. Rather, it
// may be necessary to reconnect the WiFi when
diff --git a/wled00/set.cpp b/wled00/set.cpp
index fb516ac7d6..092778bb80 100644
--- a/wled00/set.cpp
+++ b/wled00/set.cpp
@@ -145,10 +145,60 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
#endif
+// AI: below section was generated by an AI
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
ethernetType = request->arg(F("ETH")).toInt();
+ // AI: read ethernet static IP configuration from form POST.
+ // Each IP address is submitted as four separate octet fields (0-255),
+ // reassembled here into IPAddress objects matching the same pattern
+ // used for WiFi static IP fields (IP{n}0-3, GW{n}0-3, SN{n}0-3).
+ if (request->hasArg(F("EIP0"))) {
+ for (int i = 0; i < 4; i++) {
+ char eip[6], egw[6], esn[6];
+ snprintf_P(eip, sizeof(eip), PSTR("EIP%d"), i);
+ snprintf_P(egw, sizeof(egw), PSTR("EGW%d"), i);
+ snprintf_P(esn, sizeof(esn), PSTR("ESN%d"), i);
+ ethStaticIP[i] = request->arg(eip).toInt();
+ ethStaticGW[i] = request->arg(egw).toInt();
+ ethStaticSN[i] = request->arg(esn).toInt();
+ }
+ }
+ // AI: read primary network interface selection.
+ // PNI field value 0 = WiFi is primary interface, 1 = Ethernet is primary interface.
+ // Radio buttons only submit when selected so use hasArg with value check.
+ bool prevPrimaryInterface = ethPrimaryInterface;
+ ethPrimaryInterface = (request->arg(F("EPI")).toInt() == 1);
+ bool ethPNIChanged = (ethPrimaryInterface != prevPrimaryInterface);
+ // AI: apply ethernet IP config changes immediately without reboot,
+ // bringing ethernet IP configuration to parity with WiFi live-update
+ // behaviour. Only calls ETH.config() if values actually changed,
+ // avoiding unnecessary network disruption on save.
+ // initEthernet() still called for first-time hardware init only.
+ if (ethernetType != WLED_ETH_NONE) {
+ IPAddress currentIP = ETH.localIP();
+ IPAddress currentGW = ETH.gatewayIP();
+ IPAddress currentSN = ETH.subnetMask();
+ bool ethIPChanged = (
+ (uint32_t)ethStaticIP != (uint32_t)currentIP ||
+ (uint32_t)ethStaticGW != (uint32_t)currentGW ||
+ (uint32_t)ethStaticSN != (uint32_t)currentSN
+ );
+ if (ethIPChanged) {
+ if ((uint32_t)ethStaticIP != 0) {
+ ETH.config(ethStaticIP, ethStaticGW, ethStaticSN, dnsAddress);
+ DEBUG_PRINTLN(F("ETH: IP config updated from settings"));
+ } else {
+ ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
+ DEBUG_PRINTLN(F("ETH: Switched to DHCP from settings"));
+ }
+ }
+ if (ethIPChanged || ethPNIChanged) {
+ setPrimaryNetworkInterface();
+ }
+ }
initEthernet();
#endif
+ // AI: end
}
//LED SETTINGS
diff --git a/wled00/wled.cpp b/wled00/wled.cpp
index e91fcca8f5..b40acb9b29 100644
--- a/wled00/wled.cpp
+++ b/wled00/wled.cpp
@@ -718,8 +718,26 @@ void WLED::initConnection()
WiFi.setHostname(hostname);
#endif
- if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) {
+// AI: below section was generated by an AI ...
+ if (multiWiFi[selectedWiFi].staticIP != 0U) {
+ // AI: apply WiFi static IP configuration.
+ // Always pass the configured gateway to WiFi.config().
+ // Default route selection between interfaces is handled by netif_set_default()
+ // in setPrimaryNetworkInterface(). Gateway of 0.0.0.0 is explicitly supported
+ // for users who want WiFi as a stub interface with no onward routing.
+ #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress);
+ DEBUG_PRINTF_P(PSTR("initC: WiFi static IP. IP=%d.%d.%d.%d GW=%d.%d.%d.%d PNI=%s\n"),
+ multiWiFi[selectedWiFi].staticIP[0], multiWiFi[selectedWiFi].staticIP[1],
+ multiWiFi[selectedWiFi].staticIP[2], multiWiFi[selectedWiFi].staticIP[3],
+ multiWiFi[selectedWiFi].staticGW[0], multiWiFi[selectedWiFi].staticGW[1],
+ multiWiFi[selectedWiFi].staticGW[2], multiWiFi[selectedWiFi].staticGW[3],
+ ethPrimaryInterface ? "ETH" : "WiFi");
+ #else
+ // AI: no ethernet support compiled in, use WiFi gateway normally
+ WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress);
+ #endif
+// AI: end comments
} else {
WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0));
}
@@ -876,9 +894,19 @@ void WLED::initInterfaces()
e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT);
ddp.begin(false, DDP_DEFAULT_PORT);
reconnectHue();
+
+// AI: below section was generated by an AI ...
+ #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
+ // AI: final attempt to set primary network interface after all
+ // interfaces are initialised. Catches the case where ethernet configured
+ // synchronously at boot before ETH events fired, meaning setPrimaryNetworkInterface()
+ // called from ETH_CONNECTED/ETH_GOT_IP events was too early.
+ setPrimaryNetworkInterface();
+ #endif
interfacesInited = true;
wasConnected = true;
}
+// AI: end comments
void WLED::handleConnection()
{
@@ -979,7 +1007,16 @@ void WLED::handleConnection()
sendImprovStateResponse(0x04);
if (improvActive > 1) sendImprovIPRPCResult(ImprovRPCType::Command_Wifi);
}
+ // AI: below section was generated by an AI ...
initInterfaces();
+ #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
+ // AI: additional setPrimaryNetworkInterface() call after interfaces
+ // are fully initialised. ETH events fire before WiFi.onEvent() is registered
+ // during boot, so earlier calls in the event handler may miss the ethernet
+ // netif. By this point both interfaces should be up and visible in netif_list.
+ setPrimaryNetworkInterface();
+ #endif
+ // AI: end comments
userConnected();
UsermodManager::connected();
lastMqttReconnectAttempt = 0; // force immediate update
diff --git a/wled00/wled.h b/wled00/wled.h
index 1a5f1b143e..5ba366e88c 100644
--- a/wled00/wled.h
+++ b/wled00/wled.h
@@ -390,6 +390,18 @@ WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm);
#else
WLED_GLOBAL int ethernetType _INIT(WLED_ETH_NONE); // use none for ethernet board type if default not defined
#endif
+// AI: below section was generated by an AI
+ // AI: separate static IP configuration for ethernet interface.
+ // These are independent of the WiFi static IP fields (staticIP/staticGW/staticSN)
+ // which live in the WiFiConfig struct inside multiWiFi.
+ // All three default to 0.0.0.0 which means DHCP will be used for ethernet.
+ WLED_GLOBAL IPAddress ethStaticIP _INIT_N(((0, 0, 0, 0)));
+ WLED_GLOBAL IPAddress ethStaticGW _INIT_N(((0, 0, 0, 0)));
+ WLED_GLOBAL IPAddress ethStaticSN _INIT_N(((255, 255, 255, 0)));
+ // AI: primary network interface selection.
+ // use for unknown network communications e.g. internet access, ntp, mqtt
+ WLED_GLOBAL bool ethPrimaryInterface _INIT(false);
+// AI: end
#endif
// LED CONFIG
diff --git a/wled00/xml.cpp b/wled00/xml.cpp
index 812ef8c207..778bfc3ad0 100644
--- a/wled00/xml.cpp
+++ b/wled00/xml.cpp
@@ -131,6 +131,7 @@ static void appendGPIOinfo(Print& settingsScript)
settingsScript.print(hardwareTX); // debug output (TX) pin
firstPin = false;
#endif
+
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
if (!firstPin) settingsScript.print(',');
@@ -288,28 +289,64 @@ void getSettingsJS(byte subPage, Print& settingsScript)
settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting
#endif
+ // AI: below section was generated by an AI
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
- printSetFormValue(settingsScript,PSTR("ETH"),ethernetType);
+ printSetFormValue(settingsScript,PSTR("ETH"),ethernetType);
+ // AI: populate ethernet static IP fields with current saved values.
+ // Each IPAddress is output as four separate octet values matching the
+ // EIP0-3, EGW0-3, ESN0-3 field naming convention in settings_wifi.htm.
+ for (int i = 0; i < 4; i++) {
+ char key[5];
+ snprintf_P(key, sizeof(key), PSTR("EIP%d"), i);
+ printSetFormValue(settingsScript, key, ethStaticIP[i]);
+ snprintf_P(key, sizeof(key), PSTR("EGW%d"), i);
+ printSetFormValue(settingsScript, key, ethStaticGW[i]);
+ snprintf_P(key, sizeof(key), PSTR("ESN%d"), i);
+ printSetFormValue(settingsScript, key, ethStaticSN[i]);
+ }
+ // AI: set the primary network interface radio button.
+ // EPI value 0 = WiFi , 1 = Ethernet.
+ // printSetFormValue on a radio button sets the checked state by value match.
+ printSetFormValue(settingsScript, PSTR("EPI"), ethPrimaryInterface ? 1 : 0);
#else
- //hide ethernet setting if not compiled in
- settingsScript.print(F("gId('ethd').style.display='none';"));
+ // AI: hide ethernet section entirely if ethernet support not compiled in
+ settingsScript.print(F("gId('ethd').style.display='none';"));
#endif
+ // AI: end
if (Network.isConnected()) //is connected
{
- char s[32];
- IPAddress localIP = Network.localIP();
- sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
-
+ char s[64] = {'\0'};
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
- if (Network.isEthernet()) strcat_P(s ,PSTR(" (Ethernet)"));
+ // AI: show both interface IPs when both are active so users can
+ // identify which IP to use from each subnet. mDNS resolves to the
+ // primary interface IP only.
+ IPAddress ethIP = ETH.localIP();
+ IPAddress wifiIP = WiFi.localIP();
+ if (ethernetType != WLED_ETH_NONE &&
+ ethIP != (uint32_t)0 &&
+ wifiIP != (uint32_t)0) {
+ // both interfaces active
+ snprintf_P(s, sizeof(s), PSTR("%d.%d.%d.%d (ETH) / %d.%d.%d.%d (WiFi)"),
+ ethIP[0], ethIP[1], ethIP[2], ethIP[3],
+ wifiIP[0], wifiIP[1], wifiIP[2], wifiIP[3]);
+ } else {
+ IPAddress localIP = Network.localIP();
+ if (Network.isEthernet()) {
+ snprintf_P(s, sizeof(s), PSTR("%d.%d.%d.%d (Ethernet)"), localIP[0], localIP[1], localIP[2], localIP[3]);
+ } else {
+ snprintf_P(s, sizeof(s), PSTR("%d.%d.%d.%d"), localIP[0], localIP[1], localIP[2], localIP[3]);
+ }
+ }
+ #else
+ IPAddress localIP = Network.localIP();
+ snprintf_P(s, sizeof(s), PSTR("%d.%d.%d.%d"), localIP[0], localIP[1], localIP[2], localIP[3]);
#endif
- printSetClassElementHTML(settingsScript,PSTR("sip"),0,s);
+ printSetClassElementHTML(settingsScript, PSTR("sip"), 0, s);
} else
{
printSetClassElementHTML(settingsScript,PSTR("sip"),0,(char*)F("Not connected"));
}
-
if (WiFi.softAPIP()[0] != 0) //is active
{
char s[16];