The Direct Answer: ARDUINO_EVENT_WIFI_STA_GOT_IP and WiFi.config()

The ARDUINO_EVENT_WIFI_STA_GOT_IP event in the ESP32 Arduino Core is the specific system interrupt that fires when the board's DHCP client successfully leases an IPv4 address from your router. If you are using WiFi.config() to assign a static IP before calling WiFi.begin(), the ESP32 bypasses the DHCP negotiation phase entirely, and the core synthesizes this event immediately upon AP association.

This guide targets the ESP32-WROOM-32 (DevKit V1 30-pin) and ESP32-S3 modules running ESP32 Arduino Core v2.0.x or v3.0.x. Older v1.x cores used SYSTEM_EVENT_STA_GOT_IP; if your code throws an undeclared identifier error for the ARDUINO_ prefix, you are compiling against an outdated core or an ESP8266 board profile.

Difficulty Rating: Intermediate (Requires understanding of non-blocking event loops and RF power draw)
Time to Complete: 20 minutes

ESP32 WiFi Event & IP Configuration Matrix

Before writing code, you need to understand the exact sequence of events the ESP32 WiFi state machine triggers. The table below maps the critical station (STA) events, their typical sequence, and how WiFi.config() alters the flow.

Event / Function Core ID (v2.x/v3.x) Trigger Condition Timing / DHCP Impact
ARDUINO_EVENT_WIFI_STA_START Event 2 WiFi hardware initialized and STA mode active. Fires ~50ms after WiFi.mode().
ARDUINO_EVENT_WIFI_STA_CONNECTED Event 4 Layer 2 association with AP successful (password accepted). Fires 1-3 seconds after WiFi.begin(). No IP yet.
ARDUINO_EVENT_WIFI_STA_GOT_IP Event 6 DHCP lease secured OR Static IP applied. DHCP: +1-5s after connect. Static: Immediate.
WiFi.config(ip, gw, sn) N/A (Function) Forces static IP, disables DHCP client. Skips DHCP Discover/Offer. Fires GOT_IP instantly on connect.
ARDUINO_EVENT_WIFI_STA_DISCONNECTED Event 5 Beacon timeout, wrong password, or AP dropped client. Requires explicit WiFi.reconnect() in event handler.

Hardware Parts List & GPIO Pin Mapping

WiFi transmission spikes can draw up to 500mA for microseconds. If your power supply cannot handle this transient load, the ESP32 will brownout and reset before the GOT_IP event ever fires. Use a quality USB cable and a 5V/2A power brick.

Bill of Materials

  • MCU: ESP32-WROOM-32 DevKit V1 (30-pin variant, Type-C or Micro-USB)
  • Indicator: 5mm Blue LED (or use the onboard GPIO2 LED)
  • Current Limiting: 330Ω 1/4W Resistor
  • Load (Optional): 5V Relay Module (Opto-isolated, active LOW)
  • Power: 5V 2A USB Power Supply + Data-rated USB Cable

Pin Mapping Table

Component ESP32 GPIO Notes / Constraints
Status LED (Anode) GPIO 2 Also the onboard boot-strapping pin. Must be LOW on boot.
Relay IN (Signal) GPIO 5 Default SPI SS pin. Safe to use as output if SPI is unused.
Relay VCC 5V (VIN) Do not power relays from the 3V3 pin (insufficient current).
Common GND GND Ensure shared ground between ESP32 and relay module.

Complete Compilable Code: Event-Driven DHCP & Static Fallback

The following code uses the modern non-blocking event handler. It attempts DHCP first. If you need a static IP, uncomment the WiFi.config() line in setup(). Notice the explicit error handling and watchdog feeding in the main loop.

#include <WiFi.h>

// --- Pin Definitions ---
const int STATUS_LED = 2; // Built-in LED on most DevKit V1 boards
const int RELAY_PIN = 5;  // External load control

// --- WiFi Credentials ---
const char* ssid = "YOUR_2.4GHZ_SSID";
const char* password = "YOUR_PASSWORD";

// --- Static IP Fallback Configuration ---
IPAddress local_IP(192, 168, 1, 150);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(8, 8, 4, 4);

bool networkReady = false;

// --- Non-Blocking Event Handler ---
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
  switch (event) {
    case ARDUINO_EVENT_WIFI_STA_START:
      Serial.println("[WiFi] STA Hardware Started");
      break;
      
    case ARDUINO_EVENT_WIFI_STA_CONNECTED:
      Serial.println("[WiFi] Layer 2 Connected to AP");
      break;
      
    case ARDUINO_EVENT_WIFI_STA_GOT_IP:
      Serial.print("[WiFi] GOT_IP Event Fired. IP: ");
      Serial.println(WiFi.localIP());
      networkReady = true;
      digitalWrite(STATUS_LED, HIGH);
      break;
      
    case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
      Serial.println("[WiFi] Disconnected. Attempting reconnect...");
      networkReady = false;
      digitalWrite(STATUS_LED, LOW);
      // Prevent infinite fast-loop reconnects if AP is down
      delay(1000); 
      WiFi.reconnect();
      break;
      
    default:
      break;
  }
}

