ESP-NOW data: values between boards
One board reads a sensor; another shows it. No router, no broker, no internet in between.
What it is
Boards send each other the values of variables over ESP-NOW, the ESP32's own radio-to-radio link - the same link the two-way radio uses for a voice. A variable on one board is shared; a variable on another takes its value from it, and every widget bound to that variable, and every automation watching it, follows. It works on every ESP32 with Wi-Fi: the original, the S2, S3, C3, C5 and C6. The sending board need not have a screen at all, or run anything made here: a sketch of a few dozen lines sends to it.
It is written for the Arduino and ESP-IDF exports, and is part of Pro: free to design and preview with, and generating code for it needs Pro.
Three ways to reach other boards
Set on the widget, under It sends to:
| Setting | Who hears it | Private | Knows when nobody answers |
|---|---|---|---|
| The board it is paired with | One other board, paired once with the Pair action. | Yes, encrypted | Yes |
| Every board with the same group name | Any number of boards built with exactly the same group name. No pairing. | No | Knows when a board goes out of range |
| Anybody listening | Every board on the Wi-Fi channel. A board set this way also hears every board on the channel, whatever group they send to. | No | Knows when a board goes out of range |
Pairing is the radio's: run the action ESP-NOW data: pair or forget on both boards within half a minute. Each finds the other, and keeps its address and a key in flash, so it survives a restart. The two halves of the key cross the air while the boards pair, so the key keeps out anybody who was not listening at that moment, and nobody who was - what Bluetooth calls "just works". If a board's flash is erased it has forgotten its partner: pair the two again.
The widget is an icon
Drop ESP-NOW data from the palette's Ready-made group. It is a small mark - two arrows passing - in the colour of what the link is doing, and it holds the link's settings. The first one in a design is the link; any others show the same thing.
| The link is | The icon | Colour to start with |
|---|---|---|
| Not linked | No board to send to yet. | Grey |
| Pairing | Flashes: looking for another board that is pairing too. | Blue |
| Linked | Paired, or on its group or channel, and quiet. | Blue |
| Receiving | Values have just arrived. | Green |
| No answer | The paired board is not acknowledging what is sent - or, on a group or broadcast, the boards that were in range have gone quiet: off, or out of range. | Amber |
Its inspector note adds a Pair button, and a Send button where one is needed, in one step each.
Sending a variable
On the Data tab, tick Send to other boards on a variable. It is sent under its key: the variable's name, or a key of its own typed beside the tick box - up to 15 characters, no spaces. When the shared values go is set on the widget, under Shared values go:
- When one changes (to start with): each as soon as it changes, five times a second at most, and all of them every ten seconds, so a board switched on later has them.
- Every few seconds: all of them, every so many seconds.
- Only when an action sends: nothing goes until the action ESP-NOW data: send runs.
The action sends every shared variable, or one, from anything that fires an event: a button, a timer automation, a variable changing, a pin. A number goes as a number, a whole number as one, a yes or no as one byte, and text as text - up to 220 characters, which is what a packet has room for beside its key.
Taking a value from another board
Set a variable's Source to ESP-NOW data > A value another board sends and type the Key the other board sends it under. When a packet carries that key, the variable takes the value; a packet that leaves it out leaves it as it was. Each side may say it its own way: a number arriving for a text variable is written out, and text arriving for a number is read as one.
The link also says things of itself, as sources under the same heading: what it is doing (a number and a sentence), whether there is a board to send to, the paired board's name, the seconds since values last arrived - for showing a reading has gone stale - the packets heard and sent, and its quality.
Link quality
As the radio's: the share of the last eight seconds' packets that got through, marked down as the other board's signal weakens - by nothing at -50 dBm, by three quarters at -90. On a pair, a short packet goes once a second while nothing else is sent, and whether it is acknowledged is what quality and "no answer" go by. On a group or broadcast nothing is acknowledged, so quality is what is heard: a board that only sends there reads 0.
Who is in range
ESP-NOW itself says nothing of who heard a broadcast. So while it has nothing else to send, every board sends a short keep-alive once a second with its name in it. On a group or broadcast each board notes every board it hears, and its sentence says "Hearing Desk B", "3 boards in range", or "Desk B is out of range" once nothing has come from it for three seconds - twelve for a board that sends no keep-alive, such as the sketch below sending every five seconds. The other board's name is a source too.
The Wi-Fi channel
ESP-NOW rides on Wi-Fi's radio, on whatever channel that is on. In a design with no Wi-Fi of its own, the link is on the channel the widget names, and every board must name the same one. In a design that joins a network, it is that network's channel, whatever the widget says, and two boards on two networks will not hear each other.
With the two-way radio
A design can have both. ESP-NOW takes one receive callback, so the radio's module takes every packet and hands the data link those that are not a voice. They are on one channel - the radio's - and when both pair, they share one partner: a board pairs once, and either Pair action pairs both. A design with the radio alone is built exactly as it was before ESP-NOW data existed.
The packet
For anybody writing the other end themselves. Little-endian, as the ESP32 is; 250 bytes at most.
| Byte | What |
|---|---|
| 0 - 2 | 'L' 'W' 'D' |
| 3 | Version: 1 |
| 4 | Kind: 1 a pairing announcement, 2 values, 3 a keep-alive (the head, then the sender's name, up to 20 characters and a zero; the name may be left out) |
| 5 - 8 | Group: 32 bits of FNV-1a of the group name; 0 on a pair and on broadcast |
| 9 - 10 | The sender's own count of its values and keep-alives, for counting what was lost |
| 11 | How many values follow |
| 12 on | Each value: its type (1 a float, 2 a 32-bit whole number, 3 a yes or no, 4 text), its key's length (1 to 15), the key, then the value - 4 bytes, 1 byte, or for text a length byte and the text |
A receiver reads values until the count is reached or the packet ends, and stops at a type it does not know. Values that do not fit one packet go in the next.
Sending from a board with no screen
A plain ESP32 sketch, with nothing but the ESP32 core, that sends a sensor's readings every five seconds. Set the design's widget to Anybody listening on the same Wi-Fi channel - or both to one group name - and give the design's variables the keys below.
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <string.h>
#define WIFI_CHANNEL 1 // the widget's Wi-Fi channel
#define GROUP_NAME "" // "" for anybody listening, or the widget's group name
#define EVERY_MS 5000
static const uint8_t EVERYBODY[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
static uint8_t packet[250];
static int len = 0;
static uint8_t count = 0;
static uint16_t seq = 0;
static uint32_t group = 0;
static uint32_t group_of(const char * s)
{
uint32_t h = 0x811C9DC5u;
while (*s) { h ^= (uint8_t)*s++; h *= 0x01000193u; }
return h;
}
static void send_values(void)
{
if (!count) return;
packet[0] = 'L'; packet[1] = 'W'; packet[2] = 'D';
packet[3] = 1; // version
packet[4] = 2; // values
memcpy(packet + 5, &group, 4);
memcpy(packet + 9, &seq, 2);
packet[11] = count;
esp_now_send(EVERYBODY, packet, len);
seq++;
len = 0;
count = 0;
}
static void put(uint8_t type, const char * key, const void * value, int vlen)
{
int klen = strlen(key);
if (klen < 1 || klen > 15) return;
if (count && len + 2 + klen + vlen > (int)sizeof(packet)) send_values();
if (!count) len = 12;
packet[len++] = type;
packet[len++] = (uint8_t)klen;
memcpy(packet + len, key, klen); len += klen;
memcpy(packet + len, value, vlen); len += vlen;
count++;
}
static void put_float(const char * key, float v) { put(1, key, &v, 4); }
static void put_int(const char * key, int32_t v) { put(2, key, &v, 4); }
void setup()
{
WiFi.mode(WIFI_STA);
esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
WiFi.setSleep(false);
esp_now_init();
esp_now_peer_info_t everybody = {};
memcpy(everybody.peer_addr, EVERYBODY, 6);
everybody.ifidx = WIFI_IF_STA;
esp_now_add_peer(&everybody);
if (GROUP_NAME[0]) group = group_of(GROUP_NAME);
}
void loop()
{
// Your sensor's readings here: a SEN66, say.
put_float("pm2.5", 5.2);
put_float("pm10", 6.4);
put_float("rh", 45.5);
put_float("temp", 21.3);
put_int("co2", 612);
send_values();
delay(EVERY_MS);
}
Nine values with short keys - a SEN66's particles, humidity, temperature, VOC, NOx and CO2 - are about 110 bytes: one packet. A sketch of its own sends to a group or to anybody listening; pairing is between two LCD Wizard designs.
What it does not do
- A sketch of its own cannot pair: it sends on a group or to anybody listening.
- A board on a pair talks to one partner. Many boards are a group.
- The MicroPython and ESPHome exports do not write it.
- A board paired for the two-way radio while ESP-NOW data is on a group or broadcast may not hear its partner's values, which are sent in the clear: set ESP-NOW data to the paired board too.
- A list is not sent; each value goes on its own.