mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[espnow] Add support for ESPNow with ESP8266 (#14407)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
pre-commit-ci-lite[bot]
Copilot
J. Nick Koston
J. Nick Koston
parent
fdf1752307
commit
6bc19d2a1a
@@ -17,6 +17,8 @@ from esphome.const import (
|
||||
CONF_ON_ERROR,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WIFI,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
@@ -24,7 +26,14 @@ import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
# network initialises esp_netif and the default event loop on the ESP32. The ESP8266 needs neither, and
|
||||
# loading it there would pull in mdns, which requires wifi
|
||||
return ["network"] if CORE.is_esp32 else []
|
||||
|
||||
|
||||
DOMAIN = "espnow"
|
||||
|
||||
peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6)
|
||||
@@ -121,15 +130,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ADDRESS): cv.mac_address,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_BROADCAST): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnBroadcastTrigger),
|
||||
cv.Optional(CONF_ADDRESS): cv.mac_address,
|
||||
}
|
||||
# The ESP8266 SDK does not report the destination address, so broadcasts are indistinguishable there
|
||||
cv.Optional(CONF_ON_BROADCAST): cv.All(
|
||||
cv.only_on_esp32,
|
||||
automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
OnBroadcastTrigger
|
||||
),
|
||||
cv.Optional(CONF_ADDRESS): cv.mac_address,
|
||||
}
|
||||
),
|
||||
),
|
||||
},
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on_esp32,
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
||||
)
|
||||
|
||||
|
||||
@@ -138,6 +153,8 @@ def _validate_variant(config: ConfigType) -> ConfigType:
|
||||
# ESP-NOW; only the ESP32-P4 has a path, via the esp32_hosted shim that
|
||||
# supplies the esp_now_* symbols. Fail here with a clear message instead of
|
||||
# letting the build reach an "undefined reference to esp_now_*" link error.
|
||||
if not CORE.is_esp32:
|
||||
return config
|
||||
variant = get_esp32_variant()
|
||||
if wifi.variant_has_wifi(variant):
|
||||
return config
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "espnow_component.h"
|
||||
|
||||
@@ -142,4 +142,4 @@ class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const ui
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
#include "espnow_component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "espnow_err.h"
|
||||
#include "espnow_platform.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_random.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#ifdef USE_WIFI
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
@@ -49,7 +43,7 @@ static const LogString *espnow_error_to_str(esp_err_t error) {
|
||||
case ESP_ERR_ESPNOW_INTERNAL:
|
||||
return LOG_STR("Internal Error");
|
||||
case ESP_ERR_ESPNOW_NO_MEM:
|
||||
return LOG_STR("Our of memory");
|
||||
return LOG_STR("Out of memory");
|
||||
case ESP_ERR_ESPNOW_NOT_FOUND:
|
||||
return LOG_STR("Peer not found");
|
||||
case ESP_ERR_ESPNOW_IF:
|
||||
@@ -57,96 +51,91 @@ static const LogString *espnow_error_to_str(esp_err_t error) {
|
||||
case ESP_OK:
|
||||
return LOG_STR("OK");
|
||||
case ESP_NOW_SEND_FAIL:
|
||||
case ESP_FAIL:
|
||||
return LOG_STR("Failed");
|
||||
default:
|
||||
return LOG_STR("Unknown Error");
|
||||
}
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status)
|
||||
#else
|
||||
void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
|
||||
#endif
|
||||
{
|
||||
// The analyzer traces a leak on the failed-push path, which cannot happen: the
|
||||
// pools are sized to the queue capacity (SIZE-1), so allocate() returns nullptr
|
||||
// before push() can find the ring full.
|
||||
// NOLINTBEGIN(clang-analyzer-unix.Malloc)
|
||||
void ESPNowComponent::send_reported(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||
// Allocate an event from the pool
|
||||
ESPNowPacket *packet = global_esp_now->receive_packet_pool_.allocate();
|
||||
ESPNowPacket *packet = this->receive_packet_pool_.allocate();
|
||||
if (packet == nullptr) {
|
||||
// No events available - queue is full or we're out of memory
|
||||
global_esp_now->receive_packet_queue_.increment_dropped_count();
|
||||
global_esp_now->enable_loop_soon_any_context();
|
||||
this->receive_packet_queue_.increment_dropped_count();
|
||||
this->enable_loop_soon_any_context();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load new packet data (replaces previous packet)
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
packet->load_sent_data(info->des_addr, status);
|
||||
#else
|
||||
packet->load_sent_data(mac_addr, status);
|
||||
#endif
|
||||
|
||||
// Push the packet to the queue
|
||||
global_esp_now->receive_packet_queue_.push(packet);
|
||||
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
|
||||
// allocate() returned non-null, the queue cannot be full.
|
||||
this->receive_packet_queue_.push(packet);
|
||||
|
||||
// Re-enable and wake the main loop to process the ESP-NOW send event
|
||||
global_esp_now->enable_loop_soon_any_context();
|
||||
this->enable_loop_soon_any_context();
|
||||
}
|
||||
|
||||
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||
void ESPNowComponent::packet_received(const uint8_t *src_addr, const uint8_t *des_addr, const uint8_t *data, int size,
|
||||
int8_t rssi, uint32_t timestamp) {
|
||||
// Drop oversized frames before copying. ESP-NOW v2 peers (IDF >= 5.4 builds a
|
||||
// v2 stack with no opt-out) can send up to ESP_NOW_MAX_DATA_LEN_V2 (1470 B),
|
||||
// but the receive buffer only fits v2 frames with ``max_payload_size``; copying a
|
||||
// but the receive buffer only fits frames up to ``max_payload_size``; copying a
|
||||
// larger frame would overflow packet_.receive.data.
|
||||
if (size < 0 || size > ESPNOW_MAX_DATA_LEN) {
|
||||
global_esp_now->receive_packet_queue_.increment_dropped_count();
|
||||
global_esp_now->enable_loop_soon_any_context();
|
||||
if (size < 0 || static_cast<size_t>(size) > ESPNOW_MAX_DATA_LEN) {
|
||||
this->receive_packet_queue_.increment_dropped_count();
|
||||
this->enable_loop_soon_any_context();
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate an event from the pool
|
||||
ESPNowPacket *packet = global_esp_now->receive_packet_pool_.allocate();
|
||||
ESPNowPacket *packet = this->receive_packet_pool_.allocate();
|
||||
if (packet == nullptr) {
|
||||
// No events available - queue is full or we're out of memory
|
||||
global_esp_now->receive_packet_queue_.increment_dropped_count();
|
||||
global_esp_now->enable_loop_soon_any_context();
|
||||
this->receive_packet_queue_.increment_dropped_count();
|
||||
this->enable_loop_soon_any_context();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load new packet data (replaces previous packet)
|
||||
packet->load_received_data(info, data, size);
|
||||
packet->load_received_data(src_addr, des_addr, data, size, rssi, timestamp);
|
||||
|
||||
// Push the packet to the queue
|
||||
global_esp_now->receive_packet_queue_.push(packet);
|
||||
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
|
||||
// allocate() returned non-null, the queue cannot be full.
|
||||
this->receive_packet_queue_.push(packet);
|
||||
|
||||
// Re-enable and wake the main loop to process the ESP-NOW receive event
|
||||
global_esp_now->enable_loop_soon_any_context();
|
||||
this->enable_loop_soon_any_context();
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-unix.Malloc)
|
||||
|
||||
ESPNowComponent::ESPNowComponent() { global_esp_now = this; }
|
||||
|
||||
void ESPNowComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "espnow:");
|
||||
// Only report driver details once enabled; with enable_on_boot: false the
|
||||
// Wi-Fi driver is not initialized yet and esp_now_get_version() would crash,
|
||||
// Wi-Fi driver is not initialized yet and the version call would crash,
|
||||
// and after a failed enable_() the values would be meaningless.
|
||||
if (this->state_ != ESPNOW_STATE_ENABLED) {
|
||||
// OFF here means enable_() failed; the core logs the FAILED marker separately
|
||||
ESP_LOGCONFIG(TAG, " %s", this->is_disabled() ? LOG_STR_LITERAL("Disabled") : LOG_STR_LITERAL("Not enabled"));
|
||||
return;
|
||||
}
|
||||
uint32_t version = 0;
|
||||
esp_now_get_version(&version);
|
||||
char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
format_mac_addr_upper(this->own_address_, own_addr_buf);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Own address: %s\n"
|
||||
" Version: v%" PRIu32 "\n"
|
||||
" Wi-Fi channel: %d",
|
||||
own_addr_buf, version, this->wifi_channel_);
|
||||
own_addr_buf, this->wifi_channel_);
|
||||
uint32_t version = platform::get_version();
|
||||
if (version != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Version: v%" PRIu32, version);
|
||||
}
|
||||
#ifdef USE_WIFI
|
||||
ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
|
||||
#endif
|
||||
@@ -183,6 +172,10 @@ void ESPNowComponent::on_wifi_connect_state(StringRef ssid, std::span<const uint
|
||||
if (this->wifi_channel_ != old_channel) {
|
||||
ESP_LOGI(TAG, "WiFi channel changed from %d to %d", old_channel, this->wifi_channel_);
|
||||
}
|
||||
// Peers added while the station was scanning were bound to the channel of that moment, so rebind on every connect
|
||||
if (this->state_ == ESPNOW_STATE_ENABLED) {
|
||||
platform::rebind_peers();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -198,41 +191,19 @@ void ESPNowComponent::enable() {
|
||||
|
||||
void ESPNowComponent::enable_() {
|
||||
if (!this->is_wifi_enabled()) {
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
ESP_ERROR_CHECK(esp_wifi_disconnect());
|
||||
|
||||
platform::init_radio();
|
||||
this->apply_wifi_channel();
|
||||
}
|
||||
this->get_wifi_channel();
|
||||
|
||||
esp_err_t err = esp_now_init();
|
||||
esp_err_t err = platform::init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(err));
|
||||
ESP_LOGE(TAG, "Init failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = esp_now_register_recv_cb(on_data_received);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = esp_now_register_send_cb(on_send_report);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
esp_wifi_get_mac(WIFI_IF_STA, this->own_address_);
|
||||
platform::read_mac(this->own_address_);
|
||||
|
||||
this->state_ = ESPNOW_STATE_ENABLED;
|
||||
|
||||
@@ -248,12 +219,9 @@ void ESPNowComponent::disable() {
|
||||
ESP_LOGD(TAG, "Disabling");
|
||||
this->state_ = ESPNOW_STATE_DISABLED;
|
||||
|
||||
esp_now_unregister_recv_cb();
|
||||
esp_now_unregister_send_cb();
|
||||
|
||||
esp_err_t err = esp_now_deinit();
|
||||
esp_err_t err = platform::deinit();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
|
||||
ESP_LOGE(TAG, "Deinit failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +239,10 @@ void ESPNowComponent::apply_wifi_channel() {
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Channel set to %d.", this->wifi_channel_);
|
||||
esp_wifi_set_promiscuous(true);
|
||||
esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE);
|
||||
esp_wifi_set_promiscuous(false);
|
||||
platform::set_channel(this->wifi_channel_);
|
||||
if (this->state_ == ESPNOW_STATE_ENABLED) {
|
||||
platform::rebind_peers();
|
||||
}
|
||||
}
|
||||
|
||||
void ESPNowComponent::loop() {
|
||||
@@ -283,7 +252,7 @@ void ESPNowComponent::loop() {
|
||||
switch (packet->type_) {
|
||||
case ESPNowPacket::RECEIVED: {
|
||||
const ESPNowRecvInfo info = packet->get_receive_info();
|
||||
if (!esp_now_is_peer_exist(info.src_addr)) {
|
||||
if (!platform::peer_exists(info.src_addr)) {
|
||||
bool handled = false;
|
||||
for (auto *handler : this->unknown_peer_handlers_) {
|
||||
if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) {
|
||||
@@ -296,7 +265,7 @@ void ESPNowComponent::loop() {
|
||||
}
|
||||
}
|
||||
// Intentionally left as if instead of else in case the peer is added above
|
||||
if (esp_now_is_peer_exist(info.src_addr)) {
|
||||
if (platform::peer_exists(info.src_addr)) {
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
@@ -309,7 +278,8 @@ void ESPNowComponent::loop() {
|
||||
format_hex_pretty_to(hex_buf, packet->packet_.receive.data,
|
||||
std::min<uint16_t>(packet->packet_.receive.size, ESP_NOW_MAX_DATA_LEN)));
|
||||
#endif
|
||||
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
|
||||
// Without a destination address every packet goes to the receive handlers
|
||||
if (ESPNOW_REPORTS_DESTINATION && memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
|
||||
for (auto *handler : this->broadcast_handlers_) {
|
||||
if (handler->on_broadcast(info, packet->packet_.receive.data, packet->packet_.receive.size))
|
||||
break; // If a handler returns true, stop processing further handlers
|
||||
@@ -374,24 +344,25 @@ void ESPNowComponent::loop() {
|
||||
}
|
||||
|
||||
uint8_t ESPNowComponent::get_wifi_channel() {
|
||||
wifi_second_chan_t dummy;
|
||||
esp_wifi_get_channel(&this->wifi_channel_, &dummy);
|
||||
this->wifi_channel_ = platform::get_channel();
|
||||
return this->wifi_channel_;
|
||||
}
|
||||
|
||||
// Same analyzer false positive as above: the send pool is sized to the send queue's capacity
|
||||
// NOLINTBEGIN(clang-analyzer-unix.Malloc)
|
||||
esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
|
||||
const send_callback_t &callback) {
|
||||
send_callback_t callback) {
|
||||
if (this->state_ != ESPNOW_STATE_ENABLED) {
|
||||
return ESP_ERR_ESPNOW_NOT_INIT;
|
||||
} else if (this->is_failed()) {
|
||||
return ESP_ERR_ESPNOW_FAILED;
|
||||
} else if (peer_address == 0ULL) {
|
||||
} else if (peer_address == nullptr) {
|
||||
return ESP_ERR_ESPNOW_PEER_NOT_SET;
|
||||
} else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
|
||||
return ESP_ERR_ESPNOW_OWN_ADDRESS;
|
||||
} else if (size > ESPNOW_MAX_DATA_LEN) {
|
||||
return ESP_ERR_ESPNOW_DATA_SIZE;
|
||||
} else if (!esp_now_is_peer_exist(peer_address)) {
|
||||
} else if (!platform::peer_exists(peer_address)) {
|
||||
if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
|
||||
esp_err_t err = this->add_peer(peer_address);
|
||||
if (err != ESP_OK) {
|
||||
@@ -410,7 +381,7 @@ esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payl
|
||||
return ESP_ERR_ESPNOW_NO_MEM;
|
||||
}
|
||||
// Load the packet data
|
||||
packet->load_data(peer_address, payload, size, callback);
|
||||
packet->load_data(peer_address, payload, size, std::move(callback));
|
||||
// Push the packet to the send queue
|
||||
this->send_packet_queue_.push(packet);
|
||||
// Loop may be disabled while idle; re-enable it to send the packet
|
||||
@@ -418,6 +389,7 @@ esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payl
|
||||
this->enable_loop_soon_any_context();
|
||||
return ESP_OK;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-unix.Malloc)
|
||||
|
||||
void ESPNowComponent::send_() {
|
||||
ESPNowSendPacket *packet = this->send_packet_queue_.pop();
|
||||
@@ -426,7 +398,7 @@ void ESPNowComponent::send_() {
|
||||
}
|
||||
|
||||
this->current_send_packet_ = packet;
|
||||
esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_);
|
||||
esp_err_t err = platform::send(packet->address_, packet->data_, packet->size_);
|
||||
if (err != ESP_OK) {
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
format_mac_addr_upper(packet->address_, addr_buf);
|
||||
@@ -451,13 +423,8 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
|
||||
return ESP_ERR_INVALID_MAC;
|
||||
}
|
||||
|
||||
if (!esp_now_is_peer_exist(peer)) {
|
||||
esp_now_peer_info_t peer_info = {};
|
||||
memset(&peer_info, 0, sizeof(esp_now_peer_info_t));
|
||||
peer_info.ifidx = WIFI_IF_STA;
|
||||
memcpy(peer_info.peer_addr, peer, ESP_NOW_ETH_ALEN);
|
||||
esp_err_t err = esp_now_add_peer(&peer_info);
|
||||
|
||||
if (!platform::peer_exists(peer)) {
|
||||
esp_err_t err = platform::add_peer(peer);
|
||||
if (err != ESP_OK) {
|
||||
char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
format_mac_addr_upper(peer, peer_buf);
|
||||
@@ -486,8 +453,8 @@ esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
|
||||
if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
|
||||
return ESP_ERR_ESPNOW_NOT_INIT;
|
||||
}
|
||||
if (esp_now_is_peer_exist(peer)) {
|
||||
esp_err_t err = esp_now_del_peer(peer);
|
||||
if (platform::peer_exists(peer)) {
|
||||
esp_err_t err = platform::del_peer(peer);
|
||||
if (err != ESP_OK) {
|
||||
char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
format_mac_addr_upper(peer, peer_buf);
|
||||
@@ -507,4 +474,4 @@ esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -4,21 +4,17 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "esphome/core/event_pool.h"
|
||||
#include "esphome/core/lock_free_queue.h"
|
||||
#include "espnow_packet.h"
|
||||
#include "espnow_types.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_mac.h>
|
||||
#include <esp_now.h>
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -27,10 +23,6 @@
|
||||
|
||||
namespace esphome::espnow {
|
||||
|
||||
// Maximum size of the ESPNow event queue - must be power of 2 for lock-free queue
|
||||
static constexpr size_t MAX_ESP_NOW_SEND_QUEUE_SIZE = 16;
|
||||
static constexpr size_t MAX_ESP_NOW_RECEIVE_QUEUE_SIZE = 16;
|
||||
|
||||
using peer_address_t = std::array<uint8_t, ESP_NOW_ETH_ALEN>;
|
||||
|
||||
enum class ESPNowTriggers : uint8_t {
|
||||
@@ -152,12 +144,10 @@ class ESPNowComponent final : public Component {
|
||||
/// @param payload Data payload to send
|
||||
/// @param callback Callback to call when the send operation is complete
|
||||
/// @return ESP_OK on success, or an error code on failure
|
||||
esp_err_t send(const uint8_t *peer_address, const std::vector<uint8_t> &payload,
|
||||
const send_callback_t &callback = nullptr) {
|
||||
return this->send(peer_address, payload.data(), payload.size(), callback);
|
||||
esp_err_t send(const uint8_t *peer_address, const std::vector<uint8_t> &payload, send_callback_t callback = nullptr) {
|
||||
return this->send(peer_address, payload.data(), payload.size(), std::move(callback));
|
||||
}
|
||||
esp_err_t send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
|
||||
const send_callback_t &callback = nullptr);
|
||||
esp_err_t send(const uint8_t *peer_address, const uint8_t *payload, size_t size, send_callback_t callback = nullptr);
|
||||
|
||||
void register_receive_handler(ESPNowReceivedPacketHandler *handler) { this->receive_handlers_.push_back(handler); }
|
||||
void register_unknown_peer_handler(ESPNowUnknownPeerHandler *handler) {
|
||||
@@ -165,14 +155,13 @@ class ESPNowComponent final : public Component {
|
||||
}
|
||||
void register_broadcast_handler(ESPNowBroadcastHandler *handler) { this->broadcast_handlers_.push_back(handler); }
|
||||
|
||||
protected:
|
||||
friend void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size);
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
friend void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status);
|
||||
#else
|
||||
friend void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status);
|
||||
#endif
|
||||
// Entry points for the radio driver callbacks in espnow_platform_*.cpp; they run outside the main loop and
|
||||
// only queue the event
|
||||
void packet_received(const uint8_t *src_addr, const uint8_t *des_addr, const uint8_t *data, int size, int8_t rssi,
|
||||
uint32_t timestamp);
|
||||
void send_reported(const uint8_t *mac_addr, esp_now_send_status_t status);
|
||||
|
||||
protected:
|
||||
void enable_();
|
||||
void send_();
|
||||
|
||||
@@ -205,4 +194,4 @@ extern ESPNowComponent *global_esp_now; // NOLINT(cppcoreguidelines-avoid-non-c
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include <esp_err.h>
|
||||
#include <esp_now.h>
|
||||
#include "espnow_types.h"
|
||||
|
||||
namespace esphome::espnow {
|
||||
|
||||
static const esp_err_t ESP_ERR_ESPNOW_CMP_BASE = (ESP_ERR_ESPNOW_BASE + 20);
|
||||
static const esp_err_t ESP_ERR_ESPNOW_FAILED = (ESP_ERR_ESPNOW_CMP_BASE + 1);
|
||||
static const esp_err_t ESP_ERR_ESPNOW_OWN_ADDRESS = (ESP_ERR_ESPNOW_CMP_BASE + 2);
|
||||
static const esp_err_t ESP_ERR_ESPNOW_DATA_SIZE = (ESP_ERR_ESPNOW_CMP_BASE + 3);
|
||||
static const esp_err_t ESP_ERR_ESPNOW_PEER_NOT_SET = (ESP_ERR_ESPNOW_CMP_BASE + 4);
|
||||
static const esp_err_t ESP_ERR_ESPNOW_PEER_NOT_PAIRED = (ESP_ERR_ESPNOW_CMP_BASE + 5);
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_CMP_BASE = (ESP_ERR_ESPNOW_BASE + 20);
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_FAILED = (ESP_ERR_ESPNOW_CMP_BASE + 1);
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_OWN_ADDRESS = (ESP_ERR_ESPNOW_CMP_BASE + 2);
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_DATA_SIZE = (ESP_ERR_ESPNOW_CMP_BASE + 3);
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_PEER_NOT_SET = (ESP_ERR_ESPNOW_CMP_BASE + 4);
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_PEER_NOT_PAIRED = (ESP_ERR_ESPNOW_CMP_BASE + 5);
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "espnow_err.h"
|
||||
#include "espnow_types.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <esp_err.h>
|
||||
#include <esp_idf_version.h>
|
||||
#include <esp_now.h>
|
||||
|
||||
namespace esphome::espnow {
|
||||
|
||||
static const uint8_t ESPNOW_BROADCAST_ADDR[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
@@ -36,16 +34,10 @@ static_assert(ESPNOW_MAX_DATA_LEN <= ESP_NOW_MAX_DATA_LEN,
|
||||
"espnow max_payload_size beyond 250 bytes requires an ESP-IDF with ESP-NOW v2 support (5.4+)");
|
||||
#endif
|
||||
|
||||
struct WifiPacketRxControl {
|
||||
int8_t rssi; // Received Signal Strength Indicator (RSSI) of packet, unit: dBm
|
||||
uint32_t timestamp; // Timestamp in microseconds when the packet was received, precise only if modem sleep or
|
||||
// light sleep is not enabled
|
||||
};
|
||||
|
||||
struct ESPNowRecvInfo {
|
||||
uint8_t src_addr[ESP_NOW_ETH_ALEN]; /**< Source address of ESPNOW packet */
|
||||
uint8_t des_addr[ESP_NOW_ETH_ALEN]; /**< Destination address of ESPNOW packet */
|
||||
wifi_pkt_rx_ctrl_t *rx_ctrl; /**< Rx control info of ESPNOW packet */
|
||||
rx_ctrl_t *rx_ctrl; /**< Rx control info of ESPNOW packet */
|
||||
};
|
||||
|
||||
using send_callback_t = std::function<void(esp_err_t)>;
|
||||
@@ -58,34 +50,28 @@ class ESPNowPacket {
|
||||
SENT,
|
||||
};
|
||||
|
||||
// Constructor for received data
|
||||
ESPNowPacket(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||
this->init_received_data_(info, data, size);
|
||||
};
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
// Constructor for sent data
|
||||
ESPNowPacket(const esp_now_send_info_t *info, esp_now_send_status_t status) {
|
||||
this->init_sent_data_(info->src_addr, status);
|
||||
}
|
||||
#else
|
||||
// Constructor for sent data
|
||||
ESPNowPacket(const uint8_t *mac_addr, esp_now_send_status_t status) { this->init_sent_data_(mac_addr, status); }
|
||||
#endif
|
||||
|
||||
// Default constructor for pre-allocation in pool
|
||||
ESPNowPacket() {}
|
||||
|
||||
void release() {}
|
||||
|
||||
void load_received_data(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||
void load_received_data(const uint8_t *src_addr, const uint8_t *des_addr, const uint8_t *data, uint16_t size,
|
||||
int8_t rssi, uint32_t timestamp) {
|
||||
this->type_ = RECEIVED;
|
||||
this->init_received_data_(info, data, size);
|
||||
memcpy(this->packet_.receive.info.src_addr, src_addr, ESP_NOW_ETH_ALEN);
|
||||
memcpy(this->packet_.receive.info.des_addr, des_addr, ESP_NOW_ETH_ALEN);
|
||||
memcpy(this->packet_.receive.data, data, size);
|
||||
this->packet_.receive.size = size;
|
||||
|
||||
this->packet_.receive.rx_ctrl.rssi = rssi;
|
||||
this->packet_.receive.rx_ctrl.timestamp = timestamp;
|
||||
this->packet_.receive.info.rx_ctrl = reinterpret_cast<rx_ctrl_t *>(&this->packet_.receive.rx_ctrl);
|
||||
}
|
||||
|
||||
void load_sent_data(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||
this->type_ = SENT;
|
||||
this->init_sent_data_(mac_addr, status);
|
||||
memcpy(this->packet_.sent.address, mac_addr, ESP_NOW_ETH_ALEN);
|
||||
this->packet_.sent.status = status;
|
||||
}
|
||||
|
||||
// Disable copy to prevent double-delete
|
||||
@@ -112,24 +98,6 @@ class ESPNowPacket {
|
||||
|
||||
esp_now_packet_type_t type() const { return this->type_; }
|
||||
const ESPNowRecvInfo &get_receive_info() const { return this->packet_.receive.info; }
|
||||
|
||||
private:
|
||||
void init_received_data_(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||
memcpy(this->packet_.receive.info.src_addr, info->src_addr, ESP_NOW_ETH_ALEN);
|
||||
memcpy(this->packet_.receive.info.des_addr, info->des_addr, ESP_NOW_ETH_ALEN);
|
||||
memcpy(this->packet_.receive.data, data, size);
|
||||
this->packet_.receive.size = size;
|
||||
|
||||
this->packet_.receive.rx_ctrl.rssi = info->rx_ctrl->rssi;
|
||||
this->packet_.receive.rx_ctrl.timestamp = info->rx_ctrl->timestamp;
|
||||
|
||||
this->packet_.receive.info.rx_ctrl = reinterpret_cast<wifi_pkt_rx_ctrl_t *>(&this->packet_.receive.rx_ctrl);
|
||||
}
|
||||
|
||||
void init_sent_data_(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||
memcpy(this->packet_.sent.address, mac_addr, ESP_NOW_ETH_ALEN);
|
||||
this->packet_.sent.status = status;
|
||||
}
|
||||
};
|
||||
|
||||
class ESPNowSendPacket {
|
||||
@@ -151,9 +119,9 @@ class ESPNowSendPacket {
|
||||
ESPNowSendPacket(const ESPNowSendPacket &) = delete;
|
||||
ESPNowSendPacket &operator=(const ESPNowSendPacket &) = delete;
|
||||
|
||||
void load_data(const uint8_t *peer_address, const uint8_t *payload, size_t size, const send_callback_t &callback) {
|
||||
void load_data(const uint8_t *peer_address, const uint8_t *payload, size_t size, send_callback_t &&callback) {
|
||||
this->init_data_(peer_address, payload, size);
|
||||
this->callback_ = callback;
|
||||
this->callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void load_data(const uint8_t *peer_address, const uint8_t *payload, size_t size) {
|
||||
@@ -180,4 +148,4 @@ class ESPNowSendPacket {
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "espnow_types.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// The radio and ESP-NOW driver calls that differ between ESP-IDF and the ESP8266 NONOS SDK. Each platform
|
||||
// implements the whole set in its own source file, so the component itself carries no platform checks.
|
||||
namespace esphome::espnow::platform {
|
||||
|
||||
// Bring up the station interface when no wifi component owns the radio
|
||||
void init_radio();
|
||||
void set_channel(uint8_t channel);
|
||||
uint8_t get_channel();
|
||||
void read_mac(uint8_t *mac);
|
||||
|
||||
// Start the driver and route its callbacks to the component
|
||||
esp_err_t init();
|
||||
esp_err_t deinit();
|
||||
uint32_t get_version(); // 0 where the SDK has no version call
|
||||
|
||||
bool peer_exists(const uint8_t *mac);
|
||||
esp_err_t add_peer(const uint8_t *mac);
|
||||
esp_err_t del_peer(const uint8_t *mac);
|
||||
// Move the registered peers to the radio's current channel; only the NONOS SDK binds peers to a channel
|
||||
void rebind_peers();
|
||||
|
||||
esp_err_t send(const uint8_t *mac, const uint8_t *data, uint16_t size);
|
||||
|
||||
} // namespace esphome::espnow::platform
|
||||
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "espnow_platform.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "espnow_component.h"
|
||||
|
||||
#include <esp_idf_version.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_wifi.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::espnow::platform {
|
||||
|
||||
static void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||
global_esp_now->packet_received(info->src_addr, info->des_addr, data, size, info->rx_ctrl->rssi,
|
||||
info->rx_ctrl->timestamp);
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
static void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status) {
|
||||
global_esp_now->send_reported(info->des_addr, status);
|
||||
}
|
||||
#else
|
||||
static void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||
global_esp_now->send_reported(mac_addr, status);
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_radio() {
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
ESP_ERROR_CHECK(esp_wifi_disconnect());
|
||||
}
|
||||
|
||||
void set_channel(uint8_t channel) {
|
||||
esp_wifi_set_promiscuous(true);
|
||||
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
|
||||
esp_wifi_set_promiscuous(false);
|
||||
}
|
||||
|
||||
uint8_t get_channel() {
|
||||
uint8_t channel = 0;
|
||||
wifi_second_chan_t second;
|
||||
esp_wifi_get_channel(&channel, &second);
|
||||
return channel;
|
||||
}
|
||||
|
||||
void read_mac(uint8_t *mac) { esp_wifi_get_mac(WIFI_IF_STA, mac); }
|
||||
|
||||
esp_err_t init() {
|
||||
esp_err_t err = esp_now_init();
|
||||
if (err == ESP_OK) {
|
||||
err = esp_now_register_recv_cb(on_data_received);
|
||||
}
|
||||
if (err == ESP_OK) {
|
||||
err = esp_now_register_send_cb(on_send_report);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t deinit() {
|
||||
esp_now_unregister_recv_cb();
|
||||
esp_now_unregister_send_cb();
|
||||
return esp_now_deinit();
|
||||
}
|
||||
|
||||
uint32_t get_version() {
|
||||
uint32_t version = 0;
|
||||
esp_now_get_version(&version);
|
||||
return version;
|
||||
}
|
||||
|
||||
bool peer_exists(const uint8_t *mac) { return esp_now_is_peer_exist(mac); }
|
||||
|
||||
esp_err_t add_peer(const uint8_t *mac) {
|
||||
esp_now_peer_info_t peer_info = {};
|
||||
peer_info.ifidx = WIFI_IF_STA;
|
||||
memcpy(peer_info.peer_addr, mac, ESP_NOW_ETH_ALEN);
|
||||
return esp_now_add_peer(&peer_info);
|
||||
}
|
||||
|
||||
esp_err_t del_peer(const uint8_t *mac) { return esp_now_del_peer(mac); }
|
||||
|
||||
// Peers are added on channel 0, which ESP-IDF treats as whatever channel the radio is on
|
||||
void rebind_peers() {}
|
||||
|
||||
esp_err_t send(const uint8_t *mac, const uint8_t *data, uint16_t size) { return esp_now_send(mac, data, size); }
|
||||
|
||||
} // namespace esphome::espnow::platform
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "espnow_platform.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include "espnow_component.h"
|
||||
|
||||
namespace esphome::espnow::platform {
|
||||
|
||||
// The SDK reports 0 for success and a non-zero value for any failure
|
||||
static esp_err_t sdk_result(int result) { return result == 0 ? ESP_OK : ESP_FAIL; }
|
||||
|
||||
// Stands in for the destination address, which the SDK does not report
|
||||
static const uint8_t UNKNOWN_ADDR[ESP_NOW_ETH_ALEN] = {0};
|
||||
|
||||
static void on_data_received(uint8_t *mac_addr, uint8_t *data, uint8_t size) {
|
||||
global_esp_now->packet_received(mac_addr, UNKNOWN_ADDR, data, size, 0, 0);
|
||||
}
|
||||
|
||||
static void on_send_report(uint8_t *mac_addr, uint8_t status) {
|
||||
global_esp_now->send_reported(mac_addr, status == 0 ? ESP_NOW_SEND_SUCCESS : ESP_NOW_SEND_FAIL);
|
||||
}
|
||||
|
||||
void init_radio() {
|
||||
// The NONOS SDK only delivers broadcast frames to ESP-NOW while the station is associated or the
|
||||
// soft-AP interface is up. Without Wi-Fi, run a hidden, open soft-AP that accepts no clients so
|
||||
// broadcasts (for example from a WizMote style remote) are received.
|
||||
softap_config ap{};
|
||||
ap.channel = 1;
|
||||
ap.authmode = AUTH_OPEN;
|
||||
ap.ssid_hidden = 1;
|
||||
ap.max_connection = 0;
|
||||
ap.beacon_interval = 60000;
|
||||
wifi_set_opmode_current(STATIONAP_MODE);
|
||||
wifi_softap_set_config_current(&ap);
|
||||
wifi_softap_dhcps_stop();
|
||||
wifi_set_sleep_type(NONE_SLEEP_T);
|
||||
wifi_station_disconnect();
|
||||
}
|
||||
|
||||
void set_channel(uint8_t channel) {
|
||||
wifi_promiscuous_enable(true);
|
||||
wifi_set_channel(channel);
|
||||
wifi_promiscuous_enable(false);
|
||||
// Keep the soft-AP from init_radio() on the same channel so the SDK never retunes the radio to its own
|
||||
softap_config ap{};
|
||||
if (wifi_get_opmode() == STATIONAP_MODE && wifi_softap_get_config(&ap) && ap.channel != channel) {
|
||||
ap.channel = channel;
|
||||
wifi_softap_set_config_current(&ap);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_channel() { return wifi_get_channel(); }
|
||||
|
||||
void read_mac(uint8_t *mac) {
|
||||
// Frames leave from the station interface unless the device runs as a soft-AP only
|
||||
wifi_get_macaddr(wifi_get_opmode() == SOFTAP_MODE ? SOFTAP_IF : STATION_IF, mac);
|
||||
}
|
||||
|
||||
esp_err_t init() {
|
||||
int result = esp_now_init();
|
||||
if (result == 0) {
|
||||
// CONTROLLER sends from the station interface; the other roles send from the soft-AP MAC whenever
|
||||
// that interface is up, so peers would no longer recognise the sender.
|
||||
result = esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
|
||||
}
|
||||
if (result == 0) {
|
||||
result = esp_now_register_recv_cb(on_data_received);
|
||||
}
|
||||
if (result == 0) {
|
||||
result = esp_now_register_send_cb(on_send_report);
|
||||
}
|
||||
return sdk_result(result);
|
||||
}
|
||||
|
||||
esp_err_t deinit() {
|
||||
esp_now_unregister_recv_cb();
|
||||
esp_now_unregister_send_cb();
|
||||
return sdk_result(esp_now_deinit());
|
||||
}
|
||||
|
||||
uint32_t get_version() { return 0; }
|
||||
|
||||
// The SDK returns 1 when the peer exists, 0 when it does not and a negative value on error
|
||||
bool peer_exists(const uint8_t *mac) { return esp_now_is_peer_exist(const_cast<uint8_t *>(mac)) > 0; }
|
||||
|
||||
esp_err_t add_peer(const uint8_t *mac) {
|
||||
return sdk_result(esp_now_add_peer(const_cast<uint8_t *>(mac), ESP_NOW_ROLE_COMBO, wifi_get_channel(), nullptr, 0));
|
||||
}
|
||||
|
||||
esp_err_t del_peer(const uint8_t *mac) { return sdk_result(esp_now_del_peer(const_cast<uint8_t *>(mac))); }
|
||||
|
||||
void rebind_peers() {
|
||||
const uint8_t channel = wifi_get_channel();
|
||||
for (uint8_t *mac = esp_now_fetch_peer(true); mac != nullptr; mac = esp_now_fetch_peer(false)) {
|
||||
esp_now_set_peer_channel(mac, channel);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t send(const uint8_t *mac, const uint8_t *data, uint16_t size) {
|
||||
return sdk_result(esp_now_send(const_cast<uint8_t *>(mac), const_cast<uint8_t *>(data), size));
|
||||
}
|
||||
|
||||
} // namespace esphome::espnow::platform
|
||||
|
||||
#endif // USE_ESP8266
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
|
||||
#include <esp_err.h>
|
||||
#include <esp_now.h>
|
||||
|
||||
#elif defined(USE_ESP8266)
|
||||
|
||||
extern "C" {
|
||||
#include <c_types.h>
|
||||
#include <espnow.h>
|
||||
#include <user_interface.h>
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace esphome::espnow {
|
||||
|
||||
struct WifiPacketRxControl {
|
||||
int8_t rssi; // Received Signal Strength Indicator (RSSI) of packet, unit: dBm
|
||||
uint32_t timestamp; // Timestamp in microseconds when the packet was received, precise only if modem sleep or
|
||||
// light sleep is not enabled
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
|
||||
// Handlers read rx_ctrl through the ESP-IDF type so existing lambdas keep compiling
|
||||
using rx_ctrl_t = wifi_pkt_rx_ctrl_t;
|
||||
static constexpr bool ESPNOW_REPORTS_DESTINATION = true;
|
||||
|
||||
// Maximum size of the ESPNow event queues - must be a power of 2 for the lock-free queue
|
||||
static constexpr size_t MAX_ESP_NOW_SEND_QUEUE_SIZE = 16;
|
||||
static constexpr size_t MAX_ESP_NOW_RECEIVE_QUEUE_SIZE = 16;
|
||||
|
||||
#elif defined(USE_ESP8266)
|
||||
|
||||
// The NONOS SDK has no esp_err_t vocabulary. These are the ESP-IDF names and values the shared espnow code
|
||||
// uses, so it reads the same on both platforms.
|
||||
using esp_err_t = int32_t;
|
||||
|
||||
static constexpr esp_err_t ESP_OK = 0;
|
||||
static constexpr esp_err_t ESP_FAIL = -1;
|
||||
static constexpr esp_err_t ESP_ERR_INVALID_MAC = 0x10B;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_BASE = 0x3064;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_NOT_INIT = ESP_ERR_ESPNOW_BASE + 1;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_ARG = ESP_ERR_ESPNOW_BASE + 2;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_NO_MEM = ESP_ERR_ESPNOW_BASE + 3;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_NOT_FOUND = ESP_ERR_ESPNOW_BASE + 5;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_INTERNAL = ESP_ERR_ESPNOW_BASE + 6;
|
||||
static constexpr esp_err_t ESP_ERR_ESPNOW_IF = ESP_ERR_ESPNOW_BASE + 8;
|
||||
|
||||
inline const char *esp_err_to_name(esp_err_t err) { return err == ESP_OK ? "ESP_OK" : "ESP_FAIL"; }
|
||||
|
||||
static constexpr uint8_t ESP_NOW_ETH_ALEN = 6;
|
||||
static constexpr uint8_t ESP_NOW_MAX_DATA_LEN = 250;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum esp_now_send_status_t : uint8_t {
|
||||
ESP_NOW_SEND_SUCCESS = 0,
|
||||
ESP_NOW_SEND_FAIL = 1,
|
||||
};
|
||||
|
||||
using rx_ctrl_t = WifiPacketRxControl;
|
||||
// The SDK reports neither the destination address nor the signal strength of a received frame
|
||||
static constexpr bool ESPNOW_REPORTS_DESTINATION = false;
|
||||
|
||||
// Half the ESP32 depth: the heap is far smaller and only one send is ever in flight
|
||||
static constexpr size_t MAX_ESP_NOW_SEND_QUEUE_SIZE = 8;
|
||||
static constexpr size_t MAX_ESP_NOW_RECEIVE_QUEUE_SIZE = 8;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace esphome::espnow
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "espnow_transport.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <span>
|
||||
|
||||
namespace esphome::espnow {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "espnow.transport");
|
||||
@@ -64,9 +66,7 @@ bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data
|
||||
return false;
|
||||
}
|
||||
|
||||
this->packet_buffer_.resize(size);
|
||||
memcpy(this->packet_buffer_.data(), data, size);
|
||||
this->process_(this->packet_buffer_);
|
||||
this->process_(std::span<const uint8_t>(data, size));
|
||||
return false; // Allow other handlers to run
|
||||
}
|
||||
|
||||
@@ -79,12 +79,10 @@ bool ESPNowTransport::on_broadcast(const ESPNowRecvInfo &info, const uint8_t *da
|
||||
return false;
|
||||
}
|
||||
|
||||
this->packet_buffer_.resize(size);
|
||||
memcpy(this->packet_buffer_.data(), data, size);
|
||||
this->process_(this->packet_buffer_);
|
||||
this->process_(std::span<const uint8_t>(data, size));
|
||||
return false; // Allow other handlers to run
|
||||
}
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "../espnow_component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/packet_transport/packet_transport.h"
|
||||
@@ -33,9 +33,8 @@ class ESPNowTransport final : public packet_transport::PacketTransport,
|
||||
bool should_send() override;
|
||||
|
||||
peer_address_t peer_address_{{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
std::vector<uint8_t> packet_buffer_;
|
||||
};
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR) || defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_HOST)
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR) || defined(USE_LIBRETINY) || defined(USE_RP2) || defined(USE_HOST) || \
|
||||
defined(USE_ESP8266)
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
espnow:
|
||||
max_payload_size: 1470
|
||||
on_broadcast:
|
||||
- logger.log:
|
||||
format: "Broadcast from: %s = '%s' RSSI: %d"
|
||||
args:
|
||||
- format_mac_address_pretty(info.src_addr).c_str()
|
||||
- format_hex_pretty(data, size).c_str()
|
||||
- info.rx_ctrl->rssi
|
||||
@@ -2,7 +2,6 @@ espnow:
|
||||
id: espnow_component
|
||||
auto_add_peer: false
|
||||
channel: 1
|
||||
max_payload_size: 1470
|
||||
peers:
|
||||
- 11:22:33:44:55:66
|
||||
on_receive:
|
||||
@@ -50,13 +49,6 @@ espnow:
|
||||
- espnow.set_channel: 6
|
||||
- espnow.set_channel:
|
||||
channel: !lambda 'return 6;'
|
||||
on_broadcast:
|
||||
- logger.log:
|
||||
format: "Broadcast from: %s = '%s' RSSI: %d"
|
||||
args:
|
||||
- format_mac_address_pretty(info.src_addr).c_str()
|
||||
- format_hex_pretty(data, size).c_str()
|
||||
- info.rx_ctrl->rssi
|
||||
on_unknown_peer:
|
||||
- logger.log:
|
||||
format: "Unknown peer: %s = '%s' RSSI: %d"
|
||||
@@ -80,8 +72,9 @@ packet_transport:
|
||||
key: "0123456789abcdef0123456789abcdef"
|
||||
|
||||
sensor:
|
||||
- platform: internal_temperature
|
||||
- platform: template
|
||||
id: espnow_temp_sensor
|
||||
lambda: return 21.5;
|
||||
|
||||
- platform: packet_transport
|
||||
transport_id: transport1
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
espnow: !include common-wifi.yaml
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
espnow: !include common.yaml
|
||||
espnow_esp32: !include common-esp32.yaml
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
espnow: !include common.yaml
|
||||
@@ -7,16 +7,29 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
)
|
||||
from esphome.components.espnow import _validate_variant
|
||||
from esphome.components.espnow import CONFIG_SCHEMA, _validate_variant
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
|
||||
def _run(
|
||||
monkeypatch, variant: str, full_config: dict, config: ConfigType
|
||||
monkeypatch,
|
||||
variant: str,
|
||||
full_config: dict,
|
||||
config: ConfigType,
|
||||
platform: str = PLATFORM_ESP32,
|
||||
) -> ConfigType:
|
||||
monkeypatch.setattr("esphome.components.espnow.get_esp32_variant", lambda: variant)
|
||||
monkeypatch.setitem(CORE.data, KEY_CORE, {KEY_TARGET_PLATFORM: platform})
|
||||
token = fv.full_config.set(full_config)
|
||||
try:
|
||||
return _validate_variant(config)
|
||||
@@ -46,3 +59,39 @@ def test_p4_with_esp32_hosted_passes(monkeypatch) -> None:
|
||||
"""The P4 with esp32_hosted present validates; config passes through."""
|
||||
config = {"id": "espnow"}
|
||||
assert _run(monkeypatch, VARIANT_ESP32P4, {"esp32_hosted": {}}, config) is config
|
||||
|
||||
|
||||
def test_esp8266_skips_variant_check(monkeypatch) -> None:
|
||||
"""The ESP8266 has one Wi-Fi radio and no variants, so the check does not run."""
|
||||
config = {"id": "espnow"}
|
||||
assert _run(monkeypatch, "", {}, config, platform=PLATFORM_ESP8266) is config
|
||||
|
||||
|
||||
def _esp8266_arduino(monkeypatch) -> None:
|
||||
monkeypatch.setitem(
|
||||
CORE.data,
|
||||
KEY_CORE,
|
||||
{KEY_TARGET_PLATFORM: PLATFORM_ESP8266, KEY_TARGET_FRAMEWORK: "arduino"},
|
||||
)
|
||||
|
||||
|
||||
def test_esp8266_accepts_the_v1_payload_size(monkeypatch) -> None:
|
||||
_esp8266_arduino(monkeypatch)
|
||||
assert (
|
||||
CONFIG_SCHEMA({"channel": 1, "max_payload_size": 250})["max_payload_size"]
|
||||
== 250
|
||||
)
|
||||
|
||||
|
||||
def test_esp8266_rejects_v2_payload_sizes(monkeypatch) -> None:
|
||||
"""The NONOS SDK only speaks ESP-NOW v1, so larger frames are refused at validation."""
|
||||
_esp8266_arduino(monkeypatch)
|
||||
with pytest.raises(cv.Invalid):
|
||||
CONFIG_SCHEMA({"channel": 1, "max_payload_size": 251})
|
||||
|
||||
|
||||
def test_esp8266_rejects_on_broadcast(monkeypatch) -> None:
|
||||
"""The NONOS SDK does not report a destination address, so broadcasts cannot be told apart."""
|
||||
_esp8266_arduino(monkeypatch)
|
||||
with pytest.raises(cv.Invalid, match="only available on"):
|
||||
CONFIG_SCHEMA({"channel": 1, "on_broadcast": []})
|
||||
|
||||
Reference in New Issue
Block a user