Chapter 28: WiFi Station Connection Flow
Chapter Objectives
By the end of this chapter, you will be able to:
- Describe the sequence of states an ESP32 goes through when connecting as a WiFi station.
- Identify the specific ESP-IDF events associated with each major state transition during connection.
- Understand the information provided within the data payload of key WiFi and IP events.
- Interpret common disconnection reason codes.
- Implement more detailed event handling logic based on the connection state.
- Appreciate the asynchronous nature of the connection process and the role of the event loop.
Introduction
In the previous chapter, we successfully connected an ESP32 to a WiFi network using the basic Station mode setup. We saw that initializing the network stack, configuring credentials, and starting the WiFi driver were prerequisites. We also introduced the concept of event handlers to react to outcomes like obtaining an IP address or getting disconnected.
However, for building robust, real-world applications, a deeper understanding of the entire connection flow is essential. What exactly happens between calling esp_wifi_connect()
and receiving the IP_EVENT_STA_GOT_IP
event? What intermediate steps occur? What specific information can we glean at each stage? This chapter dissects the WiFi station connection process, mapping the internal state transitions to the events generated by the ESP-IDF esp_event
system. Understanding this flow allows for more precise control, better error handling, and more informative diagnostics in your applications.
Theory
The Asynchronous Connection Journey
Connecting to a WiFi network isn’t an instantaneous action. It’s a multi-step, asynchronous process involving communication between the ESP32 (Station) and the Access Point (AP). The ESP-IDF WiFi driver manages this process internally and uses the event loop system (esp_event
) to notify the application about significant milestones or changes in status.
Let’s break down the typical sequence after esp_wifi_init()
and esp_netif_create_default_wifi_sta()
have been called:
- Driver Start (
esp_wifi_start()
):- Action: This function enables the WiFi driver and associated hardware/software resources according to the mode previously set (e.g.,
WIFI_MODE_STA
viaesp_wifi_set_mode
). The configuration applied viaesp_wifi_set_config
is loaded. - State: The station interface transitions from Idle to Started.
- Event:
WIFI_EVENT_STA_START
is posted to the event loop. This signals that the station is ready and it’s now appropriate to initiate a connection attempt.
- Action: This function enables the WiFi driver and associated hardware/software resources according to the mode previously set (e.g.,
- Connection Initiation (
esp_wifi_connect()
):- Action: Typically called within the handler for
WIFI_EVENT_STA_START
. This function tells the WiFi driver to actively attempt to connect to the AP specified in the configuration (SSID, password, etc.). - State: The station transitions from Started to Connecting. This involves several sub-steps internally:
- Scanning (Optional but common): The station may briefly scan to find the target AP based on its SSID.
- Authentication: The station exchanges authentication frames with the AP. For WPA2/WPA3-Personal, this involves verifying credentials (implicitly via the handshake later). For open networks, this step is minimal.
- Association: If authentication is successful (or not required for open networks), the station sends an association request to the AP. The AP responds with an association response, granting (or denying) access to the network at the MAC layer.
- Event: If association is successful,
WIFI_EVENT_STA_CONNECTED
is posted.
- Action: Typically called within the handler for
- Connected (MAC Layer):
- State: The station is now Connected to the AP at the 802.11 MAC layer. It can exchange MAC-level frames with the AP but does not yet have an IP address and cannot communicate using TCP/IP protocols.
- Event Data (
WIFI_EVENT_STA_CONNECTED
): Theevent_data
for this event is of typewifi_event_sta_connected_t
. It contains information like:ssid
: The SSID of the connected AP.ssid_len
: Length of the SSID.bssid
: The MAC address (BSSID) of the AP the station connected to.channel
: The channel used for the connection.authmode
: The authentication mode used.
Event Data for: WIFI_EVENT_STA_CONNECTED (Type: wifi_event_sta_connected_t* ) |
||
---|---|---|
Field Name | Data Type | Description |
ssid |
uint8_t[32] |
The SSID (network name) of the Access Point the ESP32 has connected to. This is not null-terminated if the SSID is 32 bytes long. Use ssid_len . |
ssid_len |
uint8_t |
The length of the SSID in bytes. |
bssid |
uint8_t[6] |
The MAC address (Basic Service Set Identifier) of the Access Point. Useful for identifying a specific AP if multiple have the same SSID. |
channel |
uint8_t |
The WiFi channel number (1-14 for 2.4GHz) on which the connection was established. |
authmode |
wifi_auth_mode_t |
The authentication mode used for the connection (e.g., WIFI_AUTH_OPEN , WIFI_AUTH_WPA2_PSK ). |
- Obtaining IP Address (DHCP):
- Action: Upon successful association (
WIFI_EVENT_STA_CONNECTED
), theesp_netif
layer (interfacing with the LwIP TCP/IP stack) automatically initiates the process to obtain an IP address. Typically, this involves sending DHCP (Dynamic Host Configuration Protocol) discovery messages to find a DHCP server on the network (usually running on the AP/router). The DHCP server responds with an IP address lease, subnet mask, gateway address, and DNS server information. - State: The station remains Connected but is actively performing DHCP negotiation.
- Event: Once the DHCP negotiation succeeds and an IP address is assigned,
IP_EVENT_STA_GOT_IP
is posted to the event loop. This event originates from theIP_EVENT
base, notWIFI_EVENT
.
- Action: Upon successful association (
- Connected (IP Layer):
- State: The station is now fully Network Ready. It has an IP address and can engage in standard network communication (TCP, UDP, HTTP, MQTT, etc.) with devices on the local network or the internet (via the gateway provided by DHCP).
- Event Data (
IP_EVENT_STA_GOT_IP
): Theevent_data
for this event is of typeip_event_got_ip_t
. It contains:if_index
: The index of the network interface that got the IP.ip_info
: A structure (esp_netif_ip_info_t
) containing the assignedip
address,netmask
, andgw
(gateway) address.ip_changed
: A boolean flag indicating if the obtained IP address is different from a previously held one (relevant for lease renewals or static IP changes).
Event Data for: IP_EVENT_STA_GOT_IP (Type: ip_event_got_ip_t* ) |
||
---|---|---|
Field Name | Data Type | Description |
if_index |
int32_t |
The index of the network interface that received the IP address. For the default STA interface, this is typically consistent. |
ip_info |
esp_netif_ip_info_t |
A structure containing the IP address information. See sub-fields below. |
ip_info.ip |
esp_ip4_addr_t |
The IPv4 address assigned to the ESP32 station. Use IP2STR() to format for printing. |
ip_info.netmask |
esp_ip4_addr_t |
The subnet mask associated with the assigned IP address. |
ip_info.gw |
esp_ip4_addr_t |
The gateway (router) IP address for the network. |
ip_changed |
bool |
Indicates if the obtained IP address is different from a previously held IP address on this interface (e.g., due to DHCP lease renewal with a new IP). true if changed, false otherwise. |
- Disconnection:
- Action: The connection can be lost for various reasons: the AP goes down, the signal is lost, authentication fails after roaming, the station explicitly calls
esp_wifi_disconnect()
, etc. - State: The station transitions to the Disconnected state. The associated IP address is typically lost.
- Event:
WIFI_EVENT_STA_DISCONNECTED
is posted. - Event Data (
WIFI_EVENT_STA_DISCONNECTED
): Theevent_data
is of typewifi_event_sta_disconnected_t
. It contains:ssid
: SSID of the network it was connected to.ssid_len
: Length of the SSID.bssid
: BSSID of the AP it was connected to.reason
: A crucial field of typewifi_err_reason_t
indicating why the disconnection occurred.
- Action: The connection can be lost for various reasons: the AP goes down, the signal is lost, authentication fails after roaming, the station explicitly calls
Event Data for: WIFI_EVENT_STA_DISCONNECTED (Type: wifi_event_sta_disconnected_t* ) |
||
---|---|---|
Field Name | Data Type | Description |
ssid |
uint8_t[32] |
The SSID of the Access Point from which the ESP32 disconnected. May not be valid if the station was not fully connected. |
ssid_len |
uint8_t |
The length of the SSID. |
bssid |
uint8_t[6] |
The MAC address (BSSID) of the AP from which disconnection occurred. |
reason |
wifi_err_reason_t (uint8_t) |
Crucial field: Provides the reason code for the disconnection. This helps determine if the issue is temporary (e.g., signal loss) or persistent (e.g., wrong password). Refer to esp_wifi_types.h for all possible reason codes. |
%%{ init: { 'flowchart': { 'curve': 'basis' } } }%% graph TD %% Node Styles classDef initialState fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef state fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef action fill:#FEF9C3,stroke:#D97706,stroke-width:1px,color:#92400E; %% Yellowish for actions/API calls classDef event fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; %% Reddish for events classDef successState fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef process fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3; %% Light indigo for internal processes Idle:::initialState -- "Call <b>esp_wifi_set_mode(STA)</b><br><b>esp_wifi_set_config()</b>" --> Configured:::state; Configured -- "Call <b>esp_wifi_start()</b>" --> S0[Driver Starting]; S0 -- "<i>WIFI_EVENT_STA_START</i>" --> Started:::state; Started -- "Handler calls <b>esp_wifi_connect()</b>" --> Connecting:::process; Connecting -- "Authentication &<br>Association with AP" --> S1[Auth/Assoc OK]; S1 -- "<i>WIFI_EVENT_STA_CONNECTED</i>" --> ConnectedMAC["<b>Connected (MAC Layer)</b><br>(SSID, BSSID, Channel info available)"]:::state; ConnectedMAC -- "DHCP Client Starts" --> ObtainingIP["Obtaining IP Address<br>(DHCP Discover, Offer, Request, Ack)"]:::process; ObtainingIP -- "<i>IP_EVENT_STA_GOT_IP</i>" --> NetworkReady["<b>Network Ready (IP Layer)</b><br>(IP, Mask, Gateway info available)"]:::successState; NetworkReady -- "Connection Lost<br>(e.g. Beacon Timeout, AP Down)" --> S2[Disconnected Event]; S2 -- "<i>WIFI_EVENT_STA_DISCONNECTED</i><br>(Reason Code available)" --> Disconnected:::state; NetworkReady -- "Call <b>esp_wifi_disconnect()</b>" --> S3[Explicit Disconnect]; S3 -- "<i>WIFI_EVENT_STA_DISCONNECTED</i><br>(Reason: AUTH_LEAVE or similar)" --> Disconnected; Disconnected -- "Retry Logic in Handler<br>calls <b>esp_wifi_connect()</b>" --> Connecting; Connecting -- "Auth/Assoc Fails<br>(e.g. Wrong Password, AP Full)" --> S4[Connection Failure Event]; S4 -- "<i>WIFI_EVENT_STA_DISCONNECTED</i><br>(Reason: AUTH_FAIL, ASSOC_FAIL, etc.)" --> Disconnected; %% Styling class Idle initialState; class Configured state; class Started state; class ConnectedMAC state; class Disconnected state; class S0 state; class Connecting state; class ObtainingIP process; class S1 event; class S2 event; class S3 event; class S4 event; class NetworkReady successState;
Key WiFi Event Data Snippets are:
Understanding Disconnection Reasons
The reason
code provided in the WIFI_EVENT_STA_DISCONNECTED
event is vital for diagnostics and implementing robust reconnection logic. Some common reasons found in esp_wifi_types.h
(wifi_err_reason_t
enum) include:
WIFI_REASON_UNSPECIFIED
: Generic reason.WIFI_REASON_AUTH_EXPIRE
: Authentication timed out or expired.WIFI_REASON_AUTH_LEAVE
: Station explicitly left (e.g.,esp_wifi_disconnect
called).WIFI_REASON_ASSOC_EXPIRE
: Association timed out.WIFI_REASON_ASSOC_TOOMANY
: AP cannot accept more stations.WIFI_REASON_NOT_AUTHED
: Station is not authenticated.WIFI_REASON_NOT_ASSOCED
: Station is not associated.WIFI_REASON_ASSOC_LEAVE
: Station explicitly left association.WIFI_REASON_ASSOC_NOT_AUTHED
: Association request rejected because station is not authenticated.WIFI_REASON_DISASSOC_PWRCAP_BAD
: Disassociated due to power capability mismatch.WIFI_REASON_DISASSOC_SUPCHAN_BAD
: Disassociated due to unsupported channels.WIFI_REASON_IE_INVALID
: Invalid Information Element in frames.WIFI_REASON_MIC_FAILURE
: Message Integrity Check failure (potential security issue or corruption).WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT
: WPA/WPA2 4-way handshake timed out (often password related).WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT
: WPA/WPA2 group key update timed out.WIFI_REASON_IE_IN_4WAY_DIFFERS
: Information Element mismatch during handshake.WIFI_REASON_GROUP_CIPHER_INVALID
: Invalid group cipher.WIFI_REASON_PAIRWISE_CIPHER_INVALID
: Invalid pairwise cipher.WIFI_REASON_AKMP_INVALID
: Invalid AKM (Authentication and Key Management Protocol).WIFI_REASON_UNSUPP_RSN_IE_VERSION
: Unsupported RSN (Robust Security Network) IE version.WIFI_REASON_INVALID_RSN_IE_CAP
: Invalid RSN IE capabilities.WIFI_REASON_802_1X_AUTH_FAILED
: Authentication failed (WPA/WPA2 Enterprise).WIFI_REASON_CIPHER_SUITE_REJECTED
: Security cipher suite rejected by AP.WIFI_REASON_BEACON_TIMEOUT
: Station did not receive beacons from the AP for a period (signal loss, AP down).WIFI_REASON_NO_AP_FOUND
: Station could not find an AP matching the specified SSID/BSSID.WIFI_REASON_AUTH_FAIL
: Authentication failed (generic, often wrong password for PSK).WIFI_REASON_ASSOC_FAIL
: Association failed (generic).WIFI_REASON_HANDSHAKE_TIMEOUT
: Handshake timed out (generic, often password related).WIFI_REASON_CONNECTION_FAIL
: Generic connection failure.WIFI_REASON_AP_TSF_RESET
: AP’s Timing Synchronization Function reset.WIFI_REASON_ROAMING
: Station is roaming to a different AP.
Checking this reason code allows the application to decide if reconnection is appropriate (e.g., for WIFI_REASON_BEACON_TIMEOUT
) or if there’s a configuration issue that needs user intervention (e.g., WIFI_REASON_AUTH_FAIL
).
Most Common ones are:
Reason Code (Name) | Value (Typical) | Likely Cause & Meaning | Category / Suggested Action |
---|---|---|---|
WIFI_REASON_AUTH_FAIL |
202 | Authentication with the AP failed. Most commonly due to an incorrect password for WPA/WPA2-PSK networks. | Config/Auth Issue: Verify password. Check AP security settings. Usually not worth retrying without change. |
WIFI_REASON_HANDSHAKE_TIMEOUT |
204 | The 4-way handshake (part of WPA/WPA2 authentication) timed out. Often related to an incorrect password, but can also be due to weak signal or AP issues. | Config/Auth/Signal: Verify password first. If correct, check signal. May retry a few times. |
WIFI_REASON_NO_AP_FOUND |
201 | The ESP32 could not find any Access Point matching the configured SSID. | Config/AP Issue: Verify SSID is correct and AP is broadcasting. Check AP range. |
WIFI_REASON_BEACON_TIMEOUT |
200 | The ESP32 stopped receiving beacon frames from the AP. This usually indicates signal loss (out of range) or the AP has gone down. | Signal/AP Issue: Likely temporary. Good candidate for retry logic with backoff. |
WIFI_REASON_ASSOC_EXPIRE |
4 | Association with the AP timed out or expired. Could be due to AP load or temporary network issues. | AP/Network Issue: May retry. |
WIFI_REASON_ASSOC_TOOMANY |
5 | The Access Point cannot accept any more associated stations (it’s full). | AP Issue: Retry after a longer delay. Problem is with AP capacity. |
WIFI_REASON_MIC_FAILURE |
14 | Message Integrity Check failure during WPA/WPA2 handshake. Could indicate a potential security attack, a very noisy environment, or a password mismatch. | Security/Config: Verify password. If persistent, investigate network security. May indicate a more serious issue. |
WIFI_REASON_AUTH_LEAVE |
3 | Station explicitly initiated the disconnection (e.g., by calling esp_wifi_disconnect() ). |
Intentional: Application controlled. Usually no retry unless intended. |
Practical Examples
Let’s enhance the previous chapter’s example to log more details about the connection flow and handle disconnection reasons.
Example 1: Detailed Event Logging and Disconnect Reason
Project Setup:
Use the project from Chapter 27.
Code (main/your_main_file.c
):
Modify the event_handler
function.
#include <stdio.h>
#include <string.h> // Required for memcpy, memset
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* --- Configuration --- */
#define EXAMPLE_ESP_WIFI_SSID CONFIG_EXAMPLE_WIFI_SSID // Using Kconfig now
#define EXAMPLE_ESP_WIFI_PASS CONFIG_EXAMPLE_WIFI_PASSWORD // Using Kconfig now
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_EXAMPLE_MAXIMUM_RETRY // Using Kconfig now
/* --- Globals --- */
static const char *TAG = "WIFI_FLOW";
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
/* --- Helper Function to Convert Reason Code to String (Simplified) --- */
// In a real application, you might want a more comprehensive lookup table or function
const char* wifi_reason_to_str(wifi_err_reason_t reason) {
switch (reason) {
case WIFI_REASON_UNSPECIFIED: return "UNSPECIFIED";
case WIFI_REASON_AUTH_EXPIRE: return "AUTH_EXPIRE";
case WIFI_REASON_AUTH_LEAVE: return "AUTH_LEAVE";
case WIFI_REASON_ASSOC_EXPIRE: return "ASSOC_EXPIRE";
case WIFI_REASON_ASSOC_TOOMANY: return "ASSOC_TOOMANY";
case WIFI_REASON_NOT_AUTHED: return "NOT_AUTHED";
case WIFI_REASON_NOT_ASSOCED: return "NOT_ASSOCED";
case WIFI_REASON_ASSOC_LEAVE: return "ASSOC_LEAVE";
case WIFI_REASON_ASSOC_NOT_AUTHED: return "ASSOC_NOT_AUTHED";
case WIFI_REASON_DISASSOC_PWRCAP_BAD: return "DISASSOC_PWRCAP_BAD";
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: return "DISASSOC_SUPCHAN_BAD";
case WIFI_REASON_IE_INVALID: return "IE_INVALID";
case WIFI_REASON_MIC_FAILURE: return "MIC_FAILURE";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: return "4WAY_HANDSHAKE_TIMEOUT";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: return "GROUP_KEY_UPDATE_TIMEOUT";
case WIFI_REASON_IE_IN_4WAY_DIFFERS: return "IE_IN_4WAY_DIFFERS";
case WIFI_REASON_GROUP_CIPHER_INVALID: return "GROUP_CIPHER_INVALID";
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: return "PAIRWISE_CIPHER_INVALID";
case WIFI_REASON_AKMP_INVALID: return "AKMP_INVALID";
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: return "UNSUPP_RSN_IE_VERSION";
case WIFI_REASON_INVALID_RSN_IE_CAP: return "INVALID_RSN_IE_CAP";
case WIFI_REASON_802_1X_AUTH_FAILED: return "802_1X_AUTH_FAILED";
case WIFI_REASON_CIPHER_SUITE_REJECTED: return "CIPHER_SUITE_REJECTED";
case WIFI_REASON_BEACON_TIMEOUT: return "BEACON_TIMEOUT";
case WIFI_REASON_NO_AP_FOUND: return "NO_AP_FOUND";
case WIFI_REASON_AUTH_FAIL: return "AUTH_FAIL";
case WIFI_REASON_ASSOC_FAIL: return "ASSOC_FAIL";
case WIFI_REASON_HANDSHAKE_TIMEOUT: return "HANDSHAKE_TIMEOUT";
case WIFI_REASON_CONNECTION_FAIL: return "CONNECTION_FAIL";
case WIFI_REASON_AP_TSF_RESET: return "AP_TSF_RESET";
case WIFI_REASON_ROAMING: return "ROAMING";
default: return "UNKNOWN";
}
}
/* --- Event Handler --- */
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
ESP_LOGI(TAG, "Received Event: Base=%s, ID=%ld", event_base, event_id);
// --- WIFI_EVENT handling ---
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "[State] WIFI_EVENT_STA_START: Station mode started.");
ESP_LOGI(TAG, "[Action] Initiating connection attempt...");
esp_wifi_connect(); // Initiate connection
break;
case WIFI_EVENT_STA_CONNECTED:
{ // Scope for local variable 'event'
wifi_event_sta_connected_t* event = (wifi_event_sta_connected_t*) event_data;
ESP_LOGI(TAG, "[State] WIFI_EVENT_STA_CONNECTED: Associated with AP!");
ESP_LOGI(TAG, " SSID: %.*s", event->ssid_len, event->ssid); // Print SSID safely
ESP_LOGI(TAG, " BSSID: " MACSTR, MAC2STR(event->bssid)); // Print BSSID
ESP_LOGI(TAG, " Channel: %d", event->channel);
ESP_LOGI(TAG, " AuthMode: %d", event->authmode);
ESP_LOGI(TAG, "[Next Step] Waiting for IP address via DHCP...");
}
break;
case WIFI_EVENT_STA_DISCONNECTED:
{ // Scope for local variable 'event'
wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*) event_data;
const char* reason_str = wifi_reason_to_str(event->reason);
ESP_LOGW(TAG, "[State] WIFI_EVENT_STA_DISCONNECTED: Lost connection.");
ESP_LOGW(TAG, " Reason Code: %d (%s)", event->reason, reason_str);
ESP_LOGW(TAG, " SSID: %.*s", event->ssid_len, event->ssid);
ESP_LOGW(TAG, " BSSID: " MACSTR, MAC2STR(event->bssid));
// Decide whether to retry based on reason code (example)
bool retry = false;
switch(event->reason) {
// Reasons typically indicating a temporary issue or signal loss
case WIFI_REASON_BEACON_TIMEOUT:
case WIFI_REASON_ASSOC_EXPIRE:
case WIFI_REASON_AUTH_EXPIRE:
case WIFI_REASON_HANDSHAKE_TIMEOUT: // Can be password, but also temporary
case WIFI_REASON_CONNECTION_FAIL:
case WIFI_REASON_AP_TSF_RESET:
case WIFI_REASON_ROAMING: // May need specific handling
retry = true;
break;
// Reasons indicating configuration issues (usually don't retry automatically)
case WIFI_REASON_AUTH_FAIL:
case WIFI_REASON_ASSOC_FAIL:
case WIFI_REASON_NO_AP_FOUND:
case WIFI_REASON_MIC_FAILURE:
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // More likely password issue
case WIFI_REASON_GROUP_CIPHER_INVALID:
case WIFI_REASON_PAIRWISE_CIPHER_INVALID:
case WIFI_REASON_AKMP_INVALID:
case WIFI_REASON_UNSUPP_RSN_IE_VERSION:
case WIFI_REASON_INVALID_RSN_IE_CAP:
case WIFI_REASON_802_1X_AUTH_FAILED:
case WIFI_REASON_CIPHER_SUITE_REJECTED:
ESP_LOGE(TAG, "Connection failed due to configuration or security issue. Not retrying automatically.");
retry = false;
break;
// Reasons where retry might depend on specific logic
case WIFI_REASON_ASSOC_TOOMANY:
ESP_LOGW(TAG, "AP is full. Will retry later.");
retry = true; // Could implement longer backoff here
break;
default:
ESP_LOGW(TAG, "Unhandled disconnect reason: %d. Retrying by default.", event->reason);
retry = true; // Default to retry for unknown reasons
break;
}
if (retry && s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
s_retry_num++;
ESP_LOGI(TAG, "[Action] Retrying connection (%d/%d)...", s_retry_num, EXAMPLE_ESP_MAXIMUM_RETRY);
// Optional: Add a delay/backoff here before reconnecting
vTaskDelay(pdMS_TO_TICKS(5000)); // e.g., wait 5 seconds
esp_wifi_connect();
} else if (retry) { // Retries exhausted
ESP_LOGE(TAG, "Connection failed after %d retries.", EXAMPLE_ESP_MAXIMUM_RETRY);
if (s_wifi_event_group) {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
} else { // Non-retryable error
if (s_wifi_event_group) {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
}
}
break;
default:
ESP_LOGI(TAG, "Unhandled WIFI_EVENT: %ld", event_id);
break;
}
}
// --- IP_EVENT handling ---
else if (event_base == IP_EVENT) {
switch(event_id) {
case IP_EVENT_STA_GOT_IP:
{ // Scope for local variable 'event'
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "[State] IP_EVENT_STA_GOT_IP: Network Ready!");
ESP_LOGI(TAG, " Assigned IP : " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, " Subnet Mask : " IPSTR, IP2STR(&event->ip_info.netmask));
ESP_LOGI(TAG, " Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
ESP_LOGI(TAG, " Interface Index: %ld", event->if_index);
ESP_LOGI(TAG, " IP Changed: %s", event->ip_changed ? "Yes" : "No");
s_retry_num = 0; // Reset retry counter on successful connection
if (s_wifi_event_group) {
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
break;
default:
ESP_LOGI(TAG, "Unhandled IP_EVENT: %ld", event_id);
break;
}
}
// --- Other event bases ---
else {
ESP_LOGW(TAG, "Received event from unknown base: %s", event_base);
}
}
/* --- WiFi Initialization Function --- */
void wifi_init_sta(void)
{
// 0. Create Event Group
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(s_wifi_event_group == NULL ? ESP_FAIL : ESP_OK); // Basic check
// 1. Initialize TCP/IP stack
ESP_ERROR_CHECK(esp_netif_init());
// 2. Create default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 3. Create default WiFi station network interface
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
ESP_ERROR_CHECK(sta_netif == NULL ? ESP_FAIL : ESP_OK); // Basic check
// 4. Initialize WiFi with default configuration
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 5. Register event handlers
esp_event_handler_instance_t instance_any_wifi_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID, // Catch all WiFi events
&event_handler,
NULL,
&instance_any_wifi_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP, // Catch only GOT_IP event
&event_handler,
NULL,
&instance_got_ip));
// 6. Configure WiFi Station (using Kconfig values)
wifi_config_t wifi_config;
memset(&wifi_config, 0, sizeof(wifi_config_t)); // Zero out structure
strncpy((char*)wifi_config.sta.ssid, EXAMPLE_ESP_WIFI_SSID, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char*)wifi_config.sta.password, EXAMPLE_ESP_WIFI_PASS, sizeof(wifi_config.sta.password) - 1);
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; // Or choose based on Kconfig if needed
// Enable PMF based on Kconfig if needed
// wifi_config.sta.pmf_cfg.capable = CONFIG_EXAMPLE_WIFI_PMF_CAPABLE;
// wifi_config.sta.pmf_cfg.required = CONFIG_EXAMPLE_WIFI_PMF_REQUIRED;
// 7. Set WiFi Mode to Station
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// 8. Set WiFi Configuration
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// 9. Start WiFi Driver
ESP_LOGI(TAG, "[Action] Starting WiFi Driver...");
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished setup. Waiting for connection events...");
/* --- Wait for connection or failure --- */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
/* Interpret results */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "-------------------------------------------");
ESP_LOGI(TAG, "WiFi Connected to AP!");
ESP_LOGI(TAG, "-------------------------------------------");
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "-------------------------------------------");
ESP_LOGE(TAG, "WiFi Failed to Connect!");
ESP_LOGE(TAG, "-------------------------------------------");
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT during wait");
}
/* Optional: Unregister handlers etc. */
// ...
}
/* --- Main Application --- */
void app_main(void)
{
ESP_LOGI(TAG, "Starting WiFi Connection Flow Example...");
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Add Kconfig options if not done in Exercise 3 of Chapter 27
// Ensure CONFIG_EXAMPLE_WIFI_SSID, CONFIG_EXAMPLE_WIFI_PASSWORD,
// and CONFIG_EXAMPLE_MAXIMUM_RETRY are defined in sdkconfig
// (via menuconfig -> Example Connection Configuration)
// Initialize WiFi
wifi_init_sta();
ESP_LOGI(TAG, "Post-connection phase. Application can proceed...");
while(1) {
vTaskDelay(pdMS_TO_TICKS(60000)); // Keep running
}
}
Kconfig:
Ensure you have added the Kconfig options from Chapter 27, Exercise 3, including one for EXAMPLE_ESP_MAXIMUM_RETRY
:
- In
main/Kconfig.projbuild
:
menu "Example Connection Configuration"
config EXAMPLE_ESP_WIFI_SSID
string "WiFi SSID"
default "YOUR_SSID"
help
SSID of the WiFi network you want to connect to.
config EXAMPLE_ESP_WIFI_PASSWORD
string "WiFi Password"
default "YOUR_PASSWORD"
help
Password of the WiFi network.
config EXAMPLE_ESP_MAXIMUM_RETRY
int "Maximum WiFi connection retries"
range 0 100
default 5
help
Number of times to retry connection on disconnection before failing permanently.
endmenu
- Run
idf.py menuconfig
, navigate toExample Connection Configuration
, and set your SSID, Password, and desired retry count. Save and exit.
Build, Flash, Monitor:
idf.py build
idf.py flash
idf.py monitor
Expected Output:
Observe the detailed log messages showing the state transitions and event data:
I (XXX) WIFI_FLOW: Starting WiFi Connection Flow Example...
I (XXX) WIFI_FLOW: [Action] Starting WiFi Driver...
I (XXX) WIFI_FLOW: wifi_init_sta finished setup. Waiting for connection events...
I (XXX) WIFI_FLOW: Received Event: Base=WIFI_EVENT, ID=2 // WIFI_EVENT_STA_START=2
I (XXX) WIFI_FLOW: [State] WIFI_EVENT_STA_START: Station mode started.
I (XXX) WIFI_FLOW: [Action] Initiating connection attempt...
I (XXX) WIFI_FLOW: Received Event: Base=WIFI_EVENT, ID=4 // WIFI_EVENT_STA_CONNECTED=4
I (XXX) WIFI_FLOW: [State] WIFI_EVENT_STA_CONNECTED: Associated with AP!
I (XXX) WIFI_FLOW: SSID: YOUR_SSID
I (XXX) WIFI_FLOW: BSSID: xx:xx:xx:xx:xx:xx // AP's MAC Address
I (XXX) WIFI_FLOW: Channel: 6 // Your channel
I (XXX) WIFI_FLOW: AuthMode: 4 // WIFI_AUTH_WPA2_PSK=4
I (XXX) WIFI_FLOW: [Next Step] Waiting for IP address via DHCP...
I (XXX) WIFI_FLOW: Received Event: Base=IP_EVENT, ID=0 // IP_EVENT_STA_GOT_IP=0
I (XXX) WIFI_FLOW: [State] IP_EVENT_STA_GOT_IP: Network Ready!
I (XXX) WIFI_FLOW: Assigned IP : 192.168.1.105 // Your IP
I (XXX) WIFI_FLOW: Subnet Mask : 255.255.255.0 // Your mask
I (XXX) WIFI_FLOW: Gateway : 192.168.1.1 // Your gateway
I (XXX) WIFI_FLOW: Interface Index: 0
I (XXX) WIFI_FLOW: IP Changed: No
I (XXX) WIFI_FLOW: -------------------------------------------
I (XXX) WIFI_FLOW: WiFi Connected to AP!
I (XXX) WIFI_FLOW: -------------------------------------------
I (XXX) WIFI_FLOW: Post-connection phase. Application can proceed...
- Test Disconnection: If you turn off your AP, you should see:
I (XXX) WIFI_FLOW: Received Event: Base=WIFI_EVENT, ID=5 // WIFI_EVENT_STA_DISCONNECTED=5
W (XXX) WIFI_FLOW: [State] WIFI_EVENT_STA_DISCONNECTED: Lost connection.
W (XXX) WIFI_FLOW: Reason Code: 201 (BEACON_TIMEOUT)
W (XXX) WIFI_FLOW: SSID: YOUR_SSID
W (XXX) WIFI_FLOW: BSSID: xx:xx:xx:xx:xx:xx
I (XXX) WIFI_FLOW: [Action] Retrying connection (1/5)...
// ... further retry attempts and events ...
- Test Wrong Password: If you configure the wrong password in
menuconfig
:
// ... STA_START ...
I (XXX) WIFI_FLOW: Received Event: Base=WIFI_EVENT, ID=5 // WIFI_EVENT_STA_DISCONNECTED=5
W (XXX) WIFI_FLOW: [State] WIFI_EVENT_STA_DISCONNECTED: Lost connection.
W (XXX) WIFI_FLOW: Reason Code: 204 (HANDSHAKE_TIMEOUT) // Or AUTH_FAIL (202) sometimes
W (XXX) WIFI_FLOW: SSID: YOUR_SSID
W (XXX) WIFI_FLOW: BSSID: xx:xx:xx:xx:xx:xx
E (XXX) WIFI_FLOW: Connection failed due to configuration or security issue. Not retrying automatically.
E (XXX) WIFI_FLOW: -------------------------------------------
E (XXX) WIFI_FLOW: WiFi Failed to Connect!
E (XXX) WIFI_FLOW: -------------------------------------------
E (XXX) WIFI_FLOW: Post-connection phase. Application can proceed... // Or handle failure
Variant Notes
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: The sequence of states and the events (
WIFI_EVENT_STA_START
,WIFI_EVENT_STA_CONNECTED
,IP_EVENT_STA_GOT_IP
,WIFI_EVENT_STA_DISCONNECTED
) described in this chapter, along with their associated data structures and reason codes, are consistent across these WiFi-enabled variants when using ESP-IDF v5.x. The underlying timing of state transitions might vary slightly due to hardware differences, but the logical flow managed byesp_wifi
andesp_netif
remains the same. - ESP32-H2: This chapter is not applicable to the ESP32-H2 as it lacks 802.11 WiFi hardware.
Common Mistakes & Troubleshooting Tips
Pitfall / Mistake | Symptom(s) | Solution / Best Practice |
---|---|---|
Premature Network Operations | Attempting TCP/IP communication (HTTP, MQTT, etc.) immediately after WIFI_EVENT_STA_CONNECTED . Network calls fail, DNS resolution errors. |
Wait for IP_EVENT_STA_GOT_IP . This event confirms an IP address is assigned and the device is network-ready. Use event groups, semaphores, or flags to signal readiness. |
Ignoring Disconnect Reasons | Blindly retrying on WIFI_EVENT_STA_DISCONNECTED without checking the reason code. Device may loop endlessly on configuration errors (e.g., wrong password). |
Inspect the reason field in wifi_event_sta_disconnected_t . Retry only for transient errors (e.g., WIFI_REASON_BEACON_TIMEOUT ). Log/handle persistent errors (e.g., WIFI_REASON_AUTH_FAIL ) differently. |
Blocking in Event Handlers | Performing long delays (vTaskDelay ), complex computations, or slow I/O directly within an event handler function. |
System may become unresponsive, miss other events, or trigger watchdog timeouts. Keep handlers short and non-blocking. Offload lengthy work to separate tasks, signaled from the handler (e.g., via queues, semaphores). |
Incorrect Event Data Casting | Casting void* event_data to the wrong structure type, or accessing members without proper casting. Accessing event_data if it’s NULL. |
Crashes (Guru Meditation), garbage data. Always cast to the correct event-specific structure pointer (e.g., wifi_event_sta_connected_t* , ip_event_got_ip_t* ). Check documentation for event data types. Use braces {} to scope variables within case statements. |
Missing NVS Initialization | Forgetting to initialize Non-Volatile Storage (nvs_flash_init() ) before WiFi operations. |
esp_wifi_init() may fail, or WiFi calibration data might be missing, leading to erratic behavior or connection issues. Ensure NVS is initialized early in app_main() . |
Exercises
- Exponential Backoff Retry: Modify the
WIFI_EVENT_STA_DISCONNECTED
handler. Instead of a fixed 5-second delay before retrying (vTaskDelay(pdMS_TO_TICKS(5000))
), implement an exponential backoff. Start with a short delay (e.g., 1 second) for the first retry, double it for the second (2 seconds), double again for the third (4 seconds), and so on, potentially capping the delay at a maximum value (e.g., 60 seconds). Reset the delay sequence upon successful connection (IP_EVENT_STA_GOT_IP
). - Log Gateway on Connect: Modify the
IP_EVENT_STA_GOT_IP
handler to specifically log only the Gateway IP address obtained via DHCP, in addition to the general “Network Ready!” message. - Semaphore Signaling: Remove the
s_wifi_event_group
entirely. Instead, create a binary semaphore (SemaphoreHandle_t xConnectedSemaphore = NULL;
initialized withxSemaphoreCreateBinary();
). In theIP_EVENT_STA_GOT_IP
handler, “give” the semaphore (xSemaphoreGive(xConnectedSemaphore);
). Inwifi_init_sta
, after starting WiFi, wait indefinitely to “take” the semaphore (xSemaphoreTake(xConnectedSemaphore, portMAX_DELAY);
). This ensures thewifi_init_sta
function only returns after a successful IP acquisition. Handle potential failure scenarios (e.g., if retries are exhausted, how doeswifi_init_sta
know to stop waiting?). You might need another signal for failure.
Summary
- Connecting in Station mode follows a sequence: Start -> Connecting (Auth/Assoc) -> Connected (MAC) -> Obtaining IP -> Network Ready.
- Key events signal transitions:
WIFI_EVENT_STA_START
,WIFI_EVENT_STA_CONNECTED
,IP_EVENT_STA_GOT_IP
. - Disconnection triggers
WIFI_EVENT_STA_DISCONNECTED
, providing a crucialreason
code. - Event data payloads (
wifi_event_sta_connected_t
,ip_event_got_ip_t
,wifi_event_sta_disconnected_t
) provide valuable context (BSSID, IP info, reason codes). - Network communication using TCP/IP is only possible after receiving
IP_EVENT_STA_GOT_IP
. - Robust applications require careful handling of the event flow, especially disconnection reasons and avoiding blocking operations in handlers.
Further Reading
- ESP-IDF Programming Guide – WiFi Events: (Refer back to the
esp_wifi.h
API reference and theesp_event
library documentation linked in Chapter 27, paying close attention to the event structures and IDs). - ESP-IDF Programming Guide – Error Handling: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-guides/error-handling.html
esp_wifi_types.h
: Examine this header file directly in your ESP-IDF installation (components/esp_wifi/include/esp_wifi_types.h
) to see the definitions of event structures andwifi_err_reason_t
.