[espnow] Stop polling the Wi-Fi driver every loop and disable loop when idle (#18027)

This commit is contained in:
J. Nick Koston
2026-08-03 19:53:37 -05:00
committed by GitHub
parent a50369fc0e
commit 43deec3063
5 changed files with 67 additions and 15 deletions
+5 -1
View File
@@ -13,7 +13,7 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WIFI, CONF_WIFI,
) )
from esphome.core import HexInt from esphome.core import CORE, HexInt
from esphome.types import ConfigType from esphome.types import ConfigType
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@@ -151,6 +151,10 @@ async def to_code(config):
cg.add_define("USE_ESPNOW") cg.add_define("USE_ESPNOW")
cg.add_define("USE_ESPNOW_MAX_PAYLOAD_SIZE", config[CONF_MAX_PAYLOAD_SIZE]) cg.add_define("USE_ESPNOW_MAX_PAYLOAD_SIZE", config[CONF_MAX_PAYLOAD_SIZE])
if CONF_WIFI in CORE.config:
# Track the Wi-Fi channel via connect events instead of polling every loop
wifi.request_wifi_connect_state_listener()
if wifi_channel := config.get(CONF_CHANNEL): if wifi_channel := config.get(CONF_CHANNEL):
cg.add(var.set_wifi_channel(wifi_channel)) cg.add(var.set_wifi_channel(wifi_channel))
+37 -14
View File
@@ -7,7 +7,6 @@
#include <algorithm> #include <algorithm>
#include <cinttypes> #include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -75,6 +74,7 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
if (packet == nullptr) { if (packet == nullptr) {
// No events available - queue is full or we're out of memory // No events available - queue is full or we're out of memory
global_esp_now->receive_packet_queue_.increment_dropped_count(); global_esp_now->receive_packet_queue_.increment_dropped_count();
global_esp_now->enable_loop_soon_any_context();
return; return;
} }
@@ -90,8 +90,8 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
// allocate() returned non-null, the queue cannot be full. // allocate() returned non-null, the queue cannot be full.
// Wake main loop immediately to process ESP-NOW send event // Re-enable and wake the main loop to process the ESP-NOW send event
App.wake_loop_threadsafe(); global_esp_now->enable_loop_soon_any_context();
} }
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) { void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
@@ -101,6 +101,7 @@ void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int
// larger frame would overflow packet_.receive.data. // larger frame would overflow packet_.receive.data.
if (size < 0 || size > ESPNOW_MAX_DATA_LEN) { if (size < 0 || size > ESPNOW_MAX_DATA_LEN) {
global_esp_now->receive_packet_queue_.increment_dropped_count(); global_esp_now->receive_packet_queue_.increment_dropped_count();
global_esp_now->enable_loop_soon_any_context();
return; return;
} }
@@ -109,6 +110,7 @@ void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int
if (packet == nullptr) { if (packet == nullptr) {
// No events available - queue is full or we're out of memory // No events available - queue is full or we're out of memory
global_esp_now->receive_packet_queue_.increment_dropped_count(); global_esp_now->receive_packet_queue_.increment_dropped_count();
global_esp_now->enable_loop_soon_any_context();
return; return;
} }
@@ -120,8 +122,8 @@ void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
// allocate() returned non-null, the queue cannot be full. // allocate() returned non-null, the queue cannot be full.
// Wake main loop immediately to process ESP-NOW receive event // Re-enable and wake the main loop to process the ESP-NOW receive event
App.wake_loop_threadsafe(); global_esp_now->enable_loop_soon_any_context();
} }
ESPNowComponent::ESPNowComponent() { global_esp_now = this; } ESPNowComponent::ESPNowComponent() { global_esp_now = this; }
@@ -156,6 +158,11 @@ bool ESPNowComponent::is_wifi_enabled() {
} }
void ESPNowComponent::setup() { void ESPNowComponent::setup() {
#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
if (wifi::global_wifi_component != nullptr) {
wifi::global_wifi_component->add_connect_state_listener(this);
}
#endif
if (this->enable_on_boot_) { if (this->enable_on_boot_) {
this->enable_(); this->enable_();
} else { } else {
@@ -163,6 +170,19 @@ void ESPNowComponent::setup() {
} }
} }
#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
void ESPNowComponent::on_wifi_connect_state(StringRef ssid, std::span<const uint8_t, 6> bssid) {
if (ssid.empty()) {
return; // Disconnected; the channel is only meaningful while associated
}
uint8_t old_channel = this->wifi_channel_;
this->get_wifi_channel();
if (this->wifi_channel_ != old_channel) {
ESP_LOGI(TAG, "WiFi channel changed from %d to %d", old_channel, this->wifi_channel_);
}
}
#endif
void ESPNowComponent::enable() { void ESPNowComponent::enable() {
if (this->state_ == ESPNOW_STATE_ENABLED) if (this->state_ == ESPNOW_STATE_ENABLED)
return; return;
@@ -254,15 +274,6 @@ void ESPNowComponent::apply_wifi_channel() {
} }
void ESPNowComponent::loop() { void ESPNowComponent::loop() {
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr && wifi::global_wifi_component->is_connected()) {
int32_t new_channel = wifi::global_wifi_component->get_wifi_channel();
if (new_channel != this->wifi_channel_) {
ESP_LOGI(TAG, "Wifi Channel is changed from %d to %" PRId32 ".", this->wifi_channel_, new_channel);
this->wifi_channel_ = new_channel;
}
}
#endif
// Process received packets // Process received packets
ESPNowPacket *packet = this->receive_packet_queue_.pop(); ESPNowPacket *packet = this->receive_packet_queue_.pop();
while (packet != nullptr) { while (packet != nullptr) {
@@ -348,6 +359,15 @@ void ESPNowComponent::loop() {
if (send_dropped > 0) { if (send_dropped > 0) {
ESP_LOGW(TAG, "Dropped %u send packets (queue full)", send_dropped); ESP_LOGW(TAG, "Dropped %u send packets (queue full)", send_dropped);
} }
// Nothing left to do; sleep until a callback or send() re-enables the loop.
// A packet in flight (current_send_packet_) needs no loop time even when more
// packets are queued behind it: the send callback re-enables the loop when
// the result arrives, and the SENT event handler above starts the next send.
if (this->receive_packet_queue_.empty() &&
(this->current_send_packet_ != nullptr || this->send_packet_queue_.empty())) {
this->disable_loop();
}
} }
uint8_t ESPNowComponent::get_wifi_channel() { uint8_t ESPNowComponent::get_wifi_channel() {
@@ -390,6 +410,9 @@ esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payl
packet->load_data(peer_address, payload, size, callback); packet->load_data(peer_address, payload, size, callback);
// Push the packet to the send queue // Push the packet to the send queue
this->send_packet_queue_.push(packet); this->send_packet_queue_.push(packet);
// Loop may be disabled while idle; re-enable it to send the packet
// (any-context variant so callers off the main loop are safe too)
this->enable_loop_soon_any_context();
return ESP_OK; return ESP_OK;
} }
@@ -2,6 +2,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -9,6 +10,10 @@
#include "esphome/core/lock_free_queue.h" #include "esphome/core/lock_free_queue.h"
#include "espnow_packet.h" #include "espnow_packet.h"
#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
#include "esphome/components/wifi/wifi_component.h"
#endif
#include <esp_idf_version.h> #include <esp_idf_version.h>
#include <esp_mac.h> #include <esp_mac.h>
@@ -88,7 +93,11 @@ class ESPNowBroadcastHandler {
virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0; virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
}; };
#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
class ESPNowComponent final : public Component, public wifi::WiFiConnectStateListener {
#else
class ESPNowComponent final : public Component { class ESPNowComponent final : public Component {
#endif
public: public:
ESPNowComponent(); ESPNowComponent();
void setup() override; void setup() override;
@@ -114,6 +123,11 @@ class ESPNowComponent final : public Component {
void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; } void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; }
#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
// WiFiConnectStateListener interface: refresh the cached channel after each (re)connect
void on_wifi_connect_state(StringRef ssid, std::span<const uint8_t, 6> bssid) override;
#endif
void enable(); void enable();
void disable(); void disable();
bool is_disabled() const { return this->state_ == ESPNOW_STATE_DISABLED; }; bool is_disabled() const { return this->state_ == ESPNOW_STATE_DISABLED; };
+9
View File
@@ -0,0 +1,9 @@
wifi:
ssid: MySSID
password: password1
espnow:
id: espnow_component
auto_add_peer: true
peers:
- 11:22:33:44:55:66
@@ -0,0 +1,2 @@
packages:
espnow: !include common-wifi.yaml