#ESP-NOW protocol/communication for ESPHome.

791 messages Β· Page 1 of 1 (latest)

grand sapphire
#

This is mend to continue conversations and test reports about the integration work for Espressif's ESP-NOW communication protocol into ESPHome.

This espnow component is a small layer above the ESP-NOW protocol that take a small part of the packet validation away from the user to make sure that the packet is coming from a know device and how it should handle the incoming data.

To make thinks easier to implement different protocols above the ESP-NOW protocol. Per packet you can define an app_id and a command code.

My vision about this is that the app_id is a 2 byte predefined code that explains what the app/protocol will do. And the command code can define different actions/command/measuring types etc. depending on the app_id.

For extended needs you also can use it to send and receive raw packet's without packet validation.

Checkout my PR draft at: https://github.com/esphome/esphome/pull/7141

Current espnow setup options: #1329034880004395009 message

Current available actions: #1329034880004395009 message

Current packet access methods: #1329034880004395009 message

Current packet header information: #1329034880004395009 message

Current EspNowApp class methods: #1329034880004395009 message

grand sapphire
#

Current predefined app_id's:

  • 5374 [St]: Standard protocol, used to simple tests. No predefined commands exists.
    can arm other device using the same app_id.
  • 4330 [C0] .. 4339 [C9]: 10 custom made protocols, same as above.
  • 4664 [Fd]: Find device service protocol. This will make searching for an given device easier,
  • 5072 [Pr]: Device Pairing service. This service protocol will search for a device that can handle the apps that it supports.
  • 5074 [Pt]: Pocket_transport protocol.

(*) The value between the [ and ] are the ascii representation of the app_id value.

#
  1. regarding combining WIFI with ESPNOW: I will not do any automations when when the channel changes.
    It will just report an Error message that the channel has been changed. The Provider (Drudge) should start searching for the Peer it self. For this i will add an search method and action to scan the channels for a the given peer.
grand sapphire
#

2). There will be more types of triggers available for you to use

  • The following new triggers will replace the on_sent trigger:
    on_succeed: when the packet is received correctly. On multi_cast you will could get multiple triggers fired.
    on_failed: when espnow is not able to deliver the packet to the given peer. On multi_cast you will could get multiple triggers fired.
  • The following triggers will be new:
    on_timeout: when the component does not get a sent result within a given time.
    on_ack: will be fired when the receiver send an "ack" message back.
    on_nack: will be fired when he receiver send an "nack" message back.
#
  1. the above triggers can be set globally at the espnow component by creating your own virtual protocol in yaml. Or part of the send action directly.
#
  1. the send action will get an option to wait for sent or ack report to continue.
grand sapphire
#
  1. when the espnow component failed to send a packet to a peer, it will automatically stop sending new packet's to that peer until this peer send a packet back. Scanning for the peer still will work of course.
keen mulch
# grand sapphire 1) regarding combining WIFI with ESPNOW: I will not do any automations when when...

Hello, I am pleased that this implementation is going forward. So if I understand that correctly so when the WiFi channel will change on the Keeper. Then Drudge will recognise that the messages aren't received by the keeper (because running WiFi is blocking to receive esp-now data on the "old" channel.) And the drudge will try search for the Peer (keeper). But I would need to change the WiFi channel on my keeper by myself because you wrote that I will not do any automations when when the channel changes.. Because if keeper would stay on the same channel then drudge could find him. Thanks in advance for an answer. πŸ™‚

grand sapphire
#

Sorry, that my explanation was not clear enough. The collector(keeper) will always use the wifi channel, youdont need to do anything for that. On the provider (drudge) side you need to start searching for the collector, when the packets are no longer delivered.

keen mulch
grand sapphire
#

Not yet, almost done. I have been testing the changes, for the last couple of days. And some works and some not yet. 😦

grand sapphire
#

What i love from you is a suggestion on how te rewrite the class?

keen mulch
#

@grand sapphire Hello, I have this problem than I have one slave that is sending data on channel number 1 for example. And my receiver my hub has set up in the yaml file wifi configuration and wifi_channel: !secret espnow_channel which is also set to 1. I know that the channel on the hub is automatically set to the WiFi channel. But now I have the problem that my receiver (hub) doesn't have any WiFi to connect and keeps finding the sensor while not receiving the data over espnow.

grand sapphire
#

when the receiver is using wifi, you cant set the wifi channel. and the slave channel needs to be set to the same as the receiver channel. withch is shown in the espnow config settings.

keen mulch
#

I know, but what happens if the WiFi is unavailable. The receiver is non-stop trying to connect the WiFI while espnow could work. Because what I've figured out that the espnow hasn't even initialised (no log related to espnow).

#

So the receiver is not using wifi at the moment the esp32 is just "trying" to use the WiFi.

keen mulch
#

@grand sapphire Because what I want to achieve that I want to at some point of code to use WiFi.disable and then just use espnow. But the espnow want initialised without the WiFi component being successful initialised. So I've tried on the receiver to enable the AP:

wifi:
  ssid: !secret WiFi_ssid # WiFi SSID from secrets.yaml
  password: !secret WiFi_password # WiFi password from secrets.yaml
  power_save_mode: none
  ap:
    ssid: 'E ink hub'
    password: !secret ap_password
    ap_timeout: 10s
    channel: !secret espnow_channel

So the WiFi component could mark as initialised. So the esp is scanning the WiFis for 10s and then the AP is active and I can see the espnow init.

[21:09:13][C][wifi:600]: WiFi:
[21:09:13][C][wifi:428]:   Local MAC: D8:3B:DA:8A:F7:A8
[21:09:13][C][wifi:433]:   SSID: ''
[21:09:13][C][wifi:440]:   BSSID: 00:00:00:00:00:00
[21:09:13][C][wifi:441]:   Hostname: '1-hub-e-ink'
[21:09:13][C][wifi:443]:   Signal strength: 0 dB β–‚β–„β–†β–ˆ
[21:09:13][C][wifi:447]:   Channel: 6
[21:09:13][C][wifi:448]:   Subnet: 0.0.0.0
[21:09:13][C][wifi:449]:   Gateway: 0.0.0.0
[21:09:13][C][wifi:450]:   DNS1: 0.0.0.0
[21:09:13][C][wifi:451]:   DNS2: 0.0.0.0
[21:09:14][C][espnow:055]: esp_now:
[21:09:14][C][espnow:057]:   Own Peer code: D8:3B:DA:8A:F7:A8.
[21:09:14][C][espnow:058]:   Wifi channel: 6.
[21:09:14][C][espnow:059]:   Auto add new peers: Yes.
[21:09:14][C][espnow:060]:   Use sent status: Yes.
[21:09:14][C][espnow:061]:   Convermation timeout: 1388ms.
[21:09:14][C][espnow:062]:   Send retries: 5.

And the esp now receiving is working but when the WiFi component is scanning WiFi you are not receiving at this time. Not even passive_scan: true helped.

random elk
#

Heya all - just dropping in with regards to encryption, again - there has to be a way to leverage either wireguard or the ota component to achieve encryption of this data. I have tried to hook into the wireguard component beforre in an attempt to encrypte byte arrays but found the code really hard to understand. Perhaps @upper walrus may be able to point to the appropriate methods - avoids reinventing the wheel.

upper walrus
#

There is a new xxtea component that could be leveraged to encrypt bytes on the fly

random elk
upper walrus
#

Probably do-able, just need to extract the noise parts into a noise components that can be loaded and functions called etc

haughty hollow
#

How well does that work with the short packets that espnow will probably be using.

upper walrus
#

it encrypts the short packets that the api sends that are proto encoded

haughty hollow
#

good point

#

How would espnow handle "reconnection" to reset the encryption stream?

grand sapphire
#

That could be the same way as api does it. Or we could create a way with my own identification validation

haughty hollow
#

API is TCP, so there is an actual disconnect and reconnect.

grand sapphire
grand sapphire
grand sapphire
#

ESP-NOW protocol/communication for ESPHome.

keen mulch
#

Hello, I have a question about how to buffer is exactly working? When I am sending message with some data every 50ms everything is ok, but when I am trying to send the e. g. every 10ms or less I will get on the reciever this error: [13:45:08]ERROR: Too many messages queued and on the transmitter is no error. So can I somehow calculate what is the best delay between messages.

grand sapphire
#

it has now space for 200 messages,

#

when you send packets very fast the buffer get full really fast,

#

In the next version you disable waiting for a conformation message, and you can send packets faster, by setting the dont wait for ack flag in every packet,

#

Sadly, i still find new issues every day. And need to solve them.

shadow raptor
#

does esp-now work over ethernet I also heard someone say "Wifi-NOW" does that mean this could technically support the other micros as well?

haughty hollow
#

ESPNow is only a wireless protocol. It's a type of wifi.

#

It can be done on a computer. I don't know if the other MCUs support the features needed.

shadow raptor
#

I just thought with that pc example any uC would then be able to do if they support the required mode

haughty hollow
#

They could, but it needs to be implemented separately for each one.

shadow raptor
#

Would it be plausible to use this in this scenario: I have a lot of esphome ifan04 controllers, when ha goes off-line they reboot in ap mode. Then people just use the RF remotes for them but they reboot 15mins or less to find ha. Which shuts of the fan or light.

Could I technically disable the restart timeout, then use 1 master esphome node that talks to HA via that new serial method or ethernet or just keeps rebooting as normal till it finds HA. Then when HA comes alive it sends all the fan controllers in ap mode an espnow packet to tell HA is alive. Then they all reboot to recontact the mothership?
Some pseudo code follows:

# Primary node checker
# Check if HA is online
tcp_ping_some_random_method_to_test_if_ha_alive:
  name: "HA Server Status"
  id: ha_server_status
  host: your_homeassistant_ip
  port: 8123
  update_interval: 30s

# ESP-NOW component
esp_now:
  id: esp_now_component

# Broadcast status to all three fans
interval:
  - interval: 30s
    then:
      # Fan 1
      - esp_now.send:
          peer_mac: "XX:XX:XX:XX:XX:XX"  # MAC of ceiling fan 1
          data: !lambda 'return (id(ha_server_status).state) ? "HA_ONLINE" : "HA_OFFLINE";'
      # Fan 2
      - esp_now.send:
          peer_mac: "YY:YY:YY:YY:YY:YY"  # MAC of ceiling fan 2
          data: !lambda 'return (id(ha_server_status).state) ? "HA_ONLINE" : "HA_OFFLINE";'
      # Fan 3
      - esp_now.send:
          peer_mac: "ZZ:ZZ:ZZ:ZZ:ZZ:ZZ"  # MAC of ceiling fan 3
          data: !lambda 'return (id(ha_server_status).state) ? "HA_ONLINE" : "HA_OFFLINE";'
#
# For each ceiling fan (with unique name/id)
esphome:
  name: ceiling_fan_1  # Change to ceiling_fan_2, ceiling_fan_3 for others

# [Other fan-specific configurations...]

# ESP-NOW component
esp_now:
  id: esp_now_component
  on_receive:
    - lambda: |-
        if (std::string(data) == "HA_ONLINE") {
          // If we're in AP mode and HA is now online, reboot to reconnect
          if (WiFi.getMode() == WIFI_AP || WiFi.getMode() == WIFI_AP_STA) {
            id(ha_is_online) = true;
            // Schedule a reboot with a short delay to complete this operation
            id(reboot_timer).start();
          }
        } else if (std::string(data) == "HA_OFFLINE") {
          id(ha_is_online) = false;
          // Stay in local mode without rebooting
        }

# Add a timer for delayed reboot
timer:
  - id: reboot_timer
    name: "Reconnection Reboot Timer"
    restore: false
    duration: 10s
    on_timeout:
      then:
        - logger.log: "Home Assistant is back online, rebooting to reconnect..."
        - delay: 1s
        - api.disconnect:
        - delay: 1s
        - output.turn_on: persistent_state_pin  # Optional: ensure state is saved
        - delay: 1s
        - restart:

# Optional - add a persistent state pin (if needed)
output:
  - platform: gpio
    id: persistent_state_pin
    pin: 
      number: GPIO16  # Use a pin that exists but isn't used for anything else
      mode: OUTPUT
    inverted: false

