diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index a63d56a22b..d38a41f46b 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -10,14 +10,14 @@ espnow_ns = cg.esphome_ns.namespace("espnow") ESPNowComponent = espnow_ns.class_("ESPNowComponent", cg.Component) ESPNowListener = espnow_ns.class_("ESPNowListener") -ESPNowPackage = espnow_ns.class_("ESPNowPackage") -ESPNowPackagePtrConst = ESPNowPackage.operator("ptr") # .operator("const") +ESPNowPacket = espnow_ns.class_("ESPNowPacket") +ESPNowPacketPtrConst = ESPNowPacket.operator("ptr") # .operator("const") ESPNowInterface = espnow_ns.class_( "ESPNowInterface", cg.Component, cg.Parented.template(ESPNowComponent) ) -ESPNowSendTrigger = espnow_ns.class_("ESPNowSendTrigger", automation.Trigger.template()) +ESPNowSentTrigger = espnow_ns.class_("ESPNowSentTrigger", automation.Trigger.template()) ESPNowReceiveTrigger = espnow_ns.class_( "ESPNowReceiveTrigger", automation.Trigger.template() ) @@ -30,12 +30,13 @@ 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_RECEIVE = "on_receive" +CONF_ON_SENT = "on_sent" CONF_ON_NEW_PEER = "on_new_peer" CONF_WIFI_CHANNEL = "wifi_channel" CONF_PEERS = "peers" CONF_AUTO_ADD_PEER = "auto_add_peer" +CONF_USE_SENT_CHECK = "use_sent_check" def validate_raw_data(value): @@ -53,14 +54,15 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(ESPNowComponent), cv.Optional(CONF_WIFI_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.Optional(CONF_USE_SENT_CHECK, default=True): cv.boolean, + cv.Optional(CONF_ON_RECEIVE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPNowReceiveTrigger), } ), - cv.Optional(CONF_ON_PACKAGE_SEND): automation.validate_automation( + cv.Optional(CONF_ON_SENT): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPNowSendTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPNowSentTrigger), } ), cv.Optional(CONF_ON_NEW_PEER): automation.validate_automation( @@ -78,40 +80,46 @@ 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: + if CORE.is_esp32 and CORE.using_arduino: cg.add_library("WiFi", None) cg.add_define("USE_ESPNOW") 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])) - for conf in config.get(CONF_ON_PACKAGE_SEND, []): + for conf in config.get(CONF_ON_SENT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(ESPNowPackagePtrConst, "it")], conf - ) + await automation.build_automation(trigger, [(ESPNowPacket, "it")], conf) - for conf in config.get(CONF_ON_PACKAGE_RECEIVED, []): + for conf in config.get(CONF_ON_RECEIVE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(ESPNowPackagePtrConst, "it")], conf - ) + await automation.build_automation(trigger, [(ESPNowPacket, "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 + trigger, [(ESPNowPacket, "it"), (bool, "status")], conf ) for conf in config.get(CONF_PEERS, []): cg.add(var.add_peer(conf.as_hex)) +PROTOCOL_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ESPNOW): cv.use_id(ESPNowComponent), + }, + cv.only_on_esp32, +).extend(cv.COMPONENT_SCHEMA) + + +async def register_protocol(var, config): + now = await cg.get_variable(config[CONF_ESPNOW]) + cg.add(now.register_protocol(var)) + + @automation.register_action( "espnow.send", SendAction, diff --git a/esphome/components/espnow/espnow.cpp b/esphome/components/espnow/espnow.cpp index 885f2b2607..b8b4a1e044 100644 --- a/esphome/components/espnow/espnow.cpp +++ b/esphome/components/espnow/espnow.cpp @@ -13,7 +13,7 @@ #ifdef USE_WIFI #include "esphome/components/wifi/wifi_component.h" #endif - +#include "esphome/core/application.h" #include "esphome/core/version.h" #include "esphome/core/log.h" @@ -22,6 +22,14 @@ namespace espnow { static const char *const TAG = "espnow"; +static const size_t SEND_BUFFER_SIZE = 200; + +static void application_task(void *param) { + // delegate onto the application + ESPNowComponent *application = (ESPNowComponent *) param; + application->runner(); +} + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 1) typedef struct { uint16_t frame_head; @@ -45,28 +53,36 @@ typedef struct { } __attribute__((packed)) espnow_frame_format_t; #endif -void ESPNowInterface::setup() { parent_->register_protocol(this); } +void ESPNowProtocol::setup() { parent_->register_protocol(this); } -ESPNowPackage::ESPNowPackage(const uint64_t mac_address, const std::vector data) { - this->mac_address_ = mac_address; - this->data_ = data; +bool ESPNowProtocol::write(const uint64_t mac_address, const uint8_t *data, uint8_t len) { + ESPNowPacket packet(mac_address, data, len, this->get_app_id()); + return this->parent_->write(packet); } - -ESPNowPackage::ESPNowPackage(const uint64_t mac_address, const uint8_t *data, size_t len) { - this->mac_address_ = mac_address; - this->data_.clear(); - // this->data_.insert(this->data_.begin(), len, *data) - // std::copy_n(data, len, this->data_.begin()); +bool ESPNowProtocol::write(const uint64_t mac_address, const std::vector data) { + ESPNowPacket packet(mac_address, (uint8_t *) data.data(), (uint8_t) data.size(), this->get_app_id()); + return this->parent_->write(packet); +} +bool ESPNowProtocol::write(ESPNowPacket packet) { + packet.app_id = this->get_app_id(); + packet.ref_id = this->get_next_ref_id(); + packet.recalc(); + return this->parent_->write(packet); } 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()); + ESPNowPacket packet(0x112233445566, (uint8_t *) TAG, 5, 0x111111); + ESP_LOGI(TAG, "test: %s |H:%02x%02x%02x A:%02x%02x%02x %02x T:%02x C:%02x%02x S:%02d", packet.to_str().c_str(), + packet.content[0], packet.content[1], packet.content[2], packet.content[3], packet.content[4], + packet.content[5], packet.content[6], packet.content[7], packet.content[8], packet.content[9], packet.size); + + ESP_LOGI(TAG, "test: A:%06x R:%02x C:%04x S:%02d", packet.app_id, packet.ref_id, packet.crc16, packet.size); + ESP_LOGI(TAG, "test: is_valid: %s", + packet.is_valid() ? "Yes" : "No"); // ESP_LOGCONFIG(TAG, " WiFi Channel: %n", WiFi.channel()); } bool ESPNowComponent::validate_channel_(uint8_t channel) { @@ -108,13 +124,6 @@ void ESPNowComponent::setup() { 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)); @@ -124,39 +133,42 @@ void ESPNowComponent::setup() { 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); + ESP_LOGE("esp_now_register_recv_cb failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } - err = esp_now_register_send_cb(ESPNowComponent::on_data_send); + err = esp_now_register_send_cb(ESPNowComponent::on_data_sent); if (err != ESP_OK) { - this->log_error_("esp_now_register_send_cb failed: %s", err); + ESP_LOGE("esp_now_register_send_cb failed: %s", esp_err_to_name(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]); + this->send_queue_ = xQueueCreate(SEND_BUFFER_SIZE, sizeof(ESPNowPacket)); + if (this->send_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create send queue"); + this->mark_failed(); + return; } - return package; + + this->receive_queue_ = xQueueCreate(SEND_BUFFER_SIZE, sizeof(ESPNowPacket)); + if (this->receive_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create receive queue"); + this->mark_failed(); + return; + } + + xTaskCreate(application_task, "espnow_task", 8192, this, 2, &this->espnow_task_handle_); + + ESP_LOGI(TAG, "ESP-NOW setup complete"); } esp_err_t ESPNowComponent::add_peer(uint64_t addr) { @@ -167,13 +179,11 @@ esp_err_t ESPNowComponent::add_peer(uint64_t addr) { 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); + memcpy((void *) peerInfo.peer_addr, (void *) &addr, 6); return esp_now_add_peer(&peerInfo); } @@ -181,97 +191,42 @@ esp_err_t ESPNowComponent::add_peer(uint64_t addr) { esp_err_t ESPNowComponent::del_peer(uint64_t addr) { uint8_t mac[6]; - uint64_to_addr(addr, (uint8_t *) &mac); + memcpy((void *) &mac, (void *) &addr, 6); 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; - } +ESPNowDefaultProtocol *ESPNowComponent::get_defaultProtocol_() { + if (this->protocols_[ESPNOW_DEFAULT_APP_ID] == nullptr) { + ESPNowDefaultProtocol *tmp = new ESPNowDefaultProtocol(); + this->protocols_[ESPNOW_DEFAULT_APP_ID] = tmp; + this->register_protocol(tmp); } - this->on_package_receved_.call(package); + return (ESPNowDefaultProtocol *) this->protocols_[ESPNOW_DEFAULT_APP_ID]; } -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::on_receive_(ESPNowPacket packet) { + if (this->protocols_[packet.app_id] == nullptr) { + ESP_LOGE(TAG, "Protocol for '%06x' is not registered", packet.app_id); + } else { + this->protocols_[packet.app_id]->on_receive(packet); } } -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; - } - } - } +void ESPNowComponent::on_sent_(ESPNowPacket packet, bool status) { + if (this->protocols_[packet.app_id] == nullptr) { + ESP_LOGE(TAG, "Protocol for '%06x' is not registered", packet.app_id); + } else { + this->protocols_[packet.app_id]->on_sent(packet, status); } +} - 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]); - } +void ESPNowComponent::on_new_peer_(ESPNowPacket packet) { + if (this->protocols_[packet.app_id] == nullptr) { + ESP_LOGE(TAG, "Protocol for '%06x' is not registered", packet.app_id); + } else { + this->protocols_[packet.app_id]->on_new_peer(packet); } } @@ -282,44 +237,151 @@ void ESPNowComponent::on_data_received(const esp_now_recv_info_t *recv_info, con void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data, int size) #endif { + ESPNowPacket packet; 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; + packet.broadcast = (*recv_info->des_addr == ESPNOW_BROADCAST_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 + packet.set_mac((espnow_addr_t *) addr); + packet.rssi = rx_ctrl->rssi; + memcpy((void *) &packet.content, (void *) data, size); + packet.size = size - 10; + packet.timestamp = rx_ctrl->timestamp; + ESP_LOGVV(TAG, "Read: %s |H:%02x%02x%02x A:%02x%02x%02x %02x T:%02x C:%02x%02x S:%02d", packet.to_str().c_str(), + packet.content[0], packet.content[1], packet.content[2], packet.content[3], packet.content[4], + packet.content[5], packet.content[6], packet.content[7], packet.content[8], packet.content[9], packet.size); - ESPNowPackage *package = new ESPNowPackage(addr_to_uint64(addr), data, size); - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 1) - package->is_broadcast(addr_to_uint64(recv_info->des_addr) == ESPNOW_BROADCAST_ADDR); -#endif - - package->rssi(rx_ctrl->rssi); - package->timestamp(rx_ctrl->timestamp); - global_esp_now->push_receive_package_(package); + if (packet.is_valid()) { + xQueueSendToBack(global_esp_now->receive_queue_, &packet, 10); + } else { + ESP_LOGE(TAG, "Invalid ESP-NOW packet received (CRC)"); + } } -void ESPNowComponent::on_data_send(const uint8_t *mac_addr, esp_now_send_status_t status) { - ESPNowPackage *package = global_esp_now->send_queue_.front(); +bool ESPNowComponent::write(ESPNowPacket packet) { + ESP_LOGVV(TAG, "Write: %s |H:%02x%02x%02x A:%02x%02x%02x %02x T:%02x C:%02x%02x S:%02d", packet.to_str().c_str(), + packet.content[0], packet.content[1], packet.content[2], packet.content[3], packet.content[4], + packet.content[5], packet.content[6], packet.content[7], packet.content[8], packet.content[9], packet.size); 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]); + packet.get_mac(&mac); + 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((uint8_t *) &mac)) { + ESP_LOGW(TAG, "Peer not registered: 0x%s.", packet.to_str().c_str()); + } else if (!packet.is_valid()) { + ESP_LOGW(TAG, "Packet is invalid. maybe you need to ::recalc(). the packat before writing."); + } else if (this->use_sent_check_) { + xQueueSendToBack(this->send_queue_, &packet, 10); + ESP_LOGVV(TAG, "Send (0x%04x.%d): 0x%s. Buffer Used: %d", packet.ref_id, packet.retrys, + format_hex(packet.mac64).c_str(), this->send_queue_used()); + return true; } else { - global_esp_now->on_package_send(package); - global_esp_now->send_queue_.pop(); + esp_err_t err = esp_now_send((uint8_t *) &mac, (uint8_t *) &packet.content, packet.size + 10); + ESP_LOGVV(TAG, "S: 0x%04x.%d B: %d%s.", packet.ref_id, packet.retrys, this->send_queue_used(), + (err == ESP_OK) ? "" : " FAILED"); + this->defer([this, packet, err]() { this->on_sent_(packet, err == ESP_OK); }); } - global_esp_now->can_send_ = true; + return false; +} + +void ESPNowComponent::loop() { + // runner(); +} + +void ESPNowComponent::runner() { + ESPNowPacket packet; + + for (;;) { + if (xQueueReceive(this->receive_queue_, &packet, (TickType_t) 1) == pdTRUE) { + espnow_addr_t mac; + packet.get_mac(&mac); + + if (!esp_now_is_peer_exist((uint8_t *) &mac)) { + if (this->auto_add_peer_) { + this->add_peer(packet.mac64); + } else { + this->defer([this, packet]() { this->on_new_peer_(packet); }); + continue; + } + } + if (esp_now_is_peer_exist((uint8_t *) &mac)) { + this->defer([this, packet]() { this->on_receive_(packet); }); + } else { + ESP_LOGE(TAG, "Peer not registered: %s", format_hex(packet.mac64).c_str()); + } + } + if (xQueueReceive(this->send_queue_, &packet, (TickType_t) 1) == pdTRUE) { + if (this->is_locked()) { + if (millis() - packet.timestamp > 1000) { + if (packet.retrys == 6) { + ESP_LOGW(TAG, "To many send retries. Packet dropped. 0x%04x", packet.ref_id); + this->unlock(); + continue; + } else { + ESP_LOGE(TAG, "TimeOut (0x%04x.%d).", packet.ref_id, packet.retrys); + packet.retry(); + } + this->unlock(); + } + } else { + packet.retry(); + if (packet.retrys == 6) { + ESP_LOGW(TAG, "To many send retries. Packet dropped. 0x%04x", packet.ref_id); + // continue; + return; + } else { + packet.timestamp = millis(); + this->lock(); + espnow_addr_t mac; + packet.get_mac(&mac); + + esp_err_t err = esp_now_send((uint8_t *) &mac, packet.data, packet.size + 10); + + if (err == ESP_OK) { + ESP_LOGV(TAG, "S: 0x%04x.%d. Wait for conformation. M: %s", packet.ref_id, packet.retrys, + packet.to_str().c_str()); + } else { + ESP_LOGE(TAG, "S: 0x%04x.%d B: %d.", packet.ref_id, packet.retrys, this->send_queue_used()); + this->unlock(); + } + } + } + xQueueSendToFront(this->send_queue_, &packet, 10 / portTICK_PERIOD_MS); + } + } +} + +void ESPNowComponent::on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) { + ESPNowPacket packet; + if (!global_esp_now->use_sent_check_) { + return; + } + uint64_t mac64 = packet.to_mac64((espnow_addr_t *) mac_addr); + if (xQueueReceive(global_esp_now->send_queue_, &packet, 10 / portTICK_PERIOD_MS) == pdTRUE) { + if (status != ESP_OK) { + ESP_LOGE(TAG, "sent packet failed (0x%04x.%d)", packet.ref_id, packet.retrys); + } else if (packet.mac64 != mac64) { + ESP_LOGE(TAG, " Invalid mac address. (0x%04x.%d) expected: %s got %s", packet.ref_id, packet.retrys, + packet.to_str().c_str(), packet.to_str(mac64)); + } else { + ESP_LOGV(TAG, "Confirm sent (0x%04x.%d)", packet.ref_id, packet.retrys); + global_esp_now->unlock(); + global_esp_now->defer([packet]() { global_esp_now->on_sent_(packet, true); }); + return; + } + global_esp_now->defer([packet]() { global_esp_now->on_sent_(packet, false); }); + xQueueSendToFront(global_esp_now->send_queue_, &packet, 10 / portTICK_PERIOD_MS); + } + global_esp_now->unlock(); } ESPNowComponent *global_esp_now = nullptr; diff --git a/esphome/components/espnow/espnow.h b/esphome/components/espnow/espnow.h index 795cae4c2a..8d23c36cfa 100644 --- a/esphome/components/espnow/espnow.h +++ b/esphome/components/espnow/espnow.h @@ -1,119 +1,79 @@ #pragma once -// #if defined(USE_ESP32) +#if defined(USE_ESP32) #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" - +#include "espnow_packet.h" #include #include #include #include #include +#include +#include namespace esphome { namespace espnow { -typedef uint8_t espnow_addr_t[6]; - -static const uint64_t ESPNOW_BROADCAST_ADDR = 0xFFFFFFFFFFFF; -static espnow_addr_t ESPNOW_ADDR_SELF = {0}; - -static void uint64_to_addr(uint64_t address, uint8_t *bd_addr) { - *(bd_addr + 0) = (address >> 40) & 0xff; - *(bd_addr + 1) = (address >> 32) & 0xff; - *(bd_addr + 2) = (address >> 24) & 0xff; - *(bd_addr + 3) = (address >> 16) & 0xff; - *(bd_addr + 4) = (address >> 8) & 0xff; - *(bd_addr + 5) = (address >> 0) & 0xff; -} - -static uint64_t addr_to_uint64(const uint8_t *address) { - uint64_t u = 0; - u |= uint64_t(*(address + 0) & 0xFF) << 40; - u |= uint64_t(*(address + 1) & 0xFF) << 32; - u |= uint64_t(*(address + 2) & 0xFF) << 24; - u |= uint64_t(*(address + 3) & 0xFF) << 16; - u |= uint64_t(*(address + 4) & 0xFF) << 8; - u |= uint64_t(*(address + 5) & 0xFF) << 0; - return u; -} - class ESPNowComponent; -template > class iterable_queue : public std::queue { +static const uint32_t ESPNOW_DEFAULT_APP_ID = 0x11CFAF; + +class ESPNowProtocol : public Component, public Parented { public: - typedef typename Container::iterator iterator; - typedef typename Container::const_iterator const_iterator; - - iterator begin() { return this->c.begin(); } - iterator end() { return this->c.end(); } - const_iterator begin() const { return this->c.begin(); } - const_iterator end() const { return this->c.end(); } -}; - -class ESPNowPackage { - public: - ESPNowPackage(const uint64_t mac_address, const std::vector data); - ESPNowPackage(const uint64_t mac_address, const uint8_t *data, size_t len); - - uint64_t mac_address() { return this->mac_address_ == 0 ? ESPNOW_BROADCAST_ADDR : this->mac_address_; } - - void mac_bytes(uint8_t *mac_addres) { - uint64_t mac = this->mac_address_ == 0 ? ESPNOW_BROADCAST_ADDR : this->mac_address_; - uint64_to_addr(mac, mac_addres); - } - - std::vector data() { return data_; } - - uint8_t get_counter() { return send_count_; } - void inc_counter() { - send_count_ = send_count_ + 1; - if (send_count_ > 5 && !is_holded_) { - set_holding(); - } - } - void reset_counter() { - send_count_ = 0; - del_holding(); - } - - void is_broadcast(bool value) { this->is_broadcast_ = value; } - bool is_broadcast() const { return this->is_broadcast_; } - - void timestamp(uint32_t value) { this->timestamp_ = value; } - uint32_t timestamp() { return this->timestamp_; } - - void rssi(int8_t rssi) { this->rssi_ = rssi; } - int8_t rssi() { return this->rssi_; } - - bool is_holded() { return this->is_holded_; } - void set_holding() { this->is_holded_ = true; } - void del_holding() { this->is_holded_ = false; } - - protected: - uint64_t mac_address_{0}; - std::vector data_; - - uint8_t send_count_{0}; - bool is_broadcast_{false}; - uint32_t timestamp_{0}; - uint8_t rssi_{0}; - - bool is_holded_{false}; -}; - -class ESPNowInterface : public Component, public Parented { - public: - ESPNowInterface(){}; + ESPNowProtocol(){}; 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; }; + virtual void on_receive(ESPNowPacket packet) { return; }; + virtual void on_sent(ESPNowPacket packet, bool status) { return; }; + virtual void on_new_peer(ESPNowPacket packet) { return; }; + + virtual uint32_t get_app_id() = 0; + uint8_t get_next_ref_id() { return next_ref_id_++; } + + bool write(const uint64_t mac_address, const uint8_t *data, uint8_t len); + bool write(const uint64_t mac_address, const std::vector data); + bool write(ESPNowPacket packet); + + protected: + uint8_t next_ref_id_{0}; +}; + +class ESPNowDefaultProtocol : public ESPNowProtocol { + public: + bool on_receive(ESPNowPacket packet) { + this->on_receive_.call(packet); + return true; + }; + bool on_sent(ESPNowPacket packet, bool status) { + this->on_sent_.call(packet, status); + return true; + }; + bool on_new_peer(ESPNowPacket packet) { + this->on_new_peer_.call(packet); + return true; + }; + + uint32_t get_app_id() override { return ESPNOW_DEFAULT_APP_ID; }; + + void add_on_sent_callback(std::function &&callback) { + this->on_sent_.add(std::move(callback)); + } + void add_on_receive_callback(std::function &&callback) { + this->on_receive_.add(std::move(callback)); + } + void add_on_peer_callback(std::function &&callback) { + this->on_new_peer_.add(std::move(callback)); + } + + protected: + CallbackManager on_sent_; + CallbackManager on_receive_; + CallbackManager on_new_peer_; }; class ESPNowComponent : public Component { @@ -126,77 +86,62 @@ class ESPNowComponent : public Component { 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); + static void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status); void dump_config() override; + float get_setup_priority() const override { return -100; } + 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 setup() override; + void runner(); 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) { - // ESPNowPackage * package = new ESPNowPackage(mac_address, data, len); - // return this->send_package(package); - return nullptr; - } + bool write(ESPNowPacket packet); - ESPNowPackage *send_package(const uint64_t mac_address, const std::vector data) { - ESPNowPackage *package = new ESPNowPackage(mac_address, data); - return this->send_package(package); - } - - ESPNowPackage *send_package(ESPNowPackage *package); - - void add_on_package_send_callback(std::function &&callback) { - this->on_package_send_.add(std::move(callback)); - } - - void add_on_package_receive_callback(std::function &&callback) { - this->on_package_receved_.add(std::move(callback)); - } - - void add_on_peer_callback(std::function &&callback) { - this->on_new_peer_.add(std::move(callback)); - } - - void register_protocol(ESPNowInterface *protocol) { + void register_protocol(ESPNowProtocol *protocol) { protocol->set_parent(this); - this->protocols_.push_back(protocol); + this->protocols_[protocol->get_app_id()] = std::move(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; } + bool send_queue_empty() { return uxQueueMessagesWaiting(this->send_queue_) == 0; } + bool send_queue_full() { return uxQueueSpacesAvailable(this->send_queue_) == 0; } + size_t send_queue_used() { return uxQueueMessagesWaiting(this->send_queue_); } + size_t send_queue_free() { return uxQueueSpacesAvailable(this->send_queue_); } - void on_package_received(ESPNowPackage *package); - void on_package_send(ESPNowPackage *package); - void on_new_peer(ESPNowPackage *package); + void lock() { this->lock_ = true; } + bool is_locked() { return this->lock_; } + void unlock() { this->lock_ = false; } - void log_error_(std::string msg, esp_err_t err); + ESPNowDefaultProtocol *get_defaultProtocol_(); 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}; + bool use_sent_check_{true}; + bool lock_{false}; - CallbackManager on_package_send_; - CallbackManager on_package_receved_; - CallbackManager on_new_peer_; + void on_receive_(ESPNowPacket packet); + void on_sent_(ESPNowPacket packet, bool status); + void on_new_peer_(ESPNowPacket packet); - iterable_queue receive_queue_{}; - iterable_queue send_queue_{}; + QueueHandle_t receive_queue_{}; + QueueHandle_t send_queue_{}; - std::vector protocols_{}; + std::map protocols_{}; std::vector peers_{}; - SemaphoreHandle_t send_lock_ = NULL; - - bool can_send_{true}; + TaskHandle_t espnow_task_handle_{nullptr}; }; template class SendAction : public Action, public Parented { @@ -215,10 +160,10 @@ template class SendAction : public Action, public Parente auto mac = this->mac_.value(x...); if (this->static_) { - this->parent_->send_package(mac, this->data_static_); + this->parent_->get_defaultProtocol_()->write(mac, this->data_static_); } else { auto val = this->data_func_(x...); - this->parent_->send_package(mac, val); + this->parent_->get_defaultProtocol_()->write(mac, val); } } @@ -253,24 +198,25 @@ template class DelPeerAction : public Action, public Pare TemplatableValue mac_{}; }; -class ESPNowSendTrigger : public Trigger { +class ESPNowSentTrigger : public Trigger { public: - explicit ESPNowSendTrigger(ESPNowComponent *parent) { - parent->add_on_package_send_callback([this](ESPNowPackage *value) { this->trigger(value); }); + explicit ESPNowSentTrigger(ESPNowComponent *parent) { + parent->get_defaultProtocol_()->add_on_sent_callback( + [this](ESPNowPacket value, bool status) { this->trigger(value, status); }); } }; -class ESPNowReceiveTrigger : public Trigger { +class ESPNowReceiveTrigger : public Trigger { public: explicit ESPNowReceiveTrigger(ESPNowComponent *parent) { - parent->add_on_package_receive_callback([this](ESPNowPackage *value) { this->trigger(value); }); + parent->get_defaultProtocol_()->add_on_receive_callback([this](ESPNowPacket value) { this->trigger(value); }); } }; -class ESPNowNewPeerTrigger : public Trigger { +class ESPNowNewPeerTrigger : public Trigger { public: explicit ESPNowNewPeerTrigger(ESPNowComponent *parent) { - parent->add_on_peer_callback([this](ESPNowPackage *value) { this->trigger(value); }); + parent->get_defaultProtocol_()->add_on_peer_callback([this](ESPNowPacket value) { this->trigger(value); }); } }; @@ -279,4 +225,4 @@ extern ESPNowComponent *global_esp_now; } // namespace espnow } // namespace esphome -// #endif +#endif diff --git a/esphome/components/espnow/espnow_packet.h b/esphome/components/espnow/espnow_packet.h new file mode 100644 index 0000000000..88a3cd415c --- /dev/null +++ b/esphome/components/espnow/espnow_packet.h @@ -0,0 +1,138 @@ +#pragma once + +// #if defined(USE_ESP32) + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include "esp_crc.h" + +#include +#include +#include +#include + +namespace esphome { +namespace espnow { + +typedef uint8_t espnow_addr_t[6]; + +static const uint64_t ESPNOW_BROADCAST_ADDR = 0xFFFFFFFFFFFF; +static espnow_addr_t ESPNOW_ADDR_SELF = {0}; +static const uint8_t MAX_ESPNOW_DATA_SIZE = 240; +#define TRANSPORT_HEADER_SIZE 3 + +static const uint8_t transport_header[TRANSPORT_HEADER_SIZE] = {0xC1, 0x99, 0x83}; + +template std::string string_format(const std::string &format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + return ("Error during formatting."); + } + auto size = static_cast(size_s); + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} + +static uint8_t last_ref_id = 0; + +struct ESPNowPacket { + uint64_t mac64 = 0; + uint8_t size = 0; + uint8_t rssi = 0; + uint8_t retrys : 4; + uint8_t is_broadcast : 1; + uint8_t dummy : 3; + uint32_t timestamp = 0; + + union { + uint8_t content[MAX_ESPNOW_DATA_SIZE + 11]; + struct { + uint8_t header[3] = {0xC1, 0x99, 0x83}; + uint32_t app_id = 0xFFFFFF; + uint1_t ref_id = 0x99; + uint8_t crc16 = 0x1234; + uint8_t data[MAX_ESPNOW_DATA_SIZE]; + uint8_t space = 0; + } __attribute__((packed)); + }; + + inline ESPNowPacket() ESPHOME_ALWAYS_INLINE : retrys(0) {} + + inline void info(std::string place) { + ESP_LOGVV("Packet", "%s: M:%s A:0x%06x R:0x%02x C:0x%04x S:%02x", place.c_str(), this->to_str().c_str(), + this->app_id, this->ref_id, this->random, this->size); + } + + inline ESPNowPacket(const uint64_t mac64_, const uint8_t *data_, uint8_t size_, + uint32_t app_id_) ESPHOME_ALWAYS_INLINE : mac64(mac64_), + size(size_), + app_id(app_id_), + retrys(0) { + if (mac64_ == 0) + this->mac64 = ESPNOW_BROADCAST_ADDR; + this->is_broadcast = this->mac64 == ESPNOW_BROADCAST_ADDR; + + this->ref_id = 0; + + this->size = std::min(MAX_ESPNOW_DATA_SIZE, size_); + std::memcpy(&data, (uint8_t *) data_, this->size); + + this->data[this->size + 1] = 0; + this->recalc(); + this->info("create"); + } + + inline void get_mac(espnow_addr_t *mac_addres) { std::memcpy(mac_addres, &mac64, 6); } + inline void set_mac(espnow_addr_t *mac_addres) { this->mac64 = this->to_mac64(mac_addres); } + + uint64_t to_mac64(espnow_addr_t *mac_addres) { + uint64_t result; + std::memcpy(&result, mac_addres, 6); + return result; + } + + void retry() { + if (this->retrys < 7) { + retrys = retrys + 1; + } + } + + inline void recalc() { + random = 0; + random = esp_crc16_le(ref_id, (uint8_t *) &content, 10 + size); + } + + bool is_valid() { + uint16_t crc = random; + recalc(); + bool valid = (std::memcmp(&header, &transport_header, 3) == 0); + valid &= (this->app_id != 0); + valid &= (this->random == crc); + if (!valid) { + ESP_LOGV("Packet", "Invalid H:%02x%02x%02x A:%06x R:%02x C:%04x ipv. %04x, %d&%d&%d=%d\n", this->header[0], + this->header[1], this->header[2], this->app_id, this->ref_id, crc, this->random, + std::memcmp(&header, &transport_header, 3) == 0, (this->app_id != 0), (this->random == crc), valid); + } + + this->random = crc; + return valid; + } + + inline std::string to_str(uint64_t mac64_ = 0) { + espnow_addr_t mac; + if (mac64_ == 0) + mac64_ = this->mac64; + memcpy((void *) &mac, &mac64_, 6); + return string_format("{\"%02x:%02x:%02x:%02x:%02x:%02x\"}", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + + inline uint8_t *dataptr() { return (uint8_t *) &content; } +}; + +} // namespace espnow +} // namespace esphome