mirror of
https://github.com/esphome/esphome.git
synced 2024-11-15 11:38:11 +01:00
New implementation; the espnow compoment.
This commit is contained in:
parent
39c0019534
commit
4750c97606
3 changed files with 811 additions and 0 deletions
198
esphome/components/espnow/__init__.py
Normal file
198
esphome/components/espnow/__init__.py
Normal file
|
@ -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
|
328
esphome/components/espnow/espnow.cpp
Normal file
328
esphome/components/espnow/espnow.cpp
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
#include "espnow.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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<uint8_t> 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
|
285
esphome/components/espnow/espnow.h
Normal file
285
esphome/components/espnow/espnow.h
Normal file
|
@ -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 <esp_now.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<typename T, typename Container = std::deque<T> > class iterable_queue : public std::queue<T, Container> {
|
||||||
|
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<uint8_t> 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<uint8_t> 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<uint8_t> 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<ESPNowComponent> {
|
||||||
|
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<uint8_t> 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<void(ESPNowPackage *)> &&callback) {
|
||||||
|
this->on_package_send_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_on_package_receive_callback(std::function<void(ESPNowPackage *)> &&callback) {
|
||||||
|
this->on_package_receved_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_on_peer_callback(std::function<void(ESPNowPackage *)> &&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<void(ESPNowPackage *)> on_package_send_;
|
||||||
|
CallbackManager<void(ESPNowPackage *)> on_package_receved_;
|
||||||
|
CallbackManager<void(ESPNowPackage *)> on_new_peer_;
|
||||||
|
|
||||||
|
iterable_queue<ESPNowPackage *> receive_queue_{};
|
||||||
|
iterable_queue<ESPNowPackage *> send_queue_{};
|
||||||
|
|
||||||
|
std::vector<ESPNowInterface *> protocols_{};
|
||||||
|
std::vector<uint64_t> peers_{};
|
||||||
|
|
||||||
|
SemaphoreHandle_t send_lock_ = NULL;
|
||||||
|
|
||||||
|
bool can_send_{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SendAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||||
|
public:
|
||||||
|
template<typename V> void set_mac(V mac) { this->mac_ = mac; }
|
||||||
|
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||||
|
this->data_func_ = func;
|
||||||
|
this->static_ = false;
|
||||||
|
}
|
||||||
|
void set_data_static(const std::vector<uint8_t> &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<uint64_t, Ts...> mac_{};
|
||||||
|
bool static_{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> {
|
||||||
|
public:
|
||||||
|
template<typename V> 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<uint64_t, Ts...> mac_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class DelPeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||||
|
public:
|
||||||
|
template<typename V> 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<uint64_t, Ts...> mac_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESPNowSendTrigger : public Trigger<ESPNowPackage *> {
|
||||||
|
public:
|
||||||
|
explicit ESPNowSendTrigger(ESPNowComponent *parent) {
|
||||||
|
parent->add_on_package_send_callback([this](ESPNowPackage *value) { this->trigger(value); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESPNowReceiveTrigger : public Trigger<ESPNowPackage *> {
|
||||||
|
public:
|
||||||
|
explicit ESPNowReceiveTrigger(ESPNowComponent *parent) {
|
||||||
|
parent->add_on_package_receive_callback([this](ESPNowPackage *value) { this->trigger(value); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESPNowNewPeerTrigger : public Trigger<ESPNowPackage *> {
|
||||||
|
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
|
Loading…
Reference in a new issue