DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
espnow.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file espnow.cpp
6 * @brief ESP-NOW envelope codec + peer registry (pure) and esp_now binding (ESP32).
7 */
8
10
11#if DETWS_ENABLE_ESPNOW
12
13#include <string.h>
14
15const uint8_t DETWS_ESPNOW_BROADCAST[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
16
17// ---------------------------------------------------------------------------
18// Envelope codec
19// ---------------------------------------------------------------------------
20size_t detws_espnow_encode(uint8_t type, const uint8_t *payload, size_t len, uint8_t *out, size_t cap)
21{
22 if (!out || len > DETWS_ESPNOW_MAX_PAYLOAD || cap < len + DETWS_ESPNOW_HDR)
23 return 0;
24 out[0] = DETWS_ESPNOW_MAGIC;
25 out[1] = type;
26 out[2] = (uint8_t)len;
27 if (len && payload)
28 memcpy(out + DETWS_ESPNOW_HDR, payload, len);
29 return len + DETWS_ESPNOW_HDR;
30}
31
32bool detws_espnow_decode(const uint8_t *buf, size_t len, uint8_t *type, const uint8_t **payload, size_t *plen)
33{
34 if (!buf || len < DETWS_ESPNOW_HDR || buf[0] != DETWS_ESPNOW_MAGIC)
35 return false;
36 size_t declared = buf[2];
37 if (declared + DETWS_ESPNOW_HDR != len) // length must match exactly (no trailing/short)
38 return false;
39 if (type)
40 *type = buf[1];
41 if (payload)
42 *payload = buf + DETWS_ESPNOW_HDR;
43 if (plen)
44 *plen = declared;
45 return true;
46}
47
48// ---------------------------------------------------------------------------
49// Peer registry
50// ---------------------------------------------------------------------------
51namespace
52{
53struct Peer
54{
55 uint8_t mac[6];
56 bool used;
57};
58
59// All ESP-NOW runtime state, owned by one instance (internal linkage): the peer registry
60// (host + radio) plus the radio binding's recv callback and channel, grouped so it is one
61// named owner, unreachable from any other translation unit.
62struct EspnowCtx
63{
64 Peer peers[DETWS_ESPNOW_MAX_PEERS];
65#ifdef ARDUINO
66 detws_espnow_recv_fn recv = nullptr;
67 uint8_t channel = 0;
68#endif
69};
70EspnowCtx s_espnow;
71
72int peer_find(const EspnowCtx &c, const uint8_t mac[6])
73{
74 for (int i = 0; i < DETWS_ESPNOW_MAX_PEERS; i++)
75 if (c.peers[i].used && memcmp(c.peers[i].mac, mac, 6) == 0)
76 return i;
77 return -1;
78}
79} // namespace
80
81void detws_espnow_peers_reset(void)
82{
83 for (int i = 0; i < DETWS_ESPNOW_MAX_PEERS; i++)
84 s_espnow.peers[i].used = false;
85}
86
87bool detws_espnow_peer_add(const uint8_t mac[6])
88{
89 if (!mac)
90 return false;
91 if (peer_find(s_espnow, mac) >= 0)
92 return true; // idempotent
93 for (int i = 0; i < DETWS_ESPNOW_MAX_PEERS; i++)
94 if (!s_espnow.peers[i].used)
95 {
96 memcpy(s_espnow.peers[i].mac, mac, 6);
97 s_espnow.peers[i].used = true;
98 return true;
99 }
100 return false; // table full
101}
102
103bool detws_espnow_peer_has(const uint8_t mac[6])
104{
105 return mac && peer_find(s_espnow, mac) >= 0;
106}
107
108bool detws_espnow_peer_remove(const uint8_t mac[6])
109{
110 int i = mac ? peer_find(s_espnow, mac) : -1;
111 if (i < 0)
112 return false;
113 s_espnow.peers[i].used = false;
114 return true;
115}
116
117int detws_espnow_peer_count(void)
118{
119 int n = 0;
120 for (int i = 0; i < DETWS_ESPNOW_MAX_PEERS; i++)
121 if (s_espnow.peers[i].used)
122 n++;
123 return n;
124}
125
126// ---------------------------------------------------------------------------
127// ESP32 radio binding
128// ---------------------------------------------------------------------------
129#ifdef ARDUINO
130
131#include <esp_idf_version.h> // ESP_IDF_VERSION / ESP_IDF_VERSION_VAL for the recv-cb ABI guard
132#include <esp_now.h>
133#include <esp_wifi.h>
134
135namespace
136{
137// The ESP-NOW receive callback signature changed in ESP-IDF 5.0 (Arduino-ESP32 3.x): the
138// source MAC moved into an esp_now_recv_info_t. Match whichever the compiled core expects.
139#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
140void on_recv(const esp_now_recv_info_t *info, const uint8_t *data, int len)
141{
142 const uint8_t *mac = info ? info->src_addr : nullptr;
143#else
144void on_recv(const uint8_t *mac, const uint8_t *data, int len)
145{
146#endif
147 if (!s_espnow.recv || len < 0 || !mac)
148 return;
149 uint8_t type;
150 const uint8_t *payload;
151 size_t plen;
152 if (detws_espnow_decode(data, (size_t)len, &type, &payload, &plen))
153 s_espnow.recv(mac, type, payload, plen);
154}
155
156bool radio_add_peer(const uint8_t mac[6], uint8_t channel)
157{
158 esp_now_peer_info_t p;
159 memset(&p, 0, sizeof(p));
160 memcpy(p.peer_addr, mac, 6);
161 p.channel = channel;
162 p.encrypt = false;
163 if (esp_now_is_peer_exist(mac))
164 return true;
165 return esp_now_add_peer(&p) == ESP_OK;
166}
167} // namespace
168
169bool detws_espnow_begin(uint8_t channel, detws_espnow_recv_fn cb)
170{
171 s_espnow.channel = channel;
172 s_espnow.recv = cb;
173 if (esp_now_init() != ESP_OK)
174 return false;
175 esp_now_register_recv_cb(on_recv);
176 detws_espnow_peers_reset();
177 return radio_add_peer(DETWS_ESPNOW_BROADCAST, channel); // broadcast is always a peer
178}
179
180bool detws_espnow_add_peer(const uint8_t mac[6])
181{
182 if (!detws_espnow_peer_add(mac))
183 return false;
184 return radio_add_peer(mac, s_espnow.channel);
185}
186
187bool detws_espnow_send(const uint8_t mac[6], uint8_t type, const uint8_t *payload, size_t len)
188{
189 uint8_t frame[DETWS_ESPNOW_HDR + DETWS_ESPNOW_MAX_PAYLOAD];
190 size_t n = detws_espnow_encode(type, payload, len, frame, sizeof(frame));
191 if (n == 0)
192 return false;
193 return esp_now_send(mac, frame, n) == ESP_OK;
194}
195
196bool detws_espnow_broadcast(uint8_t type, const uint8_t *payload, size_t len)
197{
198 return detws_espnow_send(DETWS_ESPNOW_BROADCAST, type, payload, len);
199}
200
201#else // host build - no radio
202
203bool detws_espnow_begin(uint8_t, detws_espnow_recv_fn)
204{
205 return false;
206}
207bool detws_espnow_add_peer(const uint8_t mac[6])
208{
209 return detws_espnow_peer_add(mac);
210}
211bool detws_espnow_send(const uint8_t *, uint8_t, const uint8_t *, size_t)
212{
213 return false;
214}
215bool detws_espnow_broadcast(uint8_t, const uint8_t *, size_t)
216{
217 return false;
218}
219
220#endif // ARDUINO
221
222#endif // DETWS_ENABLE_ESPNOW
ESP-NOW peer messaging with a typed envelope (DETWS_ENABLE_ESPNOW).