-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Add independent Ethernet/WiFi IP config and primary network interface selection #5650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
015e2e2
302a3b6
551b5c6
e949871
353126c
1661874
eb0f4c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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")); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| 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) { | ||
|
Comment on lines
+322
to
+336
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No—there is no documented, stable guarantee in ESP-IDF/Arduino-ESP32 that the underlying lwIP netif name will always use exactly “st” for WiFi STA and “en” for Ethernet across supported versions. In ESP-IDF, the recommended stable abstraction is ESP-NETIF: it creates and manages network interfaces and exposes handles for “default” WiFi (station/AP) and Ethernet interfaces. The ESP-NETIF API explicitly provides helpers for default WiFi STA/Ethernet initialization and returns an esp_netif_t handle, rather than requiring you to parse lwIP netif->name. For example, ESP-NETIF default Wi-Fi initialization is provided by functions that return an esp_netif handle (e.g., esp_netif_create_default_wifi_sta) and similarly Ethernet examples exist; the programming guide describes these as the convenient/default initialization paths. [1][2] If you still need to identify which underlying lwIP netif corresponds to an esp_netif_t, ESP-NETIF provides a stable API to query the “net interface name” used by the underlying TCP/IP stack implementation (lwIP): esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char *name). This is the API intended to read the implementation-specific name, not to rely on a fixed naming convention like “st”/“en”. [3] Stable approach to identify the default WiFi/Ethernet netif: 1) Use the ESP-NETIF default-interface creation APIs and keep the returned esp_netif_t handles. 2) If needed for compatibility/debugging, call esp_netif_get_netif_impl_name(handle, buf) to get the underlying lwIP name. Evidence: ESP-IDF’s ESP-NETIF documentation states that it “provides some convenient methods of creating the most common ones, such as Wi‑Fi and Ethernet” and that these APIs return an esp_netif handle configured with default settings. [1] Separately, the esp_netif API explicitly defines esp_netif_get_netif_impl_name as “Get net interface name from network stack implementation,” clarifying that the name comes from the underlying TCP/IP stack and is therefore implementation-/stack-dependent. [3] In contrast, the lwIP netif naming scheme (netif->name is only two characters plus index/num in classic lwIP) is an implementation detail of lwIP itself and is not presented by ESP-NETIF docs as a contractual guarantee to remain “st”/“en” forever. (So depending on netif->name for semantic identity is not a documented contract in ESP-IDF/ESP-NETIF.) [4] Citations:
Fix fragile WiFi/Ethernet selection by removing hardcoded lwIP
🤖 Prompt for AI Agents |
||
| 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")); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| UNLOCK_TCPIP_CORE(); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| #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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.