Chapter 35: Advanced WiFi Scan Features and Filtering

Volume 2: Connectivity Fundamentals – WiFi

Chapter Objectives

By the end of this chapter, you will be able to:

  • Utilize the wifi_scan_config_t structure to customize scan behavior.
  • Filter scan results based on SSID, BSSID, or channel.
  • Perform passive WiFi scans and understand their characteristics.
  • Configure the ESP32 to scan for hidden networks.
  • Adjust scan timing parameters and understand their impact.
  • Implement targeted scanning strategies for specific use cases.

Introduction

In the previous chapter, we learned the fundamentals of WiFi scanning using default parameters, allowing the ESP32 to discover all nearby visible Access Points. While effective for general network discovery, this broad approach isn’t always optimal. Sometimes, we need more control:

  • What if we only care about a specific network name (SSID)?
  • What if we need to find a particular Access Point based on its MAC address (BSSID)?
  • What if we want to minimize scan time by only checking a specific channel?
  • What if we need to find a network that doesn’t broadcast its SSID (a “hidden” network)?
  • Can we perform a less intrusive scan or adjust how long the ESP32 listens on each channel?

This chapter explores the advanced configuration options available for WiFi scanning in ESP-IDF. By leveraging the wifi_scan_config_t structure, we can tailor the scanning process to be more efficient, targeted, and suitable for diverse application requirements, such as faster connections, specific AP monitoring, or reduced power consumption during scans.

Theory

The key to customizing the scan process lies in the wifi_scan_config_t structure, which is passed as the first argument to esp_wifi_scan_start(). Let’s examine its fields in detail:

C
typedef struct {
    uint8_t *ssid;           /**< Pointer to the SSID to scan for (NULL = all SSIDs) */
    uint8_t *bssid;          /**< Pointer to the BSSID (MAC address) to scan for (NULL = all BSSIDs) */
    uint8_t channel;         /**< Channel to scan (0 = all channels) */
    bool show_hidden;        /**< Send probe requests for hidden APs (true) or not (false) */
    wifi_scan_type_t scan_type; /**< Scan type: WIFI_SCAN_TYPE_ACTIVE or WIFI_SCAN_TYPE_PASSIVE */
    wifi_scan_time_t scan_time; /**< Structure defining scan time per channel */
} wifi_scan_config_t;

