|
4 | 4 |
|
5 | 5 |
|
6 | 6 | #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) |
| 7 | +#include "lwip/netif.h" // AI: required for netif_set_default() and netif_list |
| 8 | +#include "lwip/tcpip.h" // AI: required for LOCK_TCPIP_CORE/UNLOCK_TCPIP_CORE |
7 | 9 | // The following six pins are neither configurable nor |
8 | 10 | // can they be re-assigned through IOMUX / GPIO matrix. |
9 | 11 | // 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() |
274 | 276 | } |
275 | 277 |
|
276 | 278 | // https://github.com/wled/WLED/issues/5247 |
277 | | - if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { |
278 | | - ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); |
| 279 | + // AI: apply ethernet static IP configuration using the new dedicated |
| 280 | + // ethernet IP variables (ethStaticIP, ethStaticGW, ethStaticSN) rather than |
| 281 | + // sharing the first WiFi network's static IP config as was previously done. |
| 282 | + // ethStaticIP of 0.0.0.0 means use DHCP for ethernet. |
| 283 | + // Gateway of 0.0.0.0 is valid — means no default route via ethernet, |
| 284 | + // lwIP will only install a subnet route for the ethernet interface. |
| 285 | + if ((uint32_t)ethStaticIP != 0x00000000) { |
| 286 | + // AI: always pass the configured gateway to ETH.config(). |
| 287 | + // Default route selection between interfaces is handled by netif_set_default() |
| 288 | + // in setPrimaryNetworkInterface(). Gateway of 0.0.0.0 is explicitly supported |
| 289 | + // for users who want ethernet as a stub interface with no onward routing. |
| 290 | + ETH.config(ethStaticIP, ethStaticGW, ethStaticSN, dnsAddress); |
| 291 | + DEBUG_PRINTF_P(PSTR("initE: Static IP configured. IP=%d.%d.%d.%d GW=%d.%d.%d.%d PNI=%s\n"), |
| 292 | + ethStaticIP[0], ethStaticIP[1], ethStaticIP[2], ethStaticIP[3], |
| 293 | + ethStaticGW[0], ethStaticGW[1], ethStaticGW[2], ethStaticGW[3], |
| 294 | + ethPrimaryInterface ? "ETH" : "WiFi"); |
279 | 295 | } else { |
| 296 | + // AI: no static IP configured, use DHCP for ethernet |
280 | 297 | ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); |
| 298 | + DEBUG_PRINTLN(F("initE: DHCP configured for ethernet")); |
281 | 299 | } |
282 | 300 |
|
283 | 301 | successfullyConfiguredEthernet = true; |
284 | 302 | DEBUG_PRINTLN(F("initE: *** Ethernet successfully configured! ***")); |
285 | 303 | return true; |
286 | 304 | } |
287 | | -#endif |
288 | 305 |
|
| 306 | +// AI: setPrimaryNetworkInterface() explicitly sets the lwIP primary |
| 307 | +// network interface based on the user's ethPrimaryInterface selection. |
| 308 | +// Directly tells lwIP which netif to use for outbound traffic, resolving |
| 309 | +// asymmetric routing issues where reply packets were routed out the wrong |
| 310 | +// interface when both WiFi and Ethernet are active simultaneously. |
| 311 | +// Interfaces are identified by name prefix ('en'=ethernet, 'st'=WiFi STA) |
| 312 | +// which works correctly for both static IP and DHCP configurations. |
| 313 | +// Called from multiple network events to ensure it fires after whichever |
| 314 | +// interface comes up last. |
| 315 | + |
| 316 | +// AI: below section was generated by an AI |
| 317 | +void setPrimaryNetworkInterface() { |
| 318 | + struct netif *netif_iter; |
| 319 | + struct netif *target = nullptr; |
| 320 | + struct netif *fallback = nullptr; |
| 321 | + |
| 322 | + // AI: interface name prefixes in arduino-esp32 IDF V4 (Tasmota platform): |
| 323 | + // 'en' = ethernet, 'st' = WiFi STA. Validated on IDF 4.4.8. |
| 324 | + const char *targetName = ethPrimaryInterface ? |
| 325 | + "en" : |
| 326 | + "st"; |
| 327 | + |
| 328 | + // AI: acquire lwIP TCP/IP core lock before accessing netif_list |
| 329 | + // and calling netif_set_default() to avoid thread-safety assertions |
| 330 | + LOCK_TCPIP_CORE(); |
| 331 | + |
| 332 | + for (netif_iter = netif_list; netif_iter != NULL; netif_iter = netif_iter->next) { |
| 333 | + if (!netif_is_up(netif_iter) || netif_iter->ip_addr.u_addr.ip4.addr == 0) continue; |
| 334 | + const bool isPreferred = (netif_iter->name[0] == targetName[0] && |
| 335 | + netif_iter->name[1] == targetName[1]); |
| 336 | + if (isPreferred) { |
| 337 | + target = netif_iter; |
| 338 | + break; |
| 339 | + } |
| 340 | + if (!fallback) fallback = netif_iter; |
| 341 | + } |
| 342 | + |
| 343 | + // AI: if preferred interface unavailable, fall back to any ready interface |
| 344 | + // prevents outbound traffic being pinned to a dead default netif |
| 345 | + if (!target && fallback) { |
| 346 | + target = fallback; |
| 347 | + DEBUG_PRINTLN(F("setPNI: Preferred interface unavailable, using fallback")); |
| 348 | + } |
| 349 | + |
| 350 | + if (target != nullptr) { |
| 351 | + netif_set_default(target); |
| 352 | + DEBUG_PRINTF_P(PSTR("setPNI: Primary netif set to %c%c%d (%d.%d.%d.%d)\n"), |
| 353 | + target->name[0], target->name[1], target->num, |
| 354 | + ip4_addr1(&target->ip_addr.u_addr.ip4), |
| 355 | + ip4_addr2(&target->ip_addr.u_addr.ip4), |
| 356 | + ip4_addr3(&target->ip_addr.u_addr.ip4), |
| 357 | + ip4_addr4(&target->ip_addr.u_addr.ip4)); |
| 358 | + } else { |
| 359 | + DEBUG_PRINTLN(F("setPNI: No ready interface found, will retry on next IP event")); |
| 360 | + } |
| 361 | + |
| 362 | + UNLOCK_TCPIP_CORE(); |
| 363 | +} |
| 364 | +#endif |
| 365 | +// AI: end |
289 | 366 |
|
290 | 367 | //by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp |
291 | 368 | int getSignalQuality(int rssi) |
@@ -383,6 +460,7 @@ bool isWiFiConfigured() { |
383 | 460 | #define ARDUINO_EVENT_WIFI_SCAN_DONE SYSTEM_EVENT_SCAN_DONE |
384 | 461 | #define ARDUINO_EVENT_ETH_START SYSTEM_EVENT_ETH_START |
385 | 462 | #define ARDUINO_EVENT_ETH_CONNECTED SYSTEM_EVENT_ETH_CONNECTED |
| 463 | + #define ARDUINO_EVENT_ETH_GOT_IP SYSTEM_EVENT_ETH_GOT_IP // AI: added for DHCP ethernet IP assignment event |
386 | 464 | #define ARDUINO_EVENT_ETH_DISCONNECTED SYSTEM_EVENT_ETH_DISCONNECTED |
387 | 465 | #endif |
388 | 466 |
|
@@ -431,6 +509,11 @@ void WiFiEvent(WiFiEvent_t event) |
431 | 509 | break; |
432 | 510 | case ARDUINO_EVENT_WIFI_STA_GOT_IP: |
433 | 511 | DEBUG_PRINT(F("WiFi-E: IP address: ")); DEBUG_PRINTLN(Network.localIP()); |
| 512 | + // AI: re-evaluate primary network interface when WiFi gets its IP |
| 513 | + // handles both static IP and DHCP scenarios for WiFi interface |
| 514 | + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) |
| 515 | + setPrimaryNetworkInterface(); |
| 516 | + #endif |
434 | 517 | break; |
435 | 518 | case ARDUINO_EVENT_WIFI_STA_CONNECTED: |
436 | 519 | // followed by IDLE and SCAN_DONE |
@@ -465,17 +548,30 @@ void WiFiEvent(WiFiEvent_t event) |
465 | 548 | case ARDUINO_EVENT_ETH_CONNECTED: |
466 | 549 | { |
467 | 550 | DEBUG_PRINTLN(F("ETH-E: Connected")); |
468 | | - if (!apActive) { |
469 | | - WiFi.disconnect(true); // disable WiFi entirely |
470 | | - } |
471 | | - char hostname[64] = {'\0'}; // any "hostname" within a Fully Qualified Domain Name (FQDN) must not exceed 63 characters |
472 | | - getWLEDhostname(hostname, sizeof(hostname), true); // create DNS name based on mDNS name if set, or fall back to standard WLED server name |
| 551 | + // AI: WiFi is intentionally kept active when ethernet connects. |
| 552 | + // Previously WiFi was disabled here to prevent routing conflicts, but |
| 553 | + // with dual-interface support, netif_set_default() handles routing |
| 554 | + // preference between interfaces. Disabling WiFi here would defeat the |
| 555 | + // purpose of the feature entirely. |
| 556 | + char hostname[64] = {'\0'}; |
| 557 | + getWLEDhostname(hostname, sizeof(hostname), true); |
473 | 558 | ETH.setHostname(hostname); |
| 559 | + // AI: attempt to set default gateway interface on ethernet connect |
| 560 | + setPrimaryNetworkInterface(); |
474 | 561 | showWelcomePage = false; |
475 | 562 | break; |
476 | 563 | } |
| 564 | + case ARDUINO_EVENT_ETH_GOT_IP: |
| 565 | + // AI: ethernet DHCP IP assigned — now safe to set default netif |
| 566 | + // this event is the reliable trigger for DHCP ethernet configuration |
| 567 | + DEBUG_PRINT(F("ETH-E: Got IP: ")); DEBUG_PRINTLN(ETH.localIP()); |
| 568 | + setPrimaryNetworkInterface(); |
| 569 | + break; |
477 | 570 | case ARDUINO_EVENT_ETH_DISCONNECTED: |
478 | 571 | DEBUG_PRINTLN(F("ETH-E: Disconnected")); |
| 572 | + // AI: re-evaluate primary network interface on ethernet disconnect |
| 573 | + // ensures fallback to WiFi if ethernet was the primary interface |
| 574 | + setPrimaryNetworkInterface(); |
479 | 575 | // This doesn't really affect ethernet per se, |
480 | 576 | // as it's only configured once. Rather, it |
481 | 577 | // may be necessary to reconnect the WiFi when |
|
0 commit comments