The Short Answer: Migrating to ESP-NETIF in Arduino ESP32

If you are searching for arduino esp-netif, you are likely hitting compilation errors after upgrading your ESP32 Board Manager package to Core v2.x or v3.x. The direct answer is that Espressif completely deprecated the legacy tcpip_adapter API in favor of the object-oriented esp-netif layer starting in ESP-IDF v4.1, which the Arduino core subsequently adopted.

Any code or third-party library still calling tcpip_adapter_init() or including tcpip_adapter.h will fail to compile on modern toolchains. To fix this, you must strip out legacy TCP/IP calls and rely either on the high-level Arduino WiFi.h abstraction (which handles the esp-netif initialization under the hood) or use the native esp_netif_get_handle_from_ifkey() functions for advanced LwIP manipulation.

Board Variant Targeted: The code and pinouts in this guide are written specifically for the ESP32-WROOM-32 DevKit V1 (30-pin or 38-pin variants). The underlying ESP-NETIF logic also applies directly to ESP32-S3 and ESP32-C3 modules running Arduino Core v2.0.14 or newer.

Hardware Spec Sheet & Pin Mapping

Before flashing network code, ensure your physical layer is stable. Network panics are frequently misdiagnosed as software bugs when they are actually brownouts caused by poor USB cables or missing decoupling capacitors on the breadboard.

ComponentExact Model / VariantNotes & Tolerances
MicrocontrollerESP32-WROOM-32 DevKit V1Must have 4MB+ Flash. Avoid ESP32-PICO for breadboarding.
Power Supply5V 2A USB-C/Micro-USB CableMust be pure copper (22 AWG). Data+Power lines required.
Status LED5mm Red LED + 330Ω ResistorWired to GPIO2 (native boot-strapping safe if pulled low at boot).
Sensor (Telemetry)
Analog Input10kΩ PotentiometerWired to GPIO34 (Input only, no internal pull-up available).

Pin Mapping Table

FunctionESP32 GPIODirectionNotes
Onboard/Status LEDGPIO 2OUTPUTActive HIGH. Tied to onboard blue LED on most DevKits.
Telemetry SensorGPIO 34INPUTADC1 Channel 6. Safe for Wi-Fi coexistence (unlike ADC2).
Hardware Serial TXGPIO 1OUTPUTUsed for Serial Monitor debug output.
Hardware Serial RXGPIO 3INPUTUsed for Serial Monitor debug input.

Complete ESP-NETIF Wi-Fi Station Code (ESP32 Core 3.x)

The following code is fully compilable in Arduino IDE 2.x. It demonstrates the correct initialization sequence, avoiding double-initialization panics, and shows how to fetch the underlying esp_netif_t handle to read IP information directly from the LwIP stack.

#include <WiFi.h>
#include <esp_wifi.h>
#include <esp_netif.h>
#include <esp_system.h>

// --- Pin Definitions ---
#define STATUS_LED_PIN 2
#define SENSOR_PIN 34

// --- Network Credentials ---
const char* ssid = "YourNetworkSSID";
const char* password = "YourNetworkPassword";

void setup() {
  Serial.begin(115200);
  delay(500); // Allow serial buffer to flush
  
  pinMode(STATUS_LED_PIN, OUTPUT);
  pinMode(SENSOR_PIN, INPUT);
  digitalWrite(STATUS_LED_PIN, LOW);

  // IMPORTANT: Do NOT call esp_netif_init() manually here.
  // The Arduino WiFi.begin() wrapper handles esp_netif_init() and 
  // esp_event_loop_create_default() internally. Calling it twice causes panics.

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  Serial.print("Connecting to Wi-Fi");
  int retries = 0;
  while (WiFi.status() != WL_CONNECTED && retries < 40) {
    delay(500);
    Serial.print(".");
    retries++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nConnected successfully!");
    digitalWrite(STATUS_LED_PIN, HIGH);

    // Fetch the ESP-NETIF handle for the default Station interface
    esp_netif_t* sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
    if (sta_netif != nullptr) {
      esp_netif_ip_info_t ip_info;
      if (esp_netif_get_ip_info(sta_netif, &ip_info) == ESP_OK) {
        Serial.printf("ESP-NETIF Native IP: " IPSTR "\n", IP2STR(&ip_info.ip));
        Serial.printf("ESP-NETIF Gateway: " IPSTR "\n", IP2STR(&ip_info.gw));
      }
    } else {
      Serial.println("Warning: Could not fetch WIFI_STA_DEF netif handle.");
    }
  } else {
    Serial.println("\nFailed to connect. Rebooting in 3 seconds...");
    delay(3000);
    ESP.restart();
  }
}

void loop() {
  // Basic watchdog for Wi-Fi drops
  if (WiFi.status() != WL_CONNECTED) {
    digitalWrite(STATUS_LED_PIN, LOW);
    Serial.println("[ERROR] Wi-Fi dropped. Attempting reconnect...");
    WiFi.reconnect();
    delay(5000); // Block briefly to prevent spamming the router
  } else {
    // Read telemetry
    int sensorVal = analogRead(SENSOR_PIN);
    Serial.printf("[TELEMETRY] Sensor ADC: %d\n", sensorVal);
    delay(2000);
  }
}

Debugging: Exact Error Strings and Ranked Causes

When working with the ESP-NETIF layer in Arduino, you will encounter two primary failure modes: compilation errors from legacy code, and runtime assertions from initialization sequence violations.

Error 1: fatal error: tcpip_adapter.h: No such file or directory