void setup() {
  Serial.begin(115200);
  delay(500); // Allow serial buffer to initialize
  
  pinMode(STATUS_LED, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(STATUS_LED, LOW);
  digitalWrite(RELAY_PIN, HIGH); // Active LOW relay default

  // Register Event Handler BEFORE starting WiFi
  WiFi.onEvent(WiFiEvent);
  WiFi.mode(WIFI_STA);

  // UNCOMMENT THE LINE BELOW TO FORCE STATIC IP (Bypasses DHCP)
  // WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS);

  Serial.print("[WiFi] Connecting to SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
}

void loop() {
  if (networkReady && WiFi.status() == WL_CONNECTED) {
    // Safe to execute MQTT, HTTP, or toggle relays here
    // Example: digitalWrite(RELAY_PIN, LOW);
  }
  
  // Feed the RTOS watchdog timer to prevent reboot during long tasks
  delay(10); 
}

Troubleshooting: When ARDUINO_EVENT_WIFI_STA_GOT_IP Never Fires

If your serial monitor shows Layer 2 Connected to AP but the GOT_IP event never triggers, your ESP32 is stuck in a DHCP timeout loop. Here are the first three things to check, ranked by probability:

  1. Router 2.4GHz Band Steering & Isolation: The ESP32 is strictly a 2.4GHz 802.11 b/g/n device. If your router uses a unified SSID for 2.4GHz and 5GHz with aggressive band steering or AP isolation enabled, the DHCP handshake will fail. Fix: Create a dedicated 2.4GHz IoT SSID on your router with AP isolation disabled.
  2. DHCP Pool Exhaustion: Home routers typically lease IPs for 24-48 hours. If you have been flashing and resetting the ESP32 repeatedly, the router may see each new MAC address (if using randomized MACs) or simply run out of pool space (e.g., pool is 192.168.1.100 to .110). Fix: Reboot the router to clear the DHCP lease table, or use WiFi.config() to assign a static IP outside the DHCP pool.
  3. RF Brownout on DHCP Discover: The DHCP Discover packet requires a high-power RF transmission. If your USB cable has high resistance or your power supply sags below 4.6V, the ESP32 will brownout and silently reset the WiFi peripheral. Fix: Measure the 5V and 3V3 pins with a multimeter during connection. Add a 100µF electrolytic capacitor across the 5V and GND pins on the DevKit.
Common Error Strings in Serial Monitor:
  • WiFi.status() == WL_NO_SSID_AVAIL: The ESP32 cannot hear the router's beacons. Check SSID spelling, case sensitivity, and physical distance.
  • E (xxxx) wifi:sta is connecting, return error: You called WiFi.begin() while a previous connection attempt was still pending. Always check WiFi.status() != WL_CONNECTED before retrying.
  • dhcp timeout: Layer 2 is up, but the router is ignoring DHCP requests. Check for MAC address filtering on the router.

Extending and Simplifying the Build

Depending on your project phase, you may need to scale this architecture up for production or strip it down for a quick bench test.

How to Extend for Production

  • Add OTA Updates: Once ARDUINO_EVENT_WIFI_STA_GOT_IP fires, initialize ArduinoOTA.begin(). This allows you to push new firmware over the network without hunting for a USB cable.
  • Implement MQTT: Do not initialize your MQTT client in setup(). Instead, trigger your MQTT client.connect() routine from inside the GOT_IP case in the event handler. This guarantees the network stack is fully ready before opening sockets.
  • Persistent Credentials: Move hardcoded SSIDs to Preferences.h (NVS storage) or use a library like WiFiManager to allow captive-portal provisioning via a smartphone.

How to Simplify for Quick Bench Tests

If you are just testing a sensor and don't care about non-blocking execution or watchdog resets, you can strip out the event handler entirely and use a blocking while loop. While not recommended for deployed IoT devices because it starves the RTOS background tasks, it reduces code volume for quick prototypes:

// Simplified Blocking Approach (Bench Testing Only)
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.println("\nIP Assigned: " + WiFi.localIP().toString());

For deeper architectural details on the ESP32 WiFi state machine, refer to the official Espressif ESP-IDF WiFi API Documentation. For community-driven edge cases and core updates, monitor the ESP32 Arduino Core GitHub Repository. Always verify your core version in the Arduino IDE Boards Manager, as event enumerations shifted significantly between v1.0.x and v2.0.x.