%%{ 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 config fill:#E0F2FE,stroke:#0EA5E9,stroke-width:1px,color:#0C4A6E; %% For config struct
    classDef result fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;

    A[<b>Start</b>: Need to Perform Customized Scan]:::primary
    B["Declare <code class='code-like'>wifi_scan_config_t scan_config;</code>"]:::config
    C["Initialize <code class='code-like'>scan_config</code> fields:<br>e.g., <code class='code-like'>memset(&scan_config, 0, sizeof(scan_config));</code> (Good practice)"]:::process
    
    D{Filter by SSID?}:::decision
    D -- "Yes" --> D_Y["Set <code class='code-like'>scan_config.ssid = (uint8_t*)\<i>TargetSSID\</i>;</code>"]:::config
    D -- "No" --> D_N["Set <code class='code-like'>scan_config.ssid = NULL;</code>"]:::config
    
    E{Filter by BSSID?}:::decision
    E -- "Yes" --> E_Y["Set <code class='code-like'>scan_config.bssid = target_bssid_mac_array;</code>"]:::config
    E -- "No" --> E_N["Set <code class='code-like'>scan_config.bssid = NULL;</code>"]:::config
    
    F{Scan Specific Channel?}:::decision
    F -- "Yes" --> F_Y["Set <code class='code-like'>scan_config.channel = target_channel_num;</code>"]:::config
    F -- "No" --> F_N["Set <code class='code-like'>scan_config.channel = 0;</code> (All Channels)"]:::config
    
    G{Scan for Hidden APs?}:::decision
    G -- "Yes" --> G_Y["Set <code class='code-like'>scan_config.show_hidden = true;</code><br>(Ensure <code class='code-like'>scan_config.ssid</code> is set for specific hidden AP)"]:::config
    G -- "No" --> G_N["Set <code class='code-like'>scan_config.show_hidden = false;</code>"]:::config
    
    H{Choose Scan Type?}:::decision
    H -- "Passive" --> H_P["Set <code class='code-like'>scan_config.scan_type = WIFI_SCAN_TYPE_PASSIVE;</code><br>Optional: Set <code class='code-like'>scan_config.scan_time.passive</code>"]:::config
    H -- "Active (Default)" --> H_A["Set <code class='code-like'>scan_config.scan_type = WIFI_SCAN_TYPE_ACTIVE;</code><br>Optional: Set <code class='code-like'>scan_config.scan_time.active</code>"]:::config
    
    I["Call <code class='code-like'>esp_wifi_scan_start(&scan_config, block_mode);</code>"]:::process
    J["Process Scan Results<br>(Get AP num, Get AP records, etc.)"]:::result

    A --> B --> C;
    C --> D;
    D_Y --> E; D_N --> E;
    E_Y --> F; E_N --> F;
    F_Y --> G; F_N --> G;
    G_Y --> H; G_N --> H;
    H_P --> I; H_A --> I;
    I --> J;

Filtering Scan Results

  • ssid (uint8_t *): If you provide a pointer to a specific SSID (as a null-terminated byte array), the scan results returned by esp_wifi_scan_get_ap_records() will only include APs matching that exact SSID. If set to NULL, APs with any SSID are included. This is useful for quickly finding your target network without sifting through neighbors’ networks.
  • bssid (uint8_t *): If you provide a pointer to a specific 6-byte BSSID (MAC address), the scan will target only that specific Access Point. This is highly specific and useful if you know the exact AP you want information about, perhaps for monitoring or diagnostics. If set to NULL, APs with any BSSID are included.
  • channel (uint8_t): If set to a specific channel number (typically 1-13 or 1-14 depending on region), the scan will only be performed on that single channel. This significantly speeds up the scan process if you know the channel your target network operates on, but it will miss all APs on other channels. If set to 0, the ESP32 scans all available channels according to its regulatory domain configuration.

Tip: You can combine filters. For example, setting both ssid and channel will find APs with the specified SSID only on the specified channel.

Scan Type (scan_type)

This field determines how the ESP32 looks for networks:

Feature Active Scan (WIFI_SCAN_TYPE_ACTIVE) Passive Scan (WIFI_SCAN_TYPE_PASSIVE)
Mechanism Device transmits “Probe Request” frames on each channel and listens for “Probe Response” frames from APs. Device listens on each channel for a period, waiting to receive “Beacon” frames periodically broadcast by APs.
Discovery Speed
  • Generally faster, especially for APs not beaconing frequently.
  • Slower, depends on AP beacon interval and listen duration per channel.
Power Consumption
  • Slightly higher due to transmissions (sending probe requests).
  • Lower as it only involves listening (radio receive mode).
Network Intrusiveness
  • More “noisy” on the RF spectrum due to active probing.
  • Less intrusive (“quieter”) as it’s purely observational.
Hidden Network Discovery
  • Can discover hidden networks if show_hidden = true and the specific SSID is provided in wifi_scan_config_t.
  • Cannot discover hidden networks (as they don’t include SSID in beacons).
Typical Use Cases Default scan type for most devices. Good for quick discovery and when hidden networks might be a target. Power-sensitive applications. Scenarios where minimal RF interference is desired. Background monitoring.
Default in ESP-IDF Yes (if wifi_scan_config_t.scan_type is not set or config is NULL). No (must be explicitly set).
  • WIFI_SCAN_TYPE_ACTIVE (Default): The ESP32 transmits “Probe Request” frames on each channel and listens for “Probe Response” frames from APs.
    • Pros: Generally faster discovery, especially for networks that aren’t currently broadcasting beacons frequently. Can discover hidden networks if show_hidden is true.
    • Cons: Requires transmission, consuming slightly more power. Slightly more “noisy” on the RF spectrum.
  • WIFI_SCAN_TYPE_PASSIVE: The ESP32 simply listens on each channel for a defined period, waiting to receive periodic “Beacon” frames naturally broadcast by APs.
    • Pros: Lower power consumption as no transmission is required. Less intrusive (“quieter”).
    • Cons: Slower, as it depends on passively receiving beacons. Might miss APs if the listen time per channel is too short or if APs beacon infrequently. Cannot discover hidden networks (as they don’t beacon their SSID).

Hidden Networks (show_hidden)

Standard APs broadcast their SSID in beacon frames. “Hidden” networks (more accurately, networks with SSID broadcasting disabled) do not include the SSID in their beacons.

  • show_hidden = false (Default): During an active scan, the ESP32 sends a generic Probe Request. Only non-hidden APs will respond with their SSID. Passive scans will also only reveal non-hidden SSIDs found in beacons.
  • show_hidden = true: During an active scan, the ESP32 sends directed Probe Requests, including the specific SSID if the ssid field in wifi_scan_config_t is also set. If an AP with that SSID (even if hidden) receives this directed probe, it is required by the standard to respond, thus revealing itself in the scan results. If ssid is NULL but show_hidden is true, the ESP32 sends a probe request with a wildcard SSID, which might elicit responses from hidden networks, but behavior can vary between AP vendors. Note: This only works with WIFI_SCAN_TYPE_ACTIVE. Passive scans cannot find hidden networks.

Warning: “Hiding” an SSID provides minimal security benefit and is primarily security through obscurity. It doesn’t encrypt the connection or prevent targeted discovery.

Scan Timing (scan_time)

The wifi_scan_time_t structure within wifi_scan_config_t allows fine-tuning how long the ESP32 spends on each channel during a scan.

C
typedef struct {
    uint32_t min;  /**< Minimum duration in milliseconds */
    uint32_t max;  /**< Maximum duration in milliseconds */
} wifi_active_scan_time_t;

typedef uint32_t wifi_passive_scan_time_t; /**< Duration in milliseconds */

typedef struct {
    wifi_active_scan_time_t active;  /**< Configuration for active scan */
    wifi_passive_scan_time_t passive; /**< Configuration for passive scan */
} wifi_scan_time_t;
  • active.min / active.max (ms): For active scans, the ESP32 stays on a channel for at least min milliseconds. If APs respond quickly, it might move on. It will stay up to max milliseconds waiting for responses before moving to the next channel. Default values are typically around 120ms max.
  • passive (ms): For passive scans, this defines the fixed duration the ESP32 listens on each channel for beacons. Default is often around 120ms.

Impact of Timing:

  • Shorter times: Faster overall scan completion. Risk of missing APs, especially in noisy environments or if AP beacons/responses are delayed.
  • Longer times: Increased likelihood of discovering all APs, including weaker or slower ones. Slower overall scan completion. Increased power consumption for passive scans (longer listening).
Parameter Field in wifi_scan_time_t Description Impact of Shorter Times Impact of Longer Times Default (Typical)
For Active Scan (scan_type = WIFI_SCAN_TYPE_ACTIVE)
active.min (ms) Minimum duration the ESP32 stays on a channel listening for probe responses after sending a probe request. Faster channel switching if APs respond quickly. May miss slower APs. More time to receive responses, but can slow down scan if APs respond fast anyway. ~20-30 ms
active.max (ms) Maximum duration the ESP32 stays on a channel waiting for probe responses before moving to the next channel. Faster overall scan. Higher risk of missing APs, especially in noisy environments or if APs are slow to respond. Increased likelihood of discovering all APs, including weaker or slower ones. Slower overall scan completion. ~120-300 ms
For Passive Scan (scan_type = WIFI_SCAN_TYPE_PASSIVE)
passive (ms) Fixed duration the ESP32 listens on each channel for beacon frames. Faster overall scan. Higher risk of missing APs if their beacon interval is long or if beacons are missed during the short listen window. Increased likelihood of receiving beacons from all APs on the channel. Slower overall scan completion. Increased power consumption (longer radio ON time). ~120-300 ms

Practical Examples

These examples build upon the basic scan code from Chapter 34. We’ll focus on modifying the wifi_scan_config_t structure. Assume the wifi_init, NVS initialization, app_main, and get_auth_mode_str function remain similar to the previous chapter’s example unless noted.

Example 1: Scanning for a Specific SSID

This example scans only for networks named “MyTargetNetwork”.

1. Modify wifi_scan function:

C
// Inside the wifi_scan function, before calling esp_wifi_scan_start:

// Target SSID (replace with your actual target network name)
const char *target_ssid = "MyTargetNetwork";

wifi_scan_config_t scan_config = {
    .ssid = (uint8_t *)target_ssid, // Point to the target SSID
    .bssid = NULL,                  // Any BSSID
    .channel = 0,                   // All channels
    .show_hidden = false,           // Standard scan for visible networks
    .scan_type = WIFI_SCAN_TYPE_ACTIVE,
    // .scan_time = { ... } // Default timing
};

ESP_LOGI(TAG, "Starting scan specifically for SSID: %s", target_ssid);

// Initiate Scan (Blocking) - Pass the config structure
esp_err_t scan_result = esp_wifi_scan_start(&scan_config, true);

// ... (rest of the scan result processing code remains the same)

2. Build, Flash, Monitor:

Run the code. The output should now only list Access Points with the SSID “MyTargetNetwork”. If none are found, it will report 0 access points.

Plaintext
I (XXX) WIFI_SCAN: Starting scan specifically for SSID: MyTargetNetwork
...
I (XXX) WIFI_SCAN: Scan completed successfully.
I (XXX) WIFI_SCAN: Found 1 access points: // Or 0 or more, if multiple APs share the SSID
I (XXX) WIFI_SCAN: Retrieved 1 records (expected 1):
I (XXX) WIFI_SCAN:   [0] SSID: MyTargetNetwork
I (XXX) WIFI_SCAN:      BSSID: xx:xx:xx:xx:xx:xx
I (XXX) WIFI_SCAN:      Channel: 6
I (XXX) WIFI_SCAN:      RSSI: -60 dBm
I (XXX) WIFI_SCAN:      Auth Mode: WPA2_PSK
I (XXX) WIFI_SCAN:      --------------------
...

Example 2: Scanning on a Specific Channel

This example scans only channel 6.

1. Modify wifi_scan function:

C
// Inside the wifi_scan function, before calling esp_wifi_scan_start:

uint8_t target_channel = 6;

wifi_scan_config_t scan_config = {
    .ssid = NULL,                   // Any SSID
    .bssid = NULL,                  // Any BSSID
    .channel = target_channel,      // Scan ONLY channel 6
    .show_hidden = false,
    .scan_type = WIFI_SCAN_TYPE_ACTIVE,
    // .scan_time = { ... } // Default timing
};

ESP_LOGI(TAG, "Starting scan only on channel: %d", target_channel);

// Initiate Scan (Blocking) - Pass the config structure
esp_err_t scan_result = esp_wifi_scan_start(&scan_config, true);

// ... (rest of the scan result processing code)

2. Build, Flash, Monitor:

The output will now only show APs operating on channel 6. The scan should complete noticeably faster as it only checks one channel.

Plaintext
I (XXX) WIFI_SCAN: Starting scan only on channel: 6
...
I (XXX) WIFI_SCAN: Scan completed successfully.
I (XXX) WIFI_SCAN: Found 2 access points: // Example: 2 APs found on channel 6
I (XXX) WIFI_SCAN: Retrieved 2 records (expected 2):
I (XXX) WIFI_SCAN:   [0] SSID: NetworkOnCh6_A
I (XXX) WIFI_SCAN:      BSSID: ...
I (XXX) WIFI_SCAN:      Channel: 6 // Should always be 6
I (XXX) WIFI_SCAN:      RSSI: -58 dBm
I (XXX) WIFI_SCAN:      Auth Mode: WPA2_PSK
I (XXX) WIFI_SCAN:      --------------------
I (XXX) WIFI_SCAN:   [1] SSID: NetworkOnCh6_B
I (XXX) WIFI_SCAN:      BSSID: ...
I (XXX) WIFI_SCAN:      Channel: 6 // Should always be 6
I (XXX) WIFI_SCAN:      RSSI: -72 dBm
I (XXX) WIFI_SCAN:      Auth Mode: WPA_WPA2_PSK
I (XXX) WIFI_SCAN:      --------------------
...

Example 3: Performing a Passive Scan

This example uses passive scanning across all channels.

1. Modify wifi_scan function:

C
// Inside the wifi_scan function, before calling esp_wifi_scan_start:

wifi_scan_config_t scan_config = {
    .ssid = NULL,
    .bssid = NULL,
    .channel = 0,                   // All channels
    .show_hidden = false,           // Passive scan cannot find hidden networks
    .scan_type = WIFI_SCAN_TYPE_PASSIVE, // Set scan type to PASSIVE
    .scan_time.passive = 200       // Optional: Listen for 200ms per channel (default ~120ms)
};

ESP_LOGI(TAG, "Starting PASSIVE WiFi scan...");

// Initiate Scan (Blocking) - Pass the config structure
esp_err_t scan_result = esp_wifi_scan_start(&scan_config, true);

// ... (rest of the scan result processing code)

2. Build, Flash, Monitor:

Observe the output. The scan might take slightly longer than the default active scan. Depending on the environment and timing, it might occasionally miss some APs compared to an active scan. Hidden networks will not appear.

Example 4: Scanning for Hidden Networks

This example attempts to find a specific hidden network named “MyHiddenNet”.

Important: You must know the SSID of the hidden network you are looking for.

1. Modify wifi_scan function:

C
// Inside the wifi_scan function, before calling esp_wifi_scan_start:

// Target SSID (replace with the actual hidden network name)
const char *hidden_ssid = "MyHiddenNet";

wifi_scan_config_t scan_config = {
    .ssid = (uint8_t *)hidden_ssid, // MUST provide the SSID to probe for
    .bssid = NULL,                  // Any BSSID
    .channel = 0,                   // All channels
    .show_hidden = true,            // Enable probing for hidden networks
    .scan_type = WIFI_SCAN_TYPE_ACTIVE, // MUST be active scan
    // .scan_time = { ... } // Default timing
};

ESP_LOGI(TAG, "Starting ACTIVE scan for specific (possibly hidden) SSID: %s", hidden_ssid);

// Initiate Scan (Blocking) - Pass the config structure
esp_err_t scan_result = esp_wifi_scan_start(&scan_config, true);

// ... (rest of the scan result processing code)

2. Build, Flash, Monitor:

If an AP with the exact SSID “MyHiddenNet” is nearby (even if its SSID broadcast is disabled), it should respond to the directed probe request and appear in the scan results. If the network doesn’t exist or is out of range, 0 APs will be reported.

Plaintext
I (XXX) WIFI_SCAN: Starting ACTIVE scan for specific (possibly hidden) SSID: MyHiddenNet
...
I (XXX) WIFI_SCAN: Scan completed successfully.
I (XXX) WIFI_SCAN: Found 1 access points: // If the hidden network responded
I (XXX) WIFI_SCAN: Retrieved 1 records (expected 1):
I (XXX) WIFI_SCAN:   [0] SSID: MyHiddenNet // The SSID is revealed in the result
I (XXX) WIFI_SCAN:      BSSID: zz:yy:xx:ww:vv:uu
I (XXX) WIFI_SCAN:      Channel: 11
I (XXX) WIFI_SCAN:      RSSI: -65 dBm
I (XXX) WIFI_SCAN:      Auth Mode: WPA2_PSK
I (XXX) WIFI_SCAN:      --------------------
...

Variant Notes

  • ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: All advanced scanning features described in this chapter, including filtering, scan types, hidden network scanning, and timing adjustments via wifi_scan_config_t, are fully supported and function consistently across these WiFi-enabled variants.
  • ESP32-H2: As this variant lacks 802.11 WiFi hardware, this chapter is not applicable.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Solution / Best Practice
Incorrect ssid or bssid Pointer/Data Scan fails (esp_wifi_scan_start error), crashes due to invalid memory access, or filtering doesn’t work. Ensure ssid points to a valid, null-terminated string. Ensure bssid points to a valid 6-byte MAC array. Memory must remain valid during scan.
Passive Scan Expecting Hidden APs Hidden networks are never found, even if show_hidden = true is set. Passive scans cannot find hidden networks. Use WIFI_SCAN_TYPE_ACTIVE, show_hidden = true, AND provide the specific ssid of the hidden network.
Scan Times Too Short Scan completes quickly but frequently misses APs, especially those further away or in noisy environments. Use default timings or tune cautiously. Start with values around 100-150ms for active.max or passive time. Test thoroughly.
Channel Filter Misses Target AP Specifying a channel different from the AP’s actual operating channel results in the AP not being found. Only filter by channel if certain of the AP’s channel. Otherwise, scan all channels (channel = 0) first to discover the AP and its current channel.
Using show_hidden = true Without Specific SSID Expecting all hidden networks to appear. Results may be unreliable or vendor-dependent for wildcard hidden scans. For reliable hidden AP discovery, provide the exact ssid in wifi_scan_config_t along with show_hidden = true and active scan type.
Misunderstanding scan_time.active.min vs .max Setting min too high can slow down scans unnecessarily if APs respond quickly. Setting max too low can miss APs. min is how long to listen at least; max is the upper limit. Balance responsiveness with discovery reliability.

Exercises

  1. Combined Filtering: Modify the scan configuration to find only APs that match a specific ssid and operate on a specific channel. Test this by configuring an AP you control or by observing your local environment.
  2. Active vs. Passive Comparison: Write code that performs an active scan (all channels, default settings), prints the results and the time taken (use esp_timer_get_time() before and after esp_wifi_scan_start). Then, immediately perform a passive scan (all channels, e.g., 150ms passive time), print its results and time taken. Compare the list of found APs and the duration for both scan types in your environment.
  3. Scan Time Tuning: Experiment with the scan_time.active.max setting for active scans. Start with a low value (e.g., 50ms), run the scan, and note the number of APs found. Gradually increase the value (e.g., 100ms, 150ms, 300ms) and repeat the scan. Observe how the number of discovered APs changes (if at all) in your environment as the scan time per channel increases.

Summary

  • The wifi_scan_config_t structure provides fine-grained control over the WiFi scanning process.
  • Scans can be filtered by specific ssid, bssid, or channel to target specific networks or speed up the process.
  • scan_type allows choosing between WIFI_SCAN_TYPE_ACTIVE (faster, requires transmission, can find hidden) and WIFI_SCAN_TYPE_PASSIVE (slower, lower power, cannot find hidden).
  • Setting show_hidden = true along with a specific ssid and WIFI_SCAN_TYPE_ACTIVE enables discovery of known hidden networks.
  • scan_time parameters control the duration spent on each channel, affecting scan speed versus discovery reliability.
  • Advanced scanning techniques allow for more efficient and targeted network discovery tailored to application needs.

Further Reading

Leave a Comment

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

Scroll to Top