Use ESP NOW and WiFi simultaneously on ESP32

Hi everyone!
Im working on a project where i want to measure some RSSI values with different technologies. Basically im using ESP NOW protocol to send some Start signals to other boards and modules, and they also exchange datas via ESP NOW. My problem is, that i want to measure WiFi RSSI too, but as far as i know, ESP NOW and WiFi cant work simultaneously, am i right? Both boards runs in WIFI_AP_STA mode, one using softAP(ssid,password) and the other WiFi.begin(ssid,password) and they cant connect. Could someone give me some helpful information, about how should i do this correctly?
I read the forum about how could i paste my code in here, but for me there isnt any symbol or option. I created my account 2 days ago, maybe it could be the reason.

ESPNOW is using the only WiFi transmitter/receiver on the board. You could get another ESP32 that's just using WiFi and have the ESP32NOW board serially send the info to the ESP32WiFi module for retransmission.

1 Like

Thank you for your fast reply!

1 Like

If you go that route, investigate an ESP-01, that's pretty just for WiFi already, you can link it to any Arduino, and they are uber cheap.

1 Like

more discussion here:
https://rntlab.com/question/esp-now-gateway-wifi_mode_sta-with-a-wifi-router/

2 Likes

Hi @schroomy,

I faced the same issue a few months ago where I found you could not run ESP-NOW & WiFi together on the same ESP32.

After some digging around, I came across this ESPNOW to Internet Gateway video on YouTube

With the code the for the ESP32's HERE

I was able to modify the code to now work with Arduino IoT cloud instead of BLYNK
I using an Adafruit ESP32-S2 Feather with BME280 Sensor that talks to an SparkFun Thing Plus - ESP32 WROOM (U.FL) via ESP-NOW This also has an 0.96 OLED display connected to the Qwiic
connector. This then has a 2nd UART connection + power to a cheap AZ-Delivery ESP32 Lolin LOLIN32 that connects to the Arduino IoT cloud. I'm also using the Scheduler task in the Arduino IoT cloud to control ON/OFF times of the OLED display.

This has now been running 24/7 for just over two months now :slightly_smiling_face:

Hope some of this helps you out?

2 Likes

Thank you guys for the answers, im sure these will help me a lot. I will check the links, and the video too :slight_smile:

As long as your WiFi router is on channel 1 the implementation of simultaneous ESP-NOW and WiFi is almost trivial.

A simplified version of what I have going so far has one ESP32 sending the status of a push-button switch to a second ESP32 using ESP-NOW. The second ESP32 is also using MQTT to communicate via WiFi with Node-RED running on a RaspberryPi.

I started with a simple one way ESP-NOW link between the two ESP32s. In order to implement the WiFi link on the second ESP32 all I had to do was change the WiFi mode from WIFI_STA to WIFI_AP_STA and add the usual code to connect to my access point. No changes were needed on the device with the push-button switch.

It's a bit trickier if your WiFi router is on a channel other than channel 1 but I think I have it working OK with an access point on on channel 11.

Don

1 Like

It doesn't have to disconnect and reconnect between ESP-NOW transmissions?
Can you please provide a sample code?

Not right now, I don't have any simple examples available. I'll try to put something together in the next few days.

Don

That would be wonderful, when you have the time. Thank you.

1 Like

I tried to set different channels on the ESP32 who is doing the WiFi.softAP(ssid,password,channel) part, but nothin worked. And both devices are set in WIFI_AP_STA mode. And what really interesting for me is, that with my phone i could connect to the Network AP device, and also, if i did WiFi hotspot with my phone, the connecting device could connect to my network. But if that device trying to connect to the other ESP32, its just returns me the WL_DISCONNECTED error.

I think you will have to explain how many devices you are talking about and how they are trying to communicate. A drawing would help.

You obviously have at least two ESP devices communicating with each other via ESP-NOW.

  • How many WiFi access points (routers) are involved?
  • What channel(s) are they on?
  • Which AP connects with which ESP?

