Chapter 71: ESP-NOW with WiFi Coexistence
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand how ESP-NOW and standard Wi-Fi (Station/SoftAP mode) can operate concurrently on an ESP32.
- Learn the implications of sharing the single Wi-Fi radio between ESP-NOW and other Wi-Fi activities.
- Configure an ESP32 to maintain a Wi-Fi connection (e.g., to an AP for internet access) while simultaneously sending or receiving ESP-NOW messages.
- Implement strategies for managing Wi-Fi channels when ESP-NOW is used with an active Wi-Fi station or AP mode.
- Analyze potential performance trade-offs (latency, throughput) and how to mitigate them.
- Develop applications that leverage both ESP-NOW for local peer communication and Wi-Fi for broader network access.
Introduction
In the previous chapter, we explored ESP-NOW as a powerful protocol for direct, low-latency communication between ESP32 devices. While ESP-NOW excels at these local, connectionless interactions, many IoT applications also require devices to connect to a traditional Wi-Fi network—to access the internet, interact with local network services, or be managed remotely.
The good news is that ESP32 devices can indeed use ESP-NOW concurrently with standard Wi-Fi operations, such as being a Wi-Fi station connected to an Access Point (AP) or even acting as a SoftAP. This dual capability is immensely useful. For example, an ESP32 sensor node could use ESP-NOW to send quick alerts to a nearby ESP32 hub, while the hub itself is connected to a Wi-Fi network to upload aggregated data to the cloud. Similarly, a device might receive configuration updates over Wi-Fi and then use ESP-NOW to propagate those settings to other local devices.
This chapter delves into the specifics of ESP-NOW and Wi-Fi coexistence. We will discuss how the shared radio is managed, the critical aspect of channel alignment, potential performance considerations, and practical examples of building such hybrid communication systems.
Theory
Radio Resource Sharing
ESP32 devices have a single 2.4 GHz Wi-Fi radio. When both ESP-NOW and standard Wi-Fi (Station or SoftAP mode) are active, they must share this physical radio resource. This sharing is managed by the Wi-Fi driver and the underlying firmware through a process similar to time-division multiplexing. The driver schedules time slots for the radio to perform ESP-NOW transmissions/receptions and standard Wi-Fi operations (like sending/receiving data to/from an AP, beaconing if in AP mode, or scanning).
100ms
50ms
150ms
70ms
130ms
50ms
200ms
60ms
Critical Role of Wi-Fi Channel
Since ESP-NOW uses the same radio hardware as Wi-Fi, it operates on standard Wi-Fi channels (1-13 or 1-11, depending on the region). For successful communication:
- ESP-NOW Sender and Receiver Must Be on the Same Channel: This is a fundamental requirement for ESP-NOW.
- Wi-Fi Station Mode: If an ESP32 is operating as a Wi-Fi station connected to an Access Point (AP), its Wi-Fi radio is tuned to the channel dictated by that AP.
- ESP-NOW on a Wi-Fi Station: When this ESP32 (STA) sends or receives ESP-NOW messages, these transmissions will occur on the same channel it’s using for its AP connection.
- Therefore, any ESP-NOW peer device wanting to communicate with this STA-connected ESP32 must also be on that AP’s channel.
- Wi-Fi SoftAP Mode: If an ESP32 is operating as a SoftAP, it defines the channel for its network.
- ESP-NOW on a SoftAP: ESP-NOW communications involving this SoftAP device will occur on the channel the SoftAP is configured to use.
- ESP-NOW peers must be on this SoftAP’s channel.
- Wi-Fi APSTA Mode (Station + SoftAP): The ESP32 maintains two interfaces. The STA interface will be on the channel of the external AP it’s connected to. The SoftAP interface will be on a channel configured for the SoftAP.
- When adding an ESP-NOW peer using
esp_now_add_peer()
, theifidx
field inesp_now_peer_info_t
specifies which interface (and thus which channel context) to use for sending to that peer. WIFI_IF_STA
: Uses the STA interface’s channel.WIFI_IF_AP
: Uses the AP interface’s channel.
- When adding an ESP-NOW peer using
- Setting the Channel:
- The ESP32’s primary channel can be queried using
esp_wifi_get_channel()
. - It can be set using
esp_wifi_set_channel()
beforeesp_wifi_start()
if not connecting as a station, or if you want to set a specific channel for AP mode. If in STA mode and connected, the channel is determined by the AP. - The
channel
field inesp_now_peer_info_t
can be set to 0 to use the current primary channel of the specifiedifidx
, or to a specific channel. If a specific channel is set for a peer, the ESP32 will attempt to switch to that channel for transmission to that peer, but this can be disruptive to an active Wi-Fi STA connection if the peer channel differs from the AP channel. It’s generally more reliable for ESP-NOW peers to align with the STA’s AP channel or the SoftAP’s channel.
- The ESP32’s primary channel can be queried using
%%{init: {"theme": "base", "themeVariables": { "fontFamily": "Open Sans"}}}%% graph TD A["Start: ESP32 configured as Wi-Fi STA"] --> B{"Connect to AP <br> esp_wifi_connect()"}; B -- "Connected" --> C["Get Current Wi-Fi Channel <br> (AP's Channel) <br> esp_wifi_get_channel(&primary_channel, ...)"]; C --> D["ESP32 STA now operates on 'primary_channel'"]; D --> E["Need to communicate with ESP-NOW Peer?"]; E -- "Yes" --> F["Prepare esp_now_peer_info_t"]; F --> G["Set peer_info.ifidx = WIFI_IF_STA;"]; G --> H["Set peer_info.channel = 0; <br> (to use STA's current 'primary_channel')"]; H --> I{"Add/Mod ESP-NOW Peer <br> esp_now_add_peer/mod_peer()"}; I -- "Success" --> J["ESP-NOW messages to this peer <br> will use 'primary_channel'"]; subgraph "ESP-NOW Peer Device" K["Peer Device Wi-Fi Init"] --> L["Set Peer's Wi-Fi Channel <br> Manually or via Agreement <br> esp_wifi_set_channel(AP_s_Channel, ...)"]; L --> M["Peer operates on 'AP_s_Channel'"]; end D -.-> |"INFO: STA on AP's Channel"| K; J -.-> |"Communication Possible"| M; B -- "Fail/Disconnected" --> N["Handle Disconnection / Retry"]; N --> B; classDef startEnd fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef important fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef peer fill:#E0F2FE,stroke:#0EA5E9,color:#0369A1; class A startEnd; class C,D,F,G,H,J process; class B,E,I decision; class K,L,M peer; class N important;
Wi-Fi Mode of ESP32 | How ESP-NOW Channel is Determined | Requirement for ESP-NOW Peers | Key esp_now_peer_info_t Fields |
---|---|---|---|
Wi-Fi Station (STA) Mode | The ESP32’s radio is tuned to the channel of the Access Point (AP) it’s connected to. ESP-NOW communication from this STA device occurs on this AP channel. | Peers must also operate on this same AP channel. | ifidx = WIFI_IF_STA; channel = 0; (to use current STA channel automatically) or explicitly set to AP’s channel. |
Wi-Fi SoftAP Mode | The ESP32 (acting as SoftAP) defines its own operating channel. ESP-NOW communication from this SoftAP device occurs on this configured SoftAP channel. | Peers must operate on this SoftAP’s configured channel. | ifidx = WIFI_IF_AP; channel = 0; (to use current AP channel) or explicitly set to SoftAP’s channel. |
Wi-Fi APSTA Mode (STA + SoftAP) | STA interface is on the external AP’s channel. SoftAP interface is on its own configured channel. ESP-NOW can use either interface. | Peers must align with the channel of the specific interface (ifidx) chosen for ESP-NOW communication with them. | ifidx must be set to WIFI_IF_STA or WIFI_IF_AP. channel = 0; to use that interface’s current channel, or specify. |
ESP-NOW Only (Wi-Fi Initialized in STA/AP mode but not connected/active) | The channel is set programmatically using esp_wifi_set_channel() before or after esp_wifi_start(). | Peers must be set to this same explicitly configured channel. | ifidx typically WIFI_IF_STA. channel = 0; (uses manually set primary channel) or explicitly set. |
Performance Implications of Coexistence
Running ESP-NOW alongside active Wi-Fi operations can impact performance:
- Latency:
- ESP-NOW transmissions might be slightly delayed if the radio is busy with Wi-Fi traffic (e.g., receiving a large packet from the AP, or the ESP32 is scanning).
- Conversely, intense ESP-NOW activity could potentially delay Wi-Fi packet processing.
- The impact is usually minimal for typical low-to-moderate traffic loads but can become noticeable under heavy use of either protocol.
- Throughput:
- The total available airtime is shared. High-bandwidth Wi-Fi operations (like video streaming or large file downloads/uploads to the ESP32) will reduce the available airtime for ESP-NOW, potentially lowering its effective throughput and increasing send failures if ACKs are missed.
- Similarly, very frequent ESP-NOW messaging could slightly reduce Wi-Fi throughput.
- Reliability:
- Increased radio activity raises the chance of collisions or interference, potentially leading to more ESP-NOW send failures (no ACK received). Robust applications should use the send callback to implement retries if necessary.
- Wi-Fi Scanning:
- Active Wi-Fi scanning (e.g.,
esp_wifi_scan_start()
) requires the radio to cycle through different channels. This can temporarily disrupt ESP-NOW communication if the ESP-NOW peers are on a channel different from the one currently being scanned. It’s best to avoid Wi-Fi scanning during critical ESP-NOW transmissions or ensure ESP-NOW peers are on the ESP32’s primary operating channel.
- Active Wi-Fi scanning (e.g.,
Performance Aspect | Impact Due to Coexistence | Reason | Mitigation Strategy |
---|---|---|---|
ESP-NOW Latency | May increase slightly. | Radio might be busy with Wi-Fi traffic (e.g., STA communication with AP, SoftAP beaconing/data). | Minimize high-bandwidth Wi-Fi activity during critical ESP-NOW periods. Ensure appropriate task priorities. |
Wi-Fi Latency | May increase slightly. | Radio might be busy with ESP-NOW transmissions/receptions. | Moderate ESP-NOW traffic frequency if Wi-Fi latency is critical. |
ESP-NOW Throughput | May decrease. | Shared airtime with Wi-Fi operations. High Wi-Fi usage leaves less time for ESP-NOW. | Schedule high-bandwidth Wi-Fi tasks away from peak ESP-NOW needs. Use send callbacks and retries. |
Wi-Fi Throughput | May decrease slightly. | Shared airtime with ESP-NOW. Very frequent ESP-NOW messaging can consume radio time. | Optimize ESP-NOW message frequency and size. |
ESP-NOW Reliability | May decrease (more send failures). | Increased overall radio activity can lead to more collisions or interference. | Always use ESP-NOW send callbacks (esp_now_register_send_cb) to check status and implement retries. Ensure good antenna design and placement. |
Wi-Fi Scanning | Can disrupt ESP-NOW. | Wi-Fi scanning requires the radio to cycle through different channels. | Avoid active Wi-Fi scanning during critical ESP-NOW transmissions. If scanning is necessary, ensure ESP-NOW peers are on the ESP32’s primary operating channel or pause ESP-NOW. |
Power Consumption | Increases. | Radio is active more often to service both ESP-NOW and Wi-Fi. | Factor into power budget. Optimize sleep schedules if possible, though coexistence implies more awake time. |
Application Design Strategies for Coexistence
- Channel Alignment: The simplest and often most effective strategy is to ensure all ESP-NOW peers operate on the same channel that the ESP32 is using for its primary Wi-Fi role (STA connected channel or AP configured channel).
- Minimize Wi-Fi “Airtime Hogs”: If ESP-NOW latency is critical, try to schedule high-bandwidth Wi-Fi operations for non-critical periods.
- Use Send Callbacks: Always register and use the ESP-NOW send callback (
esp_now_register_send_cb
) to check the status of transmissions and implement retries if needed, especially in a busy RF environment. - Data Prioritization (Application Level): If both Wi-Fi and ESP-NOW data are important, your application might need logic to prioritize or queue data based on importance.
- Task Priorities: Ensure that Wi-Fi and ESP-NOW processing tasks (and the underlying Bluetooth/Wi-Fi controller tasks) have appropriate FreeRTOS priorities to prevent starvation. The ESP-IDF defaults are generally well-tuned.
- Power Consumption: Coexistence means the radio is active more often, which will increase power consumption compared to using only one mode or having long sleep periods. Factor this into your power budget.
Strategy | Description | Benefit / Purpose |
---|---|---|
Channel Alignment | Ensure all ESP-NOW peers operate on the same channel as the ESP32’s primary Wi-Fi interface (STA connected channel or AP configured channel). | Fundamental for basic ESP-NOW communication; simplifies setup and improves reliability. |
Minimize Wi-Fi “Airtime Hogs” | Schedule high-bandwidth or continuous Wi-Fi operations (e.g., large data transfers, frequent polling) during periods non-critical for ESP-NOW latency. | Reduces potential delays and improves responsiveness for ESP-NOW messages. |
Use Send Callbacks & Retries | Always register and use esp_now_register_send_cb to monitor transmission status. Implement a retry mechanism for failed ESP-NOW sends. | Improves reliability of ESP-NOW communication, especially in noisy RF environments or when radio is busy. |
Data Prioritization (Application Level) | If both Wi-Fi and ESP-NOW data streams are active, implement application logic to prioritize or queue data based on importance or real-time requirements. | Ensures critical data (either Wi-Fi or ESP-NOW) is handled preferentially. |
Task Priorities (FreeRTOS) | Ensure Wi-Fi tasks, ESP-NOW processing tasks, and underlying system tasks have appropriate FreeRTOS priorities. Usually, ESP-IDF defaults are well-tuned. | Prevents task starvation and ensures responsiveness of the communication stacks. |
Manage Wi-Fi Scanning | Avoid or carefully manage active Wi-Fi scanning (esp_wifi_scan_start()) when ESP-NOW communication is active and critical, as scanning disrupts the current channel. | Prevents temporary interruption of ESP-NOW links. |
Power Consumption Awareness | Recognize that radio coexistence increases overall radio active time, leading to higher power consumption. | Factor into device power budget calculations, especially for battery-powered devices. |
Graceful Wi-Fi Disconnection Handling | If an ESP32 STA loses its AP connection, its channel might become undefined or change upon reconnection. Design application to handle this. | Maintains ESP-NOW reliability if the primary Wi-Fi link is unstable. May involve re-checking channel and re-configuring peers. |
Practical Examples
Prerequisites:
- At least two ESP32 boards. One will act as the “Coexistence Node” (running ESP-NOW and Wi-Fi STA), and the other(s) as simple ESP-NOW peer(s).
- A Wi-Fi Access Point (router) with known SSID and password.
- VS Code with the Espressif IDF Extension.
- ESP-IDF v5.x.
Example 1: ESP32 as Wi-Fi Station and ESP-NOW Sender
The “Coexistence Node” connects to an AP and periodically sends an ESP-NOW message to another ESP32 (the “ESP-NOW Peer Receiver”).
Coexistence Node (Sender + Wi-Fi STA) Code (main/coex_sender_main.c):
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h" // For Wi-Fi connection events
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_now.h"
#define COEX_SENDER_TAG "COEX_SENDER"
// Wi-Fi Configuration
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
// ESP-NOW Peer Configuration
// Replace with the MAC address of the ESP-NOW Peer Receiver
static uint8_t s_peer_mac[ESP_NOW_ETH_ALEN] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
// Channel will be determined by the AP the sender connects to.
// The ESP-NOW peer receiver must be manually set to this same channel.
static int s_retry_num = 0;
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 && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
ESP_LOGI(COEX_SENDER_TAG, "Wi-Fi station started, attempting to connect...");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 5) { // Example retry limit
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(COEX_SENDER_TAG, "Wi-Fi disconnected, retrying to connect...");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
ESP_LOGE(COEX_SENDER_TAG, "Failed to connect to AP after multiple retries.");
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(COEX_SENDER_TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
static void wifi_init_sta(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // Adjust as per your AP
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(COEX_SENDER_TAG, "wifi_init_sta finished.");
// Wait for connection
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(COEX_SENDER_TAG, "Connected to AP SSID:%s", WIFI_SSID);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(COEX_SENDER_TAG, "Failed to connect to SSID:%s", WIFI_SSID);
} else {
ESP_LOGE(COEX_SENDER_TAG, "UNEXPECTED WIFI EVENT");
}
}
// ESP-NOW Send Callback
static void app_esp_now_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) {
ESP_LOGI(COEX_SENDER_TAG, "ESP-NOW Send CB to " MACSTR ": %s", MAC2STR(mac_addr),
(status == ESP_NOW_SEND_SUCCESS) ? "Success" : "Fail");
}
static void esp_now_sender_task(void *pvParameter) {
// Wait until Wi-Fi is connected to know the channel
ESP_LOGI(COEX_SENDER_TAG, "ESP-NOW sender task waiting for Wi-Fi connection...");
xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
ESP_LOGI(COEX_SENDER_TAG, "Wi-Fi connected, proceeding with ESP-NOW peer setup.");
uint8_t primary_channel;
wifi_second_chan_t second_channel;
ESP_ERROR_CHECK(esp_wifi_get_channel(&primary_channel, &second_channel));
ESP_LOGI(COEX_SENDER_TAG, "Current Wi-Fi STA Channel: %d", primary_channel);
esp_now_peer_info_t peer_info = {0};
memcpy(peer_info.peer_addr, s_peer_mac, ESP_NOW_ETH_ALEN);
peer_info.channel = 0; // Use current STA channel (important for coexistence)
// Alternatively, set to primary_channel if known and peer is configured
peer_info.ifidx = ESP_IF_WIFI_STA;
peer_info.encrypt = false;
if (esp_now_is_peer_exist(s_peer_mac)) {
ESP_LOGI(COEX_SENDER_TAG, "Peer " MACSTR " exists, modifying.", MAC2STR(s_peer_mac));
ESP_ERROR_CHECK(esp_now_mod_peer(&peer_info));
} else {
ESP_LOGI(COEX_SENDER_TAG, "Adding ESP-NOW peer " MACSTR " on channel %d", MAC2STR(s_peer_mac), primary_channel);
ESP_ERROR_CHECK(esp_now_add_peer(&peer_info));
}
uint32_t count = 0;
char send_data[100];
while (true) {
// Example: Simulate some Wi-Fi activity (e.g. an HTTP request would go here)
ESP_LOGI(COEX_SENDER_TAG, "Simulating some Wi-Fi activity (e.g. check connection)...");
wifi_ap_record_t ap_info;
if(esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK){
ESP_LOGI(COEX_SENDER_TAG, "Still connected to AP. RSSI: %d", ap_info.rssi);
} else {
ESP_LOGW(COEX_SENDER_TAG, "Not connected to AP currently.");
}
vTaskDelay(pdMS_TO_TICKS(1000)); // Delay for "Wi-Fi activity"
sprintf(send_data, "Coexistence Msg! Count: %" PRIu32, count++);
esp_err_t ret = esp_now_send(s_peer_mac, (uint8_t *)send_data, strlen(send_data));
if (ret == ESP_OK) {
ESP_LOGI(COEX_SENDER_TAG, "ESP-NOW Sent: %s", send_data);
} else {
ESP_LOGE(COEX_SENDER_TAG, "ESP-NOW Send error: %s", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(4000)); // Send ESP-NOW message every 5 seconds total
}
}
void app_main(void) {
ESP_ERROR_CHECK(nvs_flash_init());
wifi_init_sta(); // Initialize Wi-Fi and connect to AP
ESP_LOGI(COEX_SENDER_TAG, "Initializing ESP-NOW...");
ESP_ERROR_CHECK(esp_now_init());
ESP_LOGI(COEX_SENDER_TAG, "Registering ESP-NOW send callback...");
ESP_ERROR_CHECK(esp_now_register_send_cb(app_esp_now_send_cb));
xTaskCreate(esp_now_sender_task, "esp_now_sender_task", 4096, NULL, 4, NULL);
}
ESP-NOW Peer Receiver Code (main/peer_receiver_main.c):
This is a simple ESP-NOW receiver, similar to Example 2 in Chapter 70.
Crucially, you must manually set its Wi-Fi channel to match the channel of the AP that the “Coexistence Node” connects to. You can find the AP’s channel from your router’s admin interface or by using a Wi-Fi analyzer app. Let’s say the AP is on channel 6.
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_now.h"
#define PEER_RECEIVER_TAG "PEER_RECEIVER"
// Manually set this to the channel your Coexistence Node's AP is on
#define EXPECTED_WIFI_CHANNEL 6 // EXAMPLE: Change this!
static void app_esp_now_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) {
// ... (same receive callback as in Chapter 70, Example 2) ...
if (recv_info == NULL || data == NULL || len <= 0) {
ESP_LOGE(PEER_RECEIVER_TAG, "Receive CB error");
return;
}
const uint8_t *mac_addr = recv_info->src_addr;
ESP_LOGI(PEER_RECEIVER_TAG, "Received %d bytes from " MACSTR ":", len, MAC2STR(mac_addr));
char buffer[len + 1];
memcpy(buffer, data, len);
buffer[len] = '\0';
ESP_LOGI(PEER_RECEIVER_TAG, "Data: %s", buffer);
ESP_LOGI(PEER_RECEIVER_TAG, "RSSI: %d", recv_info->rx_ctrl->rssi);
}
static void wifi_init_for_esp_now(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); // STA mode is fine for just ESP-NOW
ESP_ERROR_CHECK(esp_wifi_start());
// Critical: Set the channel to match the sender's AP channel
ESP_LOGI(PEER_RECEIVER_TAG, "Setting Wi-Fi channel to %d for ESP-NOW", EXPECTED_WIFI_CHANNEL);
ESP_ERROR_CHECK(esp_wifi_set_channel(EXPECTED_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE));
}
void app_main(void) {
ESP_ERROR_CHECK(nvs_flash_init());
wifi_init_for_esp_now();
ESP_LOGI(PEER_RECEIVER_TAG, "Initializing ESP-NOW...");
ESP_ERROR_CHECK(esp_now_init());
ESP_LOGI(PEER_RECEIVER_TAG, "Registering ESP-NOW receive callback...");
ESP_ERROR_CHECK(esp_now_register_recv_cb(app_esp_now_recv_cb));
uint8_t mac_addr[6];
esp_wifi_get_mac(ESP_IF_WIFI_STA, mac_addr);
ESP_LOGI(PEER_RECEIVER_TAG, "My STA MAC: " MACSTR " - Waiting for ESP-NOW messages on channel %d...", MAC2STR(mac_addr), EXPECTED_WIFI_CHANNEL);
}
Build, Flash, and Observe:
- Determine the Wi-Fi channel your AP (e.g.,
YOUR_WIFI_SSID
) operates on. UpdateEXPECTED_WIFI_CHANNEL
inpeer_receiver_main.c
. - Get the MAC address of the
Peer Receiver
ESP32 and updates_peer_mac
incoex_sender_main.c
. - Build and flash both projects to their respective ESP32s.
- Open serial monitors for both.
- The
Coexistence Node
will connect to your Wi-Fi AP. Once connected, it will determine its operating channel and start sending ESP-NOW messages. - The
Peer Receiver
, if set to the correct channel, should receive these messages. - Observe the logs on the
Coexistence Node
for both Wi-Fi activity (connection, IP address) and ESP-NOW send callbacks.
This example demonstrates that the Wi-Fi STA connection dictates the channel for ESP-NOW operations on that device. The ESP-NOW peer must be aware of and configured to this channel.
Variant Notes
The ability for ESP-NOW to coexist with Wi-Fi STA or AP mode is a standard feature of the ESP-IDF Wi-Fi stack and is generally applicable to all ESP32 variants that support both Wi-Fi and ESP-NOW:
- ESP32 (Original Series): Supports Wi-Fi + ESP-NOW coexistence.
- ESP32-S2: Supports Wi-Fi + ESP-NOW coexistence.
- ESP32-S3: Supports Wi-Fi + ESP-NOW coexistence.
- ESP32-C3: Supports Wi-Fi + ESP-NOW coexistence.
- ESP32-C6: Supports Wi-Fi + ESP-NOW coexistence.
- ESP32-H2: Supports Wi-Fi + ESP-NOW coexistence.
The fundamental mechanisms for radio time-sharing and channel management are handled by the common Wi-Fi driver components in ESP-IDF. Performance characteristics (e.g., how much latency is introduced) might subtly vary between chip generations due to differences in CPU speed, memory, and radio hardware specifics, but the principles of coexistence remain the same. Always refer to the latest ESP-IDF documentation for any chip-specific notes on Wi-Fi performance or coexistence.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Channel Mismatch (Most Common) | ESP-NOW messages not received by peer. ESP-NOW send callbacks report failure (ESP_NOW_SEND_FAIL). |
Ensure channel alignment.
– If ESP32 is Wi-Fi STA: Determine AP channel (esp_wifi_get_channel() after connection). ESP-NOW peer must be on this channel. Set peer_info.channel = 0 for STA’s ESP-NOW peer additions. – If ESP32 is SoftAP: ESP-NOW peer must be on the SoftAP’s configured channel. – Manually set ESP-NOW-only peers to the correct channel using esp_wifi_set_channel(). |
ESP-NOW Init Order / Timing with Wi-Fi STA | esp_now_add_peer() fails, or ESP-NOW transmissions don’t work if channel isn’t correctly established when peer is added (e.g., STA not yet connected). |
Initialize ESP-NOW after esp_wifi_start().
For STA mode, add ESP-NOW peers (especially with peer_info.channel = 0) after the STA has successfully connected to the AP and the operating channel is stable and known. |
Aggressive Wi-Fi Activity Starving ESP-NOW | High ESP-NOW latency, frequent send failures, poor ESP-NOW throughput. |
Balance radio time. If possible, introduce small delays/pauses in continuous high-bandwidth Wi-Fi operations.
If ESP-NOW is critical, consider throttling Wi-Fi activity or redesigning Wi-Fi interaction patterns. |
Incorrect ifidx in APSTA Mode | ESP-NOW messages sent on the wrong channel (channel of the unintended interface), leading to communication failure. | Set peer_info.ifidx correctly to WIFI_IF_STA or WIFI_IF_AP based on which interface/channel the specific ESP-NOW peer is expected to communicate on. |
Wi-Fi Disconnections Not Handled for ESP-NOW | ESP-NOW works, then stops after Wi-Fi STA disconnects and reconnects (possibly on a new channel). |
Handle Wi-Fi events. Upon STA reconnection (IP_EVENT_STA_GOT_IP), re-check current channel with esp_wifi_get_channel().
If channel changed, you may need to update ESP-NOW peer channel info (esp_now_mod_peer()) or notify peers (complex). Simpler: ensure AP channel is stable. |
Forgetting to Start Wi-Fi | esp_now_init() might succeed, but esp_now_send() or adding peers fails. Underlying Wi-Fi radio is not active. | Ensure esp_wifi_start() is called after Wi-Fi initialization and mode setting, even if not connecting to an AP (for ESP-NOW only devices that need channel setting). |
Exercises
- Wi-Fi Connected Thermostat with ESP-NOW Display:
- Thermostat ESP32: Connects to your Wi-Fi AP. Simulates reading a temperature (e.g., using a variable that changes over time or via an actual sensor). Periodically sends this temperature value via ESP-NOW to a Display ESP32.
- Display ESP32: Operates as an ESP-NOW-only device. It must be manually set to the same Wi-Fi channel as the Thermostat’s AP. Receives temperature data via ESP-NOW and prints it to the serial monitor (or an actual display if you have one).
- ESP-NOW Remote Control for a Wi-Fi Web-Connected Device:
- Web Device ESP32: Connects to Wi-Fi. Runs a simple HTTP server (from a previous chapter example) that can toggle an LED state (e.g.,
/led_on
,/led_off
). - Remote Control ESP32: ESP-NOW only. Has two buttons. One button sends an “ON” command via ESP-NOW to the Web Device. The other sends “OFF”.
- The Web Device ESP32, upon receiving “ON” or “OFF” via ESP-NOW, internally toggles its LED state (and could also update its HTTP server status page if you want to extend it). This demonstrates ESP-NOW triggering Wi-Fi related actions.
- Web Device ESP32: Connects to Wi-Fi. Runs a simple HTTP server (from a previous chapter example) that can toggle an LED state (e.g.,
- Channel Hopping Resilience (Conceptual):
- This is more advanced. Think about how you might design a system where an ESP-NOW sender (also a Wi-Fi STA) and an ESP-NOW receiver could attempt to re-establish communication if the STA’s AP changes its channel.
- Hint: The receiver might periodically scan a few common channels if it loses contact. The sender, upon AP reconnection and channel change, might try sending on a few pre-agreed fallback channels or its new channel. This is non-trivial to implement robustly. Just outline the logic.
Summary
- ESP32 devices can use ESP-NOW and standard Wi-Fi (STA/AP) concurrently, sharing the same radio.
- Successful coexistence heavily relies on channel alignment: ESP-NOW peers must operate on the same Wi-Fi channel as the ESP32’s active Wi-Fi interface (STA’s AP channel or SoftAP’s configured channel).
- The Wi-Fi driver manages radio time-sharing, but high activity in one mode can impact the performance (latency, throughput) of the other.
- When an ESP32 is a Wi-Fi STA, its operating channel is determined by the AP it connects to. This channel is then used for its ESP-NOW communications.
- Careful application design, including using ESP-NOW send callbacks for reliability and managing Wi-Fi activity, is important for robust coexistence.
- All Wi-Fi capable ESP32 variants (ESP32, S2, S3, C3, C6, H2) support ESP-NOW and Wi-Fi coexistence.
Further Reading
- ESP-IDF Programming Guide – ESP-NOW: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_now.html
- ESP-IDF Programming Guide – Wi-Fi: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_wifi.html (For functions like
esp_wifi_get_channel
,esp_wifi_set_channel
).
