Fix ESP-NOW “invalid conversion … to 'esp_now_recv_cb_t'” (ESP32 core 3.x)
ESP32 core 3.x changed the ESP-NOW callbacks. Receive gets an esp_now_recv_info_t*; on core 3.3+ send gets a wifi_tx_info_t*. Update the function signatures.
Community draft — report a mistake
What you see
EspNowReceiver.ino: In function 'void setup()':
EspNowReceiver.ino:10:28: error: invalid conversion from 'void (*)(const uint8_t*, const uint8_t*, int)' {aka 'void (*)(const unsigned char*, const unsigned char*, int)'} to 'esp_now_recv_cb_t' {aka 'void (*)(const esp_now_recv_info*, const unsigned char*, int)'} [-fpermissive]
10 | esp_now_register_recv_cb(onRecv);
| ^~~~~~
esp_now.h:172:54: note: initializing argument 1 of 'esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t)'
EspNowReceiver.ino:11:28: error: invalid conversion from 'void (*)(const uint8_t*, esp_now_send_status_t)' {aka 'void (*)(const unsigned char*, esp_now_send_status_t)'} to 'esp_now_send_cb_t' {aka 'void (*)(const wifi_tx_info_t*, esp_now_send_status_t)'} [-fpermissive]
exit status 1EspNowReceiver.ino: In function 'void setup()':
EspNowReceiver.ino:10:28: error: invalid conversion from 'void (*)(const uint8_t*, const uint8_t*, int)' {aka 'void (*)(const unsigned char*, const unsigned char*, int)'} to 'esp_now_recv_cb_t' {aka 'void (*)(const esp_now_recv_info*, const unsigned char*, int)'} [-fpermissive]
10 | esp_now_register_recv_cb(onRecv);
| ^~~~~~
esp_now.h:172:54: note: initializing argument 1 of 'esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t)'
EspNowReceiver.ino:11:28: error: invalid conversion from 'void (*)(const uint8_t*, esp_now_send_status_t)' {aka 'void (*)(const unsigned char*, esp_now_send_status_t)'} to 'esp_now_send_cb_t' {aka 'void (*)(const wifi_tx_info_t*, esp_now_send_status_t)'} [-fpermissive]
exit status 1
ESP-NOW code written for Arduino-ESP32 2.x fails on 3.x because the callback function types changed. C++ won’t convert a function pointer of one shape to another, so you get a long invalid conversion error naming both shapes. Updating the parameter lists is the whole fix:
void onRecv(const esp_now_recv_info_t* info, const uint8_t* data, int len) {
const uint8_t* mac = info->src_addr;
// ... use mac, data, len as before
}
void onSent(const wifi_tx_info_t* info, esp_now_send_status_t status) { // core 3.3+
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "sent" : "failed");
}Quick fix — try in order
- 1
Change the receive callback
void onRecv(const uint8_t* mac, const uint8_t* data, int len)becomesvoid onRecv(const esp_now_recv_info_t* info, const uint8_t* data, int len). The sender’s MAC is nowinfo->src_addr. - 2
Change the send callback (core 3.3 and newer)
void onSent(const uint8_t* mac, esp_now_send_status_t status)becomesvoid onSent(const wifi_tx_info_t* info, esp_now_send_status_t status). Cores 3.0–3.2 still use the old send signature. - 3
Support several cores with #if
Wrap the send callback in
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)…#else…#endif, as in the sketch below. - 4
Don’t cast the old function
Casting to
(esp_now_recv_cb_t)onRecvcompiles but your code then reads the info struct as a MAC address — wrong data, possible crashes.
Diagnostic sketch
// ESP-NOW with the callback signatures of Arduino-ESP32 3.x. Flash the same sketch on two
// boards: each broadcasts a counter every 2 s and prints what it receives.
// Receive callback (3.x): void f(const esp_now_recv_info_t* info, const uint8_t* data, int len)
// Send callback (3.3+): void f(const wifi_tx_info_t* info, esp_now_send_status_t status)
// Send callback (3.0-3.2): void f(const uint8_t* mac, esp_now_send_status_t status)
#include <WiFi.h>
#include <esp_now.h>
const uint8_t BROADCAST[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint32_t counter = 0;
void onRecv(const esp_now_recv_info_t* info, const uint8_t* data, int len) {
const uint8_t* m = info->src_addr; // the sender's MAC moved into info
uint32_t value = 0;
if (len == sizeof value) memcpy(&value, data, sizeof value);
Serial.printf("From %02X:%02X:%02X:%02X:%02X:%02X value %lu\n", m[0], m[1], m[2], m[3], m[4], m[5], (unsigned long)value);
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
void onSent(const wifi_tx_info_t* info, esp_now_send_status_t status) {
#else
void onSent(const uint8_t* mac, esp_now_send_status_t status) {
#endif
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "sent" : "send failed");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("esp_now_init failed");
return;
}
esp_now_register_recv_cb(onRecv);
esp_now_register_send_cb(onSent);
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, BROADCAST, 6);
peer.channel = 0; // current Wi-Fi channel
peer.encrypt = false;
esp_now_add_peer(&peer);
Serial.printf("My MAC: %s\n", WiFi.macAddress().c_str());
}
void loop() {
counter++;
esp_now_send(BROADCAST, (const uint8_t*)&counter, sizeof counter);
delay(2000);
}// ESP-NOW with the callback signatures of Arduino-ESP32 3.x. Flash the same sketch on two
// boards: each broadcasts a counter every 2 s and prints what it receives.
// Receive callback (3.x): void f(const esp_now_recv_info_t* info, const uint8_t* data, int len)
// Send callback (3.3+): void f(const wifi_tx_info_t* info, esp_now_send_status_t status)
// Send callback (3.0-3.2): void f(const uint8_t* mac, esp_now_send_status_t status)
#include <WiFi.h>
#include <esp_now.h>
const uint8_t BROADCAST[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint32_t counter = 0;
void onRecv(const esp_now_recv_info_t* info, const uint8_t* data, int len) {
const uint8_t* m = info->src_addr; // the sender's MAC moved into info
uint32_t value = 0;
if (len == sizeof value) memcpy(&value, data, sizeof value);
Serial.printf("From %02X:%02X:%02X:%02X:%02X:%02X value %lu\n", m[0], m[1], m[2], m[3], m[4], m[5], (unsigned long)value);
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
void onSent(const wifi_tx_info_t* info, esp_now_send_status_t status) {
#else
void onSent(const uint8_t* mac, esp_now_send_status_t status) {
#endif
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "sent" : "send failed");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("esp_now_init failed");
return;
}
esp_now_register_recv_cb(onRecv);
esp_now_register_send_cb(onSent);
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, BROADCAST, 6);
peer.channel = 0; // current Wi-Fi channel
peer.encrypt = false;
esp_now_add_peer(&peer);
Serial.printf("My MAC: %s\n", WiFi.macAddress().c_str());
}
void loop() {
counter++;
esp_now_send(BROADCAST, (const uint8_t*)&counter, sizeof counter);
delay(2000);
}Why it happens
Why did the callbacks change?
ESP-IDF 5 passes more information (sender and destination address, RSSI in rx_ctrl) to the receive callback, and IDF 5.5 does the same for the send callback. Arduino-ESP32 3.x exposes the IDF types directly.
Most ESP-NOW tutorials show the old form
They were written for core 2.x. The rest of the code (esp_now_init, add_peer, send) is unchanged.
Getting the RSSI
With the new receive callback, info->rx_ctrl->rssi gives the signal strength of each packet — useful for range tests.
Board-specific notes
ESP32: Both boards must be on the same Wi-Fi channel. With one board also joined to a router, ESP-NOW uses the router’s channel.
ESP8266: Uses its own espnow.h API with the old-style callbacks; it can talk to an ESP32 but the code differs.
Did this fix it?
Thanks — that helps us keep this page right.