In my current application I have two ESP devices and one AP. The first ESP makes a measurement and sends the result via ESP-NOW to the second ESP. The second ESP passes the measurement on to other devices via WiFi and the router.

In any case all of the devices have to be using the same channel and things are (currently) a lot easier if that is channel 1.

Don

Now i have 3 ESP32 boards, 1 is acting like an AP, one is acting like STA and connected to it, and the last one is only communicating via ESP NOW. The first 2 boards are both running in AP_STA mode. But in the end, hopefully in 1-2 weeks, i'll have 6 ESP32 boards, 4 AP device, 1 STA device, and 1 only ESP NOW using device.

I'll just explain the current situation. The ESP NOW device is sending out a "Start" signal to the STA device, to start the measurements. That device sends this "Start" signal forwards for the AP device via ESP NOW too, to start sending his packets, and measures RF and WiFi RSSI values. In this part comes in the WiFi connecting, i have to measure the WiFi RSSI values while they are connected because it takes much less time, then just scanning the available networks. After the measurements are completed, this ESP stores the datas into arrays, and then send them to the only ESP NOW using ESP32 via ESP NOW. And thats it. All device is using channel 1 by the way.

Now im able to connect to this network and measure its RSSI, but when i will have 3 more AP devices, i'll have to disconnect after the measurement is completed and have to connect to the others one by one. This is the hard part now.

Another question, why am i not able to connect to a WiFi network in the loop function? I have to make sure, that after every measurements, the ESP is disconnected from the previous network, and can connect to the next network. But i cant do that only in the Setup function

Here are the sample programs promised in reply #10.

This 'Initiator' program sends ESP-NOW data to the other programs.
In this example the data is information about the state of a push-button switch.

// use ESP-NOW to send input level information to another device
//   input can be from switch, PIR sensor, light sensor, etc.

// Compiled using Arduino 1.8.19, and ESP32 v2.0.2
// Compiled for board: ESP32 Dev Module
//   (but running on an AI Thinker ESP-CAM since I have a bunch of them)

// ...... program information
char programName[] = "InitiatorA_22A";
char versionNumber[] = "v01";
char programDate[] = "2022-09-25";
char programAuthor[] = "Donald Weiman";

// ...... hardware connections
//  Push-button switch between Vcc and GPIO13
//    or 
//  HC-SR501 PIR Motion Detector module connected to GPIO13

// ...... required libraries
#include <esp_now.h>
#include <WiFi.h>

// ...... esp-now
// remote MAC Address
uint8_t responderAddress[] = {0x34, 0x94, 0x54, 0x24, 0x3D, 0xE0};

// specify where to store information about the other devices
//   NOTE: this structure is defined in 'esp_now.h'
esp_now_peer_info_t responderInfo;

// define data structure for transmitted data
struct s_message {
    int i_id;
    int i_count; 
    char c_level[20];
} ;

// create structured data object
s_message s_switchInfo;          // to responder

// ...... switch
const byte inputPin = 13;
int oldValue = 0;
bool newValueFlag = false;
int lowCount = 0;
int highCount = 0;

// ...... misc
char thisDeviceMac[20] ;            // Mac address for this device (for info only)
int unitId = 102;                   // arbitrary 'unit id'

//  ....................................................................................
void setup() {
  Serial.begin(115200);
  delay(1000);

// ...... display sign-on message
  Serial.println();
  Serial.println();   
  Serial.print(programName);
  Serial.print(" ");
  Serial.print(versionNumber);
  Serial.print("   ");
  Serial.print(programDate);
  Serial.print("   ");
  Serial.print(programAuthor); 
  Serial.print("\n");

// ...... display MAC address (of this device)
  WiFi.macAddress().toCharArray(thisDeviceMac, 20);
  Serial.printf("\nMAC address: %s", thisDeviceMac);
  
// ...... switch
  pinMode(inputPin, INPUT_PULLDOWN);           // active HIGH

// ...... WiFi
  WiFi.mode(WIFI_STA);
  
// ...... esp-now 
  // initalize ESP-NOW 
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }  

  // register callback function
  esp_now_register_send_cb(OnDataSent);  

  // register peer (remote device)
  //   put peer information into the 'responderInfo' data structure
  memcpy(responderInfo.peer_addr, responderAddress, 6);      // address has 6 bytes
  responderInfo.channel = 0;                                 // use current channel 
  responderInfo.encrypt = false;                             // don't encrypt

  // add peer to paired device list 
  if (esp_now_add_peer(&responderInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
  }

// ...... end of setup
  Serial.print("\nsetup complete\n");  
}

