Chapter 33: Combining Station and AP Modes (APSTA)
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the concept and use cases for APSTA mode.
- Configure the ESP32 to operate simultaneously as a WiFi Station and an Access Point.
- Initialize and manage the two required network interfaces (
esp_netif
). - Handle WiFi events correctly in APSTA mode.
- Recognize the performance implications and channel constraints of APSTA mode.
- Implement a basic APSTA configuration using ESP-IDF v5.x.
Introduction
In previous chapters, we explored configuring the ESP32 exclusively as a WiFi Station (STA) to connect to an existing network (Chapter 27-30) or as a Soft Access Point (AP) to allow other devices to connect to it (Chapter 31-32). However, certain applications benefit immensely from the ESP32 performing both roles simultaneously. This dual role is known as APSTA mode.
Imagine scenarios like:
- WiFi Range Extender: The ESP32 connects to a distant router (STA mode) and creates its own local network (AP mode) for nearby devices, effectively extending the WiFi coverage.
- Device Provisioning: A new device starts in AP mode, allowing a user to connect directly via a phone app. The app then sends the home WiFi credentials (SSID/password) to the ESP32, which then connects to the home network in STA mode while potentially keeping the AP active for local control.
- Local Control with Internet Access: An IoT device provides a local control interface via its AP, while simultaneously connecting to the internet via STA mode to report data or receive commands from the cloud.
This chapter delves into the configuration and management of APSTA mode on the ESP32 using the ESP-IDF framework. We will learn how to set up the necessary network interfaces, configure both AP and STA parameters, and handle the combined event flow.
Theory
What is APSTA Mode?
APSTA mode refers to the capability of a WiFi device, like the ESP32, to operate concurrently as both an Access Point (AP) and a Station (STA). The ESP32 achieves this using its single physical WiFi radio through sophisticated time-division multiplexing. The radio rapidly switches between listening for beacons from the upstream router (STA behavior), sending its own beacons (AP behavior), and managing data transmission for both interfaces.
How it Works: Time-Sharing and Channel Constraint
- Time-Sharing: The ESP32’s WiFi radio doesn’t truly operate both modes in parallel in the physical sense. Instead, it allocates small time slices to service the STA connection (communicating with the upstream AP) and the SoftAP (communicating with its connected clients). This rapid switching creates the illusion of simultaneous operation.
- Channel Constraint: A fundamental limitation is that both the STA interface and the AP interface must operate on the same WiFi channel. When the ESP32 connects to an upstream AP in STA mode, it must tune its radio to the channel used by that AP. Consequently, the ESP32’s own SoftAP must also operate on this same channel. It cannot connect to a router on channel 6 and simultaneously host its own AP on channel 11. The STA connection dictates the operating channel for both interfaces.
Performance Implications
Due to the time-sharing nature of the single radio, the maximum theoretical throughput for both the STA and AP interfaces is shared. Expect a reduction in bandwidth compared to operating solely in STA or AP mode. The actual performance depends on various factors, including the number of connected clients, traffic patterns, signal strength, and RF interference. APSTA mode is generally suitable for applications where high throughput is not the primary requirement, such as control, monitoring, and basic data transfer.
Configuration Steps
Configuring APSTA mode involves extending the initialization process we saw for individual STA or AP modes:
Configuration Feature | STA Mode Only | AP Mode Only | APSTA Mode |
---|---|---|---|
Netif Creation | esp_netif_create_default_wifi_sta() (One interface) |
esp_netif_create_default_wifi_ap() (One interface) |
Both esp_netif_create_default_wifi_sta() AND esp_netif_create_default_wifi_ap() must be called (Two interfaces). |
Set WiFi Mode | esp_wifi_set_mode(WIFI_MODE_STA) |
esp_wifi_set_mode(WIFI_MODE_AP) |
esp_wifi_set_mode(WIFI_MODE_APSTA) |
wifi_config_t Usage |
Populate wifi_config.sta members. |
Populate wifi_config.ap members. |
Populate both wifi_config.sta AND wifi_config.ap members within the same wifi_config_t structure. |
esp_wifi_set_config() Call |
esp_wifi_set_config(WIFI_IF_STA, &wifi_config) |
esp_wifi_set_config(WIFI_IF_AP, &wifi_config) |
Typically called once, e.g., esp_wifi_set_config(WIFI_IF_STA, &wifi_config) . The driver applies relevant parts to both interfaces based on WIFI_MODE_APSTA . Some examples might show setting for AP interface too, but STA is common. |
DHCP Server | N/A (Acts as DHCP client) | Started by default with esp_netif_create_default_wifi_ap() for its clients. |
Started by default with esp_netif_create_default_wifi_ap() for clients connecting to the ESP32’s AP. STA interface acts as DHCP client to upstream router. |
Channel Control | Determined by the AP it connects to. | Configured via wifi_config.ap.channel . |
The STA interface’s connection to an upstream AP dictates the operating channel for both STA and the ESP32’s AP. The wifi_config.ap.channel setting is often overridden. |
- Initialize NVS: Non-Volatile Storage is required by the WiFi driver.
2. Initialize TCP/IP Stack: esp_netif_init()
is called once.
3. Create Event Loop: esp_event_loop_create_default()
is called once.
4. Create Network Interfaces (Netifs): This is a key difference. You must create two distinct esp_netif
instances:
- One for the STA interface:
esp_netif_create_default_wifi_sta()
- One for the AP interface:
esp_netif_create_default_wifi_ap()
5. Initialize WiFi Stack: Create the default WiFi initialization configuration (WIFI_INIT_CONFIG_DEFAULT()
) and call esp_wifi_init()
.
6. Register Event Handlers: Register handlers for WiFi events (ESP_EVENT_ANY_ID
for WIFI_EVENT
) and IP events (ESP_EVENT_ANY_ID
for IP_EVENT
). The handler function will need to differentiate events related to the STA interface versus the AP interface.
7. Set WiFi Mode: Explicitly set the mode to WIFI_MODE_APSTA
using esp_wifi_set_mode(WIFI_MODE_APSTA)
. This must be done before setting the configuration.
8. Configure AP and STA Parameters: Populate a wifi_config_t
structure. This structure contains members for both AP (ap
) and STA (sta
) configurations. You need to provide SSID, password, security settings, etc., for both roles.
wifi_config_t wifi_config = {
.ap = {
.ssid = "MyESP32-AP",
.ssid_len = strlen("MyESP32-AP"),
.channel = 1, // Note: This might be overridden by STA connection
.password = "password123",
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
// .pmf_cfg = { ... } // Optional PMF config
},
.sta = {
.ssid = "MyHomeRouter",
.password = "routerPassword",
.scan_method = WIFI_FAST_SCAN, // Or WIFI_ALL_CHANNEL_SCAN
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
// .pmf_cfg = { ... } // Optional PMF config
},
};
9. Set WiFi Configuration: Apply the combined configuration using esp_wifi_set_config(WIFI_IF_AP, &wifi_config)
and esp_wifi_set_config(WIFI_IF_STA, &wifi_config)
. Note that you typically call esp_wifi_set_config()
once with the interface set to ESP_IF_WIFI_STA
or ESP_IF_WIFI_AP
when setting the combined config after setting the mode to APSTA. The IDF driver internally applies the relevant parts. A common practice is to call it for the STA interface: esp_wifi_set_config(WIFI_IF_STA, &wifi_config)
.
10. Start WiFi: Call esp_wifi_start()
. This initiates both the AP and the STA connection process.
11. Connect STA (Optional but common): Call esp_wifi_connect()
to explicitly trigger the STA connection attempt. The AP interface becomes active automatically after esp_wifi_start()
.
%%{ 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 config fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef netif fill:#E0F2FE,stroke:#0EA5E9,stroke-width:1px,color:#0C4A6E; %% For Netif creation classDef event fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; A[<b>Start</b>: Initialize APSTA Mode]:::primary B["Initialize NVS<br><code class="code-like">nvs_flash_init()</code>"]:::process C["Initialize TCP/IP Stack<br><code class="code-like">esp_netif_init()</code>"]:::process D["Create Default Event Loop<br><code class="code-like">esp_event_loop_create_default()</code>"]:::process E["Create STA Netif<br><code class="code-like">esp_netif_create_default_wifi_sta()</code>"]:::netif F["Create AP Netif<br><code class="code-like">esp_netif_create_default_wifi_ap()</code>"]:::netif G["Initialize WiFi Stack<br><code class="code-like">wifi_init_config_default()</code> & <code class="code-like">esp_wifi_init()</code>"]:::process H["Register Event Handlers<br>(for <code class="code-like">WIFI_EVENT</code> & <code class="code-like">IP_EVENT</code>)"]:::config I["Set WiFi Mode to APSTA<br><code class="code-like">esp_wifi_set_mode(WIFI_MODE_APSTA)</code>"]:::config J["Populate <code class="code-like">wifi_config_t</code><br>with both <code class="code-like">.ap</code> and <code class="code-like">.sta</code> parameters"]:::config K["Set WiFi Configuration<br>(e.g., <code class="code-like">esp_wifi_set_config(WIFI_IF_STA, &wifi_config)</code>)"]:::config L["Start WiFi Driver<br><code class="code-like">esp_wifi_start()</code>"]:::process M["AP Interface Starts (Event: <code class='code-like'>WIFI_EVENT_AP_START</code>)<br>STA Interface Starts (Event: <code class='code-like'>WIFI_EVENT_STA_START</code>)"]:::event N["In STA_START handler, call <code class='code-like'>esp_wifi_connect()</code><br>to connect STA to upstream AP"]:::process O["<b>APSTA Mode Active</b><br>AP broadcasting, STA connecting/connected"]:::success A --> B; B --> C; C --> D; D --> E; E --> F; F --> G; G --> H; H --> I; I --> J; J --> K; K --> L; L --> M; M --> N; N --> O;
Event Handling in APSTA Mode
The single event handler registered for WIFI_EVENT
will receive events for both interfaces. Key events include:
WIFI_EVENT_STA_START
: Station interface has started.WIFI_EVENT_STA_CONNECTED
: Station has connected to the upstream AP.WIFI_EVENT_STA_DISCONNECTED
: Station has disconnected from the upstream AP.WIFI_EVENT_AP_START
: SoftAP interface has started.WIFI_EVENT_AP_STACONNECTED
: A client has connected to the ESP32’s SoftAP.WIFI_EVENT_AP_STADISCONNECTED
: A client has disconnected from the ESP32’s SoftAP.
Similarly, the handler for IP_EVENT
will primarily receive IP_EVENT_STA_GOT_IP
when the ESP32’s STA interface successfully obtains an IP address from the upstream router’s DHCP server.
Event Name | Event Base | Event ID | Relevant Interface | Key Data / Purpose |
---|---|---|---|---|
Station Interface Started | WIFI_EVENT |
WIFI_EVENT_STA_START |
STA | STA interface is up. Typically call esp_wifi_connect() here. |
Station Connected to Upstream AP | WIFI_EVENT |
WIFI_EVENT_STA_CONNECTED |
STA | STA successfully associated with the upstream AP. Data: wifi_event_sta_connected_t (SSID, BSSID of upstream AP). |
Station Disconnected from Upstream AP | WIFI_EVENT |
WIFI_EVENT_STA_DISCONNECTED |
STA | STA lost connection to upstream AP. Data: wifi_event_sta_disconnected_t (reason code). Implement retry logic. |
Station Interface Got IP | IP_EVENT |
IP_EVENT_STA_GOT_IP |
STA | STA interface obtained an IP address from upstream DHCP. Data: ip_event_got_ip_t (IP info). ESP32 can now access network via STA. |
SoftAP Interface Started | WIFI_EVENT |
WIFI_EVENT_AP_START |
AP | ESP32’s SoftAP is up and broadcasting. Its channel is now dictated by the STA connection. |
Client Connected to SoftAP | WIFI_EVENT |
WIFI_EVENT_AP_STACONNECTED |
AP | A client device connected to the ESP32’s SoftAP. Data: wifi_event_ap_staconnected_t (client MAC, AID). |
Client Disconnected from SoftAP | WIFI_EVENT |
WIFI_EVENT_AP_STADISCONNECTED |
AP | A client device disconnected from the ESP32’s SoftAP. Data: wifi_event_ap_stadisconnected_t (client MAC, AID). |
SoftAP Client Assigned IP (Optional) | IP_EVENT |
IP_EVENT_AP_STAIPASSIGNED |
AP | ESP32’s DHCP server assigned an IP to a connected client. Data: ip_event_ap_staipassigned_t (client IP). Useful for logging. |
Your event handler function receives event_base
and event_id
arguments. You can use these to determine the context of the event (e.g., event_base == WIFI_EVENT
and event_id == WIFI_EVENT_STA_CONNECTED
). For events like STACONNECTED
or STADISCONNECTED
related to the AP, the event_data
structure contains the MAC address of the client.
Practical Examples
Example 1: Basic APSTA Configuration
This example demonstrates setting up the ESP32 in APSTA mode. It will connect to a pre-defined upstream router (CONFIG_EXAMPLE_WIFI_SSID
, CONFIG_EXAMPLE_WIFI_PASSWORD
) while simultaneously creating a SoftAP (CONFIG_EXAMPLE_ESP_WIFI_SSID
, CONFIG_EXAMPLE_ESP_WIFI_PASSWORD
).
Prerequisites:
- ESP-IDF v5.x environment with VS Code.
- An ESP32 board.
- An existing WiFi router with known SSID and password.
- Another WiFi device (like a smartphone) to connect to the ESP32’s AP.
1. Project Setup:
- Create a new ESP-IDF project in VS Code or navigate to an existing one.
- Use
idf.py menuconfig
(or the VS Code equivalent) to set the WiFi credentials:Component config
->Example Connection Configuration
- Set
WiFi SSID
(for STA connection, e.g., “MyHomeRouter”) - Set
WiFi Password
(for STA connection, e.g., “routerPassword”)
- Set
Component config
->Example WiFi Configuration
- Set
WiFi AP SSID
(for the ESP32’s AP, e.g., “ESP32-APSTA”) - Set
WiFi AP Password
(for the ESP32’s AP, e.g., “esp32apsta”) - Ensure
WiFi AP Max Connections
is set (e.g., 4). - Ensure
WiFi AP Channel
is set (e.g., 1, but remember it will be overridden by the STA connection).
- Set
- Save the configuration and exit menuconfig.
2. Code (main/apsta_example_main.c
):
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h" // For event groups (optional, can use direct logging)
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sys.h"
// Get credentials from Kconfig
#define EXAMPLE_ESP_WIFI_SSID CONFIG_EXAMPLE_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_EXAMPLE_WIFI_PASSWORD
#define EXAMPLE_ESP_AP_WIFI_SSID CONFIG_EXAMPLE_ESP_WIFI_SSID
#define EXAMPLE_ESP_AP_WIFI_PASS CONFIG_EXAMPLE_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_AP_WIFI_CHANNEL CONFIG_EXAMPLE_ESP_WIFI_CHANNEL
#define EXAMPLE_MAX_STA_CONN CONFIG_EXAMPLE_MAX_STA_CONN
static const char *TAG = "APSTA_EXAMPLE";
// Event handler for WiFi and IP events
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "WIFI_EVENT_STA_START: Station mode started, connecting to AP...");
esp_wifi_connect(); // Trigger connection attempt
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI(TAG, "WIFI_EVENT_STA_CONNECTED: Connected to AP SSID:%s", EXAMPLE_ESP_WIFI_SSID);
// Note: Channel is now fixed by the AP we connected to.
break;
case WIFI_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED: Disconnected from AP, attempting reconnect...");
esp_wifi_connect(); // Attempt to reconnect
break;
case WIFI_EVENT_AP_START:
ESP_LOGI(TAG, "WIFI_EVENT_AP_START: SoftAP started, SSID:%s", EXAMPLE_ESP_AP_WIFI_SSID);
// Log the actual channel the AP is using (which is dictated by STA)
wifi_config_t conf;
esp_wifi_get_config(WIFI_IF_AP, &conf);
ESP_LOGI(TAG, "AP Channel: %d", conf.ap.channel);
break;
case WIFI_EVENT_AP_STACONNECTED: {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED: Station " MACSTR " joined, AID=%d",
MAC2STR(event->mac), event->aid);
break;
}
case WIFI_EVENT_AP_STADISCONNECTED: {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "WIFI_EVENT_AP_STADISCONNECTED: Station " MACSTR " left, AID=%d",
MAC2STR(event->mac), event->aid);
break;
}
default:
ESP_LOGI(TAG, "Unhandled WIFI_EVENT: %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 from AP:" IPSTR, IP2STR(&event->ip_info.ip));
// You can now use the STA interface for network communication
break;
}
case IP_EVENT_AP_STAIPASSIGNED: { // Optional: Log IP assigned by ESP32's DHCP server
ip_event_ap_staipassigned_t* event = (ip_event_ap_staipassigned_t*) event_data;
ESP_LOGI(TAG, "IP_EVENT_AP_STAIPASSIGNED: Assigned IP " IPSTR " to station", IP2STR(&event->ip));
break;
}
default:
ESP_LOGI(TAG, "Unhandled IP_EVENT: %ld", event_id);
break;
}
}
}
// Function to initialize WiFi in APSTA mode
void wifi_init_apsta(void)
{
// 1. 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);
// 2. Initialize TCP/IP Stack
ESP_ERROR_CHECK(esp_netif_init());
// 3. Create Default Event Loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 4. Create Network Interfaces (Netifs) - *** Create BOTH STA and AP ***
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
assert(sta_netif); // Ensure creation was successful
assert(ap_netif);
// 5. Initialize WiFi Stack
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 6. Register Event Handlers
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
// 7. Set WiFi Mode to APSTA
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
// 8. Configure AP and STA Parameters
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_AP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_AP_WIFI_SSID),
.channel = EXAMPLE_ESP_AP_WIFI_CHANNEL, // Will be overridden by STA connection channel
.password = EXAMPLE_ESP_AP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.required = false,
},
},
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
.scan_method = WIFI_FAST_SCAN, // Adjust as needed
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // Adjust based on router security
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
// Basic security check for AP password
if (strlen(EXAMPLE_ESP_AP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
ESP_LOGW(TAG, "AP running in OPEN mode (no password)");
} else if (strlen(EXAMPLE_ESP_AP_WIFI_PASS) < 8) {
ESP_LOGE(TAG, "AP password must be at least 8 characters long");
// Handle error appropriately - perhaps stop initialization
return;
}
// 9. Set WiFi Configuration (Applying config for STA implicitly handles AP too in APSTA mode)
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// 10. Start WiFi
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_apsta finished.");
ESP_LOGI(TAG, "Connect to AP SSID: %s Password: %s", EXAMPLE_ESP_AP_WIFI_SSID, EXAMPLE_ESP_AP_WIFI_PASS);
ESP_LOGI(TAG, "ESP32 will attempt to connect to STA SSID: %s", EXAMPLE_ESP_WIFI_SSID);
// Note: esp_wifi_connect() is called by the WIFI_EVENT_STA_START handler
}
void app_main(void)
{
ESP_LOGI(TAG, "Starting ESP32 APSTA Example...");
wifi_init_apsta();
// The application can now proceed with other tasks.
// WiFi connection and AP management happen in the background via events.
}
3. Build, Flash, and Monitor:
- Connect your ESP32 board.
- Run
idf.py build
- Run
idf.py flash monitor
4. Observe Output:
You should see logs similar to this (timing and specific MAC addresses/IPs will vary):
I (XXX) APSTA_EXAMPLE: Starting ESP32 APSTA Example...
I (XXX) APSTA_EXAMPLE: wifi_init_apsta finished.
I (XXX) APSTA_EXAMPLE: Connect to AP SSID: ESP32-APSTA Password: esp32apsta
I (XXX) APSTA_EXAMPLE: ESP32 will attempt to connect to STA SSID: MyHomeRouter
I (XXX) wifi:wifi_init_apsta finished.
I (XXX) wifi:wifi driver task: 3ffdbf48, prio:23, stack:4096, core=0 // WiFi driver starts
I (XXX) wifi:wifi init success // WiFi stack initialized
I (XXX) wifi:Set ps type: 0 // Power save mode (default: none)
I (XXX) APSTA_EXAMPLE: WIFI_EVENT_STA_START: Station mode started, connecting to AP... // STA interface starts
I (XXX) APSTA_EXAMPLE: WIFI_EVENT_AP_START: SoftAP started, SSID:ESP32-APSTA // AP interface starts
I (XXX) APSTA_EXAMPLE: AP Channel: 1 // Initial AP channel (might change)
I (XXX) wifi:state: init -> auth (b0) // STA attempting connection
I (XXX) wifi:state: auth -> assoc (0)
I (XXX) wifi:state: assoc -> run (10)
I (XXX) wifi:connected with MyHomeRouter, channel 6, bw 20 // STA connected! Note the channel.
I (XXX) wifi:pm start, type:0
I (XXX) APSTA_EXAMPLE: WIFI_EVENT_STA_CONNECTED: Connected to AP SSID:MyHomeRouter // Event confirms STA connection
I (XXX) esp_netif_handlers: sta ip: 192.168.1.105, mask: 255.255.255.0, gw: 192.168.1.1 // STA gets IP
I (XXX) APSTA_EXAMPLE: IP_EVENT_STA_GOT_IP: Got IP address from AP:192.168.1.105 // Event confirms IP
// --- Now connect your phone/laptop to the "ESP32-APSTA" network ---
I (XXX) wifi:new:<6,1>, old:<1,0>, ap:<1,1>, sta:<6,1>, prof:1 // WiFi driver logs client connection
I (XXX) wifi:station: xx:xx:xx:xx:xx:xx join, AID = 1 // Client MAC address joining AP
I (XXX) APSTA_EXAMPLE: WIFI_EVENT_AP_STACONNECTED: Station xx:xx:xx:xx:xx:xx joined, AID=1 // Event confirms client connection
I (XXX) dhcpserver: Client xx:xx:xx:xx:xx:xx assigned IP 192.168.4.2 // ESP32 DHCP assigns IP to client
I (XXX) APSTA_EXAMPLE: IP_EVENT_AP_STAIPASSIGNED: Assigned IP 192.168.4.2 to station // Event confirms IP assignment
At this point, your ESP32 is connected to your home router and is also providing its own WiFi network that your phone is connected to.
Variant Notes
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: All these variants fully support the
WIFI_MODE_APSTA
configuration as described in this chapter. The underlying principles and ESP-IDF APIs are consistent. Minor performance differences might exist due to variations in radio hardware and processing power, but the functionality is the same. - ESP32-H2: This variant does not support 802.11 WiFi (it focuses on 802.15.4 protocols like Thread and Zigbee, plus Bluetooth LE). Therefore, APSTA mode is not applicable to the ESP32-H2.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Solution / Best Practice |
---|---|---|
Forgetting to Create Both Netifs | Only one interface (STA or AP) functions correctly, or initialization fails. Errors related to missing network interfaces. | Explicitly call both esp_netif_create_default_wifi_sta() AND esp_netif_create_default_wifi_ap() . |
Setting Mode After Config/Start | WiFi mode (WIFI_MODE_APSTA ) not set correctly. Unexpected behavior (only STA or AP active). Initialization errors. |
Call esp_wifi_set_mode(WIFI_MODE_APSTA) before esp_wifi_set_config() and esp_wifi_start() . |
Incorrect Event Handling Logic | Application misinterprets connection status (e.g., STA connected when an AP client connected). Incorrect reactions to events. | Carefully check both event_base and event_id in the handler to differentiate STA and AP events. |
Ignoring Channel Constraint | ESP32’s AP doesn’t operate on the configured AP channel. Clients may have trouble connecting to the ESP32’s AP. | Understand that the STA connection’s channel dictates the operating channel for both interfaces. Log the actual AP channel after WIFI_EVENT_AP_START . |
Unrealistic Performance Expectations | Slow data transfer speeds when significant traffic flows through both STA and AP interfaces simultaneously. | Design for APSTA understanding performance trade-offs due to radio time-sharing. Best for control, monitoring, lower-bandwidth tasks. |
Missing NVS Initialization | WiFi functions return ESP_ERR_NVS_NOT_INITIALIZED . WiFi fails to start or behaves erratically. |
Always call nvs_flash_init() successfully in app_main() before any WiFi operations. |
Incorrect wifi_config_t Population |
Forgetting to set parameters for either .ap or .sta members, or using incorrect security settings for one of them. |
Ensure both wifi_config.ap and wifi_config.sta substructures are fully and correctly populated with desired SSIDs, passwords, and auth modes. |
Exercises
- Connection Status LEDs: Modify the example to use two GPIOs connected to LEDs.
- Turn LED1 ON when the STA interface successfully connects to the upstream AP (
WIFI_EVENT_STA_CONNECTED
) and gets an IP (IP_EVENT_STA_GOT_IP
). Turn it OFF onWIFI_EVENT_STA_DISCONNECTED
. - Turn LED2 ON when at least one client is connected to the ESP32’s SoftAP (
WIFI_EVENT_AP_STACONNECTED
). Turn it OFF when the last client disconnects (WIFI_EVENT_AP_STADISCONNECTED
). You’ll need to maintain a client count based on connect/disconnect events.
- Turn LED1 ON when the STA interface successfully connects to the upstream AP (
- APSTA Status Task: Create a separate FreeRTOS task that periodically (e.g., every 10 seconds) checks and logs:
- The STA connection status (Is it connected? What’s its IP address?). Use
esp_netif_get_ip_info()
for the STA netif. - The list of clients connected to the SoftAP (MAC addresses and RSSI). Use
esp_wifi_ap_get_sta_list()
as shown in Chapter 32.
- The STA connection status (Is it connected? What’s its IP address?). Use
- Simple Network Bridge Concept (Conceptual):Note: A full, efficient network bridge/NAT is complex and often requires deeper lwIP configuration or external components. For this exercise, simply verify connectivity:
- Ensure the ESP32 is in APSTA mode and connected to the internet via STA.
- Connect a client (e.g., laptop) to the ESP32’s AP.
- From the client connected to the ESP32’s AP, try to ping the IP address of the upstream router (the ESP32’s gateway obtained via
IP_EVENT_STA_GOT_IP
). - Observe if the ping is successful. This demonstrates basic IP-level reachability through the ESP32, although it doesn’t implement full bridging/NAT. Discuss why this might work (basic IP forwarding might be enabled by default in some lwIP configurations) or fail (lack of proper routing/NAT).
- Dynamic STA Reconfiguration: Modify the
app_main
or create a command interface (e.g., via UART). Add functionality to:- Disconnect the current STA connection (
esp_wifi_disconnect()
). - Update the
sta
portion of thewifi_config_t
structure with new SSID/password received via the command interface. - Apply the new configuration using
esp_wifi_set_config(WIFI_IF_STA, &wifi_config)
. - Initiate a connection attempt to the new AP using
esp_wifi_connect()
. - Ensure the SoftAP remains active throughout this process.
- Disconnect the current STA connection (
Summary
- APSTA Mode: Allows the ESP32 to act as a WiFi Station and an Access Point simultaneously using a single radio via time-sharing.
- Use Cases: Ideal for range extenders, device provisioning, and providing local control with simultaneous internet access.
- Configuration: Requires initializing two
esp_netif
instances (one STA, one AP), setting the mode toWIFI_MODE_APSTA
, and providing configuration details for both interfaces inwifi_config_t
. - Channel Constraint: The STA connection dictates the operating WiFi channel for both the STA and AP interfaces.
- Performance: Throughput is shared between the two interfaces, expect lower bandwidth compared to dedicated modes.
- Event Handling: A single event handler receives events for both interfaces; logic must differentiate based on
event_base
andevent_id
. - Initialization Order: Initialize NVS, TCP/IP, Event Loop, Netifs (STA & AP), WiFi Stack, Register Handlers, Set Mode (APSTA), Set Config, Start WiFi.
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 forWIFI_MODE_APSTA
,esp_wifi_set_mode
,wifi_config_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 foresp_netif_create_default_wifi_sta
,esp_netif_create_default_wifi_ap
) - ESP-IDF AP_STA Example: https://github.com/espressif/esp-idf/tree/v5.4/examples/wifi/softap_sta