From 4750c97606fcac6afeb15f21ad83b577de1fb8a7 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Fri, 26 Jul 2024 16:06:16 +0200 Subject: [PATCH] New implementation; the espnow compoment. --- esphome/components/espnow/__init__.py | 198 ++++++++++++++++ esphome/components/espnow/espnow.cpp | 328 ++++++++++++++++++++++++++ esphome/components/espnow/espnow.h | 285 ++++++++++++++++++++++ 3 files changed, 811 insertions(+) create mode 100644 esphome/components/espnow/__init__.py create mode 100644 esphome/components/espnow/espnow.cpp create mode 100644 esphome/components/espnow/espnow.h diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py new file mode 100644 index 0000000000..2bdaa8682b --- /dev/null +++ b/esphome/components/espnow/__init__.py @@ -0,0 +1,198 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_DATA, + CONF_ID, + CONF_MAC_ADDRESS, + CONF_TRIGGER_ID, + CONF_WIFI, +) +from esphome.core import CORE + +CODEOWNERS = ["@LumenSoftNL", "@jesserockz"] + +espnow_ns = cg.esphome_ns.namespace("esp_now") +ESPNowComponent = espnow_ns.class_("ESPNowComponent", cg.Component) +ESPNowListener = espnow_ns.class_("ESPNowListener") + +ESPNowPackage = espnow_ns.class_("ESPNowPackage") +ESPNowPackagePtrConst = ESPNowPackage.operator("ptr") # .operator("const") + +ESPNowInterface = espnow_ns.class_( + "ESPNowInterface", cg.Component, cg.Parented.template(ESPNowComponent) +) + +ESPNowSendTrigger = espnow_ns.class_("ESPNowSendTrigger", automation.Trigger.template()) +ESPNowReceiveTrigger = espnow_ns.class_( + "ESPNowReceiveTrigger", automation.Trigger.template() +) +ESPNowNewPeerTrigger = espnow_ns.class_( + "ESPNowNewPeerTrigger", automation.Trigger.template() +) + +SendAction = espnow_ns.class_("SendAction", automation.Action) +NewPeerAction = espnow_ns.class_("NewPeerAction", automation.Action) +DelPeerAction = espnow_ns.class_("DelPeerAction", automation.Action) + +CONF_ESPNOW = "espnow" +CONF_ON_PACKAGE_RECEIVED = "on_package_received" +CONF_ON_PACKAGE_SEND = "on_package_send" +CONF_ON_NEW_PEER = "on_new_peer" +CONF_CHANNEL = "wifi_channel" +CONF_PEERS = "peers" +CONF_AUTO_ADD_PEER = "auto_add_peer" + + +def validate_raw_data(value): + if isinstance(value, str): + return value.encode("utf-8") + if isinstance(value, list): + return cv.Schema([cv.hex_uint8_t])(value) + raise cv.Invalid( + "data must either be a string wrapped in quotes or a list of bytes" + ) + + +def disallowed_component(comp): + """Validate that this option can only be specified when the component `comp` is not loaded.""" + + def validator(value): + if comp in CORE.loaded_integrations: + raise cv.Invalid(f"This component requires component {comp}") + return value + + return validator + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESPNowComponent), + cv.Optional(CONF_CHANNEL, default=0): cv.int_range(0, 14), + cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean, + cv.Optional(CONF_ON_PACKAGE_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPNowReceiveTrigger), + } + ), + cv.Optional(CONF_ON_PACKAGE_SEND): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPNowSendTrigger), + } + ), + cv.Optional(CONF_ON_NEW_PEER): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPNowNewPeerTrigger), + } + ), + cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address), + }, + disallowed_component(CONF_WIFI), +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + if CORE.is_esp8266: + cg.add_library("ESP8266WiFi", None) + elif CORE.is_esp32 and CORE.using_arduino: + cg.add_library("WiFi", None) + elif CORE.is_rp2040: + cg.add_library("WiFi", None) + + cg.add_define("USE_ESPNOW") + + cg.add(var.set_wifi_channel(config[CONF_CHANNEL])) + cg.add(var.set_auto_add_peer(config[CONF_AUTO_ADD_PEER])) + + for conf in config.get(CONF_ON_PACKAGE_SEND, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(ESPNowPackagePtrConst, "it")], conf + ) + + for conf in config.get(CONF_ON_PACKAGE_RECEIVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(ESPNowPackagePtrConst, "it")], conf + ) + + for conf in config.get(CONF_ON_NEW_PEER, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(ESPNowPackagePtrConst, "it")], conf + ) + + for conf in config.get(CONF_PEERS, []): + cg.add(var.add_peer(conf.as_hex)) + + +@automation.register_action( + "espnow.send", + SendAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(ESPNowComponent), + cv.Optional(CONF_MAC_ADDRESS): cv.templatable(cv.mac_address), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + }, + key=CONF_DATA, + ), +) +async def send_action(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + if CONF_MAC_ADDRESS in config: + template_ = await cg.templatable( + config[CONF_MAC_ADDRESS].as_hex, args, cg.uint64 + ) + cg.add(var.set_mac(template_)) + + data = config.get(CONF_DATA, []) + if isinstance(data, bytes): + data = list(data) + + if cg.is_template(data): + templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_data_template(templ)) + else: + cg.add(var.set_data_static(data)) + return var + + +@automation.register_action( + "espnow.new.peer", + NewPeerAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(ESPNowComponent), + cv.Required(CONF_MAC_ADDRESS): cv.templatable(cv.mac_address), + }, + key=CONF_MAC_ADDRESS, + ), +) +async def new_peer_action(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + template_ = await cg.templatable(config[CONF_MAC_ADDRESS].as_hex, args, cg.uint64) + cg.add(var.set_mac(template_)) + return var + + +@automation.register_action( + "espnow.del.peer", + DelPeerAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(ESPNowComponent), + cv.Required(CONF_MAC_ADDRESS): cv.templatable(cv.mac_address), + }, + key=CONF_MAC_ADDRESS, + ), +) +async def del_peer_action(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + template_ = await cg.templatable(config[CONF_MAC_ADDRESS].as_hex, args, cg.uint64) + cg.add(var.set_mac(template_)) + return var diff --git a/esphome/components/espnow/espnow.cpp b/esphome/components/espnow/espnow.cpp new file mode 100644 index 0000000000..bd3f326177 --- /dev/null +++ b/esphome/components/espnow/espnow.cpp @@ -0,0 +1,328 @@ +#include "espnow.h" + +#include + +#include "esp_mac.h" +#include "esp_random.h" + +#include "esp_wifi.h" +#include "esp_crc.h" +#include "esp_now.h" +#include "esp_event.h" + +#ifdef USE_WIFI +#include "esphome/components/wifi/wifi_component.h" +#endif + +#include "esphome/core/version.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace esp_now { + +static const char *const TAG = "esp_now"; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 1) +typedef struct { + uint16_t frame_head; + uint16_t duration; + uint8_t destination_address[6]; + uint8_t source_address[6]; + uint8_t broadcast_address[6]; + uint16_t sequence_control; + + uint8_t category_code; + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t random_values[4]; + struct { + uint8_t element_id; // 0xdd + uint8_t lenght; // + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t type; // 4 + uint8_t version; + uint8_t body[0]; + } vendor_specific_content; +} __attribute__((packed)) espnow_frame_format_t; +#endif + +void ESPNowInterface::setup() { parent_->register_protocol(this); } + +ESPNowPackage::ESPNowPackage(const uint64_t mac_address, const std::vector data) { + this->mac_address_ = mac_address; + this->data_ = data; +} + +ESPNowPackage::ESPNowPackage(const uint64_t mac_address, const uint8_t *data, size_t len) { + this->data_.resize(len); + std::copy_n(data, len, this->data_.begin()); +} + +ESPNowComponent::ESPNowComponent() { global_esp_now = this; } + +void ESPNowComponent::log_error_(std::string msg, esp_err_t err) { ESP_LOGE(TAG, msg.c_str(), esp_err_to_name(err)); } + +void ESPNowComponent::dump_config() { + ESP_LOGCONFIG(TAG, "esp_now:"); + ESP_LOGCONFIG(TAG, " MAC Address: " MACSTR, MAC2STR(ESPNOW_ADDR_SELF)); + // ESP_LOGCONFIG(TAG, " WiFi Channel: %n", WiFi.channel()); +} + +bool ESPNowComponent::validate_channel_(uint8_t channel) { + wifi_country_t g_self_country; + esp_wifi_get_country(&g_self_country); + if (channel >= g_self_country.schan + g_self_country.nchan) { + ESP_LOGE(TAG, "Can't set channel %d, not allowed in country %c%c%c.", channel, g_self_country.cc[0], + g_self_country.cc[1], g_self_country.cc[2]); + return false; + } + return true; +} + +void ESPNowComponent::setup() { + ESP_LOGI(TAG, "Setting up ESP-NOW..."); + +#ifdef USE_WIFI + global_wifi_component.disable(); +#else // Set device as a Wi-Fi Station + 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_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()); + +#endif + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + +#ifdef CONFIG_ESPNOW_ENABLE_LONG_RANGE + esp_wifi_get_protocol(ESP_IF_WIFI_STA, WIFI_PROTOCOL_LR); +#endif + + esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE); + esp_wifi_set_promiscuous(false); + + this->send_lock_ = xSemaphoreCreateMutex(); + if (!this->send_lock_) { + ESP_LOGE(TAG, "Create send semaphore mutex fail"); + this->mark_failed(); + return; + } + + esp_err_t err = esp_now_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + err = esp_now_register_recv_cb(ESPNowComponent::on_data_received); + if (err != ESP_OK) { + this->log_error_("esp_now_register_recv_cb failed: %s", err); + this->mark_failed(); + return; + } + + err = esp_now_register_send_cb(ESPNowComponent::on_data_send); + if (err != ESP_OK) { + this->log_error_("esp_now_register_send_cb failed: %s", err); + this->mark_failed(); + return; + } + + esp_wifi_get_mac(WIFI_IF_STA, ESPNOW_ADDR_SELF); + + ESP_LOGI(TAG, "ESP-NOW add peers."); + for (auto &address : this->peers_) { + ESP_LOGI(TAG, "Add peer 0x%s .", format_hex(address).c_str()); + add_peer(address); + } + ESP_LOGI(TAG, "ESP-NOW setup complete"); +} + +ESPNowPackage *ESPNowComponent::send_package(ESPNowPackage *package) { + uint8_t mac[6]; + package->mac_bytes((uint8_t *) &mac); + + if (esp_now_is_peer_exist((uint8_t *) &mac)) { + this->send_queue_.push(std::move(package)); + } else { + ESP_LOGW(TAG, "Peer does not exist cant send this package: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); + } + return package; +} + +esp_err_t ESPNowComponent::add_peer(uint64_t addr) { + if (!this->is_ready()) { + this->peers_.push_back(addr); + return ESP_OK; + } else { + uint8_t mac[6]; + this->del_peer(addr); + + uint64_to_addr(addr, (uint8_t *) &mac); + + esp_now_peer_info_t peerInfo = {}; + memset(&peerInfo, 0, sizeof(esp_now_peer_info_t)); + peerInfo.channel = this->wifi_channel_; + peerInfo.encrypt = false; + memcpy(peerInfo.peer_addr, mac, 6); + + return esp_now_add_peer(&peerInfo); + } +} + +esp_err_t ESPNowComponent::del_peer(uint64_t addr) { + uint8_t mac[6]; + uint64_to_addr(addr, (uint8_t *) &mac); + if (esp_now_is_peer_exist((uint8_t *) &mac)) + return esp_now_del_peer((uint8_t *) &mac); + return ESP_OK; +} + +void ESPNowComponent::on_package_received(ESPNowPackage *package) { + for (auto *protocol : this->protocols_) { + if (protocol->on_package_received(package)) { + return; + } + } + this->on_package_receved_.call(package); +} + +void ESPNowComponent::on_package_send(ESPNowPackage *package) { + for (auto *protocol : this->protocols_) { + if (protocol->on_package_send(package)) { + return; + } + } + this->on_package_send_.call(package); +} + +void ESPNowComponent::on_new_peer(ESPNowPackage *package) { + for (auto *protocol : this->protocols_) { + if (protocol->on_new_peer(package)) { + return; + } + } + this->on_new_peer_.call(package); +} + +void ESPNowComponent::unHold_send_(uint64_t mac) { + for (ESPNowPackage *package : this->send_queue_) { + if (package->is_holded() && package->mac_address() == mac) { + package->reset_counter(); + } + } +} + +void ESPNowComponent::loop() { + if (!send_queue_.empty() && this->can_send_ && !this->status_has_warning()) { + ESPNowPackage *package = this->send_queue_.front(); + ESPNowPackage *front = package; + while (package->is_holded()) { + this->send_queue_.pop(); + this->send_queue_.push(package); + package = this->send_queue_.front(); + if (front == package) + break; + } + + if (!package->is_holded()) { + if (package->get_counter() == 5) { + this->status_set_warning("to many send retries. Stopping sending until new package received."); + } else { + uint8_t mac_address[6]; + package->mac_bytes((uint8_t *) &mac_address); + esp_err_t err = esp_now_send(mac_address, package->data().data(), package->data().size()); + package->inc_counter(); + if (err != ESP_OK) { + this->log_error_("esp_now_init failed: %s", err); + this->send_queue_.pop(); + this->send_queue_.push(package); + } else { + this->can_send_ = false; + } + } + } + } + + while (!receive_queue_.empty()) { + ESPNowPackage *package = std::move(this->receive_queue_.front()); + this->receive_queue_.pop(); + uint8_t mac[6]; + package->mac_bytes((uint8_t *) &mac); + if (!esp_now_is_peer_exist((uint8_t *) &mac)) { + if (this->auto_add_peer_) { + this->add_peer(addr_to_uint64((uint8_t *) &mac)); + } else { + this->on_new_peer(package); + } + } + if (esp_now_is_peer_exist((uint8_t *) &mac)) { + this->unHold_send_(package->mac_address()); + this->on_package_received(package); + } else { + ESP_LOGW(TAG, "Peer does not exist can't handle this package: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], + mac[2], mac[3], mac[4], mac[5]); + } + } +} + +/**< callback function of receiving ESPNOW data */ +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 1) +void ESPNowComponent::on_data_received((const esp_now_recv_info_t *recv_info, const uint8_t *data, int size) +#else +void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data, int size) +#endif +{ + wifi_pkt_rx_ctrl_t *rx_ctrl = NULL; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 1) + uint8_t *addr = recv_info->src_addr; + rx_ctrl = recv_info->rx_ctrl; +#else + wifi_promiscuous_pkt_t *promiscuous_pkt = + (wifi_promiscuous_pkt_t *) (data - sizeof(wifi_pkt_rx_ctrl_t) - sizeof(espnow_frame_format_t)); + rx_ctrl = &promiscuous_pkt->rx_ctrl; +#endif + + ESPNowPackage *package = new ESPNowPackage(addr_to_uint64(addr), data, size); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 1) + package->is_broadcast(memcmp(recv_info->des_addr, ESP_NOW.BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0); +#endif + + package->rssi(rx_ctrl->rssi); + package->timestamp(rx_ctrl->timestamp); + global_esp_now->push_receive_package_(package); + +} + +void ESPNowComponent::on_data_send(const uint8_t *mac_addr, esp_now_send_status_t status) { + ESPNowPackage *package = global_esp_now->send_queue_.front(); + espnow_addr_t mac; + uint64_to_addr(package->mac_address(), mac); + if (status != ESP_OK) { + ESP_LOGE(TAG, "on_data_send failed"); + } else if (std::memcmp(mac, mac_addr, 6) != 0) { + ESP_LOGE(TAG, "on_data_send Invalid mac address."); + ESP_LOGW(TAG, "expected: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + ESP_LOGW(TAG, "returned: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], + mac_addr[4], mac_addr[5]); + } else { + global_esp_now->on_package_send(package); + global_esp_now->send_queue_.pop(); + } + global_esp_now->can_send_ = true; +} + + +ESPNowComponent *global_esp_now = nullptr; + +} // namespace esp_now +} // namespace esphome diff --git a/esphome/components/espnow/espnow.h b/esphome/components/espnow/espnow.h new file mode 100644 index 0000000000..99260e4a5b --- /dev/null +++ b/esphome/components/espnow/espnow.h @@ -0,0 +1,285 @@ +#pragma once + +// #if defined(USE_ESP32) + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +#include + +#include +#include +#include +#include + +namespace esphome { +namespace esp_now { + +typedef uint8_t espnow_addr_t[6]; + +static const uint64_t ESPNOW_BROADCAST_ADDR = 0xFFFFFFFFFFFF; +static espnow_addr_t ESPNOW_ADDR_SELF = {0}; + +static void uint64_to_addr(uint64_t address, uint8_t *bd_addr) { + *(bd_addr + 0) = (address >> 40) & 0xff; + *(bd_addr + 1) = (address >> 32) & 0xff; + *(bd_addr + 2) = (address >> 24) & 0xff; + *(bd_addr + 3) = (address >> 16) & 0xff; + *(bd_addr + 4) = (address >> 8) & 0xff; + *(bd_addr + 5) = (address >> 0) & 0xff; +} + +static uint64_t addr_to_uint64(const uint8_t *address) { + uint64_t u = 0; + u |= uint64_t(*(address + 0) & 0xFF) << 40; + u |= uint64_t(*(address + 1) & 0xFF) << 32; + u |= uint64_t(*(address + 2) & 0xFF) << 24; + u |= uint64_t(*(address + 3) & 0xFF) << 16; + u |= uint64_t(*(address + 4) & 0xFF) << 8; + u |= uint64_t(*(address + 5) & 0xFF) << 0; + return u; +} + +class ESPNowComponent; + +template > class iterable_queue : public std::queue { + public: + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + iterator begin() { return this->c.begin(); } + iterator end() { return this->c.end(); } + const_iterator begin() const { return this->c.begin(); } + const_iterator end() const { return this->c.end(); } +}; + +class ESPNowPackage { + public: + ESPNowPackage(const uint64_t mac_address, const std::vector data); + ESPNowPackage(const uint64_t mac_address, const uint8_t *data, size_t len); + + uint64_t mac_address() { return this->mac_address_ == 0 ? ESPNOW_BROADCAST_ADDR : this->mac_address_; } + + void mac_bytes(uint8_t *mac_addres) { + uint64_t mac = this->mac_address_ == 0 ? ESPNOW_BROADCAST_ADDR : this->mac_address_; + uint64_to_addr(mac, mac_addres); + } + + std::vector data() { return data_; } + + uint8_t get_counter() { return send_count_; } + void inc_counter() { + send_count_ = send_count_ + 1; + if (send_count_ > 5 && !is_holded_) { + set_holding(); + } + } + void reset_counter() { + send_count_ = 0; + del_holding(); + } + + void is_broadcast(bool value) { this->is_broadcast_ = value; } + bool is_broadcast() const { return this->is_broadcast_; } + + void timestamp(uint32_t value) { this->timestamp_ = value; } + uint32_t timestamp() { return this->timestamp_; } + + void rssi(int8_t rssi) { this->rssi_ = rssi; } + int8_t rssi() { return this->rssi_; } + + bool is_holded() { return this->is_holded_; } + void set_holding() { this->is_holded_ = true; } + void del_holding() { this->is_holded_ = false; } + + protected: + uint64_t mac_address_{0}; + std::vector data_; + + uint8_t send_count_{0}; + bool is_broadcast_{false}; + uint32_t timestamp_{0}; + uint8_t rssi_{0}; + + bool is_holded_{false}; +}; + +class ESPNowInterface : public Component, public Parented { + public: + ESPNowInterface(){}; + + void setup() override; + + virtual bool on_package_received(ESPNowPackage *package) { return false; }; + virtual bool on_package_send(ESPNowPackage *package) { return false; }; + virtual bool on_new_peer(ESPNowPackage *package) { return false; }; +}; + +class ESPNowComponent : public Component { + public: + ESPNowComponent(); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 1) + static void on_data_received((const esp_now_recv_info_t *recv_info, const uint8_t *data, int size); +#else + static void on_data_received(const uint8_t *addr, const uint8_t *data, int size); +#endif + + static void on_data_send(const uint8_t *mac_addr, esp_now_send_status_t status); + + void dump_config() override; + float get_setup_priority() const override { + return -100; } + + void setup() override; + + void loop() override; + void set_wifi_channel(uint8_t channel) { + this->wifi_channel_ = channel; } + + ESPNowPackage *send_package(const uint64_t mac_address, const uint8_t *data, int len) { + auto package = new ESPNowPackage(mac_address, data, len); + return this->send_package(package); + } + + ESPNowPackage *send_package(const uint64_t mac_address, const std::vector data) { + auto package = new ESPNowPackage(mac_address, data); + return this->send_package(package); + } + + ESPNowPackage *send_package(ESPNowPackage *package); + + void add_on_package_send_callback(std::function &&callback) { + this->on_package_send_.add(std::move(callback)); + } + + void add_on_package_receive_callback(std::function &&callback) { + this->on_package_receved_.add(std::move(callback)); + } + + void add_on_peer_callback(std::function &&callback) { + this->on_new_peer_.add(std::move(callback)); + } + + void register_protocol(ESPNowInterface *protocol) { + protocol->set_parent(this); + this->protocols_.push_back(protocol); + } + + esp_err_t add_peer(uint64_t addr); + esp_err_t del_peer(uint64_t addr); + + void set_auto_add_peer(bool value) { + this->auto_add_peer_ = value; } + + void on_package_received(ESPNowPackage *package); + void on_package_send(ESPNowPackage *package); + void on_new_peer(ESPNowPackage *package); + + void log_error_(std::string msg, esp_err_t err); + + protected: + void unHold_send_(uint64_t mac); + void push_receive_package_(ESPNowPackage *package) { + this->receive_queue_.push(std::move(package)); } + bool validate_channel_(uint8_t channel); + uint8_t wifi_channel_{0}; + bool auto_add_peer_{false}; + + CallbackManager on_package_send_; + CallbackManager on_package_receved_; + CallbackManager on_new_peer_; + + iterable_queue receive_queue_{}; + iterable_queue send_queue_{}; + + std::vector protocols_{}; + std::vector peers_{}; + + SemaphoreHandle_t send_lock_ = NULL; + + bool can_send_{true}; +}; + +template class SendAction : public Action, public Parented { + public: + template void set_mac(V mac) { this->mac_ = mac; } + void set_data_template(std::function(Ts...)> func) { + this->data_func_ = func; + this->static_ = false; + } + void set_data_static(const std::vector &data) { + this->data_static_ = data; + this->static_ = true; + } + + void play(Ts... x) override { + auto mac = this->mac_.value(x...); + + if (this->static_) { + this->parent_->send_package(mac, this->data_static_); + } else { + auto val = this->data_func_(x...); + this->parent_->send_package(mac, val); + } + } + + protected: + TemplatableValue mac_{}; + bool static_{false}; + std::function(Ts...)> data_func_{}; + std::vector data_static_{}; +}; + +template class NewPeerAction : public Action, public Parented { + public: + template void set_mac(V mac) { this->mac_ = mac; } + void play(Ts... x) override { + auto mac = this->mac_.value(x...); + parent_->add_peer(mac); + } + + protected: + TemplatableValue mac_{}; +}; + +template class DelPeerAction : public Action, public Parented { + public: + template void set_mac(V mac) { this->mac_ = mac; } + void play(Ts... x) override { + auto mac = this->mac_.value(x...); + parent_->del_peer(mac); + } + + protected: + TemplatableValue mac_{}; +}; + +class ESPNowSendTrigger : public Trigger { + public: + explicit ESPNowSendTrigger(ESPNowComponent *parent) { + parent->add_on_package_send_callback([this](ESPNowPackage *value) { this->trigger(value); }); + } +}; + +class ESPNowReceiveTrigger : public Trigger { + public: + explicit ESPNowReceiveTrigger(ESPNowComponent *parent) { + parent->add_on_package_receive_callback([this](ESPNowPackage *value) { this->trigger(value); }); + } +}; + +class ESPNowNewPeerTrigger : public Trigger { + public: + explicit ESPNowNewPeerTrigger(ESPNowComponent *parent) { + parent->add_on_peer_callback([this](ESPNowPackage *value) { this->trigger(value); }); + } +}; + +extern ESPNowComponent *global_esp_now; + +} // namespace esp_now +} // namespace esphome + +// #endif