//  ....................................................................................
void loop() {

  checkInputLevel();

  // send message if appropriate
  if(newValueFlag == true) {
    newValueFlag = false; 
    if(esp_now_send(responderAddress, (uint8_t *) &s_switchInfo, sizeof(s_switchInfo)) != ESP_OK) {
      Serial.println("Failed to send the data");    
    }
  }
 
  // do something else ...
  
}

//  ....................................................................................
void OnDataSent(const uint8_t *macAddr, esp_now_send_status_t status) {
  // callback when data is sent
  Serial.print("\nMessage sent");
    
  // display MAC of where the data was sent
 char macStr[25];
  snprintf(macStr, sizeof(macStr)," to %02X:%02X:%02X:%02X:%02X:%02X",
            macAddr[0], macAddr[1], macAddr[2], 
             macAddr[3], macAddr[4], macAddr[5]);
  Serial.print(macStr);

  // display the status returned by the other device
  Serial.print(", status: ");
  Serial.print(status == ESP_NOW_SEND_SUCCESS ? "OK" : "Fail");

  // display the data that was sent 
  displayTransmittedData();  
}

//  ....................................................................................
void displayTransmittedData() {
// desired data is in 's_switchInfo' structure
char dataStr[50];
  snprintf(dataStr, sizeof(dataStr), "\n Device number %u, Level %s, Count %u ",
            s_switchInfo.i_id, s_switchInfo.c_level, s_switchInfo.i_count);

  Serial.print(dataStr);
  
}

//  ....................................................................................
void checkInputLevel() {
  int value = digitalRead(inputPin);
  if (value != oldValue) {
    newValueFlag = true;  
    oldValue = value; 
      
    if(value == 1) {
      Serial.print("\n--> level has gone high");
      ++highCount;
      s_switchInfo.i_id = unitId;
      s_switchInfo.i_count = highCount;
      strcpy(s_switchInfo.c_level, "HIGH");
    }
    else {
    if(value == 0)
      Serial.print("\n--> level has gone low");
      ++lowCount;  
      s_switchInfo.i_id = unitId;
      s_switchInfo.i_count = lowCount;
      strcpy(s_switchInfo.c_level, "LOW");
    }
    delay(10);                                          // crude debounce
  }
}

//  ....................................................................................

This 'Responder' program acts upon the data sent by the 'Initiator' program.
In this example it activates some LEDs.

// use ESP-NOW to do something based on a message from another device
//   in this case just light an LED

// Compiled using Arduino 1.8.19, and ESP32 v2.0.2
// Compiled for board: ESP32 Dev Module
//   (but running on an AI Thinker ESP-CAM since I have a bunch of them)

// ...... program information
char programName[] = "ResponderA_22A";
char versionNumber[] = "v01";
char programDate[] = "2022-09-25";
char programAuthor[] = "Donald Weiman";

// ...... required libraries
#include <esp_now.h>
#include <WiFi.h>

// define data structure for received data
struct s_message {
    int i_id;
    int i_count; 
    char c_level[20];
} ;

// create structured data object
s_message s_switchInfo;             // from initiator

// ...... LED
const byte flashLedPin = 4;         // white led on AI Thinker ESP-CAM board
#define flashLedOff 0               // active high

const byte builtinLedPin = 33;      // red led on AI Thinker ESP-CAM board
#define builtinLedOff 1             // active low

// ...... misc
char thisDeviceMac[20] ;            // Mac address for this device (for info only)
bool newDataFlag = false;

