diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index ed348106dc..a11e09c312 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -1,5 +1,6 @@ from esphome import automation, core import esphome.codegen as cg +from esphome.components.globals import GlobalsComponent import esphome.config_validation as cv from esphome.const import ( CONF_COMMAND, @@ -7,7 +8,9 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_PAYLOAD, CONF_TRIGGER_ID, + CONF_WIFI, ) +import esphome.final_validate as fv CODEOWNERS = ["@nielsnl68", "@jesserockz"] @@ -18,7 +21,7 @@ ESPNowProtocol = espnow_ns.class_("ESPNowProtocol") ESPNowListener = espnow_ns.class_("ESPNowListener") ESPNowPacket = espnow_ns.class_("ESPNowPacket") -ESPNowPeer = cg.uint64 +ESPNowPeer = GlobalsComponent ESPNowPacketConst = ESPNowPacket.operator("const") @@ -41,7 +44,7 @@ SendAction = espnow_ns.class_("SendAction", automation.Action) NewPeerAction = espnow_ns.class_("NewPeerAction", automation.Action) DelPeerAction = espnow_ns.class_("DelPeerAction", automation.Action) SetKeeperAction = espnow_ns.class_("SetKeeperAction", automation.Action) -SetStaticPeerAction = espnow_ns.class_("SetStaticPeerAction", automation.Action) +SetChannelAction = espnow_ns.class_("SetChannelAction", automation.Action) CONF_AUTO_ADD_PEER = "auto_add_peer" CONF_CONFORMATION_TIMEOUT = "conformation_timeout" @@ -59,6 +62,7 @@ CONF_WIFI_CHANNEL = "wifi_channel" CONF_PROTOCOL_MODE = "protocol_mode" validate_command = cv.Range(min=1, max=250) +validate_channel = cv.int_range(1, 14) ESPNowProtocolMode = espnow_ns.enum("ESPNowProtocolMode") ENUM_MODE = { @@ -86,9 +90,8 @@ def espnow_hex(mac_address): DEFINE_PEER_CONFIG = cv.maybe_simple_value( { - cv.Optional(CONF_PEER_ID): cv.declare_id(ESPNowPeer), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_WIFI_CHANNEL): cv.int_range(0, 14), + cv.Optional(CONF_WIFI_CHANNEL): validate_channel, }, key=CONF_MAC_ADDRESS, ) @@ -97,7 +100,7 @@ DEFINE_PEER_CONFIG = cv.maybe_simple_value( CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESPNowComponent), - cv.Optional(CONF_WIFI_CHANNEL): cv.int_range(0, 14), + cv.Optional(CONF_WIFI_CHANNEL): validate_channel, cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean, cv.Optional(CONF_USE_SENT_CHECK, default=True): cv.boolean, cv.Optional( @@ -211,6 +214,25 @@ async def register_protocol(var, config): cg.add(var.set_protocol_mode(config[CONF_PROTOCOL_MODE])) +def _final_validate(config): + full_config = fv.full_config.get() + if CONF_WIFI_CHANNEL in config and CONF_WIFI in full_config: + raise cv.Invalid( + f"When you have {CONF_WIFI} configured, You can not set the {CONF_WIFI_CHANNEL} variable." + ) + if CONF_WIFI_CHANNEL not in config and CONF_WIFI not in full_config: + raise cv.Invalid( + f"When you don't use the {CONF_WIFI} component, You need to set the {CONF_WIFI_CHANNEL} variable." + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + +# ========================================== A C T I O N S ================================================ + + def validate_peer(value): if isinstance(value, cv.Lambda): return cv.returning_lambda(value) @@ -282,7 +304,7 @@ async def send_action(config, action_id, template_arg, args): { cv.GenerateID(): cv.use_id(ESPNowComponent), cv.Required(CONF_MAC_ADDRESS): validate_peer, - cv.Optional(CONF_WIFI_CHANNEL): cv.int_range(0, 14), + cv.Optional(CONF_WIFI_CHANNEL): cv.templatable(validate_channel), }, key=CONF_MAC_ADDRESS, ), @@ -298,26 +320,32 @@ async def send_action(config, action_id, template_arg, args): key=CONF_MAC_ADDRESS, ), ) -@automation.register_action( - "espnow.static.peer", - SetStaticPeerAction, - cv.Schema( - { - cv.GenerateID(): cv.use_id(ESPNowComponent), - cv.Required(CONF_PEER_ID): cv.use_id(ESPNowPeer), - cv.Required(CONF_MAC_ADDRESS): validate_peer, - } - ), -) async def peer_action(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - if peer_id := config.get(CONF_PEER_ID): - peer = await cg.get_variable(peer_id) - cg.add(var.set_peer_id(peer)) if CONF_WIFI_CHANNEL in config: - cg.add(var.set_wifi_channel(config[CONF_WIFI_CHANNEL])) + template_ = await cg.templatable(config[CONF_WIFI_CHANNEL], args, cg.uint8) + cg.add(var.set_wifi_channel(template_)) await register_peer(var, config, args) return var + + +@automation.register_action( + "espnow.channel.set", + SetChannelAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(ESPNowComponent), + cv.Required(CONF_WIFI_CHANNEL): cv.templatable(validate_channel), + }, + key=CONF_WIFI_CHANNEL, + ), +) +async def channel_action(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_WIFI_CHANNEL], args, cg.uint8) + cg.add(var.set_channel(template_)) + return var diff --git a/esphome/components/espnow/espnow.cpp b/esphome/components/espnow/espnow.cpp index 2c33aec079..17cb4d5aa0 100644 --- a/esphome/components/espnow/espnow.cpp +++ b/esphome/components/espnow/espnow.cpp @@ -266,8 +266,8 @@ void ESPNowComponent::set_wifi_channel(uint8_t 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) { @@ -276,6 +276,11 @@ esp_err_t ESPNowComponent::add_peer(uint64_t peer, int8_t channel) { esp_now_peer_info_t peer_info = {}; if (this->is_ready()) { + if (peer == this->own_peer_address_) { + ESP_LOGE(TAG, "Tried to peer your self."); + this->mark_failed(); + return ESP_ERR_INVALID_MAC; + } if (esp_now_is_peer_exist((uint8_t *) &peer)) { esp_now_get_peer((const uint8_t *) &peer, &peer_info); old_channel = peer_info.channel; @@ -444,8 +449,7 @@ void ESPNowComponent::handle_internal_commands(ESPNowPacket packet) { 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); + ESP_LOGI(TAG, "The channel for peer %s is changed to: %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()); @@ -453,7 +457,9 @@ void ESPNowComponent::handle_internal_commands(ESPNowPacket packet) { } bool ESPNowComponent::send(ESPNowPacket packet) { - if (!this->is_ready()) { + if (packet.peer == this->own_peer_address_) { + ESP_LOGE(TAG, "Tried to peer your self."); + } else 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"); @@ -482,7 +488,7 @@ bool ESPNowComponent::send(ESPNowPacket packet) { this->call_on_sent_(packet, err == ESP_OK); return true; } - + this->mark_failed(); return false; } @@ -518,7 +524,7 @@ 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"; +const char *const SetChannel::TAG = "espnow.changechannel"; } // namespace espnow } // namespace esphome diff --git a/esphome/components/espnow/espnow.h b/esphome/components/espnow/espnow.h index c4ccdae689..1a9f6ccf93 100644 --- a/esphome/components/espnow/espnow.h +++ b/esphome/components/espnow/espnow.h @@ -340,33 +340,18 @@ template class DelPeerAction : public Action, public Pare } }; -template class SetStaticPeerAction : public Action, public Parented { - public: - TEMPLATABLE_VALUE(uint64_t, mac_address); - void set_peer_id(uint64_t &peer_id) { this->peer_id_ = &peer_id; } - void play(Ts... x) override { - uint64_t mac_address = this->mac_address_.value(x...); - *(this->peer_id_) = mac_address; - if (mac_address != 0) - parent_->add_peer(mac_address); - } - - protected: - uint64_t *peer_id_; -}; - -class ChangeChannel { +class SetChannel { public: // could be made inline with C++17 static const char *const TAG; }; -template class ChangeChannelAction : public Action, public Parented { +template class SetChannelAction : 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."); + esph_log_e(SetChannel::TAG, "Manual changing the channel is not possible with WIFI enabled."); #else int8_t value = this->channel_.value(x...); parent_->set_wifi_channel(value); @@ -374,7 +359,7 @@ template class ChangeChannelAction : public Action, publi } }; -/********************************* triggers **************************************/ +/********************************* triggers **************************************/ class ESPNowSentTrigger : public Trigger { public: explicit ESPNowSentTrigger(ESPNowComponent *parent) { diff --git a/esphome/components/espnow/test1.yaml b/esphome/components/espnow/test1.yaml index 0bd5016eff..026699aba2 100644 --- a/esphome/components/espnow/test1.yaml +++ b/esphome/components/espnow/test1.yaml @@ -12,29 +12,19 @@ esp32: esphome: name: "${name}" - # Friendly names are used where appropriate in Home Assistant - friendly_name: "${friendly_name}" - # Automatically add the mac address to the name - # so you can use a single firmware for all devices - name_add_mac_suffix: false - - # This will allow for (future) project identification, - # configuration and updates. - project: - name: LumenSoft.espnow-test - version: "1.0" # To be able to get logs from the device via serial and api. logger: - level: verbose + level: debug espnow: auto_add_peer: true + wifi_channel: 1 predefined_peers: - - mac_address: 11:22:33:44:55:66 + - mac_address: E8:6B:EA:23:CD:98 on_receive: - logger.log: - format: "Received: '%s' from '%s' command: %d RSSI: %d" + format: "Received from: %s = '%s' cmd: %d RSSI: %d" args: [ packet.get_payload(), @@ -43,15 +33,11 @@ espnow: packet.rssi, ] - # this works only when esp_idf v5.1.5+ is being used. - on_broadcast: - - command: 123 - then: - - logger.log: - format: "Broadcast from: '%s' RSSI: %d: %s" - args: - [ - packet.get_peer_code().c_str(), - packet.rssi, - packet.get_payload(), - ] +interval: + - interval: 10sec + startup_delay: 20sec + then: + - espnow.send: + mac_address: E8:6B:EA:23:CD:98 + payload: "Test 1." + command: 222 diff --git a/esphome/components/espnow/test2.yaml b/esphome/components/espnow/test2.yaml index e0be87cdc3..2139eadabf 100644 --- a/esphome/components/espnow/test2.yaml +++ b/esphome/components/espnow/test2.yaml @@ -26,23 +26,22 @@ esphome: # To be able to get logs from the device via serial and api. logger: - level: verbose + level: debug globals: - id: hub_address type: uint64_t - initial_value: "0xE86BEA23CD98" + initial_value: "0x0" restore_value: yes espnow: auto_add_peer: true + wifi_channel: 1 predefined_peers: - - peer_id: keeper - mac_address: E8:6B:EA:23:CD:98 - + - mac_address: e8:6b:ea:24:22:04 on_receive: - logger.log: - format: "Received: '%s' from '%s' command: %d RSSI: %d" + format: "Received from: %s = '%s' cmd: %d RSSI: %d" args: [ packet.get_payload(), @@ -51,52 +50,15 @@ espnow: 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, - packet.get_payload(), - ] - interval: - interval: 30sec - startup_delay: 20sec + startup_delay: 10sec then: - - espnow.broadcast: - payload: "Broadcast message" - command: 123 + - espnow.channel.set: 5 - interval: 5sec + startup_delay: 10sec then: - espnow.send: - mac_address: keeper - payload: "Used static keeper value" - command: 222 - - espnow.send: - mac_address: E8:6B:EA:23:CD:98 - payload: "used fixed mac address" + mac_address: e8:6b:ea:24:22:04 + payload: "Test 2." command: 123 - - espnow.send: - # dynamic peer address - mac_address: !lambda return keeper; - payload: "use keeper dynamicly " - command: 62 - - espnow.send: - mac_address: !lambda return id(hub_address); - payload: "Using a global numberic value dynamicly" - command: 132 - -binary_sensor: - - platform: gpio - pin: GPIO39 - name: Button - on_click: - - espnow.static.peer: - peer_id: keeper - mac_address: 80:6B:EA:23:CD:87 - - espnow.peer.add: 80:6B:EA:23:AA:BB - - espnow.peer.del: 80:6B:EA:23:AA:BB diff --git a/tests/components/espnow/common.yaml b/tests/components/espnow/common.yaml index eb9753ca1f..b3341ffa35 100644 --- a/tests/components/espnow/common.yaml +++ b/tests/components/espnow/common.yaml @@ -1,5 +1,5 @@ globals: - - id: hub_address + - id: keeper type: uint64_t initial_value: "0xE86BEA23CD98" restore_value: yes @@ -8,8 +8,7 @@ espnow: auto_add_peer: true predefined_peers: - FF:FF:FF:FF:FF:FF - - peer_id: keeper - mac_address: 11:22:33:44:55:66 + - mac_address: 11:22:33:44:55:66 wifi_channel: 2 on_receive: - logger.log: @@ -40,21 +39,13 @@ interval: - espnow.broadcast: payload: "hallo everyone" command: 123 - - espnow.send: - mac_address: keeper - payload: "hallo everyone" - command: 230 - espnow.send: mac_address: 44:55:66:77:88:33 payload: "hallo everyone" command: 230 + - espnow.send: - # dynamic peer address - mac_address: !lambda return keeper; - payload: "use keeper dynamicly " - command: 62 - - espnow.send: - mac_address: !lambda return id(hub_address); + mac_address: !lambda return id(keeper); payload: "Using a global numberic value dynamicly" command: 132