Same approach, but several changes. Here is a sketch that works with either ESP8266 or ESP32, using conditional compilation -- the usual C/C++ skullduggery
#if __has_include(<espnow.h>)
#define ESP_NOW_BACK_THEN
#pragma message "ESP8266-NOW"
#include <ESP8266WiFi.h>
#include <espnow.h>
#define ESP_OK 0 // not defined for ESP8266
#define WIFI_MODE_STA WIFI_STA // no "MODE" on ESP8266
#else
#pragma message "ESP32-NOW"
#include <WiFi.h>
#include <esp_wifi.h> // just for esp_wifi_set_channel()
#include <esp_now.h>
#include <MacAddress.h> // to make it easier to print those
#endif
// Either everyone connects to the same WiFi Access Point
// - that puts everyone on the same channel
// = then you can disconnect if you're not going to use normal WiFi
// or you can hard-code a channel
#define WIFI_CHANNEL 6 // the "middle" channel; coincidentally same number of octets in a MAC address
uint8_t target_mac[6]; // don't feel like typing six FFs and five commas
bool switch_to_found_peer = true; // used by ESP8266 only
constexpr uint64_t payload_id{ 0xACE0FBA5E00FF1CE };
struct NOW_Payload {
uint64_t signature{ payload_id };
unsigned long millis;
} payload;
#ifdef ESP_NOW_BACK_THEN
void OnDataSent(uint8_t *des_addr, uint8_t status) {
#else
void OnDataSent(const uint8_t *des_addr, esp_now_send_status_t status) {
#endif
static bool wantToSeeAllOfThem = false;
if (wantToSeeAllOfThem || status != ESP_OK) {
Serial.print("OnDataSent: \040\040\040\040\040");
Serial.print(millis());
Serial.print('\t');
Serial.println(status);
}
}
void printMac(const uint8_t *mac) {
#ifdef ESP_NOW_BACK_THEN
char macStr[18];
sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
#else
MacAddress macStr{ mac };
#endif
Serial.print(macStr);
}
#ifdef ESP_NOW_BACK_THEN
void OnDataRecv(uint8_t *src_addr, uint8_t *data, uint8_t data_len) {
if (switch_to_found_peer) {
auto err = esp_now_add_peer(src_addr, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, nullptr, 0);
if (err) {
Serial.println("failed to add found-peer");
} else {
memcpy(target_mac, src_addr, sizeof(target_mac));
Serial.println("switched to found-peer");
}
switch_to_found_peer = false;
}
#else
void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len) {
const uint8_t *src_addr = esp_now_info->src_addr;
#endif
NOW_Payload pay;
if (data_len != sizeof(pay)) {
Serial.print("NOW received ");
Serial.print(data_len);
Serial.println(" foreign bytes");
return;
}
memcpy(&pay, data, sizeof(pay));
if (pay.signature != payload_id) {
Serial.print("NOW received signature that does not match: ");
Serial.println(pay.signature, HEX);
return;
}
Serial.print("NOW received from ");
printMac(src_addr);
Serial.print(" sent at their ");
Serial.println(pay.millis);
}
void setup() {
Serial.begin(115200);
for (auto &b : target_mac) b = 0xFF; // start with broadcast
WiFi.mode(WIFI_MODE_STA);
if (esp_now_init() == ESP_OK) {
#ifdef ESP_NOW_BACK_THEN
Serial.println("ESP-NOW initialized");
#else
uint32_t version; // 32 bits for either 1 or 2
esp_now_get_version(&version);
Serial.print("ESP-NOW initialized, version: ");
Serial.print(version, HEX);
#endif
} else {
for (;;) {
Serial.println("Initializing ESP-NOW failed");
delay(3000);
}
}
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
#ifdef ESP_NOW_BACK_THEN
auto err = esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
if (err) {
for (;;) {
Serial.print("esp_now_set_self_role failed: ");
Serial.println(err);
delay(3000);
}
}
err = esp_now_add_peer(target_mac, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, nullptr, 0);
#else
esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
esp_now_peer_info_t peerInfo{}; // zero-init all fields
// peerInfo.channel = 0; // or WIFI_CHANNEL
// peerInfo.encrypt = false;
memcpy(peerInfo.peer_addr, target_mac, sizeof(target_mac));
auto err = esp_now_add_peer(&peerInfo);
#endif
if (err) {
for (;;) {
Serial.print("esp_now_add_peer failed: ");
Serial.println(err);
delay(3000);
}
}
}
void loop() {
payload.millis = millis();
auto err = esp_now_send(target_mac, reinterpret_cast<uint8_t *>(&payload), sizeof(payload));
Serial.print("esp_now_send at: ");
Serial.print(payload.millis);
if (err) {
Serial.print(" failed: ");
Serial.println(err);
delay(3500);
} else {
Serial.println();
}
delay(random(750, 1750));
}
First, Instead of looking for another macro that indicates ESP8266, __has_include worked to find espnow.h with no underscore. In doing so, define a custom empty macro that indicates it's the old style, to be used later. You also need WiFi for both, and a couple more for ESP32.
Next, note the difference in function signatures for the send callback
#ifdef ESP_NOW_BACK_THEN
void OnDataSent(uint8_t *des_addr, uint8_t status) {
#else
void OnDataSent(const uint8_t *des_addr, esp_now_send_status_t status) {
#endif
On ESP32, the destination MAC is through a pointer-to-const; and the status is a specific type.
It's easy to print MAC addresses on ESP32.
Also pointers-to-const on the receive callback. More importantly, as mentioned earlier the first argument is no longer a simple pointer to the sender's MAC. But that is easily extracted. Will talk about switch_to_found_peer later.
#ifdef ESP_NOW_BACK_THEN
void OnDataRecv(uint8_t *src_addr, uint8_t *data, uint8_t data_len) {
if (switch_to_found_peer) {
auto err = esp_now_add_peer(src_addr, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, nullptr, 0);
if (err) {
Serial.println("failed to add found-peer");
} else {
memcpy(target_mac, src_addr, sizeof(target_mac));
Serial.println("switched to found-peer");
}
switch_to_found_peer = false;
}
#else
void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len) {
const uint8_t *src_addr = esp_now_info->src_addr;
#endif
The init code differs only because ESP32 now supports two versions of ESP-NOW
#ifdef ESP_NOW_BACK_THEN
Serial.println("ESP-NOW initialized");
#else
uint32_t version; // 32 bits for either 1 or 2
esp_now_get_version(&version);
Serial.print("ESP-NOW initialized, version: ");
Serial.print(version, HEX);
#endif
With the correct function signatures, the callback registration is straightforward
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
The biggest difference might be with the ESP-NOW setup. ESP8266 requires a call to _set_self_role. Then _add_peer requires a role and a channel. (The last two arguments -- empty in this case -- are supposedly for encryption.)
#ifdef ESP_NOW_BACK_THEN
auto err = esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
if (err) {
for (;;) {
Serial.print("esp_now_set_self_role failed: ");
Serial.println(err);
delay(3000);
}
}
err = esp_now_add_peer(target_mac, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, nullptr, 0);
#else
esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
esp_now_peer_info_t peerInfo{}; // zero-init all fields
// peerInfo.channel = 0; // or WIFI_CHANNEL
// peerInfo.encrypt = false;
memcpy(peerInfo.peer_addr, target_mac, sizeof(target_mac));
auto err = esp_now_add_peer(&peerInfo);
#endif
On ESP32, you first set the "base" WiFi channel to match the peers. So even though each peer info contains a channel, it doesn't seem like those can vary. A common way to get everyone on the same channel is for them to join the same Access Point, and then disconnect if that's not actually needed.
Those are the functional differences.
Does this actually work though? For me, ESP8266 receives most of the messages from ESP32; but vice-versa, ESP32 receives almost none. Two ESP32 work OK. I don't have a second ESP8266 to try.
For a broadcast to all-FF, the send callback never indicates an error. On ESP8266, I added switch_to_found_peer so that in the receive callback, it could try transmitting directly. In that case, OnDataSent accurately reports it almost never worked. Unfortunately, no more details on that; even on ESP32, the status type is just
typedef enum {
ESP_NOW_SEND_SUCCESS = 0, /**< Send ESPNOW data successfully */
ESP_NOW_SEND_FAIL, /**< Send ESPNOW data fail */
} esp_now_send_status_t;
BTW, OnDataRecv does implement a "magic number" check -- in this case, a bit of hex speak -- to verify the payload is of the expected kind.