//  ....................................................................................
void setup() {
  Serial.begin(115200);
  delay(1000);

// ...... display sign-on message
  Serial.println();
  Serial.println();   
  Serial.print(programName);
  Serial.print(" ");
  Serial.print(versionNumber);
  Serial.print("   ");
  Serial.print(programDate);
  Serial.print("   ");
  Serial.print(programAuthor); 
  Serial.print("\n");

// ...... display MAC address (of this device)
  WiFi.macAddress().toCharArray(thisDeviceMac, 20);
  Serial.printf("\nMAC address: %s", thisDeviceMac);

// ...... WiFi
  WiFi.mode(WIFI_STA);
  
// ...... esp-now 
  // initalize ESP-NOW 
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }  

  // register callback function
  esp_now_register_recv_cb(OnDataRecv); 

// ...... LEDs
  pinMode(flashLedPin, OUTPUT);           // turn LEDs off
  digitalWrite(flashLedPin, flashLedOff);
  
  pinMode(builtinLedPin, OUTPUT);
  digitalWrite(builtinLedPin, builtinLedOff);
  
// ...... end of setup
  Serial.print("\nsetup complete\n");
}

//  ....................................................................................
void loop() {

  // act on received data if appropriate
  if(newDataFlag == true) {
    newDataFlag = false; 
    // act on received data
    if(strcmp (s_switchInfo.c_level, "HIGH") == 0) {
      digitalWrite(flashLedPin, !flashLedOff);
      digitalWrite(builtinLedPin, builtinLedOff);      
    } else {
      digitalWrite(flashLedPin, flashLedOff);
      digitalWrite(builtinLedPin, !builtinLedOff);
    } 
  } 

  // do something else ...

}

//  ....................................................................................
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  // extract the received data
  char receivedData[100];
  memcpy(&s_switchInfo, incomingData, sizeof(s_switchInfo));

  // deal with received data
  newDataFlag = true;           // handled in loop()

  // display received data - (interesting, but not essential)
  char recvStr[50];  
  snprintf(recvStr, sizeof(recvStr), "\n %u  %u  %s",
           s_switchInfo.i_id, s_switchInfo.i_count, s_switchInfo.c_level);
  Serial.print(recvStr);
  
  // display MAC of sending device - (interesting, but not essential)
  char macStr[20];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.print("   Received from ");
  Serial.print(macStr);
} 

//  ....................................................................................

This 'Gateway' program is an expansion of the 'Responder' program.
In addition to activating the LEDs it also obtains a UNIX timestamp from an internet NTP server and displays that timestamp on the serial monitor.
I have reduced the time between NTP updates from the default 3600 seconds to 120 seconds so that it is easier to see that the WiFi capability is maintained along with the ESP-NOW capability.

// use ESP-NOW to do something based on a message from another device
//   and also connect to WiFi to display a web page, deal with MQTT etc
//     in this case just light an LED and get UNIX timestamp from an NTP server

// NOTE: WiFi router must be on Channel 1 for this simple version to work

// Compiled using Arduino 1.8.19, and ESP32 v2.0.2
// Compiled for board: ESP32 Dev Module
//   (but running on an AI Thinker ESP-CAM since I have a bunch of them)

// Based on "ResponderA_22A.ino v01

// ...... program information
char programName[] = "ResponderA_22A";
char versionNumber[] = "v01";
char programDate[] = "2022-09-25";
char programAuthor[] = "Donald Weiman";

// ...... required libraries
#include <esp_now.h>
#include <WiFi.h>
#include "esp_sntp.h"               // ntp

// define data structure for received data
struct s_message {
    int i_id;
    int i_count; 
    char c_level[20];
} ;

// create structured data object
s_message s_switchInfo;             // from initiator

// ...... LED
const byte flashLedPin = 4;         // white led on AI Thinker ESP-CAM board
#define flashLedOff 0               // active high

const byte builtinLedPin = 33;      // red led on AI Thinker ESP-CAM board
#define builtinLedOff 1             // active low

