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 via esp_wifi_set_mode). The configuration applied via esp_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.
  • 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.
  • 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): The event_data for this event is of type wifi_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), the esp_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 the IP_EVENT base, not WIFI_EVENT.
  • 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): The event_data for this event is of type ip_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 assigned ip address, netmask, and gw (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): The event_data is of type wifi_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 type wifi_err_reason_t indicating why the disconnection occurred.
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.

C
#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:
Plaintext
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 to Example 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:

Plaintext
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:

Plaintext
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:

Plaintext
 // ... 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 by esp_wifi and esp_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

  1. 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).
  2. 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.
  3. Semaphore Signaling: Remove the s_wifi_event_group entirely. Instead, create a binary semaphore (SemaphoreHandle_t xConnectedSemaphore = NULL; initialized with xSemaphoreCreateBinary();). In the IP_EVENT_STA_GOT_IP handler, “give” the semaphore (xSemaphoreGive(xConnectedSemaphore);). In wifi_init_sta, after starting WiFi, wait indefinitely to “take” the semaphore (xSemaphoreTake(xConnectedSemaphore, portMAX_DELAY);). This ensures the wifi_init_sta function only returns after a successful IP acquisition. Handle potential failure scenarios (e.g., if retries are exhausted, how does wifi_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 crucial reason 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 the esp_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 and wifi_err_reason_t.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top