This is a compile-time error. The compiler cannot find the legacy header because it was removed from the ESP-IDF toolchain bundled with Arduino Core v2.0.0+.

  • Cause 1 (Most Likely): You are using an outdated third-party library (like an old fork of ESPAsyncWebServer or AsyncTCP) that still references tcpip_adapter.h. Fix: Update to a modern fork like mathieucarbou/ESPAsyncWebServer which is patched for ESP-NETIF.
  • Cause 2: You copy-pasted raw ESP-IDF C code from a pre-2021 tutorial into your Arduino sketch. Fix: Replace tcpip_adapter_get_ip_info() with esp_netif_get_ip_info() as shown in the code block above.

Error 2: assert failed: esp_netif_create_default_wifi_sta or ESP_ERR_ESP_NETIF_IF_NOT_READY

This is a runtime Guru Meditation panic or error log. It occurs when the network interface is accessed before the underlying LwIP stack or event loop is fully instantiated.

  • Cause 1 (Most Likely): Double initialization. You manually called esp_netif_init() in setup(), and then called WiFi.begin(), which tries to initialize it again. Fix: Remove manual esp_netif_init() calls when using the Arduino WiFi.h wrapper.
  • Cause 2: Calling esp_netif_get_handle_from_ifkey("WIFI_STA_DEF") before WiFi.begin() has completed its connection handshake. Fix: Only query the netif handle after verifying WiFi.status() == WL_CONNECTED.
  • Cause 3: Missing default event loop. Fix: Ensure esp_event_loop_create_default() is called if you are bypassing WiFi.h and using raw esp_wifi_init().
The First Three Things to Check When ESP-NETIF Fails:
1. Board Manager Version: Verify you are on ESP32 Core v2.0.14 or v3.0.x. Do not mix v1.0.x libraries with v3.x cores.
2. Initialization Order: Ensure WiFi.mode() and WiFi.begin() execute before any native esp_netif_* API calls.
3. Library Dependencies: Check the verbose compile output to identify which specific library is pulling in the deprecated tcpip_adapter.h header.

Extending and Simplifying Your Network Build

Depending on your project requirements, you can either strip away the ESP-NETIF complexity or leverage it for advanced networking topologies.

How to Simplify:
If you only need to connect to a router and send HTTP/MQTT payloads, do not use native ESP-NETIF functions. Rely entirely on the Arduino WiFi.h and WiFi.localIP() abstractions. The Arduino wrapper handles the LwIP thread priorities, event loops, and interface creation. Adding native esp_netif calls only increases the risk of pointer faults and watchdog timeouts without providing tangible benefits for simple IoT nodes.

How to Extend:
If you are building a multi-homed device (e.g., an ESP32 acting as a bridge between Wi-Fi and Ethernet, or managing custom VLANs), the ESP-NETIF layer is mandatory. You can extend the build by:

  1. Adding Ethernet: Use esp_netif_create_default_eth_handle() to bind an ENC28J60 or W5500 SPI Ethernet module to a secondary network interface key (e.g., "ETH_DEF").
  2. Binding Static IPs: Use esp_netif_dhcpc_stop(sta_netif) followed by esp_netif_set_ip_info(sta_netif, &custom_ip) to hardcode network parameters at the LwIP level, bypassing the slower Arduino WiFi.config() wrapper.
  3. Custom DNS Routing: Hook into the IP_EVENT_STA_GOT_IP event via the ESP event loop to trigger MQTT connections the exact millisecond the DHCP lease is secured, eliminating arbitrary delay() polling.

Frequently Asked Questions

Why did Espressif replace tcpip_adapter with esp-netif in Arduino?

The legacy tcpip_adapter was a monolithic, tightly coupled API that made it nearly impossible to support multiple network interfaces simultaneously (like Wi-Fi STA + Ethernet + SoftAP). Espressif introduced ESP-NETIF to create an object-oriented abstraction layer. It allows developers to attach different network drivers (Wi-Fi, SPI Ethernet, USB RNDIS) to independent LwIP network interfaces without the underlying TCP/IP stack colliding. The Arduino core adopted this to support newer SoCs like the ESP32-S3 and ESP32-C6.

Can I use esp-netif to bind a static IP on the ESP32-S3?

Yes. While you can use the standard Arduino WiFi.config(local_ip, gateway, subnet) function, using the native ESP-NETIF API is more robust for ESP32-S3 projects that require immediate static binding before the DHCP client even attempts a request. You fetch the WIFI_STA_DEF handle, stop the DHCP client using esp_netif_dhcpc_stop(), and inject the esp_netif_ip_info_t struct directly. This prevents the 2-3 second delay often seen when the Arduino wrapper waits for DHCP to time out before falling back to static.

How do I fix the "esp_netif_handlers: sta ip" log spam in Serial Monitor?

The log line I (xxx) esp_netif_handlers: sta ip: 192.168.1.x, mask: 255.255.255.0, gw: 192.168.1.1 is not an error; it is an Info-level log generated by the default ESP-NETIF event handler when a DHCP lease is acquired. If you want to suppress this to keep your Serial Monitor clean for sensor data, you can adjust the log verbosity in the Arduino IDE. Go to Tools > Core Debug Level and change it from "Info" or "Debug" to "Warn" or "Error". Alternatively, add esp_log_level_set("esp_netif_handlers", ESP_LOG_WARN); at the start of your setup() function.

Does esp-netif work with the standard Arduino Ethernet library?

No, not natively. The standard Arduino Ethernet.h library (designed for AVR and W5100/W5500 shields) uses its own socket abstraction and does not integrate with the ESP-IDF's LwIP stack or the ESP-NETIF layer. If you want to use an SPI Ethernet module (like the W5500) with ESP-NETIF on an ESP32, you must use Espressif's native esp_eth drivers or a specialized wrapper library like ETH.h that bridges the ESP-IDF Ethernet MAC/PHY layers to the Arduino networking APIs.