diff --git a/esphome/components/espnow/espnow.cpp b/esphome/components/espnow/espnow.cpp index 804d43fc54..2f16efe6de 100644 --- a/esphome/components/espnow/espnow.cpp +++ b/esphome/components/espnow/espnow.cpp @@ -41,12 +41,51 @@ std::string peer_str(const uint64_t peer) { return "[Not Set]"; if (peer == ESPNOW_BROADCAST_ADDR) return "[Broadcast]"; + if (peer == ESPNOW_MASS_SEND_ADDR) + return "[Mass Send]"; uint8_t *peer_ = (uint8_t *) &peer; snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", peer_[0], peer_[1], peer_[2], peer_[3], peer_[4], peer_[5]); return mac; } +/* ESPNowPacket ********************************************************************** */ + +inline ESPNowPacket::ESPNowPacket(uint64_t peer, const uint8_t *data, uint8_t size, uint32_t protocol, + uint8_t command) { + if (size > MAX_ESPNOW_DATA_SIZE) { + ESP_LOGE(TAG, "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(TAG, "No Peer defined."); + return; + } + + this->peer = peer; + this->is_broadcast = (peer == ESPNOW_BROADCAST_ADDR); + + this->set_protocol(protocol); + if (command != 0) { + this->set_command(command); + } + this->size = size; + std::memcpy(this->get_payload(), data, size); +} + +inline ESPNowPacket::ESPNowPacket(const uint8_t *peer, const uint8_t *data, uint8_t size) { + if (size > MAX_ESPNOW_DATA_SIZE + this->prefix_size()) { + ESP_LOGE(TAG, "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); +} + /* ESPNowComponent ********************************************************************** */ ESPNowComponent::ESPNowComponent() { ESPNowComponent::static_ = this; } // NOLINT @@ -94,7 +133,8 @@ void ESPNowComponent::setup() { esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE); esp_wifi_set_promiscuous(false); #else - // this->wifi_channel_ = wifi::global_wifi_component-> + if (wifi::global_wifi_component != nullptr) + this->wifi_channel_ = wifi::global_wifi_component->get_wifi_channel(); #endif esp_err_t err = esp_now_init(); @@ -124,8 +164,8 @@ void ESPNowComponent::setup() { esp_wifi_get_mac(WIFI_IF_STA, (uint8_t *) &this->own_peer_address_); - for (auto id : this->peers_) { - this->add_peer(id & 0x00FFFFFFFFFFFF, (int8_t) id >> (64 - 8)); + for (const auto &kv : this->peers_) { + this->add_peer(kv.first, kv.second.channel); } this->send_queue_ = xQueueCreate(SEND_BUFFER_SIZE, sizeof(ESPNowPacket)); @@ -148,6 +188,22 @@ void ESPNowComponent::loop() { xTaskCreate(espnow_task, "espnow_task", 4096, this, tskIDLE_PRIORITY + 1, nullptr); this->task_running_ = true; } +#ifdef USE_WIFI + int32_t new_channel = wifi::global_wifi_component->get_wifi_channel(); + if (new_channel != this->wifi_channel_) { + this->defer([this, new_channel]() { this->change_channel(new_channel); }); + } +#endif +} + +bool ESPNowComponent::can_proceed() { +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->is_ready(); + return false; +#else + return true; +#endif } void ESPNowComponent::espnow_task(void *param) { @@ -206,10 +262,12 @@ void ESPNowComponent::espnow_task(void *param) { } void ESPNowComponent::set_wifi_channel(uint8_t channel) { - if (this->is_ready() && (this->wifi_channel_ == channel)) { + if (this->is_ready() && (this->wifi_channel_ != channel)) { ESPNowPacket packet(ESPNOW_MASS_SEND_ADDR, &channel, 1, ESPNOW_MAIN_PROTOCOL_ID, 251); + this->send(packet); + ESP_LOGD(TAG, "Wifi Channel is changed from %d to %d.", this->wifi_channel_, channel); + this->wifi_channel_ = channel; } - this->wifi_channel_ = channel; } esp_err_t ESPNowComponent::add_peer(uint64_t peer, int8_t channel) { @@ -217,10 +275,7 @@ esp_err_t ESPNowComponent::add_peer(uint64_t peer, int8_t channel) { int8_t old_channel = this->wifi_channel_; esp_now_peer_info_t peer_info = {}; - if (!this->is_ready()) { - this->peers_.push_back((peer & 0x00FFFFFFFFFFFF) + (channel << (64 - 8))); - return result; - } else { + if (this->is_ready()) { if (esp_now_is_peer_exist((uint8_t *) &peer)) { esp_now_get_peer((const uint8_t *) &peer, &peer_info); old_channel = peer_info.channel; @@ -232,13 +287,20 @@ esp_err_t ESPNowComponent::add_peer(uint64_t peer, int8_t channel) { memset(&peer_info, 0, sizeof(esp_now_peer_info_t)); peer_info.channel = (channel = -1) ? old_channel : channel; peer_info.encrypt = false; + peer_info.ifidx = WIFI_IF_STA; memcpy((void *) peer_info.peer_addr, (void *) &peer, 6); esp_err_t result = esp_now_add_peer(&peer_info); if (result == ESP_OK) { this->call_on_add_peer_(peer); } - return result; } + if (result == ESP_OK) { + ESPNowPeer info; + info.channel = channel; + this->peers_[peer] = info; + } + + return result; } esp_err_t ESPNowComponent::del_peer(uint64_t peer) { @@ -365,12 +427,31 @@ void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data, show_packet("Receive", packet); if (packet.is_valid()) { - xQueueSendToBack(ESPNowComponent::static_->receive_queue_, (void *) &packet, 10); + if (packet.get_protocol() == ESPNOW_MAIN_PROTOCOL_ID && packet.get_command() > 250) { + ESPNowComponent *that = ESPNowComponent::static_; + that->defer([that, packet]() { that->handle_internal_commands(packet); }); + } else { + xQueueSendToBack(ESPNowComponent::static_->receive_queue_, (void *) &packet, 10); + } } else { ESP_LOGE(TAG, "Invalid ESP-NOW packet received."); } } +void ESPNowComponent::handle_internal_commands(ESPNowPacket packet) { + int8_t channel; + switch (packet.get_command()) { + case 251: + channel = (int8_t) *packet.get_payload(); + this->add_peer(packet.peer, channel); + ESP_LOGI(TAG, "The channel for peer %s. is changed toCommand not used: %d.", packet.get_peer_code().c_str(), + channel); + break; + default: + ESP_LOGE(TAG, "Invalid internal ESP-NOW packet. Command not used: %d.", packet.get_command()); + } +} + bool ESPNowComponent::send(ESPNowPacket packet) { if (!this->is_ready()) { ESP_LOGE(TAG, "Cannot send espnow packet, espnow is not setup yet."); @@ -437,6 +518,8 @@ bool ESPNowProtocol::send(uint64_t peer, const uint8_t *data, uint8_t len, uint8 return this->parent_->send(packet); } +const char *const ChangeChannel::TAG = "espnow.changechannel"; + } // namespace espnow } // namespace esphome diff --git a/esphome/components/espnow/espnow.h b/esphome/components/espnow/espnow.h index e5eb0addac..52d377b85a 100644 --- a/esphome/components/espnow/espnow.h +++ b/esphome/components/espnow/espnow.h @@ -46,6 +46,10 @@ std::string peer_str(const uint64_t peer); void show_packet(const std::string &title, const ESPNowPacket &packet); +struct ESPNowPeer { + int8_t channel{-1}; +}; + struct ESPNowPacket { uint64_t peer{0}; uint8_t rssi{0}; @@ -66,40 +70,8 @@ struct ESPNowPacket { // Create packet to be send. inline ESPNowPacket(uint64_t peer, const uint8_t *data, uint8_t size, uint32_t protocol, - uint8_t command = 0) ESPHOME_ALWAYS_INLINE { - 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); // nolint - 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) { - this->set_command(command); - } - this->size = size; - 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); // nolint - return; - } - - this->set_peer(peer); - - this->size = size - this->prefix_size(); - std::memcpy(this->get_content(), data, size); - } + uint8_t command = 0) ESPHOME_ALWAYS_INLINE; + inline ESPNowPacket(const uint8_t *peer, const uint8_t *data, uint8_t size) ESPHOME_ALWAYS_INLINE; uint8_t prefix_size() const { return sizeof(this->content.prefix); } @@ -268,6 +240,7 @@ class ESPNowComponent : public Component { void setup() override; void loop() override; + bool can_proceed() override; bool is_paired(uint64_t to_peer); @@ -295,6 +268,8 @@ class ESPNowComponent : public Component { static void espnow_task(void *params); + void handle_internal_commands(ESPNowPacket packet); + protected: bool validate_channel_(uint8_t channel); ESPNowProtocol *get_protocol_(uint32_t protocol); @@ -321,7 +296,8 @@ class ESPNowComponent : public Component { QueueHandle_t send_queue_{}; std::map protocols_{}; - std::vector peers_{}; + std::map peers_{}; + bool task_running_{false}; static ESPNowComponent *static_; // NOLINT }; @@ -379,6 +355,25 @@ template class SetStaticPeerAction : public Action, publi uint64_t *peer_id_; }; +class ChangeChannel { + public: + // could be made inline with C++17 + static const char *const TAG; +}; + +template class ChangeChannelAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(int8_t, channel); + void play(Ts... x) override { +#ifdef USE_WIFI + esph_log_e(ChangeChannel::TAG, "Manual changing the channel is not possible with WIFI enabled."); +#else + int8_t value = this->channel_.value(x...); + parent_->set_wifi_channel(value); +#endif + } +}; + /********************************* triggers **************************************/ class ESPNowSentTrigger : public Trigger { public: