The Direct Fix for ARDUINO_EVENT_WIFI_STA_GOT_IP Failures
When building IoT devices on the ESP32, the ARDUINO_EVENT_WIFI_STA_GOT_IP event is the definitive signal that your board has successfully associated with an access point and secured a DHCP lease. If this event never fires, your board is stuck in the authentication or DHCP discovery phase, and any network-dependent code (MQTT, HTTP, NTP) will hang or crash.
This guide targets the Espressif ESP32-S3-DevKitC-1 (N8R2) running ESP32 Arduino Core v3.0.x or v3.1.x. In the modern v3.x core, the legacy SYSTEM_EVENT_* macros were deprecated in favor of the ARDUINO_EVENT_* enum. If you are copying older code, this namespace shift is your first point of failure.
- 2.4 GHz Band Steering: Modern WiFi 6/7 routers often use a single SSID for 2.4 GHz and 5 GHz. The ESP32 is strictly 2.4 GHz (802.11 b/g/n). If your router's band steering is aggressive, it may reject the ESP32's association request. Create a dedicated 2.4 GHz IoT SSID.
- DHCP Pool Exhaustion: Home routers typically lease IPs for 24 hours and cap the pool at 50-100 devices. If your pool is full, the ESP32 will associate (
CONNECTED) but time out waiting for an IP offer. - MAC Address Randomization: While mostly a mobile OS feature, ensure your router doesn't have strict MAC filtering or 'Unknown Device Blocking' enabled, which silently drops DHCP discovery packets from new ESP32 MAC addresses.
ESP32 WiFi Event Sequence and DHCP Timing Data
To debug a missing IP event, you must understand the chronological sequence the ESP32 WiFi driver executes. The table below maps the exact event macros to their typical timing on a clean network with strong RSSI (-40 dBm). Use this to identify exactly where your connection is stalling.
| Event ID (Enum) | Macro Name | Typical Time (ms) | Description & Debugging Note |
|---|---|---|---|
| 0 | ARDUINO_EVENT_WIFI_READY |
~5 ms | WiFi driver initialized. If you don't see this, your core installation is corrupted. |
| 2 | ARDUINO_EVENT_WIFI_STA_START |
~15 ms | Station mode started. The radio is now scanning for the target SSID. |
| 4 | ARDUINO_EVENT_WIFI_STA_CONNECTED |
1200 - 2500 ms | Layer 2 association complete. 4-way handshake passed. DHCP Discover is sent here. |
| 6 | ARDUINO_EVENT_WIFI_STA_GOT_IP |
2800 - 4500 ms | DHCP Offer received and acknowledged. Layer 3 is up. Safe to open sockets. |
| 5 | ARDUINO_EVENT_WIFI_STA_DISCONNECTED |
Varies | Connection lost or auth failed. Check the wifi_err_reason_t payload. |
For a deeper look at the underlying ESP-IDF event loop that the Arduino core wraps, refer to the Espressif WiFi Driver API documentation. Understanding that the Arduino core is just a C++ wrapper around these native C events will save you hours of debugging.
Hardware Parts List and Status Pin Mapping
While the WiFi radio is internal, a robust embedded build requires physical feedback mechanisms to diagnose network states without relying solely on the serial monitor. Below is the exact bill of materials and pin mapping for a reliable diagnostic node.
Parts List
- MCU: Espressif ESP32-S3-DevKitC-1 (N8R2 variant - 8MB Flash, 2MB PSRAM)
- Indicator LED: 5mm Green Diffused LED (Vf = 2.2V, If = 20mA)
- Current Limiting Resistor: 220Ω 1/4W (Yields ~12mA, safe for GPIO limits)
- Tactile Switch: 6x6mm momentary pushbutton (for manual WiFi reset)
- Power: 5V 2A USB-C supply (WiFi TX spikes can draw 350mA; standard 500mA PC USB ports often cause brownouts during DHCP negotiation).
Pin Mapping Table
| Component | ESP32-S3 Pin | Direction | Implementation Notes |
|---|---|---|---|
| Status LED (Anode) | GPIO 2 | OUTPUT | Active HIGH. Connect cathode to GND via 220Ω resistor. |
| Reset Button | GPIO 0 | INPUT_PULLUP | Active LOW. Ties to GND when pressed. Internal pull-up enabled. |
Complete Event-Driven DHCP Code with Error Handling
The following code uses the non-blocking WiFi.onEvent() architecture. It explicitly handles the ARDUINO_EVENT_WIFI_STA_GOT_IP event to trigger application logic, and includes robust error handling for disconnects. Copy this directly into your Arduino IDE (ensure board package is esp32 by Espressif Systems v3.0.0 or higher).
#include <WiFi.h>
// --- Pin Definitions ---
#define STATUS_LED_PIN 2
#define RESET_BTN_PIN 0
// --- Network Credentials ---
const char* ssid = "YourNetwork_2.4GHz";
const char* password = "YourSecurePassword";
// --- State Variables ---
bool ipAcquired = false;
unsigned long lastReconnectAttempt = 0;
const unsigned long RECONNECT_INTERVAL = 5000; // 5 seconds
// --- WiFi Event Callback ---
void wifiEventCallback(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_START:
Serial.println("[WiFi] Station started, attempting connection...");
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
Serial.println("[WiFi] Associated with AP. Waiting for DHCP...");
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
Serial.printf("[WiFi] DHCP Success! IP: %s\n", WiFi.localIP().toString().c_str());
digitalWrite(STATUS_LED_PIN, HIGH); // Solid ON = Connected
ipAcquired = true;
// TODO: Initialize MQTT, NTP, or HTTP clients here.
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
digitalWrite(STATUS_LED_PIN, LOW);
ipAcquired = false;
// Extract exact disconnect reason
uint8_t reason = info.wifi_sta_disconnected.reason;
Serial.printf("[WiFi] Disconnected. Reason Code: %d\n", reason);
// Prevent rapid reconnect loops on auth failures
if (reason == WIFI_REASON_AUTH_FAIL || reason == WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT) {
Serial.println("[WiFi] Auth failed. Check password or router MAC filtering.");
delay(2000); // Brief pause before retrying bad credentials
}
// Trigger reconnect
WiFi.reconnect();
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(STATUS_LED_PIN, OUTPUT);
pinMode(RESET_BTN_PIN, INPUT_PULLUP);
// Register the event callback BEFORE starting WiFi
WiFi.onEvent(wifiEventCallback);
// Configure WiFi behavior
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(false); // We handle reconnects manually in the event loop
WiFi.setHostname("ESP32S3-SensorNode");
Serial.println("[Setup] Initializing WiFi...");
WiFi.begin(ssid, password);
}
void loop() {
// 1. Handle manual reset button
if (digitalRead(RESET_BTN_PIN) == LOW) {
Serial.println("[Button] Reset pressed. Restarting WiFi...");
WiFi.disconnect(true);
delay(500);
WiFi.begin(ssid, password);
while(digitalRead(RESET_BTN_PIN) == LOW) { delay(50); } // Debounce
}
// 2. Application logic (only runs if IP is acquired)
if (ipAcquired) {
// Run sensors, publish MQTT, etc.
}
delay(10); // Yield to RTOS WiFi task
}
Ranked Causes: When the Event Never Fires
If your serial monitor shows ARDUINO_EVENT_WIFI_STA_CONNECTED but never reaches ARDUINO_EVENT_WIFI_STA_GOT_IP, the failure is happening at Layer 3 (DHCP). If it fails before CONNECTED, it's a Layer 2 (Authentication/Association) issue. Here are the ranked causes based on bench testing.
1. DHCP Server Ignoring Discover Packets (Most Common)
Symptom: Stalls at CONNECTED for 10+ seconds, then throws ARDUINO_EVENT_WIFI_STA_DISCONNECTED with reason WIFI_REASON_ASSOC_LEAVE (8) or times out silently.
Fix: Log into your router. Check the DHCP lease table. If it's full, increase the pool size or reduce the lease time to 2 hours. Alternatively, assign a Static IP in your ESP32 code using WiFi.config(local_ip, gateway, subnet) before calling WiFi.begin().
2. 4-Way Handshake Timeout (Reason 15)
Symptom: Exact error string in serial: Disconnected. Reason Code: 15 (WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT).
Fix: This almost always means the WPA2 password is incorrect, or you are trying to connect to a WPA3-Only network. The ESP32 supports WPA2, and while newer cores support WPA3, many routers require 'WPA2/WPA3 Transitional' mode. Change your router security setting to WPA2-Personal (AES).
3. No AP Found (Reason 201)
Symptom: Exact error string: Disconnected. Reason Code: 201 (WIFI_REASON_NO_AP_FOUND).
Fix: The SSID string in your code has a typo, or the router is broadcasting on 5 GHz only. Use a smartphone WiFi analyzer app to verify the exact SSID spelling and confirm the 2.4 GHz radio is active.
4. Power Supply Brownout During TX
Symptom: The ESP32 reboots randomly right after CONNECTED. The serial monitor shows rst:0xc (SW_CPU_RESET) or similar.
Fix: The DHCP request requires a burst of RF transmission. If your USB cable is high-resistance or your supply is limited to 500mA, the voltage drops below the 3.3V LDO threshold, resetting the chip. Use a thick, short USB-C cable and a 5V 2A+ power brick.
Extending and Simplifying the Build
Depending on your project constraints, you may need to alter the WiFi architecture.
How to Simplify (Blocking Mode)
If you are building a simple data logger that doesn't need to multitask while connecting, you can strip out the event callback entirely and use a blocking loop. This reduces code complexity but freezes the main thread until DHCP resolves.
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(WiFi.localIP());
}
How to Extend (Static IP Fallback)
For industrial or remote deployments where a DHCP server might be offline, extend the ARDUINO_EVENT_WIFI_STA_DISCONNECTED handler. Track the number of DHCP failures; if it exceeds 3 attempts, tear down the WiFi driver, re-initialize, and apply a hardcoded static IP using WiFi.config(). This ensures the node remains reachable on the local subnet for OTA updates even if the router's DHCP service crashes.
For more advanced network topologies, including mesh networking and ESP-NOW protocols that bypass the DHCP requirement entirely, consult the Arduino ESP32 WiFi Library repository for the latest example sketches and API changes in the v3.x core branches.






