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 yet another way to do it.
- 2x ESP32 boards
- any sensor for testing, I'll be using an SHT30
You'll also need a working Home Assistant installation, ESPHome (either as a HA addon or as the CLI), and an MQTT broker (also can be an HA addon).
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
Our sensor ESP32 will send an ESP-Now message like this:
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 :
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.
ESP-Now also uses channels, although 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.
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 and just add it as an integration. When you are done, you'll have an MQTT box in Settings > Devices & Services.
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
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.
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
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.
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 is essentially a copy of the sensor device discussed in the previous article. That was estimated to live for around a month using a tiny 260 mAh battery. The biggest weakness was its several seconds to connect to WiFi and send the MQTT messages.
This new YAML is more straightforward and can run and send our two sensor measurements in 100ms. An improvement that will let it run for around 5 months.
I don't have a profiler handy, so 75.25 above is the peak current use, not the average.
The sensor coded 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: 5s 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 seconds.
I plan to continue tinkering with this. Some improvements I have in mind are:
- adding the missing icon and device information to the ESP-Now message — finished this
- re-creating the bridge in ESPHome — this wasn't so hard; the old Arduino-based bridge is here
- figure out the exact timing to put the sensor to sleep as soon as possible rather than a hard-coded
run_duration— added this one as well. It is probably the most significant saving out of all these minor improvements, saving a few milliseconds of active time equals hours of total runtime.
- keep sorting out the channel issue