Chapter 44: Custom WiFi Event Handlers
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the ESP-IDF event handling mechanism, particularly for WiFi and IP events.
- Explain why custom event handlers are necessary for robust WiFi applications.
- Register and unregister event handlers for specific events or event sources.
- Correctly interpret event bases, event IDs, and event-specific data passed to handlers.
- Design event handlers that are efficient and non-blocking.
- Pass user-defined context to event handlers for stateful operations.
- Implement custom logic based on various WiFi events, such as connection, disconnection (with reason codes), and obtaining an IP address.
- Apply best practices for managing WiFi state and behavior through event-driven programming.
Introduction
Throughout the previous chapters on WiFi connectivity, we’ve encountered event handlers. These are fundamental to how ESP-IDF applications interact with asynchronous operations like WiFi connection attempts, disconnections, or IP address acquisition. While the default event handlers provided in examples offer basic functionality, real-world applications often require more nuanced responses to network events. For instance, you might want to implement custom retry strategies, log detailed diagnostics, update a display, or trigger other application-specific logic based on the WiFi state.
This chapter focuses on empowering you to create custom WiFi event handlers. We will delve into the ESP-IDF event loop library, explore how to register handlers for specific events, understand the data provided with each event, and learn best practices for writing effective handlers. Mastering custom event handling is key to building resilient, responsive, and intelligent connected ESP32 applications.
Theory
ESP-IDF Event Handling System: A Recap
The ESP-IDF features a versatile event handling system that allows different system components (modules) to declare events and other components to subscribe to (listen for) these events. This promotes a decoupled architecture where modules don’t need direct knowledge of each other.
Key Components:
- Event Loops: An event loop is responsible for dispatching events to their registered handlers. ESP-IDF provides a default system event loop, which is used by many system services, including WiFi and TCP/IP stack events. Applications can also create custom event loops, but for most WiFi scenarios, interacting with the default loop is sufficient.
- Event Sources (Event Bases): These are identifiers that group related events. For WiFi and IP networking, the primary event sources we’re concerned with are:
WIFI_EVENT
: For events related to the WiFi driver and station/AP state (e.g.,WIFI_EVENT_STA_START
,WIFI_EVENT_STA_CONNECTED
,WIFI_EVENT_STA_DISCONNECTED
,WIFI_EVENT_AP_START
).IP_EVENT
: For events related to the TCP/IP stack, primarily IP address acquisition (e.g.,IP_EVENT_STA_GOT_IP
,IP_EVENT_ETH_GOT_IP
).SC_EVENT
: For SmartConfig events (as seen in Chapter 41).
- Event IDs: Each event source can have multiple specific events, identified by an
event_id
. For example, underWIFI_EVENT
,WIFI_EVENT_STA_DISCONNECTED
is a specific event ID.ESP_EVENT_ANY_ID
can be used to register a handler for all events from a specific event base. - Event Data: When an event is dispatched, it can carry associated data relevant to that specific event. For instance, a
WIFI_EVENT_STA_DISCONNECTED
event carries data including the reason for disconnection. This data is passed to the handler as avoid*
pointer and must be cast to the correct type. - Event Handlers: These are callback functions that you write and register with an event loop. When an event occurs for which a handler is registered, the event loop invokes that handler. An event handler function has the following signature:
void event_handler_function(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
handler_arg
: A user-defined argument passed during handler registration. This allows you to pass context or state to your handler.event_base
: The event source (e.g.,WIFI_EVENT
).event_id
: The specific event ID (e.g.,WIFI_EVENT_STA_DISCONNECTED
).event_data
: A pointer to the event-specific data.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans', 'primaryColor': '#DBEAFE', /* Light Blue for process nodes */ 'primaryBorderColor': '#2563EB', 'primaryTextColor': '#1E40AF', 'lineColor': '#5B21B6', /* Purple for lines */ 'textColor': '#1F2937', 'startNodeFill': '#EDE9FE', 'startNodeStroke': '#5B21B6', 'startNodeColor': '#5B21B6', /* Event Source */ 'decisionNodeFill': '#FEF3C7', 'decisionNodeStroke': '#D97706', 'decisionNodeColor': '#92400E', /* Event Loop */ 'endNodeFill': '#D1FAE5', 'endNodeStroke': '#059669', 'endNodeColor': '#065F46' /* Handler Execution */ } } }%% graph TD A["Event Source<br>(e.g., WiFi Driver, TCP/IP Stack)"]:::startNode -- Posts Event with Data --> B(Event Queue in Event Loop); class B processNode; subgraph "Application Initialization Phase" direction LR C("App Code: Calls<br><i>esp_event_loop_create_default()</i>") --> D(Default Event Loop Task Starts); class C processNode; class D processNode; E("App Code: Calls<br><i>esp_event_handler_register()</i>") --> F(Handler Function is<br>Registered with Loop<br>for Specific Event Base/ID); class E processNode; class F processNode; end B --> G{"Default Event Loop Task<br>(Continuously Monitors Queue)"}; style G fill:#FEF3C7,stroke:#D97706,color:#92400E; G -- Event Found --> H{Lookup Registered Handlers<br>for this Event Base/ID}; class H processNode; H -- Handler(s) Found --> I("Invoke Registered Handler Function(s)<br>Passes: <i>handler_arg</i>, <i>event_base</i>, <i>event_id</i>, <i>event_data</i>"); style I fill:#D1FAE5,stroke:#059669,color:#065F46; I --> J[Handler Executes Custom Logic]; class J processNode; H -- No Handler Found --> G; J --> G; J --- note1["Handler should be non-blocking.<br>If lengthy work, offload to another task."] style note1 fill:#FFF7B2,stroke:#B5A136,color:#6B6022
Component | Description | Key Details / Examples |
---|---|---|
Event Loops | Responsible for receiving posted events and dispatching them to registered handlers. Manages a queue of events. | ESP-IDF provides a default system event loop used by WiFi, TCP/IP, etc. Custom loops can be created via esp_event_loop_create() . |
Event Sources (Event Bases) | Unique identifiers (esp_event_base_t ) that group related events from a specific module or system component. |
WIFI_EVENT (for WiFi driver/station/AP states)IP_EVENT (for TCP/IP stack, IP address events)SC_EVENT (for SmartConfig) |
Event IDs | Specific identifiers (int32_t ) for individual events within an event base. |
Under WIFI_EVENT : WIFI_EVENT_STA_START , WIFI_EVENT_STA_DISCONNECTED .Under IP_EVENT : IP_EVENT_STA_GOT_IP .ESP_EVENT_ANY_ID can be used to handle all events from a base. |
Event Data | A void* pointer to data associated with a specific event. This data provides context about the event. |
For WIFI_EVENT_STA_DISCONNECTED , data is wifi_event_sta_disconnected_t* (contains reason, SSID, etc.).Must be cast to the correct type by the handler. |
Event Handlers | Callback functions (type esp_event_handler_t ) written by the application to process specific events. |
Signature: void func(void* arg, esp_event_base_t base, int32_t id, void* data) .Registered using esp_event_handler_register() . |
Handler Argument (handler_arg ) |
A user-defined void* pointer passed to esp_event_handler_register() . This argument is then passed to the event handler function when it’s invoked. |
Allows passing custom context, state variables, or pointers to objects into the event handler, enabling stateful operations. |
Why Custom Event Handlers?
While basic examples often use a single, simple event handler, custom handlers offer several advantages:
- Granular Control: Execute specific logic for different events instead of a monolithic if-else structure.
- Specific Actions: Perform actions like logging detailed diagnostics, updating a UI, attempting reconnection with specific strategies, or signaling other tasks.
- State Management: Maintain application state based on network events (e.g.,
is_connected
,retry_count
). - Modularity: Separate event handling logic from main application code, improving readability and maintainability.
- Debugging: Custom logging within handlers can provide invaluable insight into WiFi behavior.
Registering and Unregistering Event Handlers
The ESP-IDF event library provides functions to manage event handlers:
esp_event_handler_register(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void* handler_arg)
:- Registers
event_handler
for a specificevent_id
from a givenevent_base
. handler_arg
is the context pointer passed to the handler.- You can register multiple handlers for the same event. They will be called in the order they were registered.
- Registers
esp_event_handler_instance_register(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void* handler_arg, esp_event_handler_instance_t *instance)
:- Similar to
esp_event_handler_register
, but it also returns anesp_event_handler_instance_t
object representing the registration. This instance object can then be used to unregister this specific handler instance. This is useful when you have multiple instances of the same handler function registered for the same event, or when you want more precise control over unregistration.
- Similar to
esp_event_handler_unregister(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler)
:- Unregisters
event_handler
for a specificevent_id
fromevent_base
. - If multiple instances of the same handler function were registered for the same event ID, this function unregisters the one that was registered last.
- Unregisters
esp_event_handler_instance_unregister(esp_event_base_t event_base, int32_t event_id, esp_event_handler_instance_t instance)
:- Unregisters a specific handler instance previously registered using
esp_event_handler_instance_register
.
- Unregisters a specific handler instance previously registered using
Typically, event handlers are registered during initialization (e.g., in wifi_init_sta()
) and may be unregistered if the component is de-initialized, although for many applications, handlers remain registered for the lifetime of the device.
Function Signature | Description | Key Parameters & Notes |
---|---|---|
esp_err_t esp_event_handler_register( |
Registers an event handler function for a specific event or for all events from a specific base. |
– event_base : The event source (e.g., WIFI_EVENT ).– event_id : The specific event ID (e.g., WIFI_EVENT_STA_CONNECTED ) or ESP_EVENT_ANY_ID .– event_handler : Pointer to the handler function.– handler_arg : User-defined context passed to the handler.
|
esp_err_t esp_event_handler_instance_register( |
Similar to esp_event_handler_register , but also returns an instance handle. |
– instance : Output parameter to store the handler instance context. This handle is required for esp_event_handler_instance_unregister .– Useful for unregistering specific instances if multiple identical handlers are registered. |
esp_err_t esp_event_handler_unregister( |
Unregisters an event handler function for a specific event. |
– If multiple instances of the same handler function were registered for the same event, this unregisters the one added last. – To unregister a specific instance, use esp_event_handler_instance_unregister .
|
esp_err_t esp_event_handler_instance_unregister( |
Unregisters a specific instance of an event handler. |
– instance : The handler instance context obtained from esp_event_handler_instance_register .– Provides precise control over which handler instance to remove. |
Default Event Loop and Task Context
By default, system events (like WiFi and IP events) are handled by the default system event loop. This loop runs in the context of a dedicated FreeRTOS task, often called the “event task” or “system event task.” This means your event handler functions will also execute in the context of this system task.
Implications:
- Non-Blocking Handlers: It is crucial that your event handlers execute quickly and do not block. Lengthy operations within an event handler can delay the processing of other system events, potentially leading to issues like watchdog timeouts or unresponsive behavior.
- Offloading Work: If an event requires significant processing, the handler should offload this work to a separate application task. This can be done by sending a message to a queue, setting an event group bit, or giving a semaphore that another task is waiting on.
Understanding Event Data
The event_data
pointer is key to getting detailed information about an event. You must cast this void*
pointer to the correct structure type based on the event_base
and event_id
.
Common Event Data Structures (from esp_wifi_types.h
and esp_event_base.h
/ esp_netif_types.h
):
WIFI_EVENT_STA_CONNECTED
:wifi_event_sta_connected_t
ssid
: SSID of the connected AP.ssid_len
: Length of the SSID.bssid
: BSSID (MAC address) of the AP.channel
: Channel of the AP.authmode
: Authentication mode used.
WIFI_EVENT_STA_DISCONNECTED
:wifi_event_sta_disconnected_t
ssid
: SSID of the AP from which disconnected.ssid_len
: Length of the SSID.bssid
: BSSID of the AP.reason
: A numeric code indicating the reason for disconnection (e.g.,WIFI_REASON_AUTH_FAIL
,WIFI_REASON_NO_AP_FOUND
,WIFI_REASON_HANDSHAKE_TIMEOUT
,WIFI_REASON_SAE_HANDSHAKE_FAILED
). These reasons are vital for diagnostics.
IP_EVENT_STA_GOT_IP
:ip_event_got_ip_t
ip_info
: Anesp_netif_ip_info_t
structure containing the IP address, netmask, and gateway address.ip_changed
: A boolean indicating if the IP address changed (relevant for DHCP renewals).
WIFI_EVENT_SCAN_DONE
:wifi_event_sta_scan_done_t
status
: Scan completion status (0 for success).number
: Number of APs found.scan_id
: ID of the scan.
Always consult the ESP-IDF documentation or header files for the exact structure associated with a particular event. Incorrectly casting or accessing event_data
can lead to crashes or undefined behavior.
Event (Base + ID) | Associated Data Structure Type | Key Members in Structure |
---|---|---|
WIFI_EVENT + WIFI_EVENT_STA_START |
N/A (No specific event data structure, event_data is typically NULL) |
Indicates WiFi station mode has started. Handler usually calls esp_wifi_connect() . |
WIFI_EVENT + WIFI_EVENT_STA_CONNECTED |
wifi_event_sta_connected_t* |
– ssid: SSID of the connected AP. – ssid_len: Length of SSID. – bssid: BSSID (MAC address) of AP. – channel: Channel of AP. – authmode: Authentication mode used. |
WIFI_EVENT + WIFI_EVENT_STA_DISCONNECTED |
wifi_event_sta_disconnected_t* |
– ssid: SSID of the disconnected AP. – ssid_len: Length of SSID. – bssid: BSSID of AP. – reason: Numeric code for disconnection reason (e.g., WIFI_REASON_AUTH_FAIL , WIFI_REASON_NO_AP_FOUND ). Crucial for diagnostics.
|
IP_EVENT + IP_EVENT_STA_GOT_IP |
ip_event_got_ip_t* |
– ip_info: (type esp_netif_ip_info_t ) Contains:– ip: IP address. – netmask: Subnet mask. – gw: Gateway address. – ip_changed: Boolean, true if IP changed (e.g., DHCP renewal). |
IP_EVENT + IP_EVENT_STA_LOST_IP |
N/A (No specific event data structure, event_data is typically NULL) |
Indicates the station has lost its IP address. |
WIFI_EVENT + WIFI_EVENT_SCAN_DONE |
wifi_event_sta_scan_done_t* |
– status: Scan completion status (0 for success). – number: Number of APs found. – scan_id: ID of the scan operation. |
WIFI_EVENT + WIFI_EVENT_AP_START |
N/A (No specific event data structure) | Indicates WiFi AP mode has started. |
WIFI_EVENT + WIFI_EVENT_AP_STACONNECTED |
wifi_event_ap_staconnected_t* |
– mac: MAC address of the connected station. – aid: Association ID of the station. |
WIFI_EVENT + WIFI_EVENT_AP_STADISCONNECTED |
wifi_event_ap_stadisconnected_t* |
– mac: MAC address of the disconnected station. – aid: Association ID of the station. |
Practical Examples
Let’s explore how to create and use custom event handlers.
Example 1: Comprehensive WiFi and IP Event Logger
This example registers a single handler function that logs details for various common WiFi and IP station events.
1. Project Setup:
- Create a new ESP-IDF project.
- Ensure basic WiFi station functionality can be set up (no need for specific WPA3/Enterprise configs for this example, a simple open or WPA2-PSK network is fine for testing event flow).
2. Code Implementation:
// main.c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h" // For potential synchronization, not strictly used here
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
static const char *TAG = "CUSTOM_EVENT_HANDLER";
// Configuration: Replace with your network credentials
#define EXAMPLE_ESP_WIFI_SSID "YOUR_SSID"
#define EXAMPLE_ESP_WIFI_PASS "YOUR_PASSWORD"
#define EXAMPLE_ESP_WIFI_AUTHMODE WIFI_AUTH_WPA2_PSK // Or WIFI_AUTH_OPEN, etc.
// Custom event handler function
static void wifi_ip_event_handler(void* handler_arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
// handler_arg can be used to pass custom context if needed
// For this example, we'll cast it to a char* to show usage, though it's NULL here.
char* custom_tag = (char*) handler_arg;
if (custom_tag) {
ESP_LOGI(custom_tag, "Event dispatched from base %s with ID %ld", event_base, event_id);
}
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "WIFI_EVENT_STA_START: WiFi station mode started. Connecting...");
esp_wifi_connect(); // Initiate connection
break;
case WIFI_EVENT_STA_CONNECTED: {
wifi_event_sta_connected_t* event = (wifi_event_sta_connected_t*) event_data;
ESP_LOGI(TAG, "WIFI_EVENT_STA_CONNECTED: Connected to AP SSID:'%s', Channel:%d, BSSID:" MACSTR,
event->ssid, event->channel, MAC2STR(event->bssid));
// You could store BSSID or other info here if needed
break;
}
case WIFI_EVENT_STA_DISCONNECTED: {
wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*) event_data;
ESP_LOGW(TAG, "WIFI_EVENT_STA_DISCONNECTED: Disconnected from AP SSID:'%s', Reason:%d",
(char*)event->ssid, event->reason);
// Detailed reason code handling can be added here
// For example, specific actions for WIFI_REASON_AUTH_FAIL vs WIFI_REASON_NO_AP_FOUND
// For now, just log and attempt reconnect
ESP_LOGI(TAG, "Attempting to reconnect...");
esp_wifi_connect();
break;
}
case WIFI_EVENT_SCAN_DONE: {
wifi_event_sta_scan_done_t* scan_data = (wifi_event_sta_scan_done_t*) event_data;
ESP_LOGI(TAG, "WIFI_EVENT_SCAN_DONE: Scan finished. Status: %d, Number of APs found: %d",
scan_data->status, scan_data->number);
// If scan_data->number > 0, you can retrieve scan results using esp_wifi_scan_get_ap_records()
break;
}
// Add cases for other WIFI_EVENTs as needed (e.g., AP mode events)
default:
ESP_LOGD(TAG, "Unhandled WIFI_EVENT: ID %ld", event_id);
break;
}
} else if (event_base == IP_EVENT) {
switch (event_id) {
case IP_EVENT_STA_GOT_IP: {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP: Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, " Netmask: " IPSTR, IP2STR(&event->ip_info.netmask));
ESP_LOGI(TAG, " Gateway: " IPSTR, IP2STR(&event->ip_info.gw));
// Signal application that connection is fully up
break;
}
case IP_EVENT_STA_LOST_IP:
ESP_LOGW(TAG, "IP_EVENT_STA_LOST_IP: Lost IP address.");
// Application might need to handle this, e.g., stop network services
break;
// Add cases for other IP_EVENTs as needed
default:
ESP_LOGD(TAG, "Unhandled IP_EVENT: ID %ld", event_id);
break;
}
} else {
ESP_LOGW(TAG, "Received event from unknown base: %s, ID: %ld", event_base, event_id);
}
}
void wifi_init_sta_custom_handler(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default()); // Ensure default event loop is created
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Register our custom event handler for ALL WIFI_EVENTs and specific IP_EVENTs
// The handler_arg (last parameter) is NULL here, but can be any void pointer.
// We could pass a tag string like (void*)"MyWiFiHandler"
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_ip_event_handler, (void*)"WiFiLogger"));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_ip_event_handler, (void*)"IPLogger"));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_LOST_IP, &wifi_ip_event_handler, (void*)"IPLogger"));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
.threshold.authmode = EXAMPLE_ESP_WIFI_AUTHMODE,
.pmf_cfg = { // Default PMF for WPA2, adjust for WPA3 if needed
.capable = true,
.required = false
},
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_LOGI(TAG, "Starting WiFi station mode...");
ESP_ERROR_CHECK(esp_wifi_start()); // This will trigger WIFI_EVENT_STA_START
ESP_LOGI(TAG, "wifi_init_sta_custom_handler finished.");
}
void app_main(void)
{
// 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);
ESP_LOGI(TAG, "ESP32 Custom WiFi Event Handler Example");
wifi_init_sta_custom_handler();
// Main task can now do other things.
// For demonstration, we'll just keep it alive.
while(1) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
3. Build, Flash, and Observe:
- Build and flash the project.
- Open the serial monitor.
- Observe the detailed logs as the ESP32 goes through WiFi states: starting, connecting, getting an IP. Try disconnecting the AP or entering wrong credentials to see disconnect events and reasons.
Example 2: Disconnect Reason Analysis and Custom Retry
This example builds on the first by adding more specific logic for WIFI_EVENT_STA_DISCONNECTED
, including a limited retry mechanism.
// main.c (modify parts of Example 1)
// ... (includes, TAG, SSID/PASS defines remain the same) ...
static EventGroupHandle_t s_wifi_event_group; // For signaling connection success/failure
const int WIFI_CONNECTED_BIT = BIT0;
const int WIFI_FAIL_BIT = BIT1; // Used if retries are exhausted
static int s_retry_num = 0;
#define MAX_RETRY_ATTEMPTS 5
// Custom context structure
typedef struct {
const char* handler_name;
int* retry_counter_ptr; // Pointer to a shared retry counter
} custom_handler_args_t;
// Modified event handler
static void advanced_wifi_event_handler(void* handler_arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
custom_handler_args_t* custom_args = (custom_handler_args_t*) handler_arg;
ESP_LOGI(custom_args->handler_name, "Event: base=%s, id=%ld", event_base, event_id);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
ESP_LOGI(TAG, "WIFI_EVENT_STA_START: Initiating connection...");
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
wifi_event_sta_connected_t* event = (wifi_event_sta_connected_t*) event_data;
ESP_LOGI(TAG, "WIFI_EVENT_STA_CONNECTED to SSID:'%s'", event->ssid);
s_retry_num = 0; // Reset retry counter on successful connection
if (s_wifi_event_group) xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT);
// IP_EVENT_STA_GOT_IP will set WIFI_CONNECTED_BIT
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*) event_data;
ESP_LOGW(TAG, "WIFI_EVENT_STA_DISCONNECTED from SSID:'%s', reason:%d", (char*)event->ssid, event->reason);
if (s_wifi_event_group) xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
bool should_retry = false;
switch (event->reason) {
case WIFI_REASON_ASSOC_LEAVE: // Disconnected by AP command or self
ESP_LOGI(TAG, "Disconnected by AP or self. Not retrying automatically.");
should_retry = false;
// Potentially signal app to decide next steps
if (s_wifi_event_group) xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
break;
case WIFI_REASON_AUTH_FAIL:
ESP_LOGE(TAG, "Authentication failed. Check credentials. Not retrying.");
should_retry = false;
if (s_wifi_event_group) xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
break;
case WIFI_REASON_NO_AP_FOUND:
case WIFI_REASON_ASSOC_EXPIRE:
case WIFI_REASON_HANDSHAKE_TIMEOUT:
case WIFI_REASON_CONNECTION_FAIL:
case WIFI_REASON_SAE_HANDSHAKE_FAILED: // Example: Retry for SAE issues too
ESP_LOGW(TAG, "Connection issue (reason %d). Will attempt retry.", event->reason);
should_retry = true;
break;
default:
ESP_LOGI(TAG, "Unhandled disconnect reason: %d. Retrying by default.", event->reason);
should_retry = true;
break;
}
if (should_retry) {
if (*(custom_args->retry_counter_ptr) < MAX_RETRY_ATTEMPTS) {
(*(custom_args->retry_counter_ptr))++;
ESP_LOGI(TAG, "Retrying connection (attempt %d/%d)...", *(custom_args->retry_counter_ptr), MAX_RETRY_ATTEMPTS);
// Add a small delay before retrying to avoid spamming the AP
vTaskDelay(pdMS_TO_TICKS(2000)); // Delay 2 seconds
esp_wifi_connect();
} else {
ESP_LOGE(TAG, "Max retry attempts (%d) reached. Giving up.", MAX_RETRY_ATTEMPTS);
if (s_wifi_event_group) xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP: Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
*(custom_args->retry_counter_ptr) = 0; // Reset retry counter on full success
if (s_wifi_event_group) xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
// ... other events from Example 1 can be merged here ...
}
// Global instance of custom arguments for the handler
static custom_handler_args_t s_handler_ctx;
void wifi_init_sta_advanced_handler(void)
{
s_wifi_event_group = xEventGroupCreate(); // Create event group
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Prepare custom context for the handler
s_handler_ctx.handler_name = "AdvancedWiFiHandler";
s_handler_ctx.retry_counter_ptr = &s_retry_num; // Pass pointer to global retry counter
// Register handler with custom context
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &advanced_wifi_event_handler, &s_handler_ctx));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &advanced_wifi_event_handler, &s_handler_ctx));
wifi_config_t wifi_config = { /* ... same as Example 1 ... */
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
.threshold.authmode = EXAMPLE_ESP_WIFI_AUTHMODE,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_LOGI(TAG, "Starting WiFi station mode (Advanced Handler)...");
ESP_ERROR_CHECK(esp_wifi_start()); // This will trigger WIFI_EVENT_STA_START
}
void app_main(void) // Rename to app_main if using this example
{
// NVS Init
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);
ESP_LOGI(TAG, "ESP32 Advanced WiFi Event Handler Example");
wifi_init_sta_advanced_handler();
// Wait for connection or permanent failure
ESP_LOGI(TAG, "Waiting for WiFi connection or failure after retries...");
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Application signaled: WiFi Connected!");
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "Application signaled: WiFi Failed after retries or due to critical error.");
}
// ... application logic ...
while(1) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
Key changes in Example 2:
- Uses an event group (
s_wifi_event_group
) to signal connection success or permanent failure toapp_main
. - A global retry counter
s_retry_num
and aMAX_RETRY_ATTEMPTS
define. - A
custom_handler_args_t
struct is defined to pass multiple arguments (a name string and a pointer to the retry counter) to the event handler. - The
WIFI_EVENT_STA_DISCONNECTED
handler now analyzesevent->reason
more closely. - It implements a simple retry mechanism with a delay for certain disconnect reasons.
IP_EVENT_STA_GOT_IP
andWIFI_EVENT_STA_CONNECTED
reset the retry counter.
Variant Notes
The ESP-IDF event handling mechanism (esp_event.h
) and the WiFi/IP specific events are consistent across all ESP32 variants that support WiFi:
- ESP32
- ESP32-S2
- ESP32-S3
- ESP32-C3
- ESP32-C6
There are no known variant-specific differences in how custom event handlers are registered, how event data is structured for common WiFi/IP events, or the general behavior of the default event loop concerning these events. The core APIs and event types are standardized within ESP-IDF for these chips.
ESP32-H2 does not support WiFi and therefore, handling WIFI_EVENT
or IP_EVENT
related to WiFi station/AP modes is not applicable. It would handle other system events relevant to its peripherals and connectivity options (like Thread, Bluetooth).
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Blocking Operations in Handler | System becomes unresponsive, watchdog timer resets, other events delayed or missed. WiFi connection might time out. | Fix: Keep handlers short. Offload lengthy processing (file I/O, complex calculations, long delays) to separate FreeRTOS tasks. Use queues, event group bits, or semaphores to signal these tasks from the handler. Short, non-blocking delays (e.g., for retries) are sometimes okay but use with caution. |
Incorrect event_data Casting |
Crashes (Guru Meditation Error), incorrect data interpretation, or undefined behavior when accessing members of the casted structure. |
Fix: Verify the correct data structure type for each event_base and event_id pair by checking ESP-IDF documentation (e.g., esp_wifi_types.h , esp_event_defs.h ). Log event_base and event_id inside the handler to confirm.
|
Not Registering Handler or Forgetting Default Loop Init | Event handler function is never called. No response to WiFi/IP events. |
Fix: Ensure esp_event_loop_create_default() is called (often by esp_netif_init() or explicitly). Confirm esp_event_handler_register() or esp_event_handler_instance_register() is called with the correct event_base , event_id , and function pointer.
|
Accessing Shared Data Without Synchronization | Data corruption, race conditions, unpredictable behavior if global variables or shared resources are modified by the handler and also accessed by other tasks. | Fix: Use FreeRTOS synchronization primitives (mutexes, semaphores, critical sections) if shared data is accessed by multiple tasks. For simple flags/counters mainly set by the event handler (which runs in a single task context for default loop), careful design or atomic operations might be sufficient. |
Infinite Retry Loops | Device continuously tries to reconnect for unrecoverable errors (e.g., wrong password – WIFI_REASON_AUTH_FAIL ), draining power and potentially spamming logs/network. |
Fix: Analyze the disconnect reason in WIFI_EVENT_STA_DISCONNECTED . Only retry for transient errors. Implement a maximum retry count. After max retries, stop, log error, and potentially enter a safe/fallback state.
|
Unregistering a Non-Existent or Incorrect Handler | esp_event_handler_unregister() or esp_event_handler_instance_unregister() might return an error (e.g., ESP_ERR_NOT_FOUND ) or have no effect. |
Fix: Ensure the parameters passed to unregister functions (event_base, event_id, handler function pointer, or instance handle) exactly match those used during registration. Keep track of registered handler instances if using instance-specific unregistration. |
Misinterpreting handler_arg Context |
Casting handler_arg to an incorrect type or accessing it when it was registered as NULL . |
Fix: Ensure the type used to cast handler_arg inside the handler matches the type of the argument passed during registration. If no context is passed (NULL ), do not attempt to dereference it.
|
Exercises
- LED Status Indicator:
- Create an event handler that controls an LED (or multiple LEDs) to indicate WiFi status:
- LED OFF: WiFi not started or disconnected (after retries).
- LED Blinking Slowly: WiFi started, attempting to connect/reconnect.
- LED Blinking Fast:
WIFI_EVENT_STA_CONNECTED
received, waiting for IP. - LED Solid ON:
IP_EVENT_STA_GOT_IP
received (fully connected).
- Create an event handler that controls an LED (or multiple LEDs) to indicate WiFi status:
- Disconnect Reason Logging to NVS:
- Modify the
WIFI_EVENT_STA_DISCONNECTED
handler. When a disconnect occurs, log thereason
code and a timestamp (if SNTP is implemented, or just a boot counter) to a specific NVS key. - On boot, read and display the last few disconnect reasons from NVS to help diagnose persistent issues. Be mindful of NVS wear if disconnects are frequent.
- Modify the
- Dynamic Handler Unregistration:
- Create a scenario where a WiFi event handler is initially registered. After a certain condition is met (e.g., successful connection and data exchange, or a specific user command via UART), unregister the detailed event logger using
esp_event_handler_instance_unregister
oresp_event_handler_unregister
. This can be useful to reduce logging verbosity after initial setup.
- Create a scenario where a WiFi event handler is initially registered. After a certain condition is met (e.g., successful connection and data exchange, or a specific user command via UART), unregister the detailed event logger using
- Task Notification on Connection:
- Create a separate FreeRTOS task that waits for a direct task notification.
- In your
IP_EVENT_STA_GOT_IP
handler, after successfully obtaining an IP, send a notification to this dedicated task usingxTaskNotifyGive()
. - The waiting task, upon receiving the notification (
ulTaskNotifyTake()
), proceeds to perform some network-dependent action (e.g., make an HTTP request).
Summary
- Custom event handlers are essential for creating responsive and robust ESP32 WiFi applications.
- The ESP-IDF event system uses event loops, event bases (sources), event IDs, and event data.
esp_event_handler_register
(and its instance variant) is used to subscribe functions to specific events.- Event handlers execute in the context of the event loop’s task (typically the default system event task for WiFi/IP). They must be fast and non-blocking.
- Event-specific data (
event_data
) provides crucial details and must be cast correctly. - Analyzing disconnect reasons (
wifi_event_sta_disconnected_t.reason
) allows for intelligent retry mechanisms and diagnostics. - Passing custom context (
handler_arg
) to handlers enables stateful operations and better code organization. - The event handling APIs are consistent across WiFi-enabled ESP32 variants.
Further Reading
- ESP-IDF Event Loop Library: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/system/esp_event.html
- ESP-IDF Wi-Fi Driver API Reference (esp_wifi.h): https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_wifi.html (Check for event types and associated data structures).
- ESP-IDF LwIP TCP/IP Stack API Reference (esp_netif.h): For IP_EVENT definitions.
- FreeRTOS Documentation: For understanding task communication (queues, event groups, notifications) if offloading work from handlers.
