- 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.
This commit is contained in:
NP v/d Spek 2024-11-17 16:49:19 +01:00
parent 4fcab8c82f
commit f42fee8e62
4 changed files with 286 additions and 225 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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 <esp_now.h>
#include <array>
@ -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<ESPNowComponent> {
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<ESPNowComponent> {
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<void(const ESPNowPacket)> &&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<void(const ESPNowPacket)> &&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<void(const ESPNowPacket, bool status)> &&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<void(const ESPNowPacket)> &&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<void(const ESPNowPacket, bool)> on_sent_;
CallbackManager<void(const ESPNowPacket)> on_receive_;
CallbackManager<void(const ESPNowPacket)> on_new_peer_;
CallbackManager<void(const ESPNowPacket)> 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<typename... Ts> class SendAction : public Action<Ts...>, public Parented<ESPNowComponent> {
public:
template<typename V> void set_peer(V peer) { this->peer_ = peer; }
template<typename V> void set_command(V command) { this->command_ = command; }
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
this->data_func_ = func;
this->dynamic_ = true;
}
void set_data_static(const std::vector<uint8_t> &data) { this->data_static_ = data; }
TEMPLATABLE_VALUE(uint64_t, peer);
TEMPLATABLE_VALUE(uint8_t, command);
TEMPLATABLE_VALUE(std::vector<uint8_t>, 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<uint8_t> 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<uint8_t, Ts...> command_{};
TemplatableValue<Peer, Ts...> peer_{};
bool dynamic_{false};
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
std::vector<uint8_t> data_static_{};
};
template<typename... Ts> class NewPeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
@ -365,19 +323,19 @@ template<typename... Ts> class NewPeerAction : public Action<Ts...>, public Pare
}
protected:
TemplatableValue<Peer, Ts...> mac_{};
TemplatableValue<uint64_t, Ts...> mac_{};
};
template<typename... Ts> class DelPeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
public:
template<typename V> void set_peer(V mac) { this->peer_ = peer; }
template<typename V> 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, Ts...> peer_{};
TemplatableValue<uint64_t, Ts...> peer_{};
};
class ESPNowSentTrigger : public Trigger<const ESPNowPacket, bool> {
@ -410,6 +368,21 @@ class ESPNowReceiveTrigger : public Trigger<const ESPNowPacket> {
uint8_t command_{0};
};
class ESPNowBroadcaseTrigger : public Trigger<const ESPNowPacket> {
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<const ESPNowPacket> {
public:
explicit ESPNowNewPeerTrigger(ESPNowComponent *parent) {

View file

@ -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