ESP-Now with ESPHome

Published on Jul 2, 2023


A web

ESP-Now is in Espresiff’s words:

a wireless communication protocol defined by Espressif, which enables the direct, quick and low-power control of smart devices, without the need of a router

The use-case here is battery-powered sensors. WiFi sensors take several seconds to start, connect, and send sensor measurements. Seconds don’t sound like a long time, but it is quite a long time in the world of low-power sensors. ESP-Now doesn’t need to negotiate a connection. It can just broadcast data. It seems well-suited for battery-powered sensors and would be great to use within ESPHome.

The issue is there isn’t an official component for ESP-Now yet. There are several projects doing so, though, and this will be a write-up of another way to do it.

BOM

  • 2x ESP32 boards
  • any sensor for testing, I’ll be using an SHT30

Architecture

Because we want our measurements to get into Home Assistant, and Home Assistant lives on WiFi, we’ll need to make some sort of bridge so our ESP-Now sensors can talk to WiFi. We also need a way to present our sensors inside Home Assistant.

To accomplish both of those things, I will use an ESP32 as a bridge. The bridge will connect to our MQTT broker configured for use with Home Assistant. The bridge will also listen to ESP-Now broadcast messages. The messages will contain enough information in them to allow the bridge to simulate an MQTT-based sensor. The advantage of simulating an MQTT sensor is it Just Works®, and doesn’t require the bridge to know anything about the sensor devices.

When you include an mqtt block in the ESPHome YAML. It sends two bits of information to the broker regarding the sensors:

  • a config string that is JSON with details about the measurement, unit of measurement, type of measurement, etc.
  • a state string which is the actual sensor measurement

With those pieces of information, it creates some MQTT entries which Home Assistant uses to create a new device.

To do the same thing, our sensor ESP32 will send an ESP-Now message like this:

espnow:humidity:measurement:humidity:%:41.6:mdi:water-percent:2023.2.1:esp32dev:

Each piece of information is delimited by a ’:‘.

device name : device class : state class : sensor name : unit of measurement : state : icon prefix : icon : version : board :

The content of each field are taken from the YAML, this is just for explanation purposes only.

WiFi Channels

There is some confusion that I wasn’t able to sort out. Your WiFi router operates on a particular channel (some jump around). When the bridge connects to the WiFi network, it discovers the channel as part of the connection procedure. Normally you don’t need to think much about it, but now we need another wireless protocol to co-exist with the WiFi connection and share the same channel.

ESP-Now also uses channels, and that is where I ran into issues. If I changed the ESP-Now channel to something other than 0 or 1, it threw errors. So either ESP-Now only works on channel 0 (not sure what that actually is) or 1, or I just missed something somewhere. I also had to change my router’s WiFi settings to use channel 1. Without the router change, this won’t work. If someone can sort this out, let me know on Discord.

1.

Make sure your MQTT broker is set up for Home Assistant. The instructions can be found in the addon inside Home Assistant. You’ll probably want to follow the instructions by making a new MQTT user and configuring it as an integration. Or you can use your own broker and just add it as an integration. When you are done, you’ll have an MQTT box in Settings > Devices & Services.

2.

For the bridge, have a look at the ESPHome YAML below.

esphome:
  name: esp-now-mqtt-bridge
esp32:
  board: esp32dev
  variant: esp32
  framework:
    type: arduino
logger:

external_components:
  - source:
      type: git
      url: https://github.com/u-fire/ESPHomeComponents/

now_mqtt_bridge:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

mqtt:
  id: mqtt_broker
  broker: homeassistant.local
  username: mqtt
  password: !secret mqtt_password

Setup the mqtt for your broker. You can change the Home Assistant discovery prefix (if you changed it for some reason), and it will also be used in the MQTT bridge.

3.

For our sensor device, look at the YAML below.

esphome:
  name: sht30-espnow
esp32:
  board: esp32dev
  framework:
    type: arduino
logger:

# import the components
external_components:
  - source:
      type: git
      url: https://github.com/u-fire/ESPHomeComponents/

# no need for api or wifi block when using just ESP-Now
now_mqtt:

i2c:
  sda: 10
  scl: 20

sensor:
  - platform: sht3xd
    id: sht
    temperature:
      name: "Temperature"
    humidity:
      name: "Humidity"
    address: 0x44

The ESP-Now component is just the single line of now_mqtt. There’s no need for the api or wifi block. The component works by waiting for each sensor to update its state. When that happens, it creates a line similar to the one explained above and sends it out as a broadcast message. No pairing is needed, but this also precludes encryption if that is important for you.

Make any changes for your particular sensors and upload the YAML through the Home Assistant ESPHome addon or the ESPHome command line. You’ll start to see the scrolling of sensor measurements and notifications of sending ESP-Now messages. You’ll also see those same messages in the bridge serial output, and if you are paying attention to your MQTT broker, you’ll see new entries being added.

And most importantly, you’ll also see the new sensor inside the MQTT section of Settings -> Devices and Services.

4.

But what can we do with this now, and how is it better than WiFi and MQTT?

Look at this slight modification of the above YAML.

esphome:
  name: sht30-espnow
esp32:
  board: seeed_xiao_esp32c3
  variant: esp32c3
  framework:
    type: arduino
logger:

# import the components
external_components:
  - source:
      type: git
      url: https://github.com/u-fire/ESPHomeComponents/

now_mqtt:

i2c:
  sda: 10
  scl: 20

sensor:
  - platform: sht3xd
    id: sht
    temperature:
      name: "Temperature"
    humidity:
      name: "Humidity"
    address: 0x44

deep_sleep:
  run_duration: 100ms
  sleep_duration: 5min

This particular device can operate on a very small 260 mAh battery for several months. 260 is quite small, so a larger battery can easily get into the year range.

5.

The sensor also has the option to get an on_sent notification. This allows you to enter deep sleep the moment all of the sensor measurements have been sent, saving awake time.

...
globals:
  - id: sent
    type: int
    restore_value: no
    initial_value: '0'

now_mqtt:
  on_sent:
    then:
      - lambda: id(sent)++; 
      - if:
          condition:
            lambda: 'return id(sent) == 5;'
          then:
          - deep_sleep.enter:
              id: sleep_
              sleep_duration: 5m


deep_sleep:
  id: sleep_
...

The code creates a global variable named sent. Then it increases the value each time a sensor measurement is sent. If sent equals 5, which in this case is the number of sensors, it goes to sleep for 5 minutes.

Fixing the channel issue

One fix for the channel issue is to use an ethernet-capable ESP32. Since it isn’t using WiFi, the channel doesn’t matter.

There are a handful of ESPHome-compatible boards.

You can find an example YAML here using an WT32-ETH01.