|
| 1 | +/* |
| 2 | + ESP-NOW Network Example |
| 3 | + Lucas Saavedra Vaz - 2024 |
| 4 | +
|
| 5 | + This example is based on the ESP-NOW example from the ESP-IDF framework. |
| 6 | +
|
| 7 | + The aim of this example is to demonstrate how to create a network of devices using the ESP-NOW protocol. |
| 8 | + The slave devices will broadcast random data to the master device every 5 seconds and from time to time |
| 9 | + they will ping the other slave devices with a "Hello!" message. |
| 10 | +
|
| 11 | + The master device will receive the data from the slave devices and print it to the Serial Monitor. From time |
| 12 | + to time, the master device will calculate the average of the priorities of the slave devices and send it to |
| 13 | + all the slave devices. |
| 14 | +
|
| 15 | + Each device will have a priority that will be used to decide which device will be the master. |
| 16 | + The device with the highest priority will be the master. |
| 17 | +
|
| 18 | + Flow: |
| 19 | + 1. Each device will generate a priority based on its MAC address. |
| 20 | + 2. The devices will broadcast their priority on the network. |
| 21 | + 3. The devices will listen to the broadcast messages and register the priorities of the other devices. |
| 22 | + 4. After all devices have been registered, the device with the highest priority will be the master. |
| 23 | + 5. The slave devices will send random data to the master every 5 seconds. |
| 24 | + - Every "REPORT_INTERVAL" messages, the slaves will send a message to the other slaves. |
| 25 | + 6. The master device will calculate the average of the data and send it to the slave devices every "REPORT_INTERVAL" messages. |
| 26 | +
|
| 27 | +*/ |
| 28 | + |
| 29 | +#include "ESP32_NOW.h" |
| 30 | +#include "WiFi.h" |
| 31 | + |
| 32 | +#include <esp_mac.h> // For the MAC2STR and MACSTR macros |
| 33 | + |
| 34 | +#include <algorithm> |
| 35 | +#include <vector> |
| 36 | + |
| 37 | +/* Definitions */ |
| 38 | + |
| 39 | +// Wi-Fi interface to be used by the ESP-NOW protocol |
| 40 | +#define ESPNOW_WIFI_IFACE WIFI_IF_STA |
| 41 | + |
| 42 | +// Channel to be used by the ESP-NOW protocol |
| 43 | +#define ESPNOW_WIFI_CHANNEL 4 |
| 44 | + |
| 45 | +// Delay between sending messages |
| 46 | +#define ESPNOW_SEND_INTERVAL_MS 5000 |
| 47 | + |
| 48 | +// Number of peers to wait for (excluding this device) |
| 49 | +#define ESPNOW_PEER_COUNT 2 |
| 50 | + |
| 51 | +// Report to other devices every 5 messages |
| 52 | +#define REPORT_INTERVAL 5 |
| 53 | + |
| 54 | +/* |
| 55 | + ESP-NOW uses the CCMP method, which is described in IEEE Std. 802.11-2012, to protect the vendor-specific action frame. |
| 56 | + The Wi-Fi device maintains a Primary Master Key (PMK) and several Local Master Keys (LMK). |
| 57 | + The lengths of both PMK and LMK need to be 16 bytes. |
| 58 | +
|
| 59 | + PMK is used to encrypt LMK with the AES-128 algorithm. If PMK is not set, a default PMK will be used. |
| 60 | +
|
| 61 | + LMK of the paired device is used to encrypt the vendor-specific action frame with the CCMP method. |
| 62 | + The maximum number of different LMKs is six. If the LMK of the paired device is not set, the vendor-specific |
| 63 | + action frame will not be encrypted. |
| 64 | +
|
| 65 | + Encrypting multicast (broadcast address) vendor-specific action frame is not supported. |
| 66 | +
|
| 67 | + PMK needs to be the same for all devices in the network. LMK only needs to be the same between paired devices. |
| 68 | +*/ |
| 69 | + |
| 70 | +// Primary Master Key (PMK) and Local Master Key (LMK) |
| 71 | +#define ESPNOW_EXAMPLE_PMK "pmk1234567890123" |
| 72 | +#define ESPNOW_EXAMPLE_LMK "lmk1234567890123" |
| 73 | + |
| 74 | +/* Structs */ |
| 75 | + |
| 76 | +// The following struct is used to send data to the peer device. |
| 77 | +// We use the attribute "packed" to ensure that the struct is not padded (all data |
| 78 | +// is contiguous in the memory and without gaps). |
| 79 | +// The maximum size of the complete message is 250 bytes (ESP_NOW_MAX_DATA_LEN). |
| 80 | + |
| 81 | +typedef struct { |
| 82 | + uint32_t count; |
| 83 | + uint32_t priority; |
| 84 | + uint32_t data; |
| 85 | + char str[7]; |
| 86 | +} __attribute__((packed)) esp_now_data_t; |
| 87 | + |
| 88 | +/* Global Variables */ |
| 89 | + |
| 90 | +uint32_t self_priority = 0; // Priority of this device |
| 91 | +uint8_t current_peer_count = 0; // Number of peers that have been found |
| 92 | +uint8_t check_count = 0; // Counter to wait after all peers have been found |
| 93 | +bool device_is_master = false; // Flag to indicate if this device is the master |
| 94 | +bool master_decided = false; // Flag to indicate if the master has been decided |
| 95 | +uint32_t sent_msg_count = 0; // Counter for the messages sent. Only starts counting after all peers have been found |
| 96 | +uint32_t recv_msg_count = 0; // Counter for the messages received. Only starts counting after all peers have been found |
| 97 | +esp_now_data_t new_msg; // Message that will be sent to the peers |
| 98 | +std::vector<uint32_t> last_data(5); // Vector that will store the last 5 data received |
| 99 | + |
| 100 | +/* Classes */ |
| 101 | + |
| 102 | +// We need to create a class that inherits from ESP_NOW_Peer to implement the _onReceive and _onSent methods. |
| 103 | +// This class will be used to store the priority of the device and to send messages to the peers. |
| 104 | +// For more information about the ESP_NOW_Peer class, see the ESP_NOW_Peer class in the ESP32_NOW.h file. |
| 105 | + |
| 106 | +class ESP_NOW_Network_Peer : public ESP_NOW_Peer { |
| 107 | +public: |
| 108 | + uint32_t priority; |
| 109 | + bool peer_is_master = false; |
| 110 | + |
| 111 | + // Revert the lmk later |
| 112 | + ESP_NOW_Network_Peer(const uint8_t *mac_addr, uint32_t priority = 0, const uint8_t *lmk = nullptr); |
| 113 | + ~ESP_NOW_Network_Peer(); |
| 114 | + |
| 115 | + bool begin(); |
| 116 | + bool send_message(const uint8_t *data, size_t len); |
| 117 | + |
| 118 | + // ESP_NOW_Peer interfaces |
| 119 | + void _onReceive(const uint8_t *data, size_t len, bool broadcast); |
| 120 | + void _onSent(bool success); |
| 121 | +}; |
| 122 | + |
| 123 | +/* Methods */ |
| 124 | + |
| 125 | +ESP_NOW_Network_Peer::ESP_NOW_Network_Peer(const uint8_t *mac_addr, uint32_t priority, const uint8_t *lmk) |
| 126 | + : ESP_NOW_Peer(mac_addr, ESPNOW_WIFI_CHANNEL, ESPNOW_WIFI_IFACE, lmk) |
| 127 | + , priority(priority) {} |
| 128 | + |
| 129 | +ESP_NOW_Network_Peer::~ESP_NOW_Network_Peer() {} |
| 130 | + |
| 131 | +bool ESP_NOW_Network_Peer::begin() { |
| 132 | + // In this example the ESP-NOW protocol will already be initialized as we require it to receive broadcast messages. |
| 133 | + if (!add()) { |
| 134 | + log_e("Failed to initialize ESP-NOW or register the peer"); |
| 135 | + return false; |
| 136 | + } |
| 137 | + return true; |
| 138 | +} |
| 139 | + |
| 140 | +bool ESP_NOW_Network_Peer::send_message(const uint8_t *data, size_t len) { |
| 141 | + if (data == NULL || len == 0) { |
| 142 | + log_e("Data to be sent is NULL or has a length of 0"); |
| 143 | + return false; |
| 144 | + } |
| 145 | + |
| 146 | + // Call the parent class method to send the data |
| 147 | + return send(data, len); |
| 148 | +} |
| 149 | + |
| 150 | +void ESP_NOW_Network_Peer::_onReceive(const uint8_t *data, size_t len, bool broadcast) { |
| 151 | + if (!broadcast) { |
| 152 | + esp_now_data_t *msg = (esp_now_data_t *)data; |
| 153 | + recv_msg_count++; |
| 154 | + if (device_is_master) { |
| 155 | + Serial.printf("Received a message from peer " MACSTR "\n", MAC2STR(addr())); |
| 156 | + Serial.printf(" Count: %lu\n", msg->count); |
| 157 | + Serial.printf(" Random data: %lu\n", msg->data); |
| 158 | + last_data.push_back(msg->data); |
| 159 | + last_data.erase(last_data.begin()); |
| 160 | + } else if (peer_is_master) { |
| 161 | + Serial.println("Received a message from the master"); |
| 162 | + Serial.printf(" Average data: %lu\n", msg->data); |
| 163 | + } |
| 164 | + else { |
| 165 | + Serial.printf("Peer " MACSTR " says: %s\n", MAC2STR(addr()), msg->str); |
| 166 | + } |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +void ESP_NOW_Network_Peer::_onSent(bool success) { |
| 171 | + bool broadcast = memcmp(addr(), ESP_NOW.BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0; |
| 172 | + if (broadcast) { |
| 173 | + log_v("Broadcast message reported as sent %s", success ? "successfully" : "unsuccessfully"); |
| 174 | + } |
| 175 | + else { |
| 176 | + log_v("Unicast message reported as sent %s to peer " MACSTR, success ? "successfully" : "unsuccessfully", MAC2STR(addr())); |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +/* Peers */ |
| 181 | + |
| 182 | +std::vector<ESP_NOW_Network_Peer *> peers; // Create a vector to store the peer pointers |
| 183 | +ESP_NOW_Network_Peer broadcast_peer(ESP_NOW.BROADCAST_ADDR, 0, NULL); // Register the broadcast peer (no encryption support for the broadcast address) |
| 184 | +ESP_NOW_Network_Peer *master_peer = nullptr; // Pointer to peer that is the master |
| 185 | + |
| 186 | +/* Helper functions */ |
| 187 | + |
| 188 | +// Function to reboot the device |
| 189 | +void fail_reboot() { |
| 190 | + Serial.println("Rebooting in 5 seconds..."); |
| 191 | + delay(5000); |
| 192 | + ESP.restart(); |
| 193 | +} |
| 194 | + |
| 195 | +// Function to check which device has the highest priority |
| 196 | +uint32_t check_highest_priority() { |
| 197 | + uint32_t highest_priority = 0; |
| 198 | + for (auto &peer : peers) { |
| 199 | + if (peer->priority > highest_priority) { |
| 200 | + highest_priority = peer->priority; |
| 201 | + } |
| 202 | + } |
| 203 | + return std::max(highest_priority, self_priority); |
| 204 | +} |
| 205 | + |
| 206 | +// Function to calculate the average of the data received |
| 207 | +uint32_t calc_average() { |
| 208 | + uint32_t avg = 0; |
| 209 | + for (auto &d : last_data) { |
| 210 | + avg += d; |
| 211 | + } |
| 212 | + avg /= last_data.size(); |
| 213 | + return avg; |
| 214 | +} |
| 215 | + |
| 216 | +/* Callbacks */ |
| 217 | + |
| 218 | +// Callback called when a new peer is found |
| 219 | +void register_new_peer(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) { |
| 220 | + esp_now_data_t *msg = (esp_now_data_t *)data; |
| 221 | + int priority = msg->priority; |
| 222 | + |
| 223 | + if (priority == self_priority) { |
| 224 | + Serial.println("ERROR! Device has the same priority as this device"); |
| 225 | + fail_reboot(); |
| 226 | + } |
| 227 | + |
| 228 | + if (current_peer_count < ESPNOW_PEER_COUNT) { |
| 229 | + Serial.printf("New peer found: " MACSTR " with priority %d\n", MAC2STR(info->src_addr), priority); |
| 230 | + ESP_NOW_Network_Peer *new_peer = new ESP_NOW_Network_Peer(info->src_addr, priority); |
| 231 | + if (!new_peer->begin()) { |
| 232 | + Serial.println("Failed to create the new peer"); |
| 233 | + delete new_peer; |
| 234 | + return; |
| 235 | + } |
| 236 | + peers.push_back(new_peer); |
| 237 | + if (!peers.back()->begin()) { |
| 238 | + Serial.println("Failed to register the new peer"); |
| 239 | + peers.pop_back(); |
| 240 | + return; |
| 241 | + } |
| 242 | + current_peer_count++; |
| 243 | + if (current_peer_count == ESPNOW_PEER_COUNT) { |
| 244 | + Serial.println("All peers have been found"); |
| 245 | + Serial.println("Broadcasting the priority 3 more times to ensure that all devices have received it"); |
| 246 | + } |
| 247 | + } |
| 248 | +} |
| 249 | + |
| 250 | +/* Main */ |
| 251 | + |
| 252 | +void setup() { |
| 253 | + uint8_t self_mac[6]; |
| 254 | + |
| 255 | + Serial.begin(115200); |
| 256 | + while (!Serial) delay(10); |
| 257 | + |
| 258 | + Serial.println("ESP-NOW Network Example"); |
| 259 | + Serial.println("Wi-Fi parameters:"); |
| 260 | + Serial.println(" Mode: STA"); |
| 261 | + Serial.println(" MAC Address: " + WiFi.macAddress()); |
| 262 | + Serial.printf(" Channel: %d\n", ESPNOW_WIFI_CHANNEL); |
| 263 | + |
| 264 | + // Initialize the Wi-Fi module |
| 265 | + WiFi.mode(WIFI_STA); |
| 266 | + WiFi.setChannel(ESPNOW_WIFI_CHANNEL); |
| 267 | + |
| 268 | + // Generate yhis device's priority based on the 3 last bytes of the MAC address |
| 269 | + WiFi.macAddress(self_mac); |
| 270 | + self_priority = self_mac[3] << 16 | self_mac[4] << 8 | self_mac[5]; |
| 271 | + Serial.printf("This device's priority: %lu\n", self_priority); |
| 272 | + |
| 273 | + // Initialize the ESP-NOW protocol |
| 274 | + if (!ESP_NOW.begin((const uint8_t *)ESPNOW_EXAMPLE_PMK)) { |
| 275 | + Serial.println("Failed to initialize ESP-NOW"); |
| 276 | + fail_reboot(); |
| 277 | + } |
| 278 | + |
| 279 | + if (!broadcast_peer.begin()) { |
| 280 | + Serial.println("Failed to initialize broadcast peer"); |
| 281 | + fail_reboot(); |
| 282 | + } |
| 283 | + |
| 284 | + // Register the callback to be called when a new peer is found |
| 285 | + ESP_NOW.onNewPeer(register_new_peer, NULL); |
| 286 | + |
| 287 | + Serial.println("Setup complete. Broadcasting own priority to find the master..."); |
| 288 | + memset(&new_msg, 0, sizeof(new_msg)); |
| 289 | + strncpy(new_msg.str, "Hello!", sizeof(new_msg.str)); |
| 290 | + new_msg.priority = self_priority; |
| 291 | +} |
| 292 | + |
| 293 | +void loop() { |
| 294 | + if (!master_decided) { |
| 295 | + // Broadcast the priority to find the master |
| 296 | + if (!broadcast_peer.send_message((const uint8_t *)&new_msg, sizeof(new_msg))) { |
| 297 | + Serial.println("Failed to broadcast message"); |
| 298 | + } |
| 299 | + |
| 300 | + // Check if all peers have been found |
| 301 | + if (current_peer_count == ESPNOW_PEER_COUNT) { |
| 302 | + // Transmit the priority 3 more times to ensure that all devices have received it |
| 303 | + if (check_count >= 3) { |
| 304 | + // Check which device has the highest priority |
| 305 | + master_decided = true; |
| 306 | + uint32_t highest_priority = check_highest_priority(); |
| 307 | + if (highest_priority == self_priority) { |
| 308 | + device_is_master = true; |
| 309 | + Serial.println("This device is the master"); |
| 310 | + } else { |
| 311 | + for (int i = 0; i < ESPNOW_PEER_COUNT; i++) { |
| 312 | + if (peers[i]->priority == highest_priority) { |
| 313 | + peers[i]->peer_is_master = true; |
| 314 | + master_peer = peers[i]; |
| 315 | + Serial.printf("Peer " MACSTR " is the master with priority %lu\n", MAC2STR(peers[i]->addr()), highest_priority); |
| 316 | + break; |
| 317 | + } |
| 318 | + } |
| 319 | + } |
| 320 | + Serial.println("The master has been decided"); |
| 321 | + } else { |
| 322 | + Serial.printf("%d...\n", check_count + 1); |
| 323 | + check_count++; |
| 324 | + } |
| 325 | + } |
| 326 | + } else { |
| 327 | + if (!device_is_master) { |
| 328 | + // Send a message to the master |
| 329 | + new_msg.count = sent_msg_count + 1; |
| 330 | + new_msg.data = random(10000); |
| 331 | + if (!master_peer->send_message((const uint8_t *)&new_msg, sizeof(new_msg))) { |
| 332 | + Serial.println("Failed to send message to the master"); |
| 333 | + } else { |
| 334 | + Serial.printf("Sent message to the master. Count: %lu, Data: %lu\n", new_msg.count, new_msg.data); |
| 335 | + sent_msg_count++; |
| 336 | + } |
| 337 | + |
| 338 | + // Check if it is time to report to peers |
| 339 | + if (sent_msg_count % REPORT_INTERVAL == 0) { |
| 340 | + // Send a message to the peers |
| 341 | + for (auto &peer : peers) { |
| 342 | + if (!peer->peer_is_master) { |
| 343 | + if (!peer->send_message((const uint8_t *)&new_msg, sizeof(new_msg))) { |
| 344 | + Serial.printf("Failed to send message to peer " MACSTR "\n", MAC2STR(peer->addr())); |
| 345 | + } else { |
| 346 | + Serial.printf("Sent message \"%s\" to peer " MACSTR "\n", new_msg.str, MAC2STR(peer->addr())); |
| 347 | + } |
| 348 | + } |
| 349 | + } |
| 350 | + } |
| 351 | + } else { |
| 352 | + // Check if it is time to report to peers |
| 353 | + if (recv_msg_count % REPORT_INTERVAL == 0) { |
| 354 | + // Report average data to the peers |
| 355 | + uint32_t avg = calc_average(); |
| 356 | + new_msg.count = sent_msg_count + 1; |
| 357 | + new_msg.data = avg; |
| 358 | + for (auto &peer : peers) { |
| 359 | + if (!peer->send_message((const uint8_t *)&new_msg, sizeof(new_msg))) { |
| 360 | + Serial.printf("Failed to send message to peer " MACSTR "\n", MAC2STR(peer->addr())); |
| 361 | + } else { |
| 362 | + Serial.printf("Sent message to peer " MACSTR ". Recv: %lu, Sent: %lu, Avg: %lu\n", MAC2STR(peer->addr()), recv_msg_count, new_msg.count, new_msg.data); |
| 363 | + sent_msg_count++; |
| 364 | + } |
| 365 | + } |
| 366 | + } |
| 367 | + } |
| 368 | + } |
| 369 | + |
| 370 | + delay(ESPNOW_SEND_INTERVAL_MS); |
| 371 | +} |
0 commit comments