# Global variable to track HA status
globals:
  - id: ha_is_online
    type: bool
    restore_value: yes
    initial_value: 'true'

# Modify WiFi configuration to disable auto-reboot
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 0s  # Disable auto-reboot
  ap:
    ssid: "${friendly_name} Fallback Hotspot"
    password: !secret ap_password
#

Something like that

haughty hollow
#

Why not just disable the reboot timeout?

shadow raptor
#

Then they would never reconnect to ha when ha comes back online?

haughty hollow
#

Why not?

#

And it's HA that does the connecting anyway.

shadow raptor
#

How will HA reconnect to an esp in ap mode?

haughty hollow
#

Why is it in AP mode? Is the wifi going out too?

#

Even so, it will re-connect when the wifi is back. (At least, it's supposed to.)

shadow raptor
#

It's a hypervisor that runs the router + AP's / etc all eggs in one basket scenario.

#

Sometimes I break something and it takes a day to fix it etc. Network completely offline / HA offline / etc

shadow raptor
#

Or it can do this without restarting?

haughty hollow
#

In ap mode, it's still scanning for the WiFi to show up again.

#

But this thread is supposed to be about ESP now testing. πŸ™‚

shadow raptor
grand sapphire
grand sapphire
#

@keen mulch finally i was able to push the next release to github. There is a lot of changes done.

keen mulch
#

Ok, I will surely check it out.

keen mulch
grand sapphire
#

Not sure what you mean but when you show me the changes you made, i will make it them optional for you so you do not need to change it later.

keen mulch
#

Ok, I commented these lines:

bool ESPNowComponent::can_proceed() {
#ifdef USE_WIFI
  // if (wifi::global_wifi_component != nullptr)
  //   return wifi::global_wifi_component->is_connected();
  // return false;
  return true;
#else
  return true;
#endif

Because with this the espnow component can initialize without the WiFi. (if the WiFi compoent has

wifi:
  enable_on_boot: false

And then I've commented this:

void ESPNowComponent::loop() {
  if (!this->task_running_) {
    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->set_wifi_channel(new_channel); });
//   }
// #endif
}
``` We don't need to change the channel because we don't use WiFi and ESPNOW at the same time.
Because my application has two modes:
- WiFi (web server, captive portal)
- ESPNOW
grand sapphire
#

okay, how do you switch between the two modes?

keen mulch
#
switch(id(esp_sensor_mode)) {
            case 0:
              id(button_rgb_led).turn_on().set_brightness(1.0).set_transition_length(10).set_rgb(1, 0.79215686274, 0.22745098039).perform(); // Yellow
              ESP_LOGD("main", "Starting ESP-NOW");
              id(captive_portal_component).end();
              id(wifi_component).stop_ap();
              id(wifi_component).disable();
              id(espnow_component)->setup();
              break;
            case 1:
              id(button_rgb_led).turn_on().set_brightness(1.0).set_transition_length(10).set_rgb(0.09019607843 , 0.74509803921, 0.34117647058).perform(); // Green
              id(espnow_communication).execute(); 
              id(deep_sleep_1).allow_deep_sleep();
              break;
            case 2:
              id(button_rgb_led).turn_on().set_brightness(1.0).set_transition_length(10).set_rgb(0, 0.35, 1).perform(); // Blue 
              ESP_LOGD("main", "Starting webserver with captive portal");
              id(espnow_communication).stop();
              id(built_in_led).turn_off();
              id(deep_sleep_1).prevent_deep_sleep();
              id(wifi_component).enable();
              id(wifi_component).start_ap();
              id(captive_portal_component).start("/captive_portal");
              break;
            default:
              break;
          }

0 - ESP NOW 24/7
1 - ESP NOW with deep sleep
2 - WiFi with captive portal and ap. For this I am using this https://github.com/esphome/esphome/pull/8160

grand sapphire
#

is that a lambda? when so where do you call it from?

keen mulch
grand sapphire
#

That is a bit more then i hoped for, but i see some options i could implement for you.

keen mulch
#

Thx in advance.

keen mulch
#

Just a quick question. Is it possible that device like esp could change their MAC address so there could communicate on behave of some other trusted device?

haughty hollow
keen mulch
#

So there would need to be established on the first connection some secret password with which would they decipher the sent messages no?

haughty hollow
#

If you're concerned about that, yes. There are encryption options.

grand sapphire
#

There is a solution to check for that. There is a (i belief) fixed identification in every esp.

#

I have used it to identify the nowtalk devices. A communication protocol that i made some years ago wish is also using ESPNow.

eternal lintel
#

@grand sapphire I added few comments to your PR.

The important question is if your code should support application-level protocol details (for now you are adding your own header including 16-bit magic / app_id / command).

I think you should keep API very close to ESP API and simply pass raw payload, without any application-level details.

grand sapphire
#

Thanks for your time evaluating my pr. The idea behind this component is allow users to havecan easy way to use the protocol, without the need to validate where an incoming packing is com8ng from.

#

However when you need to send/receive raw packets, the component has also the option to do so.

grand sapphire
#

I found some other issues as well that am fixing right now.

eternal lintel
#

BTW you wrote above I can send raw frame without app_id / command, how? I'm trying:

  - espnow.send.broadcast:
      payload: "foo"

and I have config validation error with very non-descriptive error: value must be at least 1. It looks I need to provide non-zero app_id

#

With app_id/command:

  - espnow.send.broadcast:
      payload: "foo"
      app_id: 1
      command: 1

I have following error: [20:08:19][E][espnow:536]: [Broadcast](S0001.1)[02] | Packet Dropped. To many attempts.

#

Probably because of lack of ACK - but broadcast frames are not ACK'ed, right?

grand sapphire
#

good question. not sure what i did with that. Let me check after i added the espnow.send.raw which i forgot to add.

grand sapphire
#

new version committed.

#

You can use the espnow.send.raw to send raw data

grand sapphire
grand sapphire
#

anyway it is running.

wise phoenix
#

I'm trying to use this like this:

#
external_components:
  - source: github://pr#7141
    components: [ espnow ]
#

and I get:

#
ImportError: cannot import name 'CONF_DEFAULT' from 'esphome.const' (/Users/chad/conda-environments/esphome/lib/python3.12/site-packages/esphome/const.py)
#

It should be 2025.3.3 . Am I doing something wrong?

haughty hollow
#

There was a change to move that constant. You probably need to use the beta.

grand sapphire
#

or better use the Dev branch πŸ˜„

wise phoenix
#

OK, it builds but I'm slightly confused about config. 'peers:' (as per the PR) is no longer valid. I just auto_add_peer:?

wise phoenix
#

Ah, I see. It's in the action.

#

Nope. Any hints on a config?

#

OK, this works - espnow.send: app_id: 0x01 mac_address: a0:b7:65:04:ca:c4 payload: "Hallo world, how is live overthere."

#

A receiver says 48:31:B7:3F:9E:14(R0001.0)[01] | Deconstruct packet. and then crashes.

#

But I had no special sensor or anything. I just had espnow: id: espnow1 auto_add_peer: true wifi_channel: 1

haughty hollow
#

crashes?

wise phoenix
#

The receiver says: [14:26:33][I][espnow:647][wifi]: triggger event {2} [14:26:33][E][espnow:085][wifi]: 48:31:B7:3F:9E:14(R0001.0)[01] | Deconstruct packet. [14:26:33]Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.

#

This is my entire config. I don't know how to respond to incoming data.

#
esphome:
  name: espnow-consumer
  friendly_name: ESP-NOW Consumer
  platformio_options:
    upload_speed: 921600
    build_flags: -D DEBUG

logger:
  level: DEBUG

esp32:
  board: esp32dev
  framework:
    type: esp-idf

external_components:
  - source: github://pr#7141
    components: [ espnow ]

espnow:
  id: espnow1
  auto_add_peer: true
  wifi_channel: 1
  on_raw_data:
    - logger.log:
        format: "Received:"```
#

OK, I didn't see the examples. Argh.

grand sapphire
wise phoenix
#

It's an enormous stack trace.

#

I read the example and my espnow: now consists of ```espnow:
id: espnow1
auto_add_peer: true
wifi_channel: 1

custom_apps:
- app_id: 123
on_receive:
- logger.log:
format: "Received from: %s = '%s' cmd: %d RSSI: %d"
args:
[
packet->payload(),
packet->peer_str().c_str(),
packet->command(),
packet->rssi(),
]```

grand sapphire
#

yeah i know πŸ˜„

wise phoenix
#

the sender has ```
binary_sensor:

  • platform: gpio
    pin:
    number: GPIO10
    inverted: true
    mode:
    input: true
    pullup: true
    name: Button
    disabled_by_default: true
    entity_category: diagnostic
    id: echo_button
    on_click:
    • logger.log:
      level: DEBUG
      format: "Button pressed"

    a0:b7:65:04:ca:c4

    • espnow.send:
      app_id: 123
      mac_address: a0:b7:65:04:ca:c4
      payload: "Hallo world, how is live overthere."
grand sapphire
#

you can store the tracelog as file

wise phoenix
#

Null pointer?

#

The sender's espnow: config is espnow: id: espnow1 auto_add_peer: true wifi_channel: 1

grand sapphire
#

sorry i need the stacklog where just after when the crash happens

wise phoenix
#

That's it. Before I pressed the button I esphome log config.yml | tee foo

haughty hollow
#

[15:13:34]Backtrace: 0x400d6904:0x3ffbea40 0x400d686d:0x3ffbea90 0x400d6814:0x3ffbeac0 0x400d68d2:0x3ffbeaf0 0x40137eb2:0x3ffbeb30 0x400d3ed6:0x3ffbeb60 0x400d667f:0x3ffbebb0 0x400d4765:0x3ffbec10 0x400d67f6:0x3ffbec40 0x400d142e:0x3ffbec60

#

Need the elf file to decode it.

grand sapphire
#

ha is that not installed by default @haughty hollow ?

wise phoenix
#

I'm using the dev venv but python -m esphome.stack_trace doesn't work. Any hints?

haughty hollow
wise phoenix
#

Dashboard?

#

I can get the elf from .pioenvs

haughty hollow
#

If you're using the cli over serial, then it should decode it already.

wise phoenix
#

Yes, cli over serial

#

When I used pio alone it needed a filter for stack traces. Do I need to do that here somehow?

haughty hollow
#

pio?

#

The esphome logs command should decode it.

#

But if not, you can use that site with the elf file.

grand sapphire
#

thanks @haughty hollow this is new terrain for me

haughty hollow
#

The elf file should be in the same directory as the firmware binary.

wise phoenix
#

That was using the webpage that ssieb provided. I'd really rather do it on the cli if you have any hints.

grand sapphire
#

use esphome run yourscript.yaml

wise phoenix
haughty hollow
# wise phoenix

This indicates that it's running a lambda in the receive trigger. That doesn't match the config you've pasted.

wise phoenix
#

I definitely don't have any lambdas.

#

I really appreciate the help, thank you very much. I hope that I can contribute in some way. Sadly, I must step away for several hours. There is some sort of command line tool for decoding stack traces but it needs a whole tool chain of junk that I don't have. I might try it later: https://github.com/esphome/esp-stacktrace-decoder

#

Just for completion, here is my entire configs: Provider: ```esphome:
name: espnow-provider
friendly_name: ESP-NOW Provider
platformio_options:
upload_speed: 921600
build_flags: -D DEBUG

logger:
level: DEBUG

esp32:
board: seeed_xiao_esp32c3
framework:
type: esp-idf

external_components:

  • source: github://pr#7141
    components: [ espnow ]

espnow:
id: espnow1
auto_add_peer: true
wifi_channel: 1

peers:

peer:

- FF:FF:FF:FF:FF:FF

binary_sensor:

  • platform: gpio
    pin:
    number: GPIO10
    inverted: true
    mode:
    input: true
    pullup: true
    name: Button
    disabled_by_default: true
    entity_category: diagnostic
    id: echo_button
    on_click:
    • logger.log:
      level: DEBUG
      format: "Button pressed"

    a0:b7:65:04:ca:c4

    • espnow.send:
      app_id: 123
      mac_address: a0:b7:65:04:ca:c4
      payload: "Hallo world, how is live overthere."

- espnow.send:

payload: [0x1F, 0x3E, 0x06, 0x5F]```

#

Consumer: ```esphome:
name: espnow-consumer
friendly_name: ESP-NOW Consumer
platformio_options:
upload_speed: 921600
build_flags: -D DEBUG

logger:
level: DEBUG

esp32:
board: esp32dev
framework:
type: esp-idf

external_components:

  • source: github://pr#7141
    components: [ espnow ]

espnow:
id: espnow1
auto_add_peer: true
wifi_channel: 1

custom_apps:
- app_id: 123
on_receive:
- logger.log:
format: "Received from: %s = '%s' cmd: %d RSSI: %d"
args:
[
packet->payload(),
packet->peer_str().c_str(),
packet->command(),
packet->rssi(),
]

    # args: [ it->data().data(),  it->rssi()]

peers:

peer:

- FF:FF:FF:FF:FF:FF

clamp c3 is 48:31:B7:3F:9E:14

cyd is a0:b7:65:04:ca:c4

haughty hollow
#

Oh, the logger.log action is converted to a lambda action.

#

So that sounds like the packet isn't valid or possibly the peer_str() is null. Hard to say exactly.

#

Try:

        - logger.log:
            format: "packet: %p"
            args: [ packet ]
wise phoenix
#

Same

haughty hollow
#

Do you still have your log line?

#

Or did you replace it with this one?

wise phoenix
#

- logger.log: "Received something" also fails

#

I removed all of the other things.

#

First I tried exactly what you put above with nothing else, then I put - logger.log: "Received something".

haughty hollow
#

ok

wise phoenix
#

I'll check in later today. Thank you again for your time.

grand sapphire
#

i will do the same tomorrow.

#

Anyway thanks for testing this new version of the espnow component.

wise phoenix
#

I'm not sure what else I can do except to ask for bare minumum working config. Once I have that I can work with it. I can even use it in my work production with a bunch of sensors in a growth chamber on battery and deep sleep.

wise phoenix
#

I got the cli stacktrace decoder working, so there's that.

wise phoenix
#

Here is pertinent stdout: ```[10:51:15][I][espnow:650][wifi]: triggger event {2}
[10:51:15][E][espnow:085][wifi]: 48:31:Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.

grand sapphire
#

let me run it local

#

From what i see above you are already running the component as barebones as possible

eternal lintel
#

I still think that basic ESP-NOW protocol should be very thin layer on top of esp-idf API. It should also reduce errors like above and simplify future maintenance.

Then all additional logic (all this frame ACK stuff, app_ids / app commands) can be implemented in separate 'layer' (separate component) if needed.

@haughty hollow what do you think?

grand sapphire
#

i think there is something wrong.

#

@eternal lintel, for developers like you and me i totally agree, but for people that has no experiences with protocol's and what so ever. This component is mend for everyone that wants to use the espnow communication as easy as possible. without the need to add layers of yaml code to be sure that the packets are coming from an known device etc.

eternal lintel
#

I agree, but it can be implemented different way, by splitting onto two components

#

This way you will be able to quickly deliver the first one, probably error-free, as it will be very simple.

#

And it will be still very useful for a lot of folks probably

#

As raw UDP is also useful

#

(without all this frame confirmation stuff)

#

you are trying to implement "TCP/IP" in one go, without separate "IP" part

#

I think this analogy is quite good here

grand sapphire
#

When you think that makes more sense then create your own and see how it will go.

eternal lintel
#

I just did exactly the same mistake in my esphome-canopen component (It implements CANOpen protocol and ESPHome entity mapper in the same component, now I clearly see it should be splitted). So I have a lot of work with my own refactor there. Also: I definitely prefer wired communication recently :]

#

I was trying to use your code for virtual CANBUS over ESPNOW (I have something like that but for UDP unicast packets). And unfortunately I was not able, because of complexity level of your component.

grand sapphire
#

It is just a small layer that allows a low level control above the simple espnow protocol.

eternal lintel
#

I think this additional layer is majority of code now (I saw that)

grand sapphire
#

correct.

eternal lintel
#

Also think about configuration - how many configuration options are for this second layer

#

And what is needed if you want to simply broadcast packets with ESPNOW

grand sapphire
#

and that is how it will be in the end. Above that there will be new app components that adds new futures above it.

#

you can do that by the action espnow.send.broadcast:

eternal lintel
#

What if I need only that? All this other stuff will stay compiled probably, right? And remember, we are doing embeded programming on constrained resources :]

#

All this other stuff will stay compiled probably, right?
Actually today compilers are really good, so maybe it will be optimized out

grand sapphire
#

Using the broadcast option is very unwisely to use. It will bug all devices that uses espnow as well.

haughty hollow
#

Unused methods don't get included.

grand sapphire
#

In this case everything is being includes. it will not make much difference.

#

because the component does not care if it is send as broatcast or directly

grand sapphire
# wise phoenix Here is pertinent stdout: ```[10:51:15][I][espnow:650][wifi]: triggger event {2}...

i just tested this script and it seems to work fine:

espnow:
  auto_add_peer: true
  wifi_channel: 1
  attempts: 3
  predefined_peers:
    - mac_address: E8:6B:EA:24:22:04
  on_new_peer:
    - logger.log:
        format: "New Peer from: %s = '%s' cmd: %d RSSI: %d"
        args:
          [
            packet->payload(),
            packet->peer_str().c_str(),
            packet->command(),
            packet->rssi(),
          ]
  custom_apps:
    - app_id: 0x5354
      on_receive:
        - logger.log:
            format: "Received from: %s = '%s' cmd: %d RSSI: %d"
            args:
              [
                packet->payload(),
                packet->peer_str().c_str(),
                packet->command(),
                packet->rssi(),
              ]

interval:
  - interval: 5sec
    startup_delay: 5sec
    then:
      - logger.log:
          format: "------------"
      - espnow.send:
          payload: "Test  2."
wise phoenix
#

I'm a little confused about the apps. in interval you send but don't define an app_id.

#

When you espnow.send, does it go to all predefined_peers? I wonder if you could make a few example configs showing what you intended with these concepts.

#

I'm most interested in sending a message from 1..n machines to a machine called foo. One gatherer.

grand sapphire
#

When you have defined one app in the custom_apps section, the component knows which app_id to use. so you do not need to enter it again and again. When you have different app's defined than you need to specify the app_id. the same for the predefined_peer. when you have just one peer then you can omit setting the mac_address.

grand sapphire
#

^ this is currently broken.

#

you use it with espnow.send.multicast:

#

It will send the packet but it breaks on the ACK support.

wise phoenix
#

So I put the same code (above) on both the test sender and the test receiver? I don’t get the chance yet.

grand sapphire
#

You need to have a script per device. because you need to change the *predefined_peers: * default mac address.

#

going to sleep now

wise phoenix
#

I put the same config on both the consumer and provider and changed the mac addresses to point to each other.

#

I get a crash with stack trace.

#

I don't know what else to do. The consumer board is board: seeed_xiao_esp32c3, and the provider is board: esp32-s3-devkitc-1. Could this somehow be related?

#

They both crash when receiving messages.

#

I'm using dev branch in a venv.

grand sapphire
#

Okay. I can reproduce this here as well. I will check it out.

keen mulch
#

Hello, which version with the - per application you can now set a triggers, functionality is working with no problem. Because when I was reading this thread above I found that there are some problems. Thx for reply.

grand sapphire
#

working on it atm.

grand sapphire
#

I found the place where it did go wrong. now i need to figure out what is the good solution to fix it.

grand sapphire
#

@keen mulch, @wise phoenix the issue is fixed now.

wise phoenix
#

I'll check it out tonight!

grand sapphire
#

this are the options you can set now:

espnow:
  wifi_channel: 1..14 
  auto_add_peer: false|true
  wait_for_ack: true|false
  conformation_timeout: 5sec
  attempts: 1  # number of retries before failing
  predefined_peers:
    - mac_address: 11:22:33:44:55:66
      default: false|true # only the first peer is set to true by default.
  on_new_peer:
     - [action list]
  on_raw_data:
     - [action list]
  custom_apps:
     - app_id: 0x5358|0x1000..0xffff # needs to be unique.
       default: false|true # only the first peer is set to true by default.
       on_receive:
          command: 0x00|0x01..0xf0
          then: 
             [action list]
       on_broadcast:
          command: 0x00|0x01..0xf0
          then: 
             [action list]
       on_succeed: # when packet was received by the sender.
          command: 0x00|0x01..0xf0
          then: 
             [action list]
       on_failed: # when the sender sends a NACK. or Timeout when on_timeout is not used
          command: 0x00|0x01..0xf0
          then: 
             [action list]
       on_timeout: # when the receiving peer does not respond within the
                   # conformation_timeout * attampts
          command: 0x00|0x01..0xf0
          then: 
             [action list]
keen mulch
#

And the sender side yaml?

grand sapphire
#

this are the available send actions atm:


esphome:
  on_boot:
    - espnow.send:
          mac_address: default|00:11:22:33:55:66|lampda
          app_id: default|0x1000..0xffff
          command: 0x00|0x01..0xf0
          payload: bytearray|string|lambda
          on_succeed: # when packet was received by the sender.
             then:
                [action list]
          on_failed: # when the sender sends a NACK or Timeout
              then:
                [action list]
          on_timeout: # when the receiving peer does not respond within the
                      # conformation_timeout * attampts
              then:
                [action list]
    - espnow.send.broadcast:
        mac_address: ff:ff:ff:ff:ff:ff
        # plus same as espnow.send
    - espnow.send.multicasr:
        mac_address: ff:ff:ff:ff:ff:fe
        # plus same as espnow.send
    - espnow.send.raw:
          mac_address: default|00:11:22:33:55:66|lampda
          payload: bytearray|string|lambda
          on_succeed: # when packet was received by the sender.
             then:
                [action list]
          on_failed: # when the sender sends a NACK or Timeout
              then:
                [action list]
          on_timeout: # when the receiving peer does not respond within the
                      # conformation_timeout * attampts
              then:
                [action list]

Other actions available:

 - espnow.peer.add:
     mac_addres: 00:11:22:33:55:66|lampda
     default: false|true
 - espnow.peer.add: 00:11:22:33:55:66|lampda
 - espnow.peer.del:
     mac_addres: 00:11:22:33:55:66|lampda
 - espnow.peer.del: 00:11:22:33:55:66|lampda
 - espnow.channel.set: 1..15

keen mulch
#

Ok, and the difference between app_id and command is that for app id I can make a trigger?

grand sapphire
#

I for got to add the command filter earlier. it is now added.

#

per app_id you can use different (0x01..0xf0) command's

keen mulch
#

So it's like another layer of filtering the message?

grand sapphire
#

yes

#

you could for example use the command to define different settings that needs to be changed.
Like sensors etc.

keen mulch
#

Nice, and the app_id is from int: 4096 - 65535
and the command from 0- 240

grand sapphire
#

app_id is from int: 0x1000 - 0xffff

keen mulch
#

Ok, I am planning to have each sensor under different app and the values of each sensors under different commands.

grand sapphire
#

When you do not set the app_id it will be set to 0x5358

#

That is not a good idea.

#

You should all sensors have set under one APP_ID and use the command for the sensor and the payload for the value.

#

or you could use the command as value and the payload to define the sensor.

keen mulch
#

Because I have 3 devices that have different sensors for example a, b, c and each sensor has value that measures for example a - temperature, humidity, b - CO2 and I wanted to have in the hub the a b c under different app id and thne measurements variables under the command, and read the values from payload.

grand sapphire
#

btw. when you omit the command in the on_succeed trigger etc. you will will trigger that when no other trigger is found with that command.

#

do you mean with the measurements variables under the command: 1 = temperature, 2 =humidity, 3 = CO2
Then that is how i see it being used indeed.

keen mulch
#

the CO2 is under a different sensor b so I can start the command from 1. Not from 3 right?

grand sapphire
#

Why would you use different APP_ID's ?
My vision of the APP_ID is that it is registered code like how zigbee or I2C works etc.

keen mulch
#

Because the sensor b is on different esp device

#

So in my vision the app is like device.

grand sapphire
#

But the mac_address is already defining the device is it not?

#

I understand what you thinking, i'm now looking on how it would fit in my vision πŸ˜„

keen mulch
#

Yes, but I am going to have multiple devices that means multiples MAC addresses, and I dont want to configure which mac adress is which sensor, I will just know that the data they are sending is corresponding to different app and then I will handle it.

grand sapphire
#

is thinking

#

Sorry, i'm missing something. do you mean you have multiple a's, b's and c's ?

#

Maybe you should create it as you think you need and share it to me. You may DM me those when you like.
Then i can get a better inside of what you thinking about.

grand sapphire
# keen mulch Ok.

You do not make it working of fancy, just to get an understanding on how you want it to work.

grand sapphire
grand sapphire
#

Here is an overview useful methods of the packet object that is accessible from within lambda's:

  packet->key() // the key is a combination of the mac_addresss and sequence number
  
  packet->size() // size of payloadf
  packet->payload() // byte array pointer to the payload

  packet->peer() // uint64_t reference to the mac address
  packet->peer_str() // returns the mac address as string like 11:22:33:44:55:66
  packet->mac_address() //  byte array pointer to the mac address  

  packet->app_id() //  returns the app_id
  packet->command()  // returns the command value
  packet->sequence() // return a unique sequence number.  

  packet->timestamp() // when is the packet send or received
  packet->attempt()  // which attempt number is this.
    
  packet->options() // get all options that are set as numeric value.
  packet->options(option) // get the state of a specific option

  packet->rssi() // from how far away was the packet send.

# read the payload with the following methods:
  packet->read8u(offset) 
  packet->read16u(offset) 
  packet->read32u(offset) 
  packet->read64u(offset) 

  packet->read(offset, bytes)  // do you need an other sized value you can use this instead

# extended information about the packet:
  packet->info() // returns a string with the most imported packet info.
 
grand sapphire
#

@keen mulch, @wise phoenix i have update the first post with all kind of information about the uses of the espnow component and the predefined app_id.
#1329034880004395009 message

wise phoenix
#

first post where?

grand sapphire
#

see the link in above message πŸ˜„

wise phoenix
#

I see hundreds of messages. that overview of useful methods is useful.

grand sapphire
#

Just click on the UP arrow at the top right corner

wise phoenix
#

ah, ok. that's what you meant,

wise phoenix
#

I understand that this is under heavy development. AND when you return to it, can you please limit log messages of type ERROR.

#

Do you have a tip for printing packet->mac_address() ?

#

nvm haha. I see peer_str(). good job with that one.

#

I'm using - espnow.send: app_id: 0x5354 payload: "Test 2."

#

The receiver says: The app 0x5354 received payload(Test 2.T). This from format: "The app 0x5354 received payload(%s)" and packet->payload(). Am I doing something wrong? Why is there a T there?

#

Other than that it's doing what I would expect, I think.

grand sapphire
#

not sure what is wrong with it?

keen mulch
grand sapphire
#

No not yet, sorry. i wanted to besure that the basic's work first.

keen mulch
#

No problem.

grand sapphire
#

Also i need to work on an other project to get my financials back into the black

keen mulch
#

I understand that. My issue isn't that hard for me to fix it by myself. But it would be good option for user to have that. When they don't want to wait for the WiFi setup.

keen mulch
#

Hello, I keep getting this error:

[21:45:33][I][espnow:156]: send check enabled
[21:45:33][E][espno
[21:45:33]assert failed: xQueueReceive IDF\components\freertos\FreeRTOS-Kernel\queue.c:1531 (( pxQueue ))
[21:45:33]Core  0 register dump:
[21:45:33]MEPC    : 0x40802a5a  RA      : 0x408093a8  SP      : 0x4085d5d0  GP      : 0x4081a444  
WARNING Decoded 0x40802a5a: panic_abort at C:\Users\Acer\.platformio\packages\[email protected]\components\esp_system/panic.c:463
WARNING Decoded 0x40802a5a: panic_abort at C:\Users\Acer\.platformio\packages\[email protected]\components\esp_system/panic.c:463

When trying to run id(espnow_component)->setup();

haughty hollow
#

Why are you running that?

#

I wouldn't be surprised if that causes a crash.

keen mulch
#

Because this line is called after the WiFI component has been used on different WiFIi channel (then disabled), and I want to set up the espnow with a different channel then the WiFI.

haughty hollow
#

Then there needs to be a method to set the channel. Calling setup is not likely to go well.

keen mulch
#

Yes, there is a way to change a channel, but changing the channel is setting up the espnow component properly after using WiFi.

grand sapphire
keen mulch
#

Hello @grand sapphire , I am slowly trying to implement your library. And there were some problems with the sprintf. These are changed that I've made to compile my code:

https://github.com/nielsnl68/esphome/blob/nvds-new-espnow/esphome/components/espnow/espnow.cpp#L234
ESP_LOGCONFIG(TAG, "esp_now: V%lu", version);

https://github.com/nielsnl68/esphome/blob/nvds-new-espnow/esphome/components/espnow/espnow.cpp#L236
ESP_LOGE(TAG, " WIFI enabled : Yes. (Make sure provided device know how to channel swap). Number of retries before failing %d", this->attempts_);

https://github.com/nielsnl68/esphome/blob/nvds-new-espnow/esphome/components/espnow/espnow.cpp#L574
ESP_LOGW(TAG, "** Removing Packet %llx, Count: %ld", key, this->packet_send_map_[key].use_count());

#

On the line 234 change %u to %lu.
On the line 236 there was no specifier to use the attempts
On the line 574 change from %d to %ld.

#

I am using esphome version 2025.2.0 because of other component, so I moved the CONF_DEFAUL bach to init.py. So I dont know if the version of esphome influence these errors.

keen mulch
keen mulch
#

Does the espnow component works with arduino framework?

grand sapphire
grand sapphire
keen mulch
grand sapphire
#

say you have multiple different App's defined and one of them is the most used. Then you can set default.
In this case when you call espnow.send.* you can omit the app_id and it will use that app as default app_id

#

the same for the predefined mac_addresses.

keen mulch
grand sapphire
#

with the predefined_peers you can change de defeult peer later using espnow.peer.new: and setting the default: true option

keen mulch
#

Ok, so I dont have to call the mac from lambda?


- espnow.send.raw:
          mac_address: !lambda 'return id(hub_address);'
          payload: !lambda |-
            return float_to_bytes(id(f1).state);

?

Because how my application works that first the hub_address is set to broadcast. After pairing it stays to specific hub mac.

grand sapphire
#

Maybe once in a espnow.peer.new: action, for now.

keen mulch
#

Ok. And I have another question. What does mean Deconstruct packet. Because I have simple code:

Transmitter

- espnow.send:
          mac_address: !lambda 'return id(hub_address);'
          app_id: 0x2001 # 0x1000..0xffff
          command: 0x01
          payload: !lambda 'return float_to_bytes(id(f1).state);'
      - espnow.send.raw:
          mac_address: !lambda 'return id(hub_address);'
          payload: !lambda |-
            return format_espnow_data_new("f1", id(f1).state*100000);
      - delay: 100ms
      - espnow.send.raw:
          mac_address: !lambda 'return id(hub_address);'
          payload: !lambda |-
            return float_to_bytes(id(f1).state);

Receiver

custom_apps:
    - app_id: 0x2001 # needs to be unique.
      on_receive:
        command: 0x01
        then: 
          - lambda: |-
              std::string received_string((char*)packet->payload(), packet->size());

              ESP_LOGI("main", "Received data from app: %s", received_string.c_str());
             
  on_raw_data:
    - logger.log:
        format: "RAW DATA Received from: %s = '%s' RSSI: %d"
        args: [packet->peer_str().c_str(), packet->payload(), packet->rssi()]
    - lambda: |-
        std::string received_string((char*)packet->payload(), packet->size());

        ESP_LOGI("main", "Received raw data: %s", received_string.c_str());

And on the receiver side I am getting a lot of errors, but the data are correctly received.

grand sapphire
#

Now i think about it, i do see some possible issue with this. Not sure when you should call the above to set the default peer.

keen mulch
grand sapphire
#

At the moment i use the different log types to distinguish the different messages

keen mulch
#

Oh, it's sended 3x because I have attempts: 3. But I thought that it would send 3x times only when the packets arent received by target.

#

But why the message sended using app is only sended 1x and using the raw it's received 3x.

grand sapphire
#

can you show the sending log?

keen mulch
#
[21:40:46][D][main:245]: Sensor data updated
[21:40:46][I][espnow:146]: Construction RAW DATA packet, size: 10
[21:40:46][E][espnow:083][espnow]: [Broadcast](S0001.1)[42]  -01[7] | Deconstruct packet.
[21:40:46][E][espnow:417][wifi]: Packet a8f78ada3bd80001 not found (ack).
[21:40:46][E][espnow:083][wifi]: D8:3B:DA:8A:F7:A8(R000e.0)[01]  -fe[8] | Deconstruct packet.
[21:40:46][I][espnow:146]: Construction RAW DATA packet, size: 7
[21:40:46][E][espnow:521][espnow]: [Broadcast](S0001.18)[03] RAW [20] | Packet Dropped. To many attempts. 
[21:40:46][I][espnow:644][espnow]: triggger event {22}
[21:40:46][W][espnow:570]: **  Removing Packet ffffffffffff0001, Count: 2
[21:40:46][E][espnow:083]: [Broadcast](S0001.146)[03] RAW [20] | Deconstruct packet.
[21:40:46][E][espnow:521][espnow]: [Broadcast](S0002.18)[03] RAW [14] | Packet Dropped. To many attempts. 
[21:40:46][I][espnow:644][espnow]: triggger event {22}
[21:40:46][W][espnow:570]: **  Removing Packet ffffffffffff0002, Count: 2
[21:40:46][E][espnow:083]: [Broadcast](S0002.146)[03] RAW [14] | Deconstruct packet.
[21:40:48][D][light:036]: 'Button RGB LED' Setting:
grand sapphire
#

I see it is still not fool proof 😦

keen mulch
#

What have I done wrong?

grand sapphire
#

Notting,

#

I'm the fool here :P{

#

Is there a reason why you send a raw broadcast message?

keen mulch
#

Now for just a testing purpose. Because how my code works, is sending data to hub address that is paired during pairing process. But when this pairing process hasn't been done after the erased flash the hub address is set to broadcast. And it's sending data to broadcast. But it shouldn't. I need to fix it. But I am planning to use the broadcast to send message readyToPair and hub will respond: sendItToMe.

grand sapphire
#

yeah that shounds good, just dont do it as RAW messages πŸ˜„

keen mulch
#

Ok, the pairing feature I am also planning to do using the apps. This raw sending was just for testing purposes.

keen mulch
grand sapphire
#

np. Now we know there is on issue that i did not think about it very well πŸ˜„

#

The component will no longer know if that packet is received or not. I'm no longer using the ESP-NOW send callback handling.

#

Because i do not get a reference back for which packet/message the callback is called.

#

within the component (using the app system) the receiver side sends an ACK message when the packet is received okay. or NACK when something went wrong.

keen mulch
#

Sounds complicating. I don't know if that is related to this problem, but I've edited my espnow.cpp file to work even when the USE_WIFI is true but the WiFi is disabled id(wifi_component).disable();. This is the setup:

void ESPNowComponent::setup() {
// #ifndef USE_WIFI
  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());

#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);
// #else
//   if (wifi::global_wifi_component != nullptr) {
//     this->wifi_channel_ = wifi::global_wifi_component->get_wifi_channel();
//   } else {
//     ESP_LOGE(TAG, "Wifi Component is not setup correctly.");
//     this->mark_failed();
//   }
// #endif

There are just commented the parts that use the WiFi. Because in my application I have the WiFi in yaml, but it's not used at the same time as esp-now.

grand sapphire
#

i think you need to uncomment the last line.

mellow bone
#

Hey guys. I have a project that requires ESPNow protocol. Just stumbled on this thread and found a PR. I have tested a proof of concept using this PR where one ESP sends a message and one receives and seems that it works (despite beacon throwing an error on decontructing packet). What do you think is the state of this effort of integrating the protocol on the ESPHome?

grand sapphire
#

(despite beacon throwing an error on deconstructing packet)
Don't worry about that message. It is just a debug message, that the packet is safely deconstructed. I used the different colors to differentiate between different logging messages. So those with more importance pops out in the log.

grand sapphire
shadow raptor
#

Would there be a fall back method for espnow, so if the wifi/wired network goes down it can use espnow instead?

#

I have a door lock that communicates over udp to the security controller to get the door contact status, both on over ethernet. When the network goes offline, thought it would be nice if ESPNow could engage the wireless communication on both units, then use espnow to communicate while the network is offline.

grand sapphire
shadow raptor
#

Correct I would like to use hardwired ethernet unless critical failure.

shadow raptor
#

Something like this

# Ethernet configuration
ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO0_IN
  phy_addr: 0
  power_pin: GPIO12
  on_disconnect:
    - logger.log: "Ethernet disconnected, switching to ESP-NOW"
    - globals.set:
        id: using_espnow
        value: "true"
  on_connect:
    - logger.log: "Ethernet connected, switching back to primary communication"
    - globals.set:
        id: using_espnow
        value: "false"

# ESP-NOW configuration
espnow:
  channel: 1  # Use the same channel on both devices
  peers:
    - YY:YY:YY:YY:YY:YY  # MAC address of your doorlock
  on_receive:
    - if:
        condition:
          lambda: "return id(using_espnow);"
        then:
          # Process door state updates
          - logger.log: "Received door update via ESP-NOW: ${packet.payload_as_bytes()}"
          # Process the door state update here

# Global to track communication mode
globals:
  - id: using_espnow
    type: bool
    restore_value: no
    initial_value: "false"

# Button to trigger lock/unlock command
button:
  - platform: template
    name: "Remote Lock Control"
    on_press:
      - if:
          condition:
            lambda: "return !id(using_espnow);"
          then:
            # Send unlock command via UDP
            - lambda: |-
                std::string message = "LOCK_COMMAND:UNLOCK";
                id(security_udp).send(message.c_str(), message.length());
          else:
            # Send unlock command via ESP-NOW
            - espnow.send:
                data: "LOCK_COMMAND:UNLOCK"
#
# Ethernet configuration
ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO0_IN
  phy_addr: 0
  power_pin: GPIO12
  # Important: Set up event handlers for connection status
  on_disconnect:
    - logger.log: "Ethernet disconnected, switching to ESP-NOW"
    - globals.set:
        id: using_espnow
        value: "true"
  on_connect:
    - logger.log: "Ethernet connected, switching back to primary communication"
    - globals.set:
        id: using_espnow
        value: "false"

# ESP-NOW configuration
espnow:
  channel: 1  # Use the same channel on both devices
  peers:
    - XX:XX:XX:XX:XX:XX  # MAC address of your security system
  on_receive:
    - if:
        condition:
          lambda: "return id(using_espnow);"
        then:
          # Handle commands received from security system
          - logger.log: "Received command via ESP-NOW: ${packet.payload_as_bytes()}"
          # Process commands here

# Global to track communication mode
globals:
  - id: using_espnow
    type: bool
    restore_value: no
    initial_value: "false"

# Door state sensor
binary_sensor:
  - platform: gpio
    pin: GPIO5  # Example pin, adjust as needed
    name: "Door State"
    id: door_state
    on_state:
      - if:
          condition:
            lambda: "return !id(using_espnow);"
          then:
            # Send door state via UDP when Ethernet is up
            - lambda: |-
                std::string message = "DOOR_STATE:";
                message += id(door_state).state ? "OPEN" : "CLOSED";
                id(doorlock_udp).send(message.c_str(), message.length());
          else:
            # Send door state via ESP-NOW when Ethernet is down
            - espnow.send:
                data: !lambda |-
                  std::string message = "DOOR_STATE:";
                  message += id(door_state).state ? "OPEN" : "CLOSED";
                  return message;
haughty hollow
#

Are you expecting the ethernet to go down?

shadow raptor
#

It's not so much my ethernet but my hypervisor that controls routing in an LXC. Basically any time something crashes (Usually GPU doing inference) it brings the house of cards down.

mellow bone
#

@grand sapphire Do you feel that the integration is mature and stable and could be used as is? For the project we are deciding wheter to write the code from scratch or use ESPHome. I would really prefer the ESPHope solution.
Have you tested the stability and the memory performance? I am talking about memory leaks and etc. For example I have tried to send a beacon every 0.2s and after 5s the ESP crashed. 1s message seams to work.

shadow raptor
#

From what you are saying is that you would write a whole component yourself if it wasn't ready... which seems weird to me, why wouldn't you help build the thing currently in development instead.

#

Test it and find out if it works, it has short comings, code some patches and make it work.

shadow raptor
mellow bone
# shadow raptor From what you are saying is that you would write a whole component yourself if i...

Not really. We have a old version of a project that was based on the atmega chip. One guy from our makerspace would like to update the project to ESP based chip and I was pitching him ESPHome for a while. We are not good programmers, especially on C πŸ™‚ Thats why I like the ESPHome - my knowledge of basic C is just enough to join everything together into working project πŸ˜„ Its either write everything on Arduino framework from scratch (not use ESPHome) or use this component.

#

The feature is that requires the ESPnow is at "we will possibly need it next year" state, so thats why I am asking how far is this effort.

grand sapphire
grand sapphire
keen mulch
#

Hello, do you think I could use the new espnow version with the Arduino framework if I would strip down some of the functionality?

grand sapphire
#

You could try, and see if it works.

keen mulch
#

Ok, you have mentioned that the espnow callback function are leveraging the esp-idf 5.1+ (#1329034880004395009 message) where are they exactly at the source code please?

north sundial
#

@grand sapphire I'm here, what do you mean by "app layer"? Do you mean that your reason for implementing espnow was to allow easier future implemetations for other components? Like how packet_transport is currently used?

grand sapphire
#

yes. that is exactly my idea indeed.

grand sapphire
#

Okay, started on the ESPNOW component again.

  • To add a solution to report back if it is a broatcase/multicast.
  • Adding a new option to report a response back to a sending package.
  • any issues that are still coming up.
fringe jewel
#

Wonder if it might not be a good idea to implement it as a transport for the packetised api, similar to udp: ?

grand sapphire
#

Yes, that is an option that I am willing to do.

fringe jewel
#

Doesn't make sense to merge yet another sensor platform, etc, when the packet_transport: is available.

grand sapphire
#

True, although i want to use the API setup instead.

#

the packet_transport: has a little bit of an overhead that is already part of the espnow: component.
Maybe @sick zenith could help me out to define the right way to implent this.

#

IMHO. above the options within packet_transport: there should also be a proxy (or bridge) added that just transfers the incoming packages directly to an other protocol like udp: etc.

fringe jewel
#

I proposed a generalisation of the bluetooth proxy to proxy arbitrary protocols, didn't seem that welcome.

#

Degenerated into a discussion on modbus.

sick zenith
grand sapphire
#

from what i understood when you started on the pocket_transport component you introduced you own header with some info. My espnow component has also a header we should look what settings are missing in espnow that you need to for the pocket_transport to work.

#

i created an espnow_app class that allows for receiving and sending specific packages

#

The espnow header has 8 bytes the following data:

  • Package identifier (always 0x4E77 {'N', 'w'} )
  • Application identifier (flexible but for pocket_transport it could be 0x5074 {'P','t'})
  • Command code (an 8 bit value)
  • sequence number (an 16 bit counter unique for the senders mac_address and app_id)
  • flags byte ( this will tell the receiving part what kind of packet it is.) New
fringe jewel
#

I wonder how much of that header is actually required? I suppose the most important thing is to prevent ESPHome from acting on a random packet received?

#

And packet_transport has anti-replay already built in

#

and I presume all the command code and flags is also already present

grand sapphire
#

The ESPNowApp class has the following methods:

  • virtual application_mode(): this tells the app if it a COLLECTOR or PROVIDER

  • virtual application_id(): this should return the chosen app_id.

  • virtual is_paired(): only used for Applications that handles pairing support to the component.

  • virtual on_setup(): is being called when the espnow component's setup() is being executed.

  • virtual on_add_peer(): is being called when a new Peer has been registered. And could be used to setup some extra variables.

  • virtual on_del_peer(): is being called when a peer is removed from the system.

  • this->parent_->make_packet(): creates a packet to be used with the this->parent_->send() method().

  • this->parent_->send(): this will automatically send a packet

  • this->parent_->set_trigger_for(): this will register methods that should be called when an espnow event is fired:

    • TRIGGER_ON_RECEIVE : will be fired when a packet is received,
    • TRIGGER_ON_BROADCAST : will be fired when a broadcast packet is received,
    • TRIGGER_ON_RAW_DATA : will be fired when raw data has been received.
    • TRIGGER_ON_RESPONSE : will be fired when an response is received other then Ack or Nack,
    • TRIGGER_ON_SUCCEED : will be fired when an Ack is received,
    • TRIGGER_ON_FAILED : will be fired when an Nack is received,
    • TRIGGER_ON_TIMEOUT : will be fired when no response has be received when expect,
grand sapphire
fringe jewel
#

As I see it, ESPNow allows bidirectional send/recieve of data to up to 10 peers (I assume there is a limit like the number of MAC addresses that can be added to a filter in the hardware). So, a node can be a COLLECTOR (receiver?), PROVIDER (sender?) or BOTH.
The UDP packet_transport: doesn't appear to do anything to stop it from trying to process a malformed packet, other than encrypting it (and verifying it can decrypt successfully, I presume).
So, it would appear that you can create an ESPNowComponent that is almost identical to a UDPComponent, just with MAC addresses instead of IP addresses.

fringe jewel
#

i.e udp_conponent has void UDPComponent::send_packet(const uint8_t *data, size_t size), which just sends that data to each defined peer

#

(tbh, that seems like a mistake, I can certainly imagine a protocol that wants to send unique data to each peer)

grand sapphire
#

See the espnow component as an extra layer above the espreffis espnow.
it can do much more to make sure a normal user is protected from packets coming from other devices that uses the espnow protocol.

fringe jewel
#

does it not check e.g. the peer MAC address?

grand sapphire
#

it does as well.

fringe jewel
#

sounds like a simple first step, and if you are concerned about spoofing, then either use WiFi (with PSK encryption) or ESPNow with packet_transport's encryption

#

(or some other encryption)

grand sapphire
#

Sorry @fringe jewel, i am not going to discuss my discussion making again. Read up the thread here and under the #devs channel why i choose for this approach.

#

I value your input on it anyway.

grand sapphire
fringe jewel
#

ok

grand sapphire
#

@sick zenith What was your drive to create a new setup to send component states? and using for example the API for it?

grand sapphire
fringe jewel
#

That was my point about checking the sender MAC address. And if you don't trust the sender MAC to be correct (i.e. an active adversary), then you should be encrypting the data anyway

#

you're either not concerned about malice (in which case, checking the MAC address is enough), or you are concerned about malice, in which case only encryption is enough

grand sapphire
#

When i started this project i saw different but good implementations but just specific for one solution.
Also Espressif has an upper layer named ESP-NOW https://github.com/espressif/esp-now/ that i used as base idea for the ESPNow component.

fringe jewel
#

I've just spent 5 minutes copying the udp component (and its packet_transport layer). s/udp/espnow/g and you are 80% of the way there. fwiw.

#

the only remaining parts are converting MAC addresses in string to bytes (for the config file), importing the right libraries, and replacing the socket commands with espnow ones. Honestly, a trivial exercise.

grand sapphire
#

a mac address is only valid when you know it from the start, when you want to enable dynamically adding peers (like me) there must be more validation then just checking the mac_address. Also be aware that the mac_address can be changed.

fringe jewel
#

sure, any MAC address can be changed. The point being that a non-malicious person won't do that, and a malicious person can copy all of your other visible (non-encrypted) information anyway - so only encryption will stop them

grand sapphire
#

There is also on extra issue with ESPNow, when a device does a broadcast the receiver was not know that the reseived packet was send as broadcast and did not know from who it was comming.

grand sapphire
#

That is why i choose to have a fixed header identification for all applications within esphome.

grand sapphire
#

Jesse, may i ask your opinion about my idea of the "APP's" structure I want to introduce for the espnow protocol? It is a very new concept within ESPHome framework. Before I want to continue with it, i like to know what you throughs about it. And if you have an other suggestion/concept that better fits the ESPHome framework, that i have not toughed about yet.

upper walrus
#

Can you provide more details?

#

As I have mentioned in the past, as long as the esp-now base component is only doing raw data transfer, any other component that uses it as a layer like packet_transport can encode the data however it likes. If you come up with a structure that uses a single byte to identify the compoenent that should decode the incoming packet, that is fine

grand sapphire
#

Although it is not a single byte, it is indeed like you requesting here. The esp-now component does what you suggesting.
What i was thinking was that for example "packet_transport" or the API would be seen as "APP" with the esp-now component.
But looking at esphome this idea is new to it. But not sure how to name it better.

upper walrus
#

well its just up to the components that receive the callback to decode or ignore if the data is not for them

#

I wouldnt go as far as to label them APPs

#

its just a component that uses another component

grand sapphire
grand sapphire
#

Question, Would any of you is waiting on the multicast support within the ESPNOW component?
I'm thinking of removing it for now. I have a hard time thinking of how to handle this method.

haughty hollow
#

What's the problem with it?

grand sapphire
#

i was hoping to have a good way to report back responses correctly.

#

i like to think an about it a bit more and want to current version properly running and able to be released.

haughty hollow
#

I would be fine with broadcast not expecting a response.

#

That's normally how broadcast works.

upper walrus
#

100%
Send and move on

#

It's up to the component layer on top to monitor for responses if it wants one, not the job of the espnow component

grand sapphire
#

Please note that multicast is not the same as broadcast πŸ˜‰
With multicast esp-now sends a message to all known/registered peers and the receiver side will report back.

upper walrus
#

multicast is not the same as multicast

Did that mean to say multicast is not the same as broadcast?

grand sapphire
#

oww yes that was what i mend to say 😦 It is very hot here and my brain is somewhat off.
I fixed the line for future use.

haughty hollow
#

What is multicast in the context of esp-now?

#

For IP, it's just a different kind of broadcast.

north sundial
#

I guess that same applies to esp now because it's L2 protocol. If 8th bit of mac address is set, then it's multicast addr. That's just a broadcast to specific address where each device can listen it. Drawback is that it's not encrypted. Encryption is only provided if devices are paired and message is unicast

upper walrus
#

It's easy enough to encrypt in the layer above espnow

grand sapphire
haughty hollow
#

Is that something specific to your implementation or is that how multicast works with esp-now? How does "registering" work?

haughty hollow
#

I think you're misunderstanding something. Multicast is equivalent to broadcast, there's no pairing. You add the multicast address instead of a specific peer.

#

There's no encryption on multicast same as broadcast. I'm having some difficulty finding out what the multicast address actually is...

#

Oh, I get it. It's the same as multicast in ordinary IPv4 networking.

grand sapphire
haughty hollow
#

What is my confusion?

#

Are we talking about different kinds of multicast?

#

I don't think so. There's no way to send to multiple devices at the same time except for broadcast/multicast.

grand sapphire
#

no no, i mean between broadcast and multicast. In the end you got it right.

haughty hollow
grand sapphire
#

i think you misunderstood me, because you are right. and that is how it is implemented

haughty hollow
#

"When you register a couple of peers in esp-now then when you send a mutlicast message" sounded like you meant the sender was registering them.

#

Anyway, so multicast is exactly the same as broadcast. Send and be done.

grand sapphire
haughty hollow
#

Weird. I guess just ignore those.

keen mulch
#

Hello, I want to ask you if espnow component could work with the new Arduino Framework 3.x . Because I know that you were using some new callbacks.

grand sapphire
#

i have not testing it. I'm working on a ligher version.

grand sapphire
keen mulch
#

Eink display, I couldn't find a esp idf library and I only found the Arduino library, I tried to rewrite it work with ESPHome but it wasn't working.

#

Yeah I know, it's stupid of me.

grand sapphire
#

is there no version that works? @sick zenith have you experience with the current versions afailable in ESPHome?

sick zenith
#

Β―_(ツ)_/Β―

grand sapphire
keen mulch
grand sapphire
#

kk. there is nothing blocking you from using Arduino. as long it uses IDF 5.0.1 and higher

grand sapphire
upper walrus
grand sapphire
#

i know πŸ˜„

#

I still think the other version is better suitable for esphome 😐 I plan now to have it as a shell around the new basic espnow component.

upper walrus
#

That is how it should work, espnow just sends or receives data, doesnt care about anything else

#

if someone wants to use packet_transport on top then all the headers are provided by that component

#

if someone wants to listen to wizmote remotes, then the wizmote component will parse the raw received data

#

if someone wants to stream audio over espnow, then a component between the microphone and espnow can encode it and wrap each packet

#

That is how ESPHome is supposed to work and be modular

grand sapphire
#

that is one of the reasons i made this version. But i wanted to have a finished solution after so long working on the other version.

#

I do not disagree on that πŸ˜„

upper walrus
grand sapphire
#

I tried to make it easier for people to use, but i got more questions then positive reactions.

north sundial
grand sapphire
#

thanks for reviewing the code sofar @north sundial

north sundial
#

Will do the rest when I catch some free time πŸ™‚

grand sapphire
#

i did have one question: ESP_LOGE(TAG, "Payload size %zu is larger than max (%d bytes)", size, ESP_NOW_MAX_DATA_LEN);
did you mean i should replace %zu with %d?

north sundial
grand sapphire
#

kk

upper walrus
#

Please dont modify this PR anymore, I have it checked out locally and making changes today to improve the code quality

grand sapphire
#

cool i will see what you come up with. i will not touch it/

upper walrus
#
[09:22:32][D][espnow:273]: <<< [D8:A0:11:8F:9D:9D -> FF:FF:FF:FF:FF:FF] 91.F1.00.00.00.20.01.01.64.46.34.AC.74 (13)
[09:22:32][D][main:224]: Received from wizmote
[09:22:32][D][main:231]: Received from any peer
[09:22:32][D][main:234]: <<< [D8:A0:11:8F:9D:9D -> FF:FF:FF:FF:FF:FF] 91.F1.00.00.00.20.01.01.64.46.34.AC.74 (13)
#
espnow:
  auto_add_peer: true
  on_receive:
    - address: D8:A0:11:8F:9D:9D
      then:
        - logger.log: "Received from wizmote"
    - then:
        - logger.log: "Received from any peer"
        - logger.log:
            format: "<<< [%s -> %s] %s"
            args:
              - format_mac_address_pretty(info.src_addr).c_str()
              - format_mac_address_pretty(info.des_addr).c_str()
              - format_hex_pretty(data, size).c_str()
grand sapphire
#

where can i see the changes?

upper walrus
#

Come look on my computer

#

🀣

#

ill push them soon

#

just doing an actual esphome to esphome device test now

#

ive only been testing with my wizmote as that was easy

grand sapphire
#

did you remove the espnawpacket this is something i need myself.

upper walrus
#

Yes and no

#

its not removed, but its purpose has changed...haha

#

More simple for this initial PR

#

I probably bumped the line count though

#

lol

grand sapphire
#

Yes then you need to remove parts again πŸ˜›

upper walrus
#

I can override too-big...haha

#

I need to buy more short usb-c cables...haha
I keep runnigng out

grand sapphire
#

this is not a big pr at all

#

i got a lot of 10cm cables

#

from ali

upper walrus
#
[09:36:06][D][espnow:273]: <<< [98:88:E0:16:A5:C8 -> 98:88:E0:16:A3:68] 42.75.74.74.6F.6E.20.50.72.65.73.73.65.64 (14)
[09:36:06][D][main:231]: Received from any peer
[09:36:06][D][espnow:273]: <<< [98:88:E0:16:A5:C8 -> FF:FF:FF:FF:FF:FF] 42.75.74.74.6F.6E.20.50.72.65.73.73.65.64.20.62.72.6F.61.64.63.61.73.74 (24)
[09:36:06][D][main:231]: Received from any peer
#
binary_sensor:
  - platform: gpio
    pin: GPIO0
    name: "GPIO0 Button"
    on_press:
      - espnow.send:
          address: 98:88:E0:16:A3:68
          data: "Button Pressed"
      - espnow.broadcast:
          data: "Button Pressed broadcast"
grand sapphire
upper walrus
#

wrong link?

#

I guess that makes for an easy bridge

grand sapphire
#

yeah or for testing

upper walrus
#

Ok im happy with my code for now

#

gonna push it up

grand sapphire
#

ohh yes i have seen them as well

upper walrus
#

pushed

grand sapphire
#

ufl?

#

or did you pushed it to my branch?

upper walrus
#

yeah, your branch/pr

grand sapphire
#

found it. i'm not sure i like the new ESPNOWPacket layout.

upper walrus
#

It is internally used to get the data from the callback in the wifi thread into the main application loop

#

It is modelled from the ble_event we use to do the same to get ble data from the bluetooth thread/callback to the main application loop

grand sapphire
#

The reason i did not do uint8_t data[ESP_NOW_MAX_DATA_LEN]; for example is that not every packet uses the same data lengte

upper walrus
#

Please look at the PR as a whole

#

This uses the LockFreeQueue

grand sapphire
#

also in newerer versions you can have sizes of 1024 or so

upper walrus
#

It pre-allocates ESPNowPacket as they get requested, or reuses them

#

so hardcoding the size allows there to me no extra memory allocated once the app has been running for a while and speeds everything up

#

is there a new version?

grand sapphire
#

yeah espnow v2

upper walrus
#

that code is not in idf?

grand sapphire
#

it is\

upper walrus
#

Well if it changes ESP_NOW_MAX_DATA_LEN, then ESPHome is already ready for it

grand sapphire
#

but i have not found a way yet to initialize

upper walrus
#

Ah i just now see ESP_NOW_MAX_DATA_LEN_V2

#

haha

#

We can expand in another PR later once you can get v2 working as well

grand sapphire
#

I will make a copy of my work and load your work tomorrow.

#

then compare the differences

#

do we really need to have the same structor of ble? o do you not mind when i clean it up

upper walrus
#

Yes, it needs to be like what I have changed it to

grand sapphire
#

can you tell why?

upper walrus
#

before you were triggering all the triggers etc still inside the wifi thread and not the main app loop

#

and as we found out in the past, incoming data with bluetooth was corrupting the heap because of this

#

that is all now gone with this model

grand sapphire
#

yeah okay when you solved that issue then it will be a good thing, i was just talking about the packet

upper walrus
#

Whats wrong with the packet?

grand sapphire
#

i find it to complex

upper walrus
#

Nothing outside the parsing of the incoming data needs to touch or know about ESPNowPacket

grand sapphire
#

i disagree on that

upper walrus
#

any other components will just call espnow->send(...) with an address pointer and a data vector

#

its the responsibility of the other component to structure its data before sending it to espnow

#

same as udp

#

I think you keep wanting to put too many concerns inside the espnow component itself

grand sapphire
#

in my oginal version the packet was used for sending and receiving packets. in future PR i want to create that back.

upper walrus
#

It should not do it that way

#

if you want a structured packet you can call write8u etc on, then that is part of your component building the data, not part of espnow, it doesnt care, it just wants to know the address and the raw bytes to send

grand sapphire
#

With on difference it will be an extension of the this espnow packet

upper walrus
#

there is no common packet structure needed to exist inside the espnow component, so nothing to extend

#

The ESPNowPacket right now is only for internal use in the espnow component, it shouldnt be used or referenced by any other component

grand sapphire
#

as i said i disagree on that part.

#

i see the packet as the backbone of the espnow component and his extensions. It will store the statuses of when and if it is sent successful or not.

upper walrus
grand sapphire
#

i do not think we see the same big picture. Continuing arguing doesn't seem like a good idea to me.
And for that; i think i will continue with my own project and create my own version

sick zenith
#

I'm with Jesse on this - the hub component should not need to know anything about the data it handles other than its length. Anything else goes in a separate component.

haughty hollow
#

Although the question is, how do those other components register? Does every packet go to every registered component or is there some sort of filtering?

upper walrus
haughty hollow
#

That's true.

upper walrus
#

The first one will most likely be packet_transport

#

But I will migrate my wizmote component to this too

#

There would be no easy way for components to register for only certain types of packet
For example these are two "off the shelf" devices that use esp-now

Wizmote On button

[10:39:11][D][espnow:273]: <<< [D8:A0:11:8F:9D:9D -> FF:FF:FF:FF:FF:FF] 91.F2.00.00.00.20.01.01.64.C9.6E.75.F3 (13)

The wizmote stuff can be cast to a packed struct for the data

M5StampFly broadcasting its existance to the RC

[10:44:03][D][espnow:275]: <<< [48:CA:43:3A:50:A0 -> FF:FF:FF:FF:FF:FF] 03.48.CA.43.3A.50.A0.AA.55.16.88 (11)

03 = Im using channel 3, come talk to me there
next 6 are mac address
final 4 are a marker to say this is a discovery "command"

#

a wizmote component could possibly be set to filter mac prefixes that are Wiz MACs

sick zenith
#

I haven't looked at the PR but presumably you can have multiple configs for different targets.

upper walrus
#

for sending?

#

not sure what you are asking sorry

grand sapphire
#

The component side is a lot cleaner then i did have. I think having a queue of 16 is much to small when having audio send over espnow.

upper walrus
#

we can increase

grand sapphire
#

Also seeing every received packets send to each component is not something i would love to see.

upper walrus
#

i spammed remote buttons and never got drops as it can go quite fast and the remote sends like 10 messages each button press

upper walrus
grand sapphire
#

no that is not a solution as well. only doing some sort of checking the data is imho the solution.

upper walrus
#

Did you read my above message with actual real data?

#

If we do that, the espnow component becomes bloated with so much other component stuff

grand sapphire
#

also when an extension handled the received packet it should return true the loop can be broken.

upper walrus
#

we certainly dont need to continue the loop when one is successful

grand sapphire
#

When you look at "M5StampFly broadcasting its existance to the RC" This one of the very bad use case of usingn the espnow protocol, They did not think ahead to make sure that every receiver knows what to do with it.

haughty hollow
#

I was going to say that all those devices seem to assume they're the only ones using it. πŸ™‚

grand sapphire
#

Yeah indeed.

haughty hollow
#

But there isn't really any way around that now.

grand sapphire
#

The only way is to make sure we do not make the same mistake.

upper walrus
#

No, it works well with multiple jsut fine, when you boot the rc, you press the button on the drone, then it goes into broadcast mode, then you press the button on the RC and it also goes into pair mode.
Once they have found each other it is unicast

#

So once you know the mac, and what "protocol" it is speaking, you can filter it better

haughty hollow
#

It needs to allow runtime settings of the mac then.

upper walrus
#

I dont disagree it would be nice for them to put a header, but we have to work with what exists and we can of course put a header inside the packet_transport platform for example, but it needs to be done in a way that that does not exist inside the espnow component to stop it getting bloated

#

the macs can be all done at runtime, nothing is required at compile time

sick zenith
upper walrus
#

Ah I see. Well it kinda does already. Either you list the peers in config, or set auto_add_peers: true and it will add them as packets come in or are sent out.

fringe jewel
#

Perhaps the ->send() method could have a sent() callback for that specific packet? Then you don't need to spam every component that has registered to receive sent notifications? Only the component that actually sent the packet in the first place?

upper walrus
#

Ill have a think on how to maybe do that

fringe jewel
#

If you need the actual data, can't you make a lambda that contains a reference to the packet that you are sending? Although that then brings lifetime rules into play, which is not great.

#

but yes, I would expect that knowing which address received the packet should be enough, assuming that packets are sent in order.

#

for example, I have a Texecom alarm that I can send a command to, get an answer from, and then need to follow up with a second command. I use the following code for that:

  ResponseCallback commit_callback_ = [this](WintexResponse response) -> optional<AsyncWintexCommand> {
    if (response.answer != WintexResponseType::ACK) {
      ESP_LOGE(TAG, "Unexpected response to command: %d", (uint8_t) response.answer);
    }
    return {};
  };
  AsyncWintexCommand commit_ = AsyncWintexCommand(WintexCommandType::COMMIT, this->commit_callback_);
  optional<AsyncWintexCommand> command_callback(WintexResponse response) {
    if (response.answer == WintexResponseType::ACK) {
      if (commit_required_) {
        return commit_;
      }
    } else {
      ESP_LOGE(TAG, "Unexpected response to command: %d", (uint8_t) response.answer);
    }
    return {};
  };

i.e. some commands require a commit answer, and some do not. So I use two different callbacks to handle that.

grand sapphire
grand sapphire
grand sapphire
fringe jewel
#

I had actually missed the comment re having multiple espnow: configurations, though. Can this not be done with that approach? i.e. each configuration gets an id:, and then you only reference that configuration in the relevant component, and so the callbacks registered for that instance of espnow would only be for the components that are using the instance, and therefore there would not be a case where the callback goes to a component that is not expecting it.

upper walrus
#

No, espnow is global and you can only create one instance and have one received and one sent callback from the underlying library

fringe jewel
#

is there a reason for that?

#

sorry, I was misreading your comment. one callback only

sick zenith
grand sapphire
#

Something like that was implemented.

fringe jewel
#

seems to me that we have a queue of packets ready to be sent. We send the first packet, and wait for the callback to say that it was successful, or a timeout. We transfer the packet to a completed queue, schedule the component callbacks and return. When we get around to the component callbacks, we have a ref to the packet that was sent, so we can provide it to the original sender as part of their callback. They can then take whatever action is required. Once the callback is complete (i.e. all components callbacks have been called, or one returned true), the packet can be freed or transferred to a free queue.

grand sapphire
fringe jewel
#

potentially, another filter function provided by the component to identify packets that it is interested in.

grand sapphire
#

based on your opinions i will take my hands of from the lighter version of espnow compoment and concentrate on PR#7141 again.

#

Sorry @jesse, i can't put my name on a project where i only see beers continueing this way.

upper walrus
fringe jewel
#

ok, so no queue needed then

sick zenith
#

The "original sender" is the hard part. Using a lambda as a callback makes it opaque and it can just be scheduled with defer() so no callback queue needed.

fringe jewel
#

I was considering the recommendation to not do anything heavy in the callback, such as calling into all of our components

grand sapphire
#

yes. that is that can be done when you have ONE intergration, when you want to beable to use multiple integerations over espnow the send callback is useless

fringe jewel
#

but if it can easly be deferred, that is also great

grand sapphire
fringe jewel
#

it does sound like a per-packet callback lambda is the way to go. If the calling component wants it to be a simple function, that is also great, but having the option to add state into the lambda sounds useful.

sick zenith
#

Yes, it allows the calling component complete control over what state it wants returned, using lambda captures.

#

All the ESPNow component need return is a result code.

grand sapphire
#

can you show a markup of how you see that working?

sick zenith
#

Not tonight πŸ›Œ

#

But it's just like using set_timeout()

grand sapphire
#

that i understand, but i do not see how this function within espnow. like what the lamda does.

upper walrus
#
global_espnow->send(address, data, [this](status){ log(success); })
grand sapphire
#

ha yes, that could work. i think

fringe jewel
#

where the lambda can hold a reference to data to know what data was successfully sent.

upper walrus
#

Well the caller can put whatever it wants in []

grand sapphire
#

what happens when 2 extensions send a packets while waiting for the first

sick zenith
#

There has to be a sending queue.

grand sapphire
#

that sounds like my old PR\

upper walrus
#

It should be anyway as the docs say

fringe jewel
#

or, get a false back to indicate that the component is busy already, and to retry later

upper walrus
#

Well the main difference in my changes is the receiving part

sick zenith
#

The key with the simplified version as I understand is that the data is opaque to the ESPNow component, all upper protocols are handled elsewhere.

fringe jewel
grand sapphire
upper walrus
#

Simpler to queue

sick zenith
fringe jewel
#

then you need backoff logic, when the queue is full

upper walrus
#

We can add a timeout though

fringe jewel
#

so still have to retry πŸ™‚

upper walrus
#

If not sent in this many millis, callback with failed

sick zenith
fringe jewel
#

especially if we are talking about sending audio (which was mentioned previously)

grand sapphire
#

all inplemented in the #7141 PR

sick zenith
#

Audo just gets dropped if the queue is full.

#

Can't retry

#

πŸ’€

fringe jewel
#

good night

upper walrus
#

😴

grand sapphire
#

good night Clyde and thanks for your input

fringe jewel
#

feels like you need a "slot" per each component using espnow, thinking about it. If you have one component ready to send a new packet as soon as its callback is invoked, there may never be an opportunity for a second component to send its own packet.

grand sapphire
#

i do not get you?

fringe jewel
#

depending on how large the queue is. Component A sends a packet, waits for the callback. As soon as it gets the callback, it sends the next packet, as is suggested in the docs. Component B wants to send a packet. If the queue has space, it is accepted and its callback will be called eventually. Or, the queue is full, and it gets dropped with an error. If there are too many components wanting to send messages, more than there are spaces in the queue, then one component can be starved.

#

so, make the sending queue size equal to the number of components actually using the espnow mechanism, I guess, since each component should only ever have a single packet in flight at any time.

grand sapphire
#

yeah you are right.

#

there are also moment's you do not want to wait for the sent callback, for example when you want to stream audio over espnow.

#

Jesse, everything we talked above was build in my original espnow version. so i will continue on that version. However i will rename that component to nowtalk so it will not be confused with the lighter espnow version i have removed my name from it because i cant support that one within the esphome environment

fringe jewel
#

well, as per the docs, you should really wait for the callback before sending the next packet. But if there is a deferred callback, then the gap between sending goes up, and the packet rate goes down.

upper walrus
#

Ok callbacks added to send function with a queue to send
Because of this I also gave the send action some more options, on_sent, on_error, continue_on_error, wait_for_sent

#

So the send action will by default not execute the action after it until the sent callback has triggered, and if you set continue_on_error: false, then it wont even continue the automation after it

grand sapphire
#

Jesse, i extended the send method with a regular pointer/ size variation, that i needed to be used with the intercom component.

grand sapphire
#

Jesse, on thing I wanted to add to the espnow componentis a wayto store the list of peers and the channel that is being used. I did do a search to see if there was an component doing the same but I was not able to find something useful. Do you have an idea on doing this?

grand sapphire
upper walrus
#

You dont

#

Oh will, you edited your message?

#

I saw while...haha

grand sapphire
#

Yeah that was a typo

upper walrus
#

The on_sent and on error will still be executed, but any action after espnow.send will execute straight away

#

Setting to false basically turns it into parallel instead of linear

grand sapphire
#

Ohh that is how it works.

upper walrus
#

So by default it is true.
Which means if you have many send actions in a list, each one will only send after the previous has received the callback

#

You don't need on_sent for the behaviour to still happen

grand sapphire
#

BTW I was not talking about the action but more about the send call directly.

upper walrus
#

There is no wait for sent for the direct send call

#

Any components need to wait themselves for the callback to trigger

grand sapphire
#

Yeah, clear got it.

#

I added a boolean flag which is set when I call send and unset within the lambda. And it will only send whan the flag is unsent.

#

Sadly other then compiling, i was not able to test it yet.

#

Because there sames something wrong with the build process. As you must be aware right now

upper walrus
#

Have you tried using WSL? There are not many people that could fix it that use Windows so its hard to reproduce and fix

grand sapphire
#

Q: i'm a bit struggling with how to filter on specific and non registered address with the intercom component. should we do this in the extension or could we do that in the espnow component.
I have i added a simple header to the stream and check it in the intercom and that works fine so far.
But i still think we should do that in the espnow component.

haughty hollow
#

What do you mean by filter? And what addresses?

upper walrus
#

Each extension can do it's very quick header check and simply return false which I'm guessing is what you have done now?

grand sapphire
#

i was more thinking about when every extension needs to check if the mac_address is registered ... then that could be more time and space spend that could be saved when it is done at espnow level.

grand sapphire
grand sapphire
# upper walrus Tbh, it won't make a lot of difference, either there is an if condition inside t...

There are a couple of reason's, outside the cpu cycles, which is not something i look at right now, why we should do some simple checks on the received packet within the espnow component, so we do not need to repeat them in every extension. Like testing the incoming mac_address if it is a registered device/node, when not. the end-user must decide if added or ignored before it can be forwarded to the receive handler.

#

I also belief we should not forward broadcast messages via the current receive handler instead it should have its own receive handle for broadcast. Splitting those will prevent unwelcome issues when an extension forget to check it there selfs.

grand sapphire
#

Okay thanks. I will add them.

grand sapphire
#

One new question do we want to support MultiCast (Sending a message to all registered/paired peers with one send call)?

fringe jewel
#

How would that be implemented? Is it a single call using the underlying API?

grand sapphire
#

yes, when you do espnow_send(nullptr, data, size) it will send to all the paired peers.

#

the big difference is on the sent callback part.

#

This will be called for every peer where the packet is send to.

grand sapphire
grand sapphire
#

There is one thing that would want to see address about the yaml triggers. they should be called after the extensions have been checked. In my original version i broke them out in two sets. but in the current version i do not see that as a solution anymore. As someone an suggestion on how to resolve this?

grand sapphire
#

@upper walrus I'm testing the espnow component with my changes but i must doing something wrong.

#

I have the following code that I use to test. But it does not like to work properly. Only the first espnow.send: seems to work

#

also the on_sent: and on_error: are not show in the log's.

upper walrus
#

ill give it a test

#

i also found some more cleanups in the current branch πŸ™‚

grand sapphire
#

from what i now can see the sendf_callback is not fired

upper walrus
#

Sounds like you broke it then as it was working fine with successive packet sending and putting delays etc in the callbacks

#

im looking now

grand sapphire
#

i did not touched the send part ar all.

#

maybe the loop() πŸ™ making sure that only can send to paired (registered) peers

upper walrus
#

you have auto_add true so that shouldnt matter

#

Ive altered the send function locally to make ti a bit cleaner

#

ill let you know what happens

#

Sender:

[10:05:16][D][binary_sensor:026]: 'GPIO0 Button': New state is ON
[10:05:16][D][binary_sensor:026]: 'GPIO0 Button': New state is OFF
[10:05:16][D][binary_sensor:026]: 'GPIO0 Button': New state is ON
[10:05:16][V][espnow:310]: >>> [98:88:E0:16:A3:68] OK
[10:05:16][D][main:257]: A: ESPNow message sent successfully
[10:05:16][V][espnow:310]: >>> [E8:6B:EA:24:22:04] Send fail
[10:05:16][D][main:055]: B: ESPNow message failed to send
[10:05:16][V][espnow:310]: >>> [FF:FF:FF:FF:FF:FF] OK
[10:05:16][D][main:065]: C: ESPNow message sent successfully
[10:05:16][V][espnow:310]: >>> [FF:FF:FF:FF:FF:FF] OK
[10:05:16][D][main:079]: D: ESPNow message sent successfully
[10:05:16][D][main:087]: done
#

Receiver:

[10:05:16][D][main:202]: Received from second ESPHome device
[10:05:16][D][main:209]: Received broadcast message
[10:05:16][D][main:209]: Received broadcast message
#

working fine for me, I copied your sending yaml above and changed the A mac address to match my receiver

#

B fails as expected but automation continues

grand sapphire
#

Hmm odd

#

can you commit your changes?

upper walrus
#

my logs from above were without any changes

grand sapphire
#

I see none of the above

upper walrus
#

i stashed my changes to make sure i didnt fix a bug randomly

#

im about to push some cleanup commits now though

grand sapphire
#

πŸ‘

upper walrus
#

deleted 30 lines of unneeded code πŸ™‚

grand sapphire
#

πŸ˜„

#

did you do that also in automations.h?

upper walrus
#

thats where i deleted the code

#

triggers dont need parent, and broadcast and unknow peer dont need address

grand sapphire
#

ha yes. that was something i needed to remove indeed.

#

this is what i see when i run it:

[23:47:20][D][binary_sensor:026]: 'Button': New state is OFF
[23:47:26][D][binary_sensor:026]: 'Button': New state is ON
[23:47:26][D][main:367]: send packet (A)
[23:47:26][D][binary_sensor:026]: 'Button': New state is OFF
[23:47:27][D][binary_sensor:026]: 'Button': New state is ON
...
upper walrus
#

ah, just realised broadcast still can use address as the source address

grand sapphire
#

yeah but i am fine when you remove it πŸ˜„

#

whould it make sense for you when the call to send() false. that we also play the on_error process?

#

In the send action

upper walrus
#

yes that makes sense

#

done and pushed

grand sapphire
#

top. One small question about the handler; would you have prefect that the on_unknown_peer, and on_broadcast and on_received in one handling class instead of separate classes as it is now?

upper walrus
#

The only thing common is the address of received and broadcasted, doesnt really make sense for now

#

oh i misunderstood your question

#

umm, one class is actually probably fine with empty function bodies as not all components need to implement all of them

#

im heading out, back in a couple of hours

grand sapphire
#

that was what i had first, but then i looked at the triggers and decided to change it to seperate handling classes.

upper walrus
#

either is fine tbh

grand sapphire
#

One more suggestion: in send_() when espnow_send() fails; i think we should call the callback as well with the error code. And call this->status_momentary_warning("send-failed"); only when there is no callback given.

#

have a nice day. i'm of to sleep.

#

callback(err);
Can you do this without checking if callback != nullptr?

upper walrus
#

I reverted what I added, calling the callback. The send function returns the value directly so no need to involve the callback at all.
Updated the trigger to check the return from send() and components using it should do the same.

#

But I call it if esp_now_send fails

grand sapphire
#

it seems to work all 3 send actions are being executed now, but i still see some strange reporting, i modified the send_callback in the espnow.send: automation for testing only.

  send_callback_t send_callback = [this, x...](esp_err_t status) {
      if (status == ESP_OK) {
        esph_log_d("espnow","send action callback: OK");
        if (this->sent_.empty() && this->flags_.wait_for_sent) {
          esph_log_d("espnow","continue");
          this->play_next_(x...);
        } else if (!this->sent_.empty()) {
          esph_log_d("espnow","play sent");
          this->sent_.play(x...);
        } else {
          esph_log_e("espnow","Why am i here?");
        }
      } else {
        esph_log_d("espnow","send action callback: ERROR");
        if (this->error_.empty() && this->flags_.wait_for_sent) {
          if (this->flags_.continue_on_error) {
            esph_log_d("espnow","continue");
            this->play_next_(x...);
          } else {
            esph_log_d("espnow","stop_complex");
            this->stop_complex();
          }
        } else if (!this->error_.empty()) {
          esph_log_d("espnow","play error");
          this->error_.play(x...);
        } else {
          esph_log_e("espnow","Why am i here?");
        }
      }
    };
#

and resulting in the following log messages:

[09:01:32][D][binary_sensor:026]: 'Button': New state is ON
[09:01:32][D][main:360]: send packet (A)
[09:01:32][D][espnow:055]: send action callback: ERROR
[09:01:32][D][espnow:065]: play error
[09:01:32][D][main:373]: A: ESPNow message failed to send
[09:01:32][D][main:377]: send packet (B)
[09:01:32][D][espnow:055]: send action callback: ERROR
[09:01:32][D][espnow:065]: play error
[09:01:32][D][main:373]: A: ESPNow message failed to send   <<= this
[09:01:32][D][binary_sensor:026]: 'Button': New state is OFF
[09:01:32][D][espnow:044]: send action callback: OK
[09:01:32][D][espnow:049]: play sent
[09:01:32][D][main:386]: B: ESPNow message sent successfully
[09:01:32][D][main:394]: send packet (C)
[09:01:32][D][espnow:044]: send action callback: OK
[09:01:32][D][espnow:049]: play sent
[09:01:32][D][main:403]: C: ESPNow message sent successfully
[09:01:32][D][main:411]: done
grand sapphire
upper walrus
#

i probably should have reverted the commits properly...haha

#

I have pushed proper revert commits now

grand sapphire
#

okay.

#

I do not belief i committed anything yet, to day

#

i will however commit the changes for using TEMPLATABLE_VALUE

upper walrus
#

I think you syncronised your local which must have already pulled the changes I made earlier today, which then merged them into the latest remote branch. Its my fault because I forced pushed HEAD back 2 commits, but its fixed now

grand sapphire
#

yes, that must be it. This should also fix the above issue i saw.

#

I have a related question; how can we use LOG_STR_ARG(espnow_error_to_str(err)) in yaml?

upper walrus
#

You cant as espnow_error_to_str is not exposed in any header, it is internal to the espnow_component.cpp file only just like the other various _to_str functions we have in ESPHome

grand sapphire
#

Let say we expose it in the header πŸ˜„

#

I love to give the user a proper error message when something fails.

#

For me that is the last thing we should have in this PR. afterthat it can be merged as soon you like πŸ˜›

upper walrus
#

As i wrote that, I found a way to do it, using something = std::array...

#

then put something in TEMPLATABLE_VALUE

grand sapphire
#

yeah i agree on that, could we put that into the follow up PR?

#

like you, I liked to use of std::array for this, but the TEMPLATABLE_VALUE did not worked for it.

#

That is the only reason i switched to the std::vector for now.

grand sapphire
upper walrus
#

Also the compile test is failing on the PR now

#

Ill have a look at the final changes in the morning, heading off work stuff for the evening

grand sapphire
#

kk.

grand sapphire
#

okay it is now compiling and running fine

grand sapphire
#

Here is the intercom example again.

grand sapphire
upper walrus
#

Sorry, didnt have time yesterday to look. Wll try today

grand sapphire
#

have fun with it πŸ˜„

upper walrus
grand sapphire
#

Jesse, about the " Disallow on_unknown_peer with auto_add_peer: true" commit. would it not better when we always call on_unknown_peer?

grand sapphire
#

@upper walrus :

async def to_code(config):
    print(config)
#

left over from testing something?

upper walrus
#

Lol woops

upper walrus
upper walrus
grand sapphire
#

I found an other issue just now. i belief enable(); will do nothing at all. Or do i really misread the code?

#

why are we setting this->state_ = ESPNOW_STATE_OFF; in it?

upper walrus
#

Off means enabled but not ready

grand sapphire
#

okay.

#

not sure why yet.

#

the thing i did is moving the loop part you added to an xTask to see if it worked faster/sounds better.
and disabled the wait for sent in the intercom component.

keen mulch
#

Hello,
I have question if it was possible to make a esp-now communication mesh.

Thanks

grand sapphire
#

how do you like to see that working?

#
  • technical πŸ˜›
keen mulch
#

Firstly the two peers would have some encryption key so the peers that are re-broadcasting the message could spy on them. And then I would send broadcast message and in the message would contain the address of peer that the message is sent to. And the peer that is "resending" the message it would only send once (he would resend the same message only once).

grand sapphire
#

i think it is a lot of work to implement. i do not see myself doing this, sorry/

weak galleon
grand sapphire
#

Jesse, i'm playing with the "on_unknown_peer" atm. and question myself is all registered extensions should be notified or like the "on_received" only until the first that returns true?
The idea behind the "on_unknown_peer" can do some prep work when a new peer is found.

#

Sadly i see a lot of bears around it and i cant get my brain to resolve the solution.

weak galleon
#

Is there any chance this might get ported to esp8266 ?
Even if it's with less functionality like only basic send and receive it would be great.

upper walrus
weak galleon
grand sapphire
keen mulch
keen mulch
#

Hello @grand sapphire,
I am still using your EspNow library from the 21/02/2025 https://github.com/nielsnl68/esphome/tree/f766c452fee4e32d51225560362c7858bf754741/esphome/components/espnow

because it has the command function and not the application. It suits my project.

And I want to ask you if you was experimenting with the

peer_info.encrypt = true;

Or you were always using the peer_info.encrypt = false;?

I am planning to try the encryption and I want to know your insights πŸ™‚

Thx in advance

grand sapphire
#

go for it πŸ˜„