// ...... misc
char thisDeviceMac[20] ;            // Mac address for this device (for info only)
bool newDataFlag = false;

// ...... WiFi
const char* ssid = "plugh";         // on WiFi channel 1
const char* pass = "xyzzy";

// ...... NTP
char ntpPool[] = "pool.ntp.org";
char ntpTzEnvData[] = "EST+5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00";    // US/Eastern
bool ntpUpdateFlag = false;
bool changeNtpSync = true;             // false --> use default 3600 seconds
const uint32_t ntpSyncInterval = 120;   // true --> use this interval (seconds)

// ...... timing parameters (for example procedure in loop())
time_t timestampNow;

//  ....................................................................................
void setup() {
  Serial.begin(115200);
  delay(1000);

// ...... display sign-on message
  Serial.println();
  Serial.println();   
  Serial.print(programName);
  Serial.print(" ");
  Serial.print(versionNumber);
  Serial.print("   ");
  Serial.print(programDate);
  Serial.print("   ");
  Serial.print(programAuthor); 
  Serial.print("\n");

// ...... display MAC address (of this device)
  WiFi.macAddress().toCharArray(thisDeviceMac, 20);
  Serial.printf("\nMAC address: %s", thisDeviceMac);

// ...... WiFi
  WiFi.mode(WIFI_AP_STA);          // AP needed to maintain ESP-NOW
  WiFi.begin(ssid, pass);

  // Wait for connection
  Serial.println("");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

// ...... NTP
  Serial.print("\nconnecting to NTP time server");
  connectToNtp(); 
  
// ...... esp-now 
  // initalize ESP-NOW 
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }  

  // register callback function
  esp_now_register_recv_cb(OnDataRecv); 

// ...... LEDs
  pinMode(flashLedPin, OUTPUT);           // turn LEDs off
  digitalWrite(flashLedPin, flashLedOff);
  
  pinMode(builtinLedPin, OUTPUT);
  digitalWrite(builtinLedPin, builtinLedOff);
  
// ...... end of setup
  Serial.print("\nsetup complete\n");
}

//  ....................................................................................
void loop() {

  // act on received data if appropriate
  if(newDataFlag == true) {
    newDataFlag = false; 
    // act on received data
    if(strcmp (s_switchInfo.c_level, "HIGH") == 0) {
      digitalWrite(flashLedPin, !flashLedOff);
      digitalWrite(builtinLedPin, builtinLedOff);      
    } else {
      digitalWrite(flashLedPin, flashLedOff);
      digitalWrite(builtinLedPin, !builtinLedOff);
    } 
  } 

  // NTP
  timestampNow = time(nullptr);
  
  if(ntpUpdateFlag == true) {
    ntpUpdateFlag = false;
    Serial.print("\n *** NTP update ***    ");
    Serial.print(timestampNow);    
  }
  
  // do something else ...

}

//  ....................................................................................
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  // extract the received data
  char receivedData[100];
  memcpy(&s_switchInfo, incomingData, sizeof(s_switchInfo));

  // deal with received data
  newDataFlag = true;           // handled in loop()

  // display received data - (interesting, but not essential)
  char recvStr[50];  
  snprintf(recvStr, sizeof(recvStr), "\n %u  %u  %s",
           s_switchInfo.i_id, s_switchInfo.i_count, s_switchInfo.c_level);
  Serial.print(recvStr);
  
  // display MAC of sending device - (interesting, but not essential)
  char macStr[20];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.print("   Received from ");
  Serial.print(macStr);
  
  // display UNIX timestamp obtained from NTP server
  Serial.print(" at ");
  Serial.print(timestampNow);
}

//  ....................................................................................
// NTP time synchronization callback
// this callback will place the current UNIX timestamp that was obtained 
//   via NTP into a structure of type 'timeval' which is defined in time.h 
// it looks like this must be done even if the data isn't subsequently used

