Poor UDP receipt reliability on ESP32s

I've just written a pair of programs, one to send and one to receive UDP messages, between two ESP32 controllers. Written using VSCode and Platform IO.
Code all works, and I see all sent messages on wireshark, but the receiver misses quite a few of the messages, regardless of how close to the sender it is. I even tried using an ESP32 with external antenna with no discernible improvement.

Would anyone care to offer an idea of why so many messages are being missed? Sometimes its say one in 10, sometimes it could be 2 or three missed in a row. I was expecting none to be missed to be honest. (I tried a couple of different ways of sending the packet, hence the commented out stuff in the sender - but I think its in the receipt that the error happens).

Here is the sender code:

#include <Arduino.h>
#include <WiFi.h>
//#include <AsyncUDP.h>
#include <WiFiUDP.h>

#define TX_PORT 1234


const char* ssid = "********";
const char* password = "********";
byte counter = 0;

//AsyncUDP udp;
WiFiUDP udp;

void setup() {
  pinMode(2, OUTPUT);
  Serial.begin(115200);

  WiFi.disconnect(true);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid,password);

  Serial.print("Connecting to Wifi");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWifi Connected");
}

void loop()
{
  //char msgBuf[100];
  //sprintf(msgBuf,"Hello from Node Msg#%d",counter);
  //udp.broadcastTo(msgBuf, TX_PORT);
  //Serial.printf("Sent UDP Message [%s]\n",msgBuf);
  //counter++;
  //udp.broadcastTo("Hello World", TX_PORT);

  uint8_t buffer[50] = "hello world";
  //This initializes udp and transfer buffer
  udp.beginPacket("192.168.0.255", TX_PORT);
  udp.write(buffer, 11);
  udp.endPacket();
  memset(buffer, 0, 50);

  delay(1000);
}

and here is the receiver code:

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncUDP.h>
#define RX_PORT 1234

const char* ssid = "********";
const char* password = "********";
byte counter = 0;

AsyncUDP udp;
bool lightOn = true;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);

  WiFi.disconnect(true);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid,password);

  Serial.print("Connecting to Wifi");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  if(udp.listen(RX_PORT))
  {
    Serial.printf("Listening for UDP on Port %04d, IP:",RX_PORT);
    Serial.println(WiFi.localIP());
    udp.onPacket([](AsyncUDPPacket packet){
      Serial.print("UDP Packet Type: ");
      Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
      Serial.print(", From: ");
      Serial.print(packet.remoteIP());
      Serial.print(":");
      Serial.print(packet.localPort());
      Serial.print(", To: ");
      Serial.print(packet.localIP());
      Serial.print(":");
      Serial.print(packet.localPort());
      Serial.print(", Length: ");
      Serial.print(packet.length());
      //Serial.print(", Data: ");
      //Serial.write(packet.data(), packet.length());
      Serial.println();

      char* tempStr = (char*) malloc (packet.length()+1);
      memcpy (tempStr, packet.data(), packet.length());
      tempStr[packet.length()] = '\0';

      String message = String(tempStr);
      free(tempStr);

      Serial.println(message);
      // do message actions here.....

      if (lightOn)
        digitalWrite(LED_BUILTIN, HIGH);
      else
        digitalWrite(LED_BUILTIN, LOW);

      lightOn = !lightOn;
      
    });
  }
}

void loop()
{
  //delay(1000);
  //udp.broadcast("Hello from a node");
}

Cheers,
Jim

UDP is not guaranteed, could be network issue that causes lost packets..
Not familiar with the lib you used, so can't comment on the codes..

did a udp send and receive demo while back for esp32..
UDP Send
UDP Receive

sends a structure and contains a sequence that increments with each packet, so I can tell if we are missing a packet..

maybe it gives you some ideas..

good luck.. ~q

1 Like

Can you try a different pair of ESPs and see if the problem goes away, if so you probably have one that is not working properly, If the problem stays it is a network problem.

1 Like

Hi Qubits
I'll take a look at your examples and see if I get any different results - thanks.
I am investigating using UDP as I am getting some issues using PainlessMesh. I am going to be using a number of message repeats in the final version to hopefully get around the issue of not guaranteed delivery. Next step will be to encode some JSON into the messages.

1 Like

Hi gilshitz.
I have actually done that already with the same results. Thanks for the input - all ideas are appreciated.

UDP is a connectionless protocol (no guarantee of reliable communication)
try TCP which is a connection oriented protocol with flow control, error checking, etc

1 Like

Hi Horace.
I’m aware its connection less thanks. The application is one to many, and it’s not absolutely necessary to guarantee delivery, hence the choice.

What I’m surprised about is the frequency of failure. The ESPs are with a few feet of each other and the WAP that they’re connected to, and the message payload is only a handful of bytes.

I’d expect something like 99% success with those parameters, not 80% which is about what I'm seeing and I don’t know why.

could you use UDP multicast
all clients receive the datagram which contains a node ID to identify the target client

@jimbo182
I agree that the failure rate should be a lot lower. I use UDP on my WiFi network and don't see the failure rate you are experiencing.

I am aware of problems with WiFi V6 enabled WAPs not working well with some legacy WiFi kit. Is your WAP fairly new and WiFi V6 enabled? If so try turning off V6 capabilities or use an older WAP not capable of V6.

1 Like

I modifed the two ESP32 async examples

I get nearly no missing transmissions over several hundred packets.

a missing packet is shown on the server side with such a line:

missed packet in=0 vs 189

and happens when I restart the client.

server

/*
    based on AsyncUDPServer
    expects an incomming message of a counter
    compares the incomming counter with its internal counter

    2023-10-23 stripped down by noiasca

*/

#include "WiFi.h"
#include "AsyncUDP.h"

// WiFi network name and password:
//#include <credentials.h>               // if you have an external file with your credentials you can use it
#ifndef STASSID                        // either use an external .h file containing STASSID and STAPSK - or
//                                     // add defines to your boards - or
#define STASSID "your-ssid"            // ... modify these line to your SSID
#define STAPSK  "your-password"        // ... and set your WIFI password
#endif
const char ssid[] = STASSID;
const char password[] = STAPSK;

AsyncUDP udp;
uint32_t otherCounter = 0;

void setup()
{
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Failed");
    while (1) {
      delay(1000);
    }
  }
  Serial.println(WiFi.localIP());
  if (udp.listen(1234)) {
    Serial.print("UDP Listening on IP: ");
    Serial.println(WiFi.localIP());
    udp.onPacket([](AsyncUDPPacket packet) {
      Serial.print("UDP Packet Type: ");
      Serial.print(packet.isBroadcast() ? "Broadcast" : packet.isMulticast() ? "Multicast" : "Unicast");
      Serial.print(", From: ");
      Serial.print(packet.remoteIP());
      Serial.print(":");
      Serial.print(packet.remotePort());
      Serial.print(", To: ");
      Serial.print(packet.localIP());
      Serial.print(":");
      Serial.print(packet.localPort());
      Serial.print(", Length: ");
      Serial.print(packet.length());
      Serial.print(", Data: ");
      Serial.write(packet.data(), packet.length());
      Serial.println();
      //reply to the client
      //packet.printf("Got %u bytes of data", packet.length());

      char buf[packet.length() + 1] = {};           // workaround for "invalid conversion from"
      memcpy(buf, packet.data(), packet.length());
      uint32_t in = atol(buf + 8);
      if (in != otherCounter + 1) {
        Serial.print("missed packet in="); Serial.print(in); Serial.print(" vs "); Serial.println(otherCounter + 1);
      }
      otherCounter = in;
    });
  }
}

void loop() {
  //do other things here

}

client - adopt server IP to your needs

/*
    based on AsyncUDPClient
    sends an internal counter via  UDP unicast to a server

    2023-10-23 stripped down by noiasca
*/


#include "WiFi.h"
#include "AsyncUDP.h"

// WiFi network name and password:
//#include <credentials.h>               // if you have an external file with your credentials you can use it
#ifndef STASSID                        // either use an external .h file containing STASSID and STAPSK - or
//                                     // add defines to your boards - or
#define STASSID "your-ssid"            // ... modify these line to your SSID
#define STAPSK  "your-password"        // ... and set your WIFI password
#endif
const char ssid[] = STASSID;
const char password[] = STAPSK;

AsyncUDP udp;

void sendUdp() {
  static unsigned long counter = 0;
  //send unicast
  if (udp.connect(IPAddress(172, 18, 67, 210), 1234)) {
    Serial.print("UDP connected - counter="); Serial.println(counter);
    udp.printf("counter=%lu", counter);
    counter++;
  }
  //Send unicast
}

void timerSendUdp() {
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 1 * 1000UL) {
    previousMillis = millis();
    //Serial.println(F("time to send"));
    sendUdp();
  }
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Failed");
    while (1) {
      delay(1000);
    }
  }
  Serial.println(WiFi.localIP());
}

void loop() {
  timerSendUdp();
}
1 Like

I am attempting to make my solution without a switch (it is battery powered and portable), so multicast would be problematic - plus I dont see how it would be better than broadcast if it was working better,

Thanks - the point about V6 is interesting. I am using new Ubiquiti WAPs at the moment but I have an ancient Netgear one I can experiment with (or tur off V6 in the Ubiquiti).
Thanks for the idea.

Thats really interesting that you get hardly any misses.
Could you change the unicast to broadcast and see if its the same? Perhaps my network doesnt like the broadcast address and is filtering?
I will add your counter for statistics - neat idea.

I have changed the situation on my sender end, and I am now injecting the UDP into the network using an ESP32 with an ethernet connection, so this removes one possible point of data loss. It seems better, but I will continue experimenting.

Yes I can.
You too. Just send a broadcast instead to call sendUdp() in the timer.

But in a first step, just use both sketches as provided and check what happens on your wifi.

I have made further changes and instead of using the sending ESP32 as an access point, I know use the ethernet connected device to a router which acts as a Wifi AP and DHCP server for the connected ESPs. The transmission now appears to MUCH more consistent.
Its strange that I could see all the message on the network via wireshark, but not see them all be received. I guess its a problem with the network forwarding the messages to the receiver.
Anyhow, it seems useable now.
Thanks for everyones input.
Does anyone have a feel for how many wifi connected devices a standard home router will accomodate?

What make and model?

my codes in #10 don't use AP mode. Both rely on a given WIFI and both get their IP via DHCP.
Have you ever tried my codes?
What was your learning from my codes?

depends on the subnet..
typically this is 255.255.255.0
allowing for 254 addresses..
This effects dhcp server and local broadcast address..
There's 2 broadcast addresses, i call them local and global..
local broadcast usually has a better chance of being routed properly and it's the one you are currently using, the other is 255.255.255.255, should be routed to all systems connected to router..
If you do change your subnet to allow for more systems 255.255.0.0 then the local broadcast should also be changed to something like 192.168.255.255..
But really try to keep it under a few hundred using a 255.255.255.0 subnet with typical home routers..

I should also add, i've found udp to be very reliable, haven't noticed any dropped packets, could be an issue with your router, try another if you can..

good luck.. ~q

Its a Netgear DGN1000SP

I didnt try your code exactly yet, just used the counter - Sorry I was working on some other part of the solution. I will make a project with your codes and let you know.