Lessons learnt from ESP NOW

For those of you who aren’t aware of ESP NOW, it’s a communication protocol developed by Espressif for their ESP modules.

What began as a simple “hook up my plant lights to HomeKit”, turned into a massive home grown firmware, but more about that in another post.

I’ve successfully setup a bunch of ESP 8266 modules to talk to one “hub” ESP 8266 module, which then talks to my WiFi network and exposes everything as HomeKit accessories.

While doing so, I learnt:

  • It’s not possible to go back to light sleep with GPIO interrupt enabled after sending a payload via ESP NOW.
  • ESP NOW really really likes WiFi channel 1. On the transmitter side, setting the channel via esp_now_add_peer() doesn’t seem to be a reliable way of having it transmit on the desired channel. u/cperiod on Reddit confirmed this. His/her solution was to spawn an AP temporarily to switch the channel: https://www.reddit.com/r/esp8266/comments/lj953m/poor_esp_now_range_if_no_line_of_sight/gnemjps/
  • Since the channel needs to be fixed, ensure that your hone WiFi network doesn’t jump. I configured my router to keep to channel 6 (the best for apartment) always.
  • ESP NOW payloads are usually delivered at their first attempt, but it doesn’t hurt to add an automatic retry feature in your firmware. For me, my low power motion sensors attempt up to ten times.
  • It’s incredibly efficient when it comes to range. Payloads were delivered across two walls in my apartment. No additional antenna was used, just the standard one on the Wemos D1 Mini. Somebody on Reddit claimed four walls in their apartment :)

That’s it! I’ll write a post about the entire setup + my home built plug and play firmware later.

Sending OTA updates over WiFi to your ESP8266

This Christmas, I added a whole bunch of lights powered by 5V power sources. My goal was to switch them on at sunset, and switch them off on sunrise, by using a MOSFET for power control :)

While I was doing this, I wanted to send OTA updates of my Lua files to the ESP8266 via WiFi. For some unknown reason, I couldn’t use luatool.py’s TCP update method.

So, I ended up building my very own OTA update protocol (which turned out to be fun!). To begin, add ota.lua to your project, and invoke it using dofile("ota.lua") in your init.lua:

Send OTA updates to remotely update lua scripts on your ESP8266.
LICENCE: http://opensource.org/licenses/MIT
Created by Jude Pereira <contact@judepereira.com>
See https://judepereira.com/blog/sending-ota-updates-over-wifi-to-your-esp8266/
srv = net.createServer(net.TCP)
current_file_name = nil
srv:listen(8080, function(conn)
conn:on("receive", function(sck, payload)
if string.sub(payload, 1, 5) == "BEGIN" then
current_file_name = string.sub(payload, 7)
file.open(current_file_name, "w")
sck:send("NodeMCU: Writing to " .. current_file_name .. '\n')
elseif string.sub(payload, 1, 4) == "DONE" then
sck:send("NodeMCU: Wrote file " .. current_file_name .. "!\n")
current_file_name = nil
elseif string.sub(payload, 1, 7) == "RESTART" then
sck:send("NodeMCU: Restart!\n")
tmr.create():alarm(500, tmr.ALARM_SINGLE, node.restart)
if file.open(current_file_name, "a+") then
if file.write(payload) then
sck:send("NodeMCU: Write failed!\n")
sck:send("NodeMCU: Open failed!\n")
conn:on("sent", function(sck) sck:close() end)
view raw ota.lua hosted with ❤ by GitHub

Then, to use this shiny new TCP endpoint created on your ESP8266/NodeMCU, create a wrapper shell script:

# Wrapper script for sending OTA updates to your ESP8266 running NodeMCU.
# See https://judepereira.com/blog/sending-ota-updates-over-wifi-to-your-esp8266/
for i in "$@"; do
echo "Sending $i"
echo -n "BEGIN $FILE" | nc $HOST $PORT
while read -r line; do
#echo -n "write: $line … "
if ! echo "$line" | nc $HOST $PORT | grep "ok" &>/dev/null; then
echo "Write failed! Please retry…"
exit 1
done <"$FILE"
echo -n "DONE" | nc $HOST $PORT
echo -n "RESTART" | nc $HOST $PORT
view raw ota.sh hosted with ❤ by GitHub

Heads up! Replace HOST with the IP of your NodeMCU.

The wrapper script will automatically trigger a restart at the end. To use the wrapper script:

$ chmod +x ota.sh
$ ./ota.sh file1.lua file2.lua init.lua

And that’s it! OTA update away!