// NOTE: This is apparently being executed in a different core from the 
//       main program.  Any Serial.print data here may be interspersed with 
//       Serial.print data from the main program.

void time_is_set (struct timeval *bogus) {
  ntpUpdateFlag = true;
}

// ....................................................................................  
void connectToNtp() {
  // possibly change NTP sync interval (default = 3600 seconds)  
  if(changeNtpSync == true) {
    sntp_set_sync_interval(ntpSyncInterval * 1000);
  }
    
  // register a callback to notify about the time synchronization process
  sntp_set_time_sync_notification_cb(time_is_set);

  // obtain system time from an NTP server, do not adjust for timezone or dst here
  configTime(0, 0, ntpPool);                // obtain time from an NTP server
  
  // set the TZ environment variable to the correct value for the device location
  setenv("TZ", ntpTzEnvData, 1);
  
  // update the C library runtime data for the new time zone
  tzset();   
}

//  ....................................................................................

Here's what the output of the 'Gateway' program looks like on the serial monitor.

ResponderA_22A v01   2022-09-25   Donald Weiman

MAC address: 34:94:54:24:3D:E0
.....
Connected to Area51
IP address: 10.12.12.214

connecting to NTP time server
setup complete

 *** NTP update ***    1664199483
 102  5  HIGH   Received from 34:94:54:24:22:58 at 1664199497
 102  5  LOW   Received from 34:94:54:24:22:58 at 1664199497
 102  6  HIGH   Received from 34:94:54:24:22:58 at 1664199497
 102  6  LOW   Received from 34:94:54:24:22:58 at 1664199498
 102  7  HIGH   Received from 34:94:54:24:22:58 at 1664199502
 102  7  LOW   Received from 34:94:54:24:22:58 at 1664199504
 102  8  HIGH   Received from 34:94:54:24:22:58 at 1664199590
 102  8  LOW   Received from 34:94:54:24:22:58 at 1664199590
 *** NTP update ***    1664199603
 102  9  HIGH   Received from 34:94:54:24:22:58 at 1664199608
 102  9  LOW   Received from 34:94:54:24:22:58 at 1664199608
 102  10  HIGH   Received from 34:94:54:24:22:58 at 1664199609
 102  10  LOW   Received from 34:94:54:24:22:58 at 1664199610
 102  11  HIGH   Received from 34:94:54:24:22:58 at 1664199664
 102  11  LOW   Received from 34:94:54:24:22:58 at 1664199664
 102  12  HIGH   Received from 34:94:54:24:22:58 at 1664199680
 102  12  LOW   Received from 34:94:54:24:22:58 at 1664199685
 102  13  HIGH   Received from 34:94:54:24:22:58 at 1664199688
 102  13  LOW   Received from 34:94:54:24:22:58 at 1664199691
 102  14  HIGH   Received from 34:94:54:24:22:58 at 1664199719
 *** NTP update ***    1664199724
 102  14  LOW   Received from 34:94:54:24:22:58 at 1664199725
 102  15  HIGH   Received from 34:94:54:24:22:58 at 1664199823
 102  15  LOW   Received from 34:94:54:24:22:58 at 1664199823
 *** NTP update ***    1664199844
 *** NTP update ***    1664199964
 *** NTP update ***    1664200084
 *** NTP update ***    1664200205
 *** NTP update ***    1664200325
 *** NTP update ***    1664200446
 *** NTP update ***    1664200566
 102  16  HIGH   Received from 34:94:54:24:22:58 at 1664200644

Note that the majority of the additional code in the 'Gateway' program is there to deal with obtaining the timestamp and this capability has been provided as a means of showing that the WiFi is working. As I mentioned in my previous post actually adding this WiFi capability is trivial.

All three programs are available in this zip file.
ProgramCode.zip (5.9 KB)

EDIT: I created the 'Gateway' program by starting with the 'Responder' program. I see that I forgot to change the name in the "program information" section so the program name shows up improperly in the serial output.

Don

2 Likes

Thank you, Don. This is a very useful contribution to the forum.
@Idahowalker check this out.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.