From f42fee8e626e7f54504afda872c4efc177d4c15d Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Sun, 17 Nov 2024 16:49:19 +0100 Subject: [PATCH] - add new trigger for receiving broadcast packet's - add new action to send broadcast packet's - on the send and broadcast action 'dara' value is renamed to "payload" - peer value can now be se set as mac_address, uint64 value or 8digit alfanummeric peer code. - log message shows now only the peer code - espnow rejects all send messages before setup correctly. - the espnow protocal events should return true when they are overloaded. --- esphome/components/espnow/__init__.py | 111 ++++++++----- esphome/components/espnow/espnow.cpp | 140 ++++++++++------ esphome/components/espnow/espnow.h | 231 ++++++++++++-------------- esphome/components/espnow/test.yaml | 29 +++- 4 files changed, 286 insertions(+), 225 deletions(-) diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index d08012128b..6aa3104aac 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -1,7 +1,7 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_COMMAND, CONF_DATA, CONF_ID, CONF_TRIGGER_ID +from esphome.const import CONF_COMMAND, CONF_ID, CONF_PAYLOAD, CONF_TRIGGER_ID from esphome.core import CORE CODEOWNERS = ["@nielsnl68", "@jesserockz"] @@ -45,6 +45,9 @@ CONF_USE_SENT_CHECK = "use_sent_check" CONF_WIFI_CHANNEL = "wifi_channel" +CONF_MAC_CHARS = "0123456789-AbCdEfGhIjKlMnOpQrStUvWxYz+aBcDeFgHiJkLmNoPqRsTuVwXyZ" + + def validate_raw_data(value): if isinstance(value, str): return value.encode("utf-8") @@ -55,18 +58,45 @@ def validate_raw_data(value): ) -def validate_espnow_peer(): - return PEER_SCHEMA +def convert_mac_address(value): + parts = value.split(":") + if len(parts) != 6: + raise cv.Invalid("MAC Address must consist of 6 : (colon) separated parts") + parts_int = 0 + if any(len(part) != 2 for part in parts): + raise cv.Invalid("MAC Address must be format XX:XX:XX:XX:XX:XX") + for part in parts: + try: + parts_int = (parts_int << 8) + int(part, 16) + except ValueError: + # pylint: disable=raise-missing-from + raise cv.Invalid( + "MAC Address parts must be hexadecimal values from 00 to FF" + ) + return parts_int -PEER_SCHEMA = cv.Any( - { - cv.templatable(validate_espnow_peer), - cv.All(cv.string, cv.Length(min=8, max=8)), - cv.mac_address, - cv.uint64_t, - } -) +def validate_peer(value): + if isinstance(value, (int)): + return value + + if value.find(":") != -1: + return convert_mac_address(value) + + if len(value) == 8: + value = cv.string_strict(value) + + mac = 0 + for x in value: + n = CONF_MAC_CHARS.find(x) + if n == -1: + raise cv.Invalid(f"peer code is invalid. ({value}|{x})") + mac = (mac << 6) + n + return mac + + raise cv.Invalid( + f"peer code '{value}' needs to be 8 characters width, or a valid Mac address or a hexidacimal value of 12 chars width starting with '0x'" + ) CONFIG_SCHEMA = cv.Schema( @@ -97,7 +127,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_COMMAND): cv.Range(min=16, max=255), } ), - cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address), + cv.Optional(CONF_PEERS): cv.ensure_list(validate_peer), }, cv.only_on_esp32, ).extend(cv.COMPONENT_SCHEMA) @@ -107,7 +137,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - if CORE.is_esp32 and CORE.using_arduino: + if CORE.using_arduino: cg.add_library("WiFi", None) cg.add_define("USE_ESPNOW") @@ -115,9 +145,12 @@ async def to_code(config): cg.add(var.set_wifi_channel(config[CONF_WIFI_CHANNEL])) cg.add(var.set_auto_add_peer(config[CONF_AUTO_ADD_PEER])) cg.add(var.set_use_sent_check(config[CONF_USE_SENT_CHECK])) - cg.add(var.set_convermation_timeout(config[CONF_CONFORMATION_TIMEOUT])) + cg.add(var.set_conformation_timeout(config[CONF_CONFORMATION_TIMEOUT])) cg.add(var.set_retries(config[CONF_RETRIES])) + for conf in config.get(CONF_PEERS, []): + cg.add(var.add_peer(conf)) + for conf in config.get(CONF_ON_SENT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_COMMAND in conf: @@ -144,9 +177,6 @@ async def to_code(config): trigger, [(ESPNowPacketConst, "packet")], conf ) - for conf in config.get(CONF_PEERS, []): - cg.add(var.add_peer(conf.as_hex)) - PROTOCOL_SCHEMA = cv.Schema( { @@ -167,46 +197,45 @@ async def register_protocol(var, config): cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(ESPNowComponent), - cv.Optional(CONF_PEER, default=0xFFFFFFFFFFFF): cv.uint64_t, - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + cv.Required(CONF_PAYLOAD): cv.templatable(validate_raw_data), cv.Optional(CONF_COMMAND): cv.templatable(cv.Range(min=16, max=255)), }, - key=CONF_DATA, + key=CONF_PAYLOAD, ), ) @automation.register_action( "espnow.send", SendAction, - cv.maybe_simple_value( + cv.Schema( { cv.GenerateID(): cv.use_id(ESPNowComponent), - cv.Required(CONF_PEER): validate_espnow_peer, - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), - cv.Optional(CONF_COMMAND): cv.templatable(cv.Range(min=16, max=255)), - }, - key=CONF_DATA, + cv.Required(CONF_PEER): cv.templatable(validate_peer), + cv.Required(CONF_PAYLOAD): cv.templatable(validate_raw_data), + cv.Optional(CONF_COMMAND, default=0): cv.templatable( + cv.Range(min=0, max=255) + ), + } ), ) 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_PEER in config: - template_ = await cg.templatable(config[CONF_PEER].as_hex, args, cg.uint64) - cg.add(var.set_peer(template_)) + peer = config.get(CONF_PEER, 0xFFFFFFFFFFFF) + template_ = await cg.templatable(peer, args, cg.uint64) + cg.add(var.set_peer(template_)) - if CONF_COMMAND in config: - template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) - cg.add(var.set_command(template_)) + command = config.get(CONF_COMMAND, 0) + template_ = await cg.templatable(command, args, cg.uint8) + cg.add(var.set_command(template_)) - data = config.get(CONF_DATA, []) + data = config.get(CONF_PAYLOAD, []) 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)) + vec_ = cg.std_vector.template(cg.uint8) + templ = await cg.templatable(data, args, vec_, vec_) + cg.add(var.set_payload(templ)) + return var @@ -216,7 +245,7 @@ async def send_action(config, action_id, template_arg, args): cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(ESPNowComponent), - cv.Required(CONF_PEER): cv.templatable(cv.mac_address), + cv.Required(CONF_PEER): cv.templatable(validate_peer), }, key=CONF_PEER, ), @@ -227,13 +256,13 @@ async def send_action(config, action_id, template_arg, args): cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(ESPNowComponent), - cv.Required(CONF_PEER): cv.templatable(cv.mac_address), + cv.Required(CONF_PEER): cv.templatable(validate_peer), }, key=CONF_PEER, ), ) 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_PEER].as_hex, args, cg.uint64) + template_ = await cg.templatable(config[CONF_PEER], args, cg.uint64) cg.add(var.set_peer(template_)) return var diff --git a/esphome/components/espnow/espnow.cpp b/esphome/components/espnow/espnow.cpp index 8bfdb83e0f..5e6d2acbc3 100644 --- a/esphome/components/espnow/espnow.cpp +++ b/esphome/components/espnow/espnow.cpp @@ -29,6 +29,33 @@ static const size_t SEND_BUFFER_SIZE = 200; ESPNowComponent *ESPNowComponent::static_{nullptr}; // NOLINT +std::string espnow_encode_peer(uint64_t peer) { + std::string str1 = ""; + if (peer == FAILED) { + return "[Not Set]"; + } else if (peer == ESPNOW_BROADCAST_ADDR) + return "[BroadCast]"; + do { + str1.push_back(chars[peer & 63]); // Add on the left + peer = peer >> 6; + } while (peer != 0); + return str1; +}; + +uint64_t espnow_decode_peer(std::string peer) { + uint64_t mac = 0; + if (peer.size() != 8) + return FAILED; + + for (int pos = peer.size(); pos > 0; pos--) { + char *p = strchr(chars, peer[pos - 1]); + if (p == nullptr) + return FAILED; + mac = (mac << 6) + (p - chars); + } + return mac; +}; + /* ESPNowComponent ********************************************************************** */ ESPNowComponent::ESPNowComponent() { ESPNowComponent::static_ = this; } // NOLINT @@ -36,7 +63,7 @@ ESPNowComponent::ESPNowComponent() { ESPNowComponent::static_ = this; } // NOLI void ESPNowComponent::dump_config() { ESP_LOGCONFIG(TAG, "esp_now:"); - ESP_LOGCONFIG(TAG, " Own Peer Address: %s.", decode_peer(this->own_peer_address_)); + ESP_LOGCONFIG(TAG, " Own Peer Address: %s.", espnow_encode_peer(this->own_peer_address_).c_str()); ESP_LOGCONFIG(TAG, " Wifi channel: %d.", this->wifi_channel_); ESP_LOGCONFIG(TAG, " Auto add new peers: %s.", this->auto_add_peer_ ? "Yes" : "No"); @@ -46,9 +73,9 @@ void ESPNowComponent::dump_config() { } void ESPNowComponent::show_packet(const std::string &title, const ESPNowPacket &packet) { - ESP_LOGV(TAG, "%s packet. Peer: %s, Header: %c%c%c, Protocol:%c%c%c-%02x, Sequents: %d.%d, Size: %d, Valid: %s", - title.c_str(), packet.peer.c_str(), packet.at(0), packet.at(1), packet.at(2), packet.at(3), packet.at(4), - packet.at(5), packet.at(6), packet.at(7), packet.attempts, packet.content_size(), + ESP_LOGV(TAG, "%s packet. Peer: '%s', Header: %c%c%c, Protocol:%c%c%c-%02x, Sequents: %d.%d, Size: %d, Valid: %s", + title.c_str(), packet.get_peer_code().c_str(), packet.at(0), packet.at(1), packet.at(2), packet.at(3), + packet.at(4), packet.at(5), packet.at(6), packet.at(7), packet.attempts, packet.content_size(), packet.is_valid() ? "Yes" : "No"); } @@ -114,9 +141,8 @@ void ESPNowComponent::setup() { esp_wifi_get_mac(WIFI_IF_STA, (uint8_t *) &this->own_peer_address_); - for (auto &peer : this->peers_) { - ESP_LOGV(TAG, "Add peer '%s'.", peer.c_str()); - add_peer(peer); + for (auto id : this->peers_) { + add_peer(id); } this->send_queue_ = xQueueCreate(SEND_BUFFER_SIZE, sizeof(ESPNowPacket)); @@ -146,25 +172,28 @@ void ESPNowComponent::espnow_task(void *param) { ESPNowPacket packet; // NOLINT for (;;) { if (xQueueReceive(this_espnow->receive_queue_, (void *) &packet, (TickType_t) 1) == pdTRUE) { - uint8_t *mac = packet.peer.mac(); - if (esp_now_is_peer_exist(mac)) { - this_espnow->call_on_receive_(packet); - } else { - if (this_espnow->auto_add_peer_) { - this_espnow->add_peer(packet.peer); + uint8_t *mac = packet.get_peer(); + if (!packet.is_broadcast || !this_espnow->call_on_broadcast_(packet)) { + if (!esp_now_is_peer_exist(mac) && !this_espnow->call_on_new_peer_(packet)) { + if (this_espnow->auto_add_peer_) { + this_espnow->add_peer(packet.peer); + } + } + if (esp_now_is_peer_exist(mac)) { + this_espnow->call_on_receive_(packet); } - this_espnow->call_on_new_peer_(packet); } } if (xQueueReceive(this_espnow->send_queue_, (void *) &packet, (TickType_t) 1) == pdTRUE) { if (packet.attempts > this_espnow->retries_) { - ESP_LOGE(TAG, "Dropped '%s' (%d.%d). To many retries.", packet.peer.c_str(), packet.get_sequents(), + ESP_LOGE(TAG, "Dropped '%s' (%d.%d). To many retries.", packet.get_peer_code().c_str(), packet.get_sequents(), packet.attempts); this_espnow->unlock(); continue; } else if (this_espnow->is_locked()) { if (packet.timestamp + this_espnow->conformation_timeout_ < millis()) { - ESP_LOGW(TAG, "TimeOut '%s' (%d.%d).", packet.peer.c_str(), packet.get_sequents(), packet.attempts); + ESP_LOGW(TAG, "TimeOut '%s' (%d.%d).", packet.get_peer_code().c_str(), packet.get_sequents(), + packet.attempts); this_espnow->unlock(); } } else { @@ -172,13 +201,13 @@ void ESPNowComponent::espnow_task(void *param) { packet.retry(); packet.timestamp = millis(); - esp_err_t err = esp_now_send(packet.peer.mac(), packet.get_content(), packet.content_size()); + esp_err_t err = esp_now_send(packet.get_peer(), packet.get_content(), packet.content_size()); if (err == ESP_OK) { - ESP_LOGD(TAG, "Sended '%s' (%d.%d) from buffer. Wait for conformation.", packet.peer.c_str(), + ESP_LOGD(TAG, "Sended '%s' (%d.%d) from buffer. Wait for conformation.", packet.get_peer_code().c_str(), packet.get_sequents(), packet.attempts); } else { - ESP_LOGE(TAG, "Sending '%s' (%d.%d) FAILED. B: %d.", packet.peer.c_str(), packet.get_sequents(), + ESP_LOGE(TAG, "Sending '%s' (%d.%d) FAILED. B: %d.", packet.get_peer_code().c_str(), packet.get_sequents(), packet.attempts, this_espnow->send_queue_used()); this_espnow->unlock(); } @@ -188,7 +217,7 @@ void ESPNowComponent::espnow_task(void *param) { } } -esp_err_t ESPNowComponent::add_peer(Peer peer) { +esp_err_t ESPNowComponent::add_peer(uint64_t peer) { if (!this->is_ready()) { this->peers_.push_back(peer); return ESP_OK; @@ -199,15 +228,15 @@ esp_err_t ESPNowComponent::add_peer(Peer peer) { memset(&peer_info, 0, sizeof(esp_now_peer_info_t)); peer_info.channel = this->wifi_channel_; peer_info.encrypt = false; - memcpy((void *) peer_info.peer_addr, (void *) peer.mac(), 6); + memcpy((void *) peer_info.peer_addr, (void *) &peer, 6); return esp_now_add_peer(&peer_info); } } -esp_err_t ESPNowComponent::del_peer(Peer peer) { - if (esp_now_is_peer_exist((uint8_t *) peer.mac())) - return esp_now_del_peer((uint8_t *) &peer.mac()); +esp_err_t ESPNowComponent::del_peer(uint64_t peer) { + if (esp_now_is_peer_exist((uint8_t *) &peer)) + return esp_now_del_peer((uint8_t *) &peer); return ESP_OK; } @@ -226,25 +255,36 @@ ESPNowProtocol *ESPNowComponent::get_protocol_(uint32_t protocol) { return this->protocols_[protocol]; } -void ESPNowComponent::call_on_receive_(ESPNowPacket &packet) { +bool ESPNowComponent::call_on_receive_(ESPNowPacket &packet) { ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol()); if (protocol != nullptr) { - this->defer([protocol, packet]() { protocol->on_receive(packet); }); + return protocol->on_receive(packet); } + return false; } -void ESPNowComponent::call_on_sent_(ESPNowPacket &packet, bool status) { +bool ESPNowComponent::call_on_broadcast_(ESPNowPacket &packet) { ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol()); if (protocol != nullptr) { - this->defer([protocol, packet, status]() { protocol->on_sent(packet, status); }); + return protocol->on_broadcast(packet); } + return false; } -void ESPNowComponent::call_on_new_peer_(ESPNowPacket &packet) { +bool ESPNowComponent::call_on_sent_(ESPNowPacket &packet, bool status) { ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol()); if (protocol != nullptr) { - this->defer([protocol, packet]() { protocol->on_new_peer(packet); }); + return protocol->on_sent(packet, status); } + return false; +} + +bool ESPNowComponent::call_on_new_peer_(ESPNowPacket &packet) { + ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol()); + if (protocol != nullptr) { + return protocol->on_new_peer(packet); + } + return false; } /**< callback function of receiving ESPNOW data */ @@ -270,6 +310,7 @@ void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data, (wifi_promiscuous_pkt_t *) (data - sizeof(wifi_pkt_rx_ctrl_t) - 39); // = sizeof (espnow_frame_format_t) rx_ctrl = &promiscuous_pkt->rx_ctrl; #endif + ESPNowPacket packet(addr, data, (uint8_t) size); // NOLINT packet.is_broadcast = broadcast; if (rx_ctrl != nullptr) { @@ -288,23 +329,26 @@ void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data, } bool ESPNowComponent::send(ESPNowPacket packet) { - if (this->is_failed()) { + if (!this->is_ready()) { + ESP_LOGE(TAG, "Cannot send espnow packet, espnow is not setup yet."); + } else if (this->is_failed()) { ESP_LOGE(TAG, "Cannot send espnow packet, espnow failed to setup"); } else if (this->send_queue_full()) { ESP_LOGE(TAG, "Send Buffer Out of Memory."); - } else if (!esp_now_is_peer_exist(packet.peer.mac())) { - ESP_LOGE(TAG, "Peer not registered: %s.", packet.peer.c_str()); + } else if (!esp_now_is_peer_exist(packet.get_peer())) { + ESP_LOGE(TAG, "Peer not registered: %s.", packet.get_peer_code().c_str()); } else if (!packet.is_valid()) { - ESP_LOGE(TAG, "This Packet is invalid: %s (%d.%d)", packet.c_str(), packet.get_sequents(), packet.attempts); + ESP_LOGE(TAG, "This Packet is invalid: %s (%d.%d)", packet.get_peer_code().c_str(), packet.get_sequents(), + packet.attempts); } else if (this->use_sent_check_ && packet.peer != ESPNOW_BROADCAST_ADDR) { - ESP_LOGV(TAG, "Placing %s (%d.%d) into send buffer. Used: %d of %d", packet.peer.c_str(), packet.get_sequents(), - packet.attempts, this->send_queue_used(), SEND_BUFFER_SIZE); + ESP_LOGV(TAG, "Placing %s (%d.%d) into send buffer. Used: %d of %d", packet.get_peer_code().c_str(), + packet.get_sequents(), packet.attempts, this->send_queue_used(), SEND_BUFFER_SIZE); xQueueSendToBack(this->send_queue_, (void *) &packet, 10); return true; } else { - esp_err_t err = esp_now_send(packet.peer.mac(), packet.get_content(), packet.content_size()); - ESP_LOGV(TAG, "Sending to %s (%d.%d) directly%s.", packet.peer.c_str(), packet.get_sequents(), packet.attempts, - (err == ESP_OK) ? "" : " FAILED"); + esp_err_t err = esp_now_send(packet.get_peer(), packet.get_content(), packet.content_size()); + ESP_LOGV(TAG, "Sending to %s (%d.%d) directly%s.", packet.get_peer_code().c_str(), packet.get_sequents(), + packet.attempts, (err == ESP_OK) ? "" : " FAILED"); this->call_on_sent_(packet, err == ESP_OK); return true; @@ -315,16 +359,19 @@ bool ESPNowComponent::send(ESPNowPacket packet) { void ESPNowComponent::on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) { ESPNowPacket packet; // NOLINT - Peer peer(mac_addr); + uint64_t peer; + memcpy((void *) &peer, mac_addr, 6); if (xQueuePeek(ESPNowComponent::static_->send_queue_, (void *) &packet, 10 / portTICK_PERIOD_MS) == pdTRUE) { if (packet.peer != peer) { - ESP_LOGE(TAG, " Invalid mac address. Expected: %s (%d.%d); got: %s", packet.peer.c_str(), packet.get_sequents(), - packet.attempts, peer.c_str()); + ESP_LOGE(TAG, " Invalid mac address. Expected: %s (%d.%d); got: %s", packet.get_peer_code().c_str(), + packet.get_sequents(), packet.attempts, espnow_encode_peer(peer).c_str()); return; } else if (status != ESP_OK) { - ESP_LOGE(TAG, "Sent packet failed for %s (%d.%d)", packet.peer.c_str(), packet.get_sequents(), packet.attempts); + ESP_LOGE(TAG, "Sent packet failed for %s (%d.%d)", packet.get_peer_code().c_str(), packet.get_sequents(), + packet.attempts); } else { - ESP_LOGV(TAG, "Confirm packet sent %s (%d.%d)", packet.peer.c_str(), packet.get_sequents(), packet.attempts); + ESP_LOGV(TAG, "Confirm packet sent %s (%d.%d)", packet.get_peer_code().c_str(), packet.get_sequents(), + packet.attempts); xQueueReceive(ESPNowComponent::static_->send_queue_, (void *) &packet, 10 / portTICK_PERIOD_MS); } ESPNowComponent::static_->call_on_sent_(packet, status == ESP_OK); @@ -334,10 +381,9 @@ void ESPNowComponent::on_data_sent(const uint8_t *mac_addr, esp_now_send_status_ /* ESPNowProtocol ********************************************************************** */ -bool ESPNowProtocol::send(Peer peer, const uint8_t *data, uint8_t len, uint8_t command) { - ESPNowPacket packet(peer, data, len, this->get_protocol_id()); // NOLINT +bool ESPNowProtocol::send(uint64_t peer, const uint8_t *data, uint8_t len, uint8_t command) { + ESPNowPacket packet(peer, data, len, this->get_protocol_id(), command); // NOLINT packet.set_sequents(this->get_next_sequents(packet.peer)); - packet.set_command(command); return this->parent_->send(packet); } diff --git a/esphome/components/espnow/espnow.h b/esphome/components/espnow/espnow.h index 476473ef5f..3d64790a05 100644 --- a/esphome/components/espnow/espnow.h +++ b/esphome/components/espnow/espnow.h @@ -5,6 +5,8 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" + #include #include @@ -31,93 +33,11 @@ static const uint8_t ESPNOW_COMMAND_RESEND = 0x05; static const char chars[] = "0123456789-AbCdEfGhIjKlMnOpQrStUvWxYz+aBcDeFgHiJkLmNoPqRsTuVwXyZ"; static const uint64_t FAILED = 0; -std::string encode_peer(uint64_t peer) { - std::string str1 = ""; - if (peer == FAILED) { - return "[Not Set]" - } else if (peer == ESPNOW_BROADCAST_ADDR) - return "[BroadCast]"; - do { - str1.push_back(chars[peer & 63]); // Add on the left - peer = peer >> 6; - } while (peer != 0); - return str1; -} - -uint64_t decode_peer(std::string peer) { - uint64_t mac = 0; - if (peer.size() != 8) - return FAILED; - - for (int pos = peer.size(); pos > 0; pos--) { - char *p = strchr(chars, peer[pos - 1]); - if (p == nullptr) - return FAILED; - mac = (mac << 6) + (p - chars); - } - return mac; -} - -struct Peer { - uint64_t id{0}; - - inline Peer() ESPHOME_ALWAYS_INLINE{}; - - inline Peer(const Peer &peer) ESPHOME_ALWAYS_INLINE { this.is = peer.id; } - inline Peer(std::String peer) ESPHOME_ALWAYS_INLINE { this->id = decode_peer(peercode); } - inline Peer(uint64_t peer) ESPHOME_ALWAYS_INLINE { this->id = peerid; } - inline Peer(const uint8_t *peer) ESPHOME_ALWAYS_INLINE { this = peer; } - - inline uint8_t *mac() const { return (uint8_t *) &(this->id); } - inline std::string code() const { return encode_peer(this->id); } - - inline char *c_str() const { return encode_peer(this->id).c_str(); } - - inline bool operator=(const Peer &peer) { // NOLINT - return this->id = peer.id; - } - inline Color &operator=(uint8_t *peer) ESPHOME_ALWAYS_INLINE { - memcpy((void *) this->mac(), (const void *) peer, 6); - return *this; - } - inline Color &operator=(std::String peer) ESPHOME_ALWAYS_INLINE { - this->id = decode_peer(peercode); - return *this; - } - inline Color &operator=(uint64_t peer) ESPHOME_ALWAYS_INLINE { - this->id = peer; - return *this; - } - - inline bool operator==(const Peer &peer) { // NOLINT - return this->id == peer.id; - } - inline bool operator==(uint64_t peer) { // NOLINT - return this->id == peer; - } - inline bool operator==(std::String peer) { // NOLINT - return this->id == decode_peer(peer); - } - inline bool operator==(const uint8_t *peer) { // NOLINT - return memcmp(peer, this->mac(), 6) == 0; - } - - inline bool operator!=(const Peer &peer) { // NOLINT - return this->id != peer.id; - } - inline bool operator!=(uint64_t peer) { // NOLINT - return this->id != rhs.raw_32; - } - inline bool operator!=(std::String peer) { // NOLINT - return this->id != decode_peer(peer); - } - inline bool operator!=(const uint8_t *peer) { // NOLINT - return memcmp(peer, this->mac(), 6) != 0; - } -} +std::string espnow_encode_peer(uint64_t peer); +uint64_t espnow_decode_peer(std::string peer); struct ESPNowPacket { - Peer &peerid{0}; + uint64_t peer{0}; uint8_t rssi{0}; int8_t attempts{0}; bool is_broadcast{false}; @@ -135,12 +55,20 @@ struct ESPNowPacket { inline ESPNowPacket() ESPHOME_ALWAYS_INLINE {} // Create packet to be send. - inline ESPNowPacket(Peer peer, const uint8_t *data, uint8_t size, uint32_t protocol, + inline ESPNowPacket(uint64_t peer, const uint8_t *data, uint8_t size, uint32_t protocol, uint8_t command = 0) ESPHOME_ALWAYS_INLINE { - assert(size <= MAX_ESPNOW_DATA_SIZE); - assert(peer == 0); + if (size > MAX_ESPNOW_DATA_SIZE) { + ESP_LOGE("ESPNowPacket", "Payload size is to large. It should be less then %d instead it is %d", + MAX_ESPNOW_DATA_SIZE, size); + return; + } + if (peer == 0ull) { + ESP_LOGE("ESPNowPacket", "No Peer defined."); + return; + } this->peer = peer; + this->is_broadcast = (peer == ESPNOW_BROADCAST_ADDR); this->set_protocol(protocol); if (command != 0) { @@ -150,10 +78,34 @@ struct ESPNowPacket { std::memcpy(this->get_payload(), data, size); } + inline ESPNowPacket(const uint8_t *peer, const uint8_t *data, uint8_t size) ESPHOME_ALWAYS_INLINE { + if (size > MAX_ESPNOW_DATA_SIZE + this->prefix_size()) { + ESP_LOGE("ESPNowPacket", "Received Payload size is to large. It should be less then %d instead it is %d", + MAX_ESPNOW_DATA_SIZE + this->prefix_size(), size); + return; + } + + this->set_peer(peer); + + this->size = size - this->prefix_size(); + std::memcpy(this->get_content(), data, size); + } + uint8_t prefix_size() const { return sizeof(this->content.prefix); } uint8_t content_size() const { return (this->prefix_size() + this->size); } + inline void set_peer(const uint8_t *peer) ESPHOME_ALWAYS_INLINE { + if (*peer == 0) { + peer = (uint8_t *) &ESPNOW_BROADCAST_ADDR; + } + memcpy((void *) this->get_peer(), (const void *) peer, 6); + }; + inline bool is_peer(const uint8_t *peer) const { return memcmp(peer, this->get_peer(), 6) == 0; } + + inline uint8_t *get_peer() const { return (uint8_t *) &(this->peer); } + inline std::string get_peer_code() const { return espnow_encode_peer(this->peer); } + inline uint32_t get_protocol() const { return this->content.prefix.protocol & 0x00FFFFFF; } inline void set_protocol(uint32_t protocol) { this->content.prefix.protocol = (this->content.prefix.protocol & 0xFF000000) + (protocol & 0x00FFFFFF); @@ -189,9 +141,11 @@ class ESPNowProtocol : public Parented { public: ESPNowProtocol(){}; - virtual void on_receive(const ESPNowPacket &packet){}; - virtual void on_sent(const ESPNowPacket &packet, bool status){}; - virtual void on_new_peer(const ESPNowPacket &packet){}; + virtual bool on_receive(const ESPNowPacket &packet) { return false; }; + virtual bool on_broadcast(const ESPNowPacket &packet) { return false; }; + + virtual bool on_sent(const ESPNowPacket &packet, bool status) { return false; }; + virtual bool on_new_peer(const ESPNowPacket &packet) { return false; }; virtual uint32_t get_protocol_id() = 0; virtual std::string get_protocol_name() = 0; @@ -214,7 +168,7 @@ class ESPNowProtocol : public Parented { return valid; } - bool send(peer peer, const uint8_t *data, uint8_t len, uint8_t command = 0); + bool send(uint64_t peer, const uint8_t *data, uint8_t len, uint8_t command = 0); protected: uint8_t next_sequents_{255}; @@ -228,22 +182,41 @@ class ESPNowDefaultProtocol : public ESPNowProtocol { void add_on_receive_callback(std::function &&callback) { this->on_receive_.add(std::move(callback)); } - void on_receive(const ESPNowPacket &packet) override { this->on_receive_.call(packet); }; + bool on_receive(const ESPNowPacket &packet) override { + this->on_receive_.call(packet); + return this->on_receive_.size() > 0; + }; + + void add_on_broadcast_callback(std::function &&callback) { + this->on_broadcast_.add(std::move(callback)); + } + + void on_broadcast(const ESPNowPacket &packet) override { + this->on_broadcast_.call(packet); + return this->on_broadcast_.size() > 0; + }; void add_on_sent_callback(std::function &&callback) { this->on_sent_.add(std::move(callback)); } - void on_sent(const ESPNowPacket &packet, bool status) override { this->on_sent_.call(packet, status); }; + bool on_sent(const ESPNowPacket &packet, bool status) override { + this->on_sent_.call(packet, status); + return this->on_sent_.size() > 0; + }; void add_on_peer_callback(std::function &&callback) { this->on_new_peer_.add(std::move(callback)); } - void on_new_peer(const ESPNowPacket &packet) override { this->on_new_peer_.call(packet); }; + void on_new_peer(const ESPNowPacket &packet) override { + this->on_new_peer_.call(packet); + return this->on_new_peer_.size() > 0; + }; protected: CallbackManager on_sent_; CallbackManager on_receive_; CallbackManager on_new_peer_; + CallbackManager on_broadcast_; }; class ESPNowComponent : public Component { @@ -265,7 +238,7 @@ class ESPNowComponent : public Component { void set_wifi_channel(uint8_t channel) { this->wifi_channel_ = channel; } void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; } void set_use_sent_check(bool value) { this->use_sent_check_ = value; } - void set_convermation_timeout(uint32_t timeout) { this->conformation_timeout_ = timeout; } + void set_conformation_timeout(uint32_t timeout) { this->conformation_timeout_ = timeout; } void set_retries(uint8_t value) { this->retries_ = value; } void setup() override; @@ -278,8 +251,8 @@ class ESPNowComponent : public Component { this->protocols_[protocol->get_protocol_id()] = protocol; } - esp_err_t add_peer(Peer peer); - esp_err_t del_peer(Peer peer); + esp_err_t add_peer(uint64_t peer); + esp_err_t del_peer(uint64_t peer); bool send_queue_empty() { return uxQueueMessagesWaiting(this->send_queue_) == 0; } bool send_queue_full() { return uxQueueSpacesAvailable(this->send_queue_) == 0; } @@ -311,9 +284,10 @@ class ESPNowComponent : public Component { bool lock_{false}; - void call_on_receive_(ESPNowPacket &packet); - void call_on_sent_(ESPNowPacket &packet, bool status); - void call_on_new_peer_(ESPNowPacket &packet); + bool call_on_receive_(ESPNowPacket &packet); + bool call_on_broadcast_(ESPNowPacket &packet); + bool call_on_sent_(ESPNowPacket &packet, bool status); + bool call_on_new_peer_(ESPNowPacket &packet); QueueHandle_t receive_queue_{}; QueueHandle_t send_queue_{}; @@ -326,34 +300,18 @@ class ESPNowComponent : public Component { template class SendAction : public Action, public Parented { public: - template void set_peer(V peer) { this->peer_ = peer; } - template void set_command(V command) { this->command_ = command; } - - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->dynamic_ = true; - } - void set_data_static(const std::vector &data) { this->data_static_ = data; } + TEMPLATABLE_VALUE(uint64_t, peer); + TEMPLATABLE_VALUE(uint8_t, command); + TEMPLATABLE_VALUE(std::vector, payload); void play(Ts... x) override { - Peer peer = this->peer_.value(x...); - uint8_t command = 0; - if (this->command_.has_value()) { - command = this->mac_.value(x...); - } + uint64_t peer = this->peer_.value(x...); + uint8_t command = this->command_.value(x...); + std::vector payload = this->payload_.value(x...); + ESP_LOGVV("SendAction", "send to 0x%12llx, command %d, payload size: %d", peer, command, payload.size()); - if (this->dynamic_) { - this->data_static_ = this->data_func_(x...); - } - this->parent_->get_default_protocol()->send(peer, this->data_static_.data(), this->data_static_.size(), command); + this->parent_->get_default_protocol()->send(peer, payload.data(), payload.size(), command); } - - protected: - TemplatableValue command_{}; - TemplatableValue peer_{}; - bool dynamic_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; }; template class NewPeerAction : public Action, public Parented { @@ -365,19 +323,19 @@ template class NewPeerAction : public Action, public Pare } protected: - TemplatableValue mac_{}; + TemplatableValue mac_{}; }; template class DelPeerAction : public Action, public Parented { public: - template void set_peer(V mac) { this->peer_ = peer; } + template void set_peer(V peer) { this->peer_ = peer; } void play(Ts... x) override { auto peer = this->peer_.value(x...); - parent_->del_peer(mac); + parent_->del_peer(peer); } protected: - TemplatableValue peer_{}; + TemplatableValue peer_{}; }; class ESPNowSentTrigger : public Trigger { @@ -410,6 +368,21 @@ class ESPNowReceiveTrigger : public Trigger { uint8_t command_{0}; }; +class ESPNowBroadcaseTrigger : public Trigger { + public: + explicit ESPNowBroadcaseTrigger(ESPNowComponent *parent) { + parent->get_default_protocol()->add_on_broadcast_callback([this](const ESPNowPacket packet) { + if ((this->command_ == 0) || this->command_ == packet.get_command()) { + this->trigger(packet); + } + }); + } + void set_command(uint8_t command) { this->command_ = command; } + + protected: + uint8_t command_{0}; +}; + class ESPNowNewPeerTrigger : public Trigger { public: explicit ESPNowNewPeerTrigger(ESPNowComponent *parent) { diff --git a/esphome/components/espnow/test.yaml b/esphome/components/espnow/test.yaml index 190a49a94e..41ad6e945a 100644 --- a/esphome/components/espnow/test.yaml +++ b/esphome/components/espnow/test.yaml @@ -3,11 +3,6 @@ substitutions: name: "lum-iot-test" friendly_name: "Project Template" -# external_components: -# - source: github://pr#7141 -# components: [ espnow ] -# refresh: 1 sec - esp32: board: esp32dev framework: @@ -35,11 +30,29 @@ espnow: auto_add_peer: true peers: - FF:FF:FF:FF:FF:FF + - 0xFFFFFFFFFFFF + - ZZZZZZZZ on_receive: - logger.log: - format: "Received: %s RSSI: %d" - args: [packet.get_payload(), packet.rssi] + format: "Received: '%s' from '%s' command: %d RSSI: %d" + args: + [ + packet.get_payload(), + packet.get_peer_code().c_str(), + packet.command, + packet.rssi, + ] + + on_broadcast: + - command: 123 + then: + - logger.log: + format: "Broadcast Received from: '%s' RSSI: %d: %s" + args: [packet.get_peer_code().c_str(), packet.rssi] + interval: - interval: 10sec then: - - espnow.broatcast: "hallo everyone" + - espnow.broatcast: + payload: "hallo everyone" + command: 123