Chapter 32: WiFi AP Client Management
Chapter Objectives
By the end of this chapter, you will be able to:
- Retrieve a list of stations currently connected to the ESP32 Access Point.
- Extract information about connected stations, such as their MAC address and RSSI.
- Understand how to correlate MAC addresses with IP addresses assigned by the DHCP server.
- Manually disconnect (deauthenticate) a specific station from the AP.
- Implement basic monitoring of client connections in AP mode.
Introduction
In the previous chapter, we successfully configured the ESP32 to act as a WiFi Access Point (AP), allowing other devices to connect to it. We saw how the WIFI_EVENT_AP_STACONNECTED
and WIFI_EVENT_AP_STADISCONNECTED
events notify us when clients join or leave.
However, simply knowing that clients connect or disconnect is often not enough. For many applications, especially those involving provisioning, local control, or security monitoring, we need more detailed information and control. How many devices are currently connected? What are their MAC addresses? How strong is their signal? Can we manually remove a specific device?

This chapter explores the ESP-IDF functions available for managing the stations connected to your ESP32 SoftAP. We’ll learn how to query the list of connected clients, retrieve basic information about them, and even programmatically disconnect a client if necessary. This provides greater visibility and control over the network hosted by your ESP32.
Theory
Monitoring Connections with Events
As covered previously, the primary way to react immediately to connection changes is through events:
WIFI_EVENT_AP_STACONNECTED
: Fires the moment a station successfully completes the association process with the ESP32 AP.event_data
: Pointer towifi_event_ap_staconnected_t
.- Key fields:
mac
(uint8_t[6] – MAC address of the client),aid
(uint8_t – Association ID assigned by the AP).
WIFI_EVENT_AP_STADISCONNECTED
: Fires when a station disconnects or is disconnected.event_data
: Pointer towifi_event_ap_stadisconnected_t
.- Key fields:
mac
(uint8_t[6] – MAC address of the client),aid
(uint8_t – Association ID).
Handling these events allows you to maintain a real-time count or list of connected devices within your application logic.
Getting the List of Connected Stations
While events tell you about changes, you often need to know the current state – which stations are connected right now? For this, ESP-IDF provides the esp_wifi_ap_get_sta_list()
function.
Function Signature:
esp_err_t esp_wifi_ap_get_sta_list(wifi_sta_list_t *sta);
sta
: A pointer to awifi_sta_list_t
structure that will be filled by the function. You must provide the memory for this structure.- Return Value:
ESP_OK
on success, or an error code on failure.
wifi_sta_list_t
Structure:
typedef struct {
wifi_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM]; // Array of station info structures
int num; // Number of stations currently connected
} wifi_sta_list_t;
sta
: An array capable of holding information for the maximum possible number of connected stations (defined byESP_WIFI_MAX_CONN_NUM
, which depends on the Kconfig settingCONFIG_ESP_WIFI_SOFTAP_MAX_CONN
).num
: The actual number of stations currently connected. This tells you how many elements in thesta
array contain valid data. Always checknum
before iterating through the array.
wifi_sta_info_t
Structure:
typedef struct {
uint8_t mac[6]; // MAC address of the station
int8_t rssi; // Received Signal Strength Indicator (RSSI) of the station (in dBm)
uint32_t phy_11b: 1; /**< bit: 0 flag to identify if 11b mode is enabled or not */
uint32_t phy_11g: 1; /**< bit: 1 flag to identify if 11g mode is enabled or not */
uint32_t phy_11n: 1; /**< bit: 2 flag to identify if 11n mode is enabled or not */
uint32_t phy_lr: 1; /**< bit: 3 flag to identify if low rate is enabled or not */
uint32_t reserved: 28; /**< bit: 4..31 reserved */
} wifi_sta_info_t;
mac
: The unique 6-byte MAC address of the connected station.rssi
: The signal strength of the client as received by the ESP32 AP. A higher value (closer to 0) indicates a stronger signal (e.g., -50 dBm is stronger than -80 dBm). This can be useful for diagnosing connection quality issues.phy_*
flags: Indicate the WiFi PHY modes supported or used by the connected station (802.11b, 802.11g, 802.11n, Low Rate).
Structures for Connected Station Information | ||
---|---|---|
Field Name | Data Type | Description |
wifi_sta_list_t (Container for station info) |
||
sta |
wifi_sta_info_t[ESP_WIFI_MAX_CONN_NUM] |
An array of wifi_sta_info_t structures, one for each potential connected station. ESP_WIFI_MAX_CONN_NUM depends on Kconfig CONFIG_ESP_WIFI_SOFTAP_MAX_CONN . |
num |
int |
The actual number of stations currently connected to the AP. Use this to iterate through the sta array. |
wifi_sta_info_t (Information for a single connected station) |
||
mac |
uint8_t[6] |
The 6-byte MAC address of the connected station. |
rssi |
int8_t |
The Received Signal Strength Indicator (RSSI) of the station’s signal as received by the ESP32 AP, in dBm (e.g., -50 dBm is stronger than -80 dBm). |
phy_11b |
uint32_t (bit field: 1) |
Flag indicating if the station supports/uses 802.11b PHY mode. |
phy_11g |
uint32_t (bit field: 1) |
Flag indicating if the station supports/uses 802.11g PHY mode. |
phy_11n |
uint32_t (bit field: 1) |
Flag indicating if the station supports/uses 802.11n PHY mode. |
phy_lr |
uint32_t (bit field: 1) |
Flag indicating if the station supports/uses Low Rate PHY mode. |
reserved |
uint32_t (bit field: 28) |
Reserved bits for future use. |
Typical Usage:
wifi_sta_list_t station_list;
// Zero out the structure for safety, although the function should fill it.
memset(&station_list, 0, sizeof(station_list));
esp_err_t err = esp_wifi_ap_get_sta_list(&station_list);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Number of connected stations: %d", station_list.num);
for (int i = 0; i < station_list.num; i++) {
wifi_sta_info_t station_info = station_list.sta[i];
ESP_LOGI(TAG, " Station %d:", i + 1);
ESP_LOGI(TAG, " MAC: " MACSTR, MAC2STR(station_info.mac));
ESP_LOGI(TAG, " RSSI: %d dBm", station_info.rssi);
}
} else {
ESP_LOGE(TAG, "Failed to get station list: %s", esp_err_to_name(err));
}
%%{ init: { 'flowchart': { 'curve': 'basis' } } }%% graph TD %% Node Styles classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef datastruct fill:#E0F2FE,stroke:#0EA5E9,stroke-width:1px,color:#0C4A6E; %% For data structures classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef fail fill:#FECACA,stroke:#B91C1C,stroke-width:2px,color:#7F1D1D; A[<b>Start</b>: Need List of Connected Stations]:::primary B["Declare <code class='code-like'>wifi_sta_list_t station_list;</code>"]:::datastruct C["Optional: <code class='code-like'>memset(&station_list, 0, sizeof(station_list));</code>"]:::process D["Call <code class='code-like'>esp_wifi_ap_get_sta_list(&station_list);</code>"]:::process E{Return == <code class='code-like'>ESP_OK</code>?}:::decision F["Log/Handle Error<br>(e.g., <code class='code-like'>esp_err_to_name()</code>)"]:::fail G["Access <code class='code-like'>station_list.num</code><br>to get count of connected stations"]:::process H{<code class='code-like'>station_list.num</code> > 0?}:::decision I["Loop <code class='code-like'>i</code> from 0 to <code class='code-like'>station_list.num - 1</code>"]:::process J["Access <code class='code-like'>station_list.sta[i]</code><br>(<code class='code-like'>wifi_sta_info_t</code> structure)"]:::datastruct K["Process Station Info:<br>- <code class='code-like'>station_list.sta[i].mac</code><br>- <code class='code-like'>station_list.sta[i].rssi</code><br>- PHY flags"]:::process L["No stations connected or<br>iteration complete."]:::success M["End of Loop"]:::process A --> B; B --> C; C --> D; D --> E; E -- "Yes" --> G; E -- "No" --> F; G --> H; H -- "Yes" --> I; H -- "No" --> L; I --> J; J --> K; K --> I; I -- "Loop Finished" --> M; M --> L;
Mapping MAC to IP Address
esp_wifi_ap_get_sta_list()
gives you MAC addresses, but often you need the IP address assigned to a client by the ESP32’s DHCP server. There isn’t a single function that directly combines this information. You need to query the DHCP server’s lease information.
The esp_netif
layer manages the DHCP server associated with the AP interface. You can query the DHCP lease information.
Method | Key Function(s) | Pros | Cons / Considerations |
---|---|---|---|
Query Netif Client List (Recommended) | esp_netif_get_sta_list() |
Provides both MAC and IP for all DHCP clients. Relatively straightforward. Standardized. | Requires iterating through the returned list to find a specific MAC if needed. The returned list is from the netif/DHCP perspective. |
Query DHCP by MAC (If available) | esp_netif_dhcps_get_clients_by_mac() |
Directly queries for the IP of one or more specific MAC addresses. Efficient if you know the MAC. | API availability might vary across ESP-IDF versions; check documentation. |
Iterate All DHCP Leases (Conceptual) | Internal DHCP server APIs (e.g., dhcps_lease_t , specific getter functions if exposed). |
Gives full access to raw lease data if needed for advanced scenarios. | Less standardized public API across all IDF versions. May involve using less stable internal APIs. More complex to implement correctly. |
Maintain Own List via Events | Handle WIFI_EVENT_AP_STACONNECTED and try to get IP (e.g., after a short delay or on first query). |
Application has full control over the data structure. | IP address might not be available immediately upon STACONNECTED. Requires careful state management and synchronization. More prone to timing issues. |
Method 1: Using esp_netif_dhcps_get_clients_by_mac
(Recommended when available)
Note: This function might not be present or stable in all ESP-IDF versions. Check the documentation for your specific version.
#include "esp_netif_defaults.h" // May be needed for DHCP functions
// Assume 'ap_netif' is the handle obtained from esp_netif_create_default_wifi_ap()
// Assume 'target_mac' is the MAC address you want to find the IP for
esp_netif_pair_mac_ip_t client_info;
memcpy(client_info.mac, target_mac, 6); // Set the MAC we are looking for
esp_err_t err = esp_netif_dhcps_get_clients_by_mac(ap_netif, 1, &client_info);
if (err == ESP_OK) {
ESP_LOGI(TAG, "IP address for MAC " MACSTR " is " IPSTR,
MAC2STR(client_info.mac), IP2STR(&client_info.ip));
} else if (err == ESP_ERR_NOT_FOUND) {
ESP_LOGW(TAG, "No DHCP lease found for MAC " MACSTR, MAC2STR(target_mac));
} else {
ESP_LOGE(TAG, "Error querying DHCP lease for MAC " MACSTR ": %s",
MAC2STR(target_mac), esp_err_to_name(err));
}
Method 2: Iterating through DHCP Leases (More General)
You can retrieve the entire list of DHCP leases and iterate through it to find the IP associated with a specific MAC.
#include "esp_netif_defaults.h" // For dhcps functions
#include "dhcpserver/dhcpserver.h" // For dhcps_lease_t (may vary slightly by IDF version)
// Assume 'ap_netif' is the handle obtained from esp_netif_create_default_wifi_ap()
dhcps_lease_t lease_info;
// Note: The exact API to get all leases might vary.
// Check esp_netif.h or dhcpserver.h for functions like esp_netif_dhcps_get_lease,
// or potentially iterate using internal structures if no public API exists.
// This part is less standardized across all versions than getting the STA list.
// --- Conceptual Example (API may differ) ---
// Let's assume a hypothetical function esp_netif_dhcps_get_all_leases exists:
// dhcps_lease_info_t* all_leases = NULL;
// int num_leases = 0;
// esp_err_t err = esp_netif_dhcps_get_all_leases(ap_netif, &all_leases, &num_leases);
// if (err == ESP_OK) {
// for (int i = 0; i < num_leases; i++) {
// ESP_LOGI(TAG, "Lease %d: MAC=" MACSTR ", IP=" IPSTR, i,
// MAC2STR(all_leases[i].mac), IP2STR(&all_leases[i].ip));
// // Compare all_leases[i].mac with the MAC from the STA list
// }
// // Remember to free 'all_leases' if allocated by the function
// }
// --- Alternative: Using esp_netif_get_list_of_clients (if available) ---
esp_netif_sta_list_t netif_sta_list; // Note: Different struct from wifi_sta_list_t
wifi_sta_mac_ip_list_t client_list_buffer; // Buffer to hold client info
uint8_t num_clients = ESP_WIFI_MAX_CONN_NUM; // Max buffer size
// Link the buffer to the list structure
netif_sta_list.num = num_clients;
netif_sta_list.sta = &client_list_buffer;
// Get the list of clients (MAC and IP) from the netif layer
esp_err_t err = esp_netif_get_sta_list(&netif_sta_list, &num_clients);
if (err == ESP_OK) {
ESP_LOGI(TAG, "DHCP Clients (%d):", num_clients);
for (int i = 0; i < num_clients; i++) {
esp_netif_sta_info_t* client = &netif_sta_list.sta[i];
ESP_LOGI(TAG, " Client %d: MAC=" MACSTR ", IP=" IPSTR, i + 1,
MAC2STR(client->mac), IP2STR(&client->ip));
}
} else {
ESP_LOGE(TAG, "Failed to get netif client list: %s", esp_err_to_name(err));
}
Tip: The
esp_netif_get_sta_list
approach seems more aligned with recent ESP-IDF versions for getting both MAC and IP. Always consult the specific API documentation for your ESP-IDF version.
Manually Disconnecting a Station
In some cases, you might want to force a specific client to disconnect from your AP (e.g., for security reasons, or to enforce connection limits). The function esp_wifi_deauth_sta()
allows you to do this.
Function Signature:
esp_err_t esp_wifi_deauth_sta(const uint8_t *mac);
mac
: A pointer to the 6-byte MAC address of the station you want to disconnect.- Return Value:
ESP_OK
if the deauthentication frame was queued successfully, or an error code. Note that success here doesn’t guarantee the client received the frame or acted upon it, only that the ESP32 attempted to send it.
%%{ init: { 'flowchart': { 'curve': 'basis' } } }%% graph TD %% Node Styles classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef event fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef fail fill:#FECACA,stroke:#B91C1C,stroke-width:2px,color:#7F1D1D; A[<b>Start</b>: Need to Disconnect a Station]:::primary B["Obtain Target Station's MAC Address<br>(e.g., from <code class='code-like'>wifi_sta_list_t</code> or other source)"]:::process C["Store MAC in <code class='code-like'>uint8_t mac_to_disconnect[6];</code>"]:::process D["Call <code class='code-like'>esp_wifi_deauth_sta(mac_to_disconnect);</code>"]:::process E{Return == <code class='code-like'>ESP_OK</code>?}:::decision F["Log/Handle Error<br>(e.g., <code class='code-like'>esp_err_to_name()</code>)"]:::fail G["Log: Deauthentication frame sent successfully."]:::success H["Monitor for <code class='code-like'>WIFI_EVENT_AP_STADISCONNECTED</code><br>event with matching MAC address in event handler."]:::event I["Station is (likely) disconnected.<br>Client might attempt to reconnect."]:::process A --> B; B --> C; C --> D; D --> E; E -- "Yes" --> G; E -- "No" --> F; G --> H; H --> I;
Usage:
// Assume 'mac_to_disconnect' holds the 6-byte MAC address
ESP_LOGI(TAG, "Attempting to deauthenticate station " MACSTR "...", MAC2STR(mac_to_disconnect));
esp_err_t err = esp_wifi_deauth_sta(mac_to_disconnect);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Deauthentication frame sent successfully.");
// Expect a WIFI_EVENT_AP_STADISCONNECTED event shortly after for this MAC.
} else {
ESP_LOGE(TAG, "Failed to send deauthentication frame: %s", esp_err_to_name(err));
}
Warning: Use
esp_wifi_deauth_sta()
judiciously. Kicking clients off unnecessarily can create a poor user experience. It’s typically used for specific administrative or security actions. The client might attempt to reconnect immediately after being deauthenticated.
ESP32 AP Client Management Functions Table
Function Name | Key Parameter(s) | Return Value | Purpose & Brief Description |
---|---|---|---|
esp_wifi_ap_get_sta_list() |
wifi_sta_list_t *sta (output) |
esp_err_t |
Retrieves a list of all stations currently connected to the ESP32 AP. Fills the provided wifi_sta_list_t structure with station information (MAC, RSSI, etc.) and the count of connected stations. |
esp_wifi_deauth_sta() |
const uint8_t *mac (input) |
esp_err_t |
Sends a deauthentication frame to the specified station (client MAC address), requesting it to disconnect from the ESP32 AP. Success indicates the frame was queued, not necessarily that the client disconnected. |
esp_netif_get_sta_list() |
esp_netif_sta_list_t *netif_sta_list (input/output), uint8_t *num (output) |
esp_err_t |
Retrieves a list of stations (MAC and IP addresses) that have obtained an IP lease from the DHCP server running on the specified network interface (typically the AP’s netif). This is useful for MAC-to-IP mapping. |
esp_netif_dhcps_get_clients_by_mac() |
esp_netif_t *esp_netif , uint8_t num_mac , esp_netif_pair_mac_ip_t *client_mac_ip (input/output) |
esp_err_t |
(Check availability in IDF version) Queries DHCP server leases for specific MAC addresses to get their corresponding IP addresses. More targeted than getting the full list. |
Practical Examples
Let’s enhance the AP example from Chapter 31 to periodically list connected stations and their RSSI.
Prerequisites:
- ESP-IDF v5.x environment with VS Code.
- Project code from Chapter 31 (Basic WPA2-PSK Access Point).
Example 1: Periodically Listing Connected Stations
We’ll create a simple task that wakes up periodically, gets the station list, and logs the information.
1. Add a Task Function:
// Add near the top with other includes
#include "freertos/timers.h" // Or use vTaskDelay in a task loop
// Task function to periodically list stations
void list_stations_task(void *pvParameter) {
ESP_LOGI(TAG, "Starting station listing task...");
wifi_sta_list_t station_list;
while (1) {
// Clear the structure before each call
memset(&station_list, 0, sizeof(station_list));
// Get the station list
esp_err_t err = esp_wifi_ap_get_sta_list(&station_list);
if (err == ESP_OK) {
ESP_LOGI(TAG, "--------------------");
ESP_LOGI(TAG, "Connected Stations (%d):", station_list.num);
for (int i = 0; i < station_list.num; i++) {
wifi_sta_info_t station_info = station_list.sta[i];
ESP_LOGI(TAG, " [%d] MAC: " MACSTR ", RSSI: %d dBm",
i, MAC2STR(station_info.mac), station_info.rssi);
// --- Optional: Add IP lookup here using esp_netif_get_sta_list ---
esp_netif_sta_list_t netif_sta_list;
wifi_sta_mac_ip_list_t client_list_buffer;
uint8_t num_clients = ESP_WIFI_MAX_CONN_NUM;
netif_sta_list.num = num_clients;
netif_sta_list.sta = &client_list_buffer;
esp_err_t netif_err = esp_netif_get_sta_list(&netif_sta_list, &num_clients);
if (netif_err == ESP_OK) {
bool ip_found = false;
for (int j = 0; j < num_clients; j++) {
// Compare MAC from WiFi list with MAC from Netif list
if (memcmp(station_info.mac, netif_sta_list.sta[j].mac, 6) == 0) {
ESP_LOGI(TAG, " IP: " IPSTR, IP2STR(&netif_sta_list.sta[j].ip));
ip_found = true;
break; // Found the IP for this MAC
}
}
if (!ip_found) {
ESP_LOGI(TAG, " IP: (Not found in DHCP leases)");
}
} else {
ESP_LOGW(TAG, " IP: (Error getting DHCP list: %s)", esp_err_to_name(netif_err));
}
// --- End Optional IP lookup ---
}
ESP_LOGI(TAG, "--------------------");
} else {
ESP_LOGE(TAG, "Failed to get station list: %s", esp_err_to_name(err));
}
// Wait for the next check
vTaskDelay(pdMS_TO_TICKS(15000)); // Check every 15 seconds
}
}
2. Start the Task in app_main
:
/* --- Main Application --- */
void app_main(void)
{
ESP_LOGI(TAG, "Starting ESP32 SoftAP Example...");
// Initialize NVS - Required for WiFi driver
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);
// Initialize WiFi in AP mode
wifi_init_softap(); // Function from Chapter 31
ESP_LOGI(TAG, "ESP32 SoftAP Initialized.");
// Wait a moment for AP to fully start before starting the monitor task
vTaskDelay(pdMS_TO_TICKS(2000));
// Create the station listing task
xTaskCreate(&list_stations_task, "list_sta_task", 4096, NULL, 5, NULL);
// The main task could now idle or do other things
// while(1) {
// vTaskDelay(pdMS_TO_TICKS(60000));
// }
// Note: Task will run indefinitely, main can exit if desired,
// but often main loops or waits on something.
}
3. Build, Flash, Monitor:
idf.py build
idf.py flash
idf.py monitor
Expected Output:
After the AP starts, you’ll see the usual logs. Then, every 15 seconds, the list_stations_task
will print the current list of connected stations.
// ... AP startup logs ...
I (XXX) WIFI_AP: ESP32 SoftAP Initialized.
I (XXX) WIFI_AP: Starting station listing task...
I (XXX) WIFI_AP: --------------------
I (XXX) WIFI_AP: Connected Stations (0): // Initially no stations
I (XXX) WIFI_AP: --------------------
// ... (Connect a client device) ...
I (XXX) WIFI_AP: Station xx:xx:xx:xx:xx:xx joined, AID=1 // Event Handler log
// ... (Wait up to 15 seconds) ...
I (XXX) WIFI_AP: --------------------
I (XXX) WIFI_AP: Connected Stations (1):
I (XXX) WIFI_AP: [0] MAC: xx:xx:xx:xx:xx:xx, RSSI: -55 dBm // MAC and RSSI from list_stations_task
I (XXX) WIFI_AP: IP: 192.168.4.2 // IP address looked up via netif
I (XXX) WIFI_AP: --------------------
// ... (Connect another client) ...
I (XXX) WIFI_AP: Station yy:yy:yy:yy:yy:yy joined, AID=2
// ... (Wait up to 15 seconds) ...
I (XXX) WIFI_AP: --------------------
I (XXX) WIFI_AP: Connected Stations (2):
I (XXX) WIFI_AP: [0] MAC: xx:xx:xx:xx:xx:xx, RSSI: -58 dBm
I (XXX) WIFI_AP: IP: 192.168.4.2
I (XXX) WIFI_AP: [1] MAC: yy:yy:yy:yy:yy:yy, RSSI: -62 dBm
I (XXX) WIFI_AP: IP: 192.168.4.3
I (XXX) WIFI_AP: --------------------
Example 2: Deauthenticating the First Client (Demonstration)
Let’s add a function to disconnect the first client found in the list. This is purely for demonstrating the API.
1. Add Deauthentication Logic (e.g., inside list_stations_task
for simplicity):
// Inside the while(1) loop of list_stations_task, after logging the list:
// --- Deauthentication Demo ---
// WARNING: Use with caution. This disconnects the *first* client found.
static bool deauth_done = false; // Simple flag to do it only once
if (!deauth_done && station_list.num > 0) {
wifi_sta_info_t first_station = station_list.sta[0];
ESP_LOGW(TAG, "DEMO: Attempting to deauthenticate first station: " MACSTR, MAC2STR(first_station.mac));
esp_err_t deauth_err = esp_wifi_deauth_sta(first_station.mac);
if (deauth_err == ESP_OK) {
ESP_LOGW(TAG, "DEMO: Deauth frame sent.");
} else {
ESP_LOGE(TAG, "DEMO: Failed to send deauth frame: %s", esp_err_to_name(deauth_err));
}
deauth_done = true; // Only try once in this simple demo
}
// --- End Deauthentication Demo ---
2. Build, Flash, Monitor:
- Connect at least one client to your ESP32 AP.
- Observe the logs. After the station list is printed for the first time with a client present, you should see the “DEMO: Attempting to deauthenticate…” message, followed by the “Deauth frame sent” message.
- Shortly after, the
wifi_event_handler
should log theWIFI_EVENT_AP_STADISCONNECTED
event for that client. - The client device will be disconnected from the WiFi network. It might try to reconnect automatically.
Variant Notes
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: The client management functions (
esp_wifi_ap_get_sta_list
,esp_wifi_deauth_sta
) and the associated structures (wifi_sta_list_t
,wifi_sta_info_t
) are standard features of the ESP-IDF WiFi stack and work consistently across these variants in AP mode. Theesp_netif
functions for DHCP lease lookup are also generally applicable. - ESP32-H2: This chapter is not applicable as the ESP32-H2 does not support 802.11 WiFi AP mode.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Solution / Best Practice |
---|---|---|
Ignoring num in wifi_sta_list_t |
Iterating through the entire sta array (up to ESP_WIFI_MAX_CONN_NUM ) without checking the actual num field. Processing garbage data. |
Always loop from 0 to station_list.num - 1 after calling esp_wifi_ap_get_sta_list() . |
Stale DHCP Lease Info / Timing | Trying to get a client’s IP immediately after WIFI_EVENT_AP_STACONNECTED might fail if DHCP transaction isn’t complete. |
Allow a short delay or query DHCP leases periodically. The IP might not be available instantly. |
Misinterpreting esp_wifi_deauth_sta() Return |
Assuming ESP_OK means the client is instantly and permanently disconnected. |
ESP_OK means the deauth frame was queued. Monitor WIFI_EVENT_AP_STADISCONNECTED . Client might reconnect. |
Concurrency Issues with Station List | Multiple tasks accessing/modifying data based on esp_wifi_ap_get_sta_list() without synchronization. |
Use mutexes to protect shared data structures updated from the station list, or use a single task to manage and distribute this information. |
Not Clearing wifi_sta_list_t Before Use |
If the structure isn’t cleared (e.g., with memset ) before calling esp_wifi_ap_get_sta_list() , it might contain stale data if the function fails or returns fewer clients than a previous call. |
It’s good practice to memset(&station_list, 0, sizeof(station_list)); before calling esp_wifi_ap_get_sta_list() , although the function should correctly populate num . |
Exercises
- Connection Counter: Modify the
wifi_event_handler
. Add a global counter variable (ensure it’s accessed safely if needed, e.g., usingatomic_fetch_add
fromesp_idf_version.h >= 5.0
or a mutex for older versions/complex updates). Increment the counter onWIFI_EVENT_AP_STACONNECTED
and decrement it onWIFI_EVENT_AP_STADISCONNECTED
. Log the current count in thelist_stations_task
. - Log RSSI on Connect/Disconnect: Enhance the
WIFI_EVENT_AP_STACONNECTED
handler. Immediately after a station connects, callesp_wifi_ap_get_sta_list()
, find the newly connected station by its MAC address in the list, and log its initial RSSI value. Do something similar on disconnect if useful (though RSSI might not be available/meaningful then). - Disconnect Specific MAC: Create a mechanism (e.g., a specific string received via UART) that allows you to input a MAC address. When the command is received, parse the MAC address and call
esp_wifi_deauth_sta()
to disconnect only that specific client.
Summary
- The ESP32 AP can monitor client connections via
WIFI_EVENT_AP_STACONNECTED
andWIFI_EVENT_AP_STADISCONNECTED
events. - The function
esp_wifi_ap_get_sta_list()
retrieves a snapshot of currently connected stations, including their MAC addresses and RSSI values. - Always check the
num
field of the returnedwifi_sta_list_t
structure before accessing the station info array. - IP addresses assigned by the DHCP server can be correlated with MAC addresses by querying the DHCP lease information using
esp_netif
functions likeesp_netif_get_sta_list
. - Specific clients can be manually disconnected using
esp_wifi_deauth_sta()
, providing their MAC address. - Managing connected clients allows for better monitoring, control, and security of the network hosted by the ESP32.
Further Reading
- ESP-IDF API Reference –
esp_wifi.h
: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_wifi.html (Search foresp_wifi_ap_get_sta_list
,esp_wifi_deauth_sta
,wifi_sta_list_t
,wifi_sta_info_t
) - ESP-IDF API Reference –
esp_netif.h
: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_netif.html (Search for DHCP server functions likeesp_netif_get_sta_list
) - ESP-IDF WiFi SoftAP Example: https://github.com/espressif/esp-idf/tree/v5.4/examples/wifi/getting_started/softAP (Check for client handling patterns)