Chapter 34: WiFi Scanning Implementation
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the purpose and process of WiFi scanning.
- Initiate a WiFi scan using ESP-IDF functions.
- Retrieve the list of discovered Access Points (APs).
- Interpret the information contained in scan results (SSID, BSSID, RSSI, Channel, Security).
- Differentiate between blocking and non-blocking scan methods.
- Implement a basic WiFi scanner application on the ESP32.
Introduction
Before an ESP32 can connect to a WiFi network as a Station (STA), it first needs to know which networks are available within its range. Just like your phone or laptop shows you a list of nearby WiFi networks, the ESP32 needs a mechanism to discover these networks. This discovery process is called scanning.
Scanning involves the ESP32 listening for special packets called “beacon frames” broadcast by Access Points, or actively probing for APs on different channels. The result is a list of networks, along with crucial information about each one, such as its name (SSID), signal strength (RSSI), and security type. This information is essential for selecting a network to connect to, troubleshooting connectivity issues, or building applications that dynamically interact with surrounding WiFi environments (like location-aware services or network analyzers).
This chapter covers the implementation of WiFi scanning using the ESP-IDF, enabling your ESP32 applications to perceive the local wireless landscape.
Theory
The Purpose of Scanning
WiFi scanning serves several key purposes:
- Network Discovery: The primary goal is to find available WiFi networks (APs) within the ESP32’s radio range.
- Network Selection: By examining the scan results (SSID, security, signal strength), the ESP32 or the user can choose the most appropriate network to connect to.
- Configuration/Provisioning: During device setup, scanning allows the user to select their home network from a list presented by the ESP32 (often via a companion app or web interface).
- Diagnostics: Analyzing scan results, particularly signal strength (RSSI), can help diagnose connection problems related to range or interference.
- Context Awareness: Some applications use the list of nearby APs (even without connecting) as a form of rough location estimation or environmental context.
Active vs. Passive Scanning
WiFi scanning can generally be categorized into two types:
- Passive Scanning: The device simply listens on each channel for a period, waiting to receive Beacon frames periodically broadcast by APs. Beacons contain information about the AP, including its SSID, channel, and security configuration. This method is slower but consumes less power and is less intrusive.
- Active Scanning: The device transmits “Probe Request” frames on each channel, asking any APs on that channel to identify themselves. APs that receive the probe request respond with “Probe Response” frames, which contain similar information to beacons. This method is generally faster at discovering networks but requires transmitting, consuming slightly more power.
The ESP-IDF esp_wifi_scan_start()
function typically performs an active scan by default, but can be configured for passive scanning.
ESP-IDF Scanning Process
Scanning with ESP-IDF generally follows these steps:
- Ensure WiFi is Initialized: The WiFi stack must be initialized (e.g., using
esp_wifi_init()
) and started (esp_wifi_start()
) typically in Station or APSTA mode. Scanning is primarily a Station function, but can be performed in APSTA mode as well. You don’t need to be connected to perform a scan. - Configure Scan Parameters (Optional): You can specify parameters like the scan type (active/passive), specific channels to scan, or SSIDs to look for using a
wifi_scan_config_t
structure. If you passNULL
for configuration, default parameters (usually an active scan across all channels) are used. - Initiate Scan: Call
esp_wifi_scan_start()
function. This function takes the optional configuration structure and ablock
parameter.block = true
: The function will not return until the scan is complete (synchronous/blocking scan). This is simpler for basic use cases but halts the calling task.block = false
: The function returns immediately, and the scan proceeds in the background (asynchronous/non-blocking scan). The application needs to wait for theWIFI_EVENT_SCAN_DONE
event to know when the results are ready.
- Wait for Completion (if blocking): If
block = true
was used, the function returnsESP_OK
upon successful completion. - Get Number of Found APs: Call
esp_wifi_scan_get_ap_num()
to find out how many APs were discovered during the scan. - Allocate Memory: Allocate a buffer large enough to hold the scan results. The size needed is
(number of APs) * sizeof(wifi_ap_record_t)
. - Retrieve Scan Results: Call
esp_wifi_scan_get_ap_records()
function, passing the allocated buffer and the number of APs expected. This function copies the details of the found APs into your buffer. - Process Results: Iterate through the buffer of
wifi_ap_record_t
structures, extracting and using the information (SSID, BSSID, RSSI, channel, security, etc.). - Free Memory: Free the buffer allocated in step 6.
%%{ 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 storage fill:#E0F2FE,stroke:#0EA5E9,stroke-width:1px,color:#0C4A6E; %% For memory/buffer 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>: Initiate WiFi Scan]:::primary B["Ensure WiFi Initialized & Started<br>(STA or APSTA mode)"]:::process C["Optional: Configure Scan Parameters<br>(<code class='code-like'>wifi_scan_config_t</code> or NULL for defaults)"]:::process D["Call <code class='code-like'>esp_wifi_scan_start(config, block)</code>"]:::process E{Scan Type: Blocking?}:::decision F["<b>Blocking Scan:</b><br>Task waits for scan completion.<br>Function returns <code class='code-like'>ESP_OK</code> on success."]:::process G["<b>Non-Blocking Scan:</b><br>Function returns immediately.<br>Scan runs in background."]:::process H["Wait for <code class='code-like'>WIFI_EVENT_SCAN_DONE</code><br>via Event Handler"]:::event I["Call <code class='code-like'>esp_wifi_scan_get_ap_num(&ap_count)</code>"]:::process J{Scan Succeeded & <code class='code-like'>ap_count</code> > 0?}:::decision K["Allocate Buffer for Results<br>(<code class='code-like'>ap_count * sizeof(wifi_ap_record_t)</code>)"]:::storage L["Call <code class='code-like'>esp_wifi_scan_get_ap_records(&ap_count, buffer)</code>"]:::process M["Process Scan Results<br>(Iterate through <code class='code-like'>wifi_ap_record_t</code> array)"]:::process N["Free Allocated Buffer"]:::storage O["Scan Complete/Processed"]:::success P["Handle Scan Failure or No APs Found"]:::fail Q["Cleanup WiFi (if necessary)"]:::process A --> B; B --> C; C --> D; D --> E; E -- "Yes (block=true)" --> F; E -- "No (block=false)" --> G; G --> H; F --> I; H --> I; I --> J; J -- "Yes" --> K; J -- "No" --> P; K --> L; L --> M; M --> N; N --> O; P --> Q; O --> Q;
Key API Functions and Structures
esp_err_t esp_wifi_scan_start(const wifi_scan_config_t *config, bool block)
config
: Pointer to awifi_scan_config_t
structure to customize the scan, orNULL
for defaults.block
:true
for a blocking scan,false
for a non-blocking scan.- Returns:
ESP_OK
on success (for blocking scan, means completion; for non-blocking, means initiation), or an error code.
%%{ 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 parallel fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3; A[Start Scan Process]:::primary subgraph "Blocking Scan" direction LR B1["Call <code class='code-like'>esp_wifi_scan_start(config, true)</code>"]:::process B2["Task Blocks...<br>(WiFi driver performs scan)"]:::parallel B3["Scan Completes"]:::process B4["<code class='code-like'>esp_wifi_scan_start()</code> returns <code class='code-like'>ESP_OK</code>"]:::success B5["Proceed to Get/Process Results"]:::process B1 --> B2 --> B3 --> B4 --> B5; end subgraph "Non-Blocking Scan " direction LR C1["Call <code class='code-like'>esp_wifi_scan_start(config, false)</code>"]:::process C2["Function Returns Immediately (<code class='code-like'>ESP_OK</code> if initiated)"]:::success C3["Application Task Continues Other Operations"]:::parallel C4["WiFi Driver Performs Scan in Background"]:::parallel C5["Event: <code class='code-like'>WIFI_EVENT_SCAN_DONE</code><br>Received by Event Handler"]:::event C6["Event Handler Signals Completion<br>(e.g., sets flag, posts to queue)"]:::process C7["Application Task Gets/Processes Results"]:::process C1 --> C2; C2 --> C3; C1 -.-> C4; C4 --> C5 --> C6 --> C7; end A --> B1; A --> C1;
esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number)
number
: Pointer to auint16_t
variable where the count of found APs will be stored.- Returns:
ESP_OK
on success.
esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records)
number
: Pointer to auint16_t
. On input, it should contain the size of theap_records
buffer (in number of records). On output, it contains the actual number of records copied.ap_records
: Pointer to the buffer where the scan results (wifi_ap_record_t
structures) will be copied.- Returns:
ESP_OK
on success.
wifi_scan_config_t
Structure: (Optional, used withesp_wifi_scan_start
)
typedef struct {
uint8_t *ssid; /**< SSID of AP */
uint8_t *bssid; /**< MAC address of AP */
uint8_t channel; /**< channel, 0 means scan all channels */
bool show_hidden; /**< enable ask for hidden APs */
wifi_scan_type_t scan_type; /**< scan type, active or passive */
wifi_scan_time_t scan_time; /**< scan time per channel */
} wifi_scan_config_t;
Allows filtering by SSID or BSSID, scanning only a specific channel, scanning for hidden networks, choosing active/passive scan type, and setting scan times. Passing NULL
to esp_wifi_scan_start
uses default settings.
Field Name | Data Type | Description | Example / Default (if NULL passed to scan_start) |
---|---|---|---|
ssid |
uint8_t * |
Pointer to the SSID of a specific AP to scan for. If NULL, scans for all SSIDs. | Default: NULL (scan all) |
bssid |
uint8_t * |
Pointer to the MAC address (BSSID) of a specific AP to scan for. If NULL, scans for all BSSIDs. | Default: NULL (scan all) |
channel |
uint8_t |
Specific channel to scan. If 0, scans all available channels. | Default: 0 (scan all channels) |
show_hidden |
bool |
Set to true to actively probe for hidden SSIDs (networks that don’t broadcast their name). |
Default: false |
scan_type |
wifi_scan_type_t |
Type of scan: WIFI_SCAN_TYPE_ACTIVE or WIFI_SCAN_TYPE_PASSIVE . |
Default: WIFI_SCAN_TYPE_ACTIVE |
scan_time |
wifi_scan_time_t |
Structure to specify scan times per channel. Members: active (min/max ms for active scan) and passive (ms for passive scan). |
Default: System-defined scan times. |
wifi_ap_record_t
Structure: (The structure holding results for one AP)
typedef struct {
uint8_t bssid[6]; /**< MAC address of AP */
uint8_t ssid[33]; /**< SSID of AP */
uint8_t primary; /**< channel of AP */
wifi_second_chan_t second; /**< secondary channel of AP */
int8_t rssi; /**< signal strength of AP */
wifi_auth_mode_t authmode; /**< authmode of AP */
wifi_cipher_type_t pairwise_cipher; /**< pairwise cipher of AP */
wifi_cipher_type_t group_cipher; /**< group cipher of AP */
wifi_ant_t ant; /**< antenna type */
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 */
// ... other fields related to phy, WPA, WPS, country code etc.
} wifi_ap_record_t;
Key Fields:
bssid
: The MAC address of the AP (unique identifier).ssid
: The human-readable network name. Note: This is a raw byte array and might not be null-terminated if the SSID is 32 bytes long. Always handle its length carefully.primary
: The primary WiFi channel the AP is operating on.rssi
: Received Signal Strength Indicator (in dBm). A higher value (closer to 0, e.g., -50 dBm) indicates a stronger signal than a lower value (e.g., -80 dBm).authmode
: The security type (WIFI_AUTH_OPEN
,WIFI_AUTH_WEP
,WIFI_AUTH_WPA_PSK
,WIFI_AUTH_WPA2_PSK
,WIFI_AUTH_WPA_WPA2_PSK
,WIFI_AUTH_WPA2_ENTERPRISE
,WIFI_AUTH_WPA3_PSK
,WIFI_AUTH_WPA2_WPA3_PSK
).
Function Name | Key Parameter(s) | Return Value | Purpose & Brief Description |
---|---|---|---|
esp_wifi_scan_start() |
const wifi_scan_config_t *config (input, optional), bool block (input) |
esp_err_t |
Initiates a WiFi scan. Can be blocking (waits for completion) or non-blocking (returns immediately, scan runs in background). Custom scan parameters can be provided via config . |
esp_wifi_scan_get_ap_num() |
uint16_t *number (output) |
esp_err_t |
Retrieves the number of Access Points found during the last completed scan. Call this before esp_wifi_scan_get_ap_records() to know buffer size. |
esp_wifi_scan_get_ap_records() |
uint16_t *number (input/output), wifi_ap_record_t *ap_records (output) |
esp_err_t |
Retrieves the detailed records of found APs. *number should initially hold the buffer size (number of records it can store); on return, it holds the actual number of records copied. |
Practical Examples
Example 1: Basic Blocking WiFi Scan
This example performs a simple, blocking scan using default parameters and prints the results to the console.
Prerequisites:
- ESP-IDF v5.x environment with VS Code.
- An ESP32 board (any variant with WiFi support).
1. Project Setup:
- Create a new ESP-IDF project or use an existing one. No specific menuconfig settings are required beyond the default project configuration.
2. Code (main/wifi_scan_main.c
):
#include <stdio.h>
#include <string.h> // For memcpy
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#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 = "WIFI_SCAN";
// Helper function to convert auth mode enum to string
static const char* get_auth_mode_str(wifi_auth_mode_t auth_mode) {
switch (auth_mode) {
case WIFI_AUTH_OPEN: return "OPEN";
case WIFI_AUTH_WEP: return "WEP";
case WIFI_AUTH_WPA_PSK: return "WPA_PSK";
case WIFI_AUTH_WPA2_PSK: return "WPA2_PSK";
case WIFI_AUTH_WPA_WPA2_PSK: return "WPA_WPA2_PSK";
case WIFI_AUTH_WPA2_ENTERPRISE: return "WPA2_ENTERPRISE";
case WIFI_AUTH_WPA3_PSK: return "WPA3_PSK";
case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2_WPA3_PSK";
case WIFI_AUTH_WAPI_PSK: return "WAPI_PSK"; // If enabled in Kconfig
default: return "UNKNOWN";
}
}
// Function to perform WiFi scan
static void wifi_scan(void)
{
ESP_LOGI(TAG, "Starting WiFi scan...");
// 1. Initialize WiFi (if not already done) - simplified for example
// In a full app, this would likely be done once at startup
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));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); // Need STA mode for scanning
ESP_ERROR_CHECK(esp_wifi_start());
// 2. Configure Scan Parameters (using defaults here by passing NULL)
wifi_scan_config_t scan_config = {
.ssid = NULL, // Scan for all SSIDs
.bssid = NULL, // Scan for all BSSIDs
.channel = 0, // Scan all channels
.show_hidden = false, // Do not scan for hidden networks
.scan_type = WIFI_SCAN_TYPE_ACTIVE, // Active scan
// .scan_time = { .active = { .min = 100, .max = 1500 } } // Optional: customize scan times
};
// 3. Initiate Scan (Blocking)
// Use &scan_config instead of NULL to apply custom settings
esp_err_t scan_result = esp_wifi_scan_start(NULL, true); // Using defaults, blocking call
if (scan_result == ESP_OK) {
ESP_LOGI(TAG, "Scan completed successfully.");
// 4. Get Number of Found APs
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
ESP_LOGI(TAG, "Found %u access points:", ap_count);
if (ap_count == 0) {
// No APs found, cleanup WiFi
ESP_LOGI(TAG, "No APs found nearby.");
goto cleanup; // Skip buffer allocation if no APs
}
// 5. Allocate Memory for Scan Results
// Allocate slightly more just in case count increases between get_num and get_records
// Though with blocking scan, this is less likely.
uint16_t max_aps_to_get = ap_count; // Use the count we just got
wifi_ap_record_t *ap_records = (wifi_ap_record_t *)malloc(max_aps_to_get * sizeof(wifi_ap_record_t));
if (ap_records == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for scan results");
goto cleanup; // Cleanup WiFi before returning
}
// 6. Retrieve Scan Results
uint16_t actual_aps_retrieved = max_aps_to_get; // Pass buffer size
esp_err_t get_records_result = esp_wifi_scan_get_ap_records(&actual_aps_retrieved, ap_records);
if (get_records_result == ESP_OK) {
ESP_LOGI(TAG, "Retrieved %u records (expected %u):", actual_aps_retrieved, ap_count);
// 7. Process Results
for (uint16_t i = 0; i < actual_aps_retrieved; i++) {
// Ensure SSID is null-terminated before printing
char safe_ssid[33]; // Max SSID length + 1 for null terminator
memset(safe_ssid, 0, sizeof(safe_ssid));
// Check actual length before copying to avoid reading past end if SSID is exactly 32 bytes
// Note: wifi_ap_record_t.ssid is uint8_t[33], ESP-IDF usually null-terminates it
// but defensive copying is good practice.
int ssid_len = strlen((const char *)ap_records[i].ssid);
memcpy(safe_ssid, ap_records[i].ssid, ssid_len < 32 ? ssid_len : 32);
ESP_LOGI(TAG, " [%u] SSID: %s", i, safe_ssid);
ESP_LOGI(TAG, " BSSID: " MACSTR, MAC2STR(ap_records[i].bssid));
ESP_LOGI(TAG, " Channel: %d", ap_records[i].primary);
ESP_LOGI(TAG, " RSSI: %d dBm", ap_records[i].rssi);
ESP_LOGI(TAG, " Auth Mode: %s", get_auth_mode_str(ap_records[i].authmode));
ESP_LOGI(TAG, " --------------------");
}
} else {
ESP_LOGE(TAG, "Failed to get scan records: %s", esp_err_to_name(get_records_result));
}
// 8. Free Memory
free(ap_records);
} else {
ESP_LOGE(TAG, "WiFi scan failed: %s", esp_err_to_name(scan_result));
}
cleanup:
// Stop and deinit WiFi (in a real app, manage this based on application state)
ESP_LOGI(TAG, "Cleaning up WiFi...");
esp_wifi_stop();
esp_wifi_deinit();
// Optional: Destroy default event loop and netif if created here
esp_event_loop_delete_default();
esp_netif_destroy(sta_netif);
// Optional: Deinit TCP/IP stack if initialized here
// esp_netif_deinit(); // Careful if other parts of app use it
}
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);
// Perform the scan
wifi_scan();
ESP_LOGI(TAG, "Scan example finished. Restarting in 10 seconds...");
vTaskDelay(pdMS_TO_TICKS(10000));
esp_restart(); // Restart to allow running again easily
}
3. Build, Flash, and Monitor:
- Connect your ESP32 board.
- Run
idf.py build
- Run
idf.py flash monitor
4. Observe Output:
The ESP32 will boot, initialize WiFi in STA mode, perform a scan, and print the results to the serial monitor. The output will look something like this (specific networks, MACs, RSSI values will differ based on your environment):
I (XXX) WIFI_SCAN: Starting WiFi scan...
I (XXX) wifi:wifi driver task: 3ffcxxxx, prio:23, stack:4096, core=0
I (XXX) wifi:wifi init success
I (XXX) wifi:Set ps type: 0
I (XXX) wifi:Set mode: STA(xx:xx:xx:xx:xx:xx)
I (XXX) wifi:wifi start success
I (XXX) WIFI_SCAN: Scan completed successfully.
I (XXX) WIFI_SCAN: Found 7 access points:
I (XXX) WIFI_SCAN: Retrieved 7 records (expected 7):
I (XXX) WIFI_SCAN: [0] SSID: MyHomeNetwork
I (XXX) WIFI_SCAN: BSSID: aa:bb:cc:11:22:33
I (XXX) WIFI_SCAN: Channel: 6
I (XXX) WIFI_SCAN: RSSI: -55 dBm
I (XXX) WIFI_SCAN: Auth Mode: WPA2_PSK
I (XXX) WIFI_SCAN: --------------------
I (XXX) WIFI_SCAN: [1] SSID: NeighborsWifi
I (XXX) WIFI_SCAN: BSSID: 11:22:33:aa:bb:cc
I (XXX) WIFI_SCAN: Channel: 11
I (XXX) WIFI_SCAN: RSSI: -78 dBm
I (XXX) WIFI_SCAN: Auth Mode: WPA_WPA2_PSK
I (XXX) WIFI_SCAN: --------------------
I (XXX) WIFI_SCAN: [2] SSID: HP-Print-XYZ
I (XXX) WIFI_SCAN: BSSID: 00:11:22:33:44:55
I (XXX) WIFI_SCAN: Channel: 1
I (XXX) WIFI_SCAN: RSSI: -68 dBm
I (XXX) WIFI_SCAN: Auth Mode: OPEN
I (XXX) WIFI_SCAN: --------------------
... (more networks) ...
I (XXX) WIFI_SCAN: Cleaning up WiFi...
I (XXX) WIFI_SCAN: Scan example finished. Restarting in 10 seconds...
Variant Notes
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: WiFi scanning functionality using
esp_wifi_scan_start
and related functions is available and works consistently across all these ESP32 variants that feature 802.11 WiFi capabilities. The core API and behavior are identical. - ESP32-H2: This variant does not have 802.11 WiFi hardware. Therefore, this chapter and the WiFi scanning APIs are not applicable to the ESP32-H2. It focuses on 802.15.4 (Thread/Zigbee) and Bluetooth LE.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Solution / Best Practice |
---|---|---|
WiFi Not Started or Incorrect Mode | esp_wifi_scan_start() returns error (e.g., ESP_ERR_WIFI_NOT_STARTED , ESP_ERR_WIFI_MODE ). No scan performed. |
Ensure WiFi is initialized (esp_wifi_init ), mode is set to STA or APSTA (esp_wifi_set_mode ), and WiFi is started (esp_wifi_start ) before scanning. |
Insufficient Buffer for Results | esp_wifi_scan_get_ap_records() returns error or writes past buffer end, causing memory corruption/crashes. Incomplete list. |
Call esp_wifi_scan_get_ap_num() first. Allocate buffer of size ap_count * sizeof(wifi_ap_record_t) . Pass this count as input *number to esp_wifi_scan_get_ap_records() . |
Misinterpreting RSSI | Incorrectly selecting weak networks or misdiagnosing signal issues by assuming lower RSSI (e.g., -80) is stronger. | Remember RSSI is in dBm (negative values). Values closer to 0 are stronger (e.g., -50 dBm is stronger than -80 dBm). |
Blocking Scan Halting Critical Tasks | Application becomes unresponsive during a blocking scan if called from a high-priority or time-sensitive task. | Use non-blocking scan (block = false ) and handle WIFI_EVENT_SCAN_DONE . Or, run blocking scans in a dedicated, lower-priority task. |
Incorrect SSID Handling from wifi_ap_record_t |
Assuming ssid field is always null-terminated like a C string, especially if SSID length is 32 bytes. Leads to printing garbage or crashes. |
Treat ssid as a byte array. Use its actual length (up to 32 bytes). Manually null-terminate in a separate buffer if using with C string functions. |
Forgetting to Free Allocated Buffer | Memory leak if the buffer allocated for ap_records is not freed after use. |
Always call free(ap_records) when done processing the scan results. |
Not Handling WIFI_EVENT_SCAN_DONE (Non-Blocking) |
If using non-blocking scan, failing to register a handler for WIFI_EVENT_SCAN_DONE means the application never knows when results are ready. |
For non-blocking scans, register an event handler for WIFI_EVENT and check for WIFI_EVENT_SCAN_DONE before calling esp_wifi_scan_get_ap_num() . |
Exercises
- Scan and Find Strongest AP: Modify the example code to iterate through the scan results and identify the AP with the highest RSSI value (strongest signal). Print the SSID, BSSID, and RSSI of only the strongest AP found.
- Filter Scan by SSID: Use the
wifi_scan_config_t
structure when callingesp_wifi_scan_start()
. Set thessid
field in the configuration to the specific name of a network you know is nearby. Verify that the scan results only contain the AP(s) matching that SSID. - Non-Blocking Scan with Event: Rewrite the scan logic to use a non-blocking scan (
block = false
).- Register an event handler for
WIFI_EVENT
. - Inside the handler, detect the
WIFI_EVENT_SCAN_DONE
event. - When
WIFI_EVENT_SCAN_DONE
is received, proceed to get the number of APs and retrieve/print the records (similar to the original example, but triggered by the event). You might need a way to signal the main task or the scan task that the results are ready (e.g., using an Event Group or a Queue).
- Register an event handler for
Summary
- WiFi scanning is used to discover available Access Points within range.
- The ESP-IDF provides
esp_wifi_scan_start()
to initiate scans, which can be blocking (synchronous) or non-blocking (asynchronous). - Scan results are retrieved using
esp_wifi_scan_get_ap_num()
andesp_wifi_scan_get_ap_records()
. - The
wifi_ap_record_t
structure contains details about each found AP, including SSID, BSSID, channel, RSSI, and authentication mode. - RSSI values are in dBm; values closer to 0 indicate stronger signals.
- Scanning requires WiFi to be initialized and started in STA or APSTA mode.
- Careful memory management is needed for the results buffer.
- Non-blocking scans require event handling (
WIFI_EVENT_SCAN_DONE
) for completion notification.
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_scan_start
,esp_wifi_scan_get_ap_num
,esp_wifi_scan_get_ap_records
,wifi_ap_record_t
,wifi_scan_config_t
) - ESP-IDF WiFi Scan Example: https://github.com/espressif/esp-idf/tree/v5.4/examples/wifi/scan