[Solved] UDP listening fails after several hours (Photon)

I am working on a local network control application using UDP to trigger various lights, reading sensors, etc. I have noticed that my UDP connection will fail to function after a period of 12-24 hours. I only call UDP.begin() once in setup. But I’m using static IP so I don’t expect to have the same issues raised here with DHCP and leases.

I am currently running similar code on an Arduino Leonardo and Ethernet Shield, which has been running smoothly for months.

The way this code works is by sending a UDP command over port 8888 from a device such as another photon or phone or PC application. The format for this command is ll;1 to turn on the light and ll;0 to turn off the light. This is boiled down, so I usually have more going on, but I trimmed it down to this and still see the issue arise.

Please let me know if there is anything wrong in my implementation or if I need to make special considerations for my application. I am hoping not to need to reset the UDP port every interval of time. And since this is a receive only function with only occasional transactions, I will not be able to get an error code to trigger a reset as suggested here.

I am also using an external antenna and have a strong WiFi connection to the router, so I don’t believe this would be the issue.

Final note: running Photon with 0.5.0 firmware rev.

//boilded down UDP script
    STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));
    
    unsigned int localPort = 8888;      // local port to listen on
    
    const int UDP_TX_PACKET_MAX_SIZE = 512;
    
    UDP Udp;
    
    char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,
    String pBStr = "";
    
    const uint8_t LEDswitchpin = D7;
    
    void setup() {
        IPAddress myAddress(10, 0, 0, 157);
        IPAddress netmask(255,255,255,0);
        IPAddress gateway(10, 0, 0, 1);
        IPAddress dns(8,8,8,8);
        WiFi.setStaticIP(myAddress, netmask, gateway, dns);
    
        pinMode(LEDswitchpin, OUTPUT);
        
        WiFi.useStaticIP();
    
        Udp.begin(localPort);
    }
    
    char *p;
    char *str;
    
    void loop() {
      int packetSize = Udp.parsePacket();
      if(packetSize)
      {
            IPAddress remote = Udp.remoteIP();
    
            // read the packet into packetBufffer
            Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE);
    
        p = packetBuffer;
    
        String dvc = String(strtok_r(p, ";", &p));
            uint8_t devstate;
            if (dvc == "ll")
            {
    
              while ((str = strtok_r(p, ";", &p)) != NULL) // delimiter is the semicolon
              {
                pBStr = String(str);
                int pBStr_len = pBStr.length();
                String op = pBStr.substring(0,1);
                if (op == "1") {
                  devstate = 1; //ON
                }
                else if (op == "0") {
                  devstate = 0; //OFF
                }
              }
              if (devstate == 1) {
                LEDturnON();
              }
              else if (devstate == 0) {
                LEDturnOFF();
              }
            }
    
        pBStr = "";
        memset(packetBuffer,'\0',UDP_TX_PACKET_MAX_SIZE);
      } //END OF DECODING COMMAND
    
    }
    
    
    void LEDturnON() {
        digitalWrite(LEDswitchpin, HIGH);
    }
    
    void LEDturnOFF() {
      digitalWrite(LEDswitchpin, LOW);
    }

Thanks for any help. :smile:

I ran a test some time ago and found some tricks were necessary to make a UDP listener work well. I’ll find that code and re-run some tests on the current system firmware and see. It will take a few days to set up the test and run it for a while and make sure it really works, however.

1 Like

I’ll let the test run for a few days to be sure, but the initial results are promising. Not only is it stable, but I can reboot the Wi-Fi access point and it picks up right where it left off without difficulty after the Photon reconnects, even in automatic mode.

The main thing is that you should use receivePacket instead of parsePacket and read in the UDP object. Aside from being more efficient (it eliminates an extra copy of the data in memory), it returns an error code when something goes wrong. This gives you an opportunity to call begin again to restart the UDP listener. And an error can be returned even when a packet hasn’t arrived because you call receivePacket continuously out of loop().

Here’s the Photon code:

#include "Particle.h"

const size_t UDP_BUFFER_SIZE = 513;
const int UDP_PORT = 7123;

UDP udp;
uint8_t udpBuffer[UDP_BUFFER_SIZE];

void setup() {
	Serial.begin(9600);

	udp.begin(UDP_PORT);

	Serial.printlnf("server=%s:%d", WiFi.localIP().toString().c_str(), UDP_PORT);
}

void loop() {
	// receivePacket() is preferable to parsePacket()/read() because it saves a buffering step but more importantly
	// it return an error code if an error occurs! This is important to reinitialize the listener on error.
	int count = udp.receivePacket(udpBuffer, UDP_BUFFER_SIZE - 1);
	if (count > 0) {
		udpBuffer[count] = 0;
		IPAddress remoteAddr = udp.remoteIP();

		Serial.printlnf("## packet size=%d addr=%s data=%s", count, remoteAddr.toString().c_str(), udpBuffer);
	}
	else
	if (count == 0) {
		// No packet
	}
	else {
		Serial.printlnf("** receivePacket error %d", count);
		udp.begin(UDP_PORT);
	}
}

The test rig is connected to the Photon by both serial and Wi-Fi, so it knows if a packet gets lost or is corrupted. It also knows if the Photon reboots. The test server sends between 1 and 3 packets over a short period of time (0 to 2 seconds), then it will wait a random amount of time (up to 30 minutes) before sending more data to sort of approximate your use case. Each packet has different random data, as well. So far so good; it’s been running for 12 hours with no errors so far.

2 Likes

I believe that with the current Wiced stack if the WiFi code decides to disconnect from and reconnect to the AP (for whatever reason, including no-good-reason-at-all), all currently open sockets get closed. I think this is the wrong behaviour for the protocol stack, but might be what is causing the application to be less reliable than it is on a vanilla arduino.

I think there’s been some discussion on github about addressing this, but I have no idea if it made it onto the backlog or when it might be scheduled. Until then, I think the advice @rickkas7 offers is good - use access functions that return errors, so that you can detect problems and recover. That will serve you well, even if/when the protocol stack gets fixed.

2 Likes

Hi! Thanks a lot for the alternative suggestion.

I finally have time tonight to implement. I’ll let you know how it goes and I’ll post the code that works for me.

My test is still running, about 3 1/2 days since Sunday morning. Using receivePacket the listener is still alive and 422 packets have been sent and successfully received with no errors yet. It hasn’t even had to udp.begin() again. I’ll leave it running a little longer.

1 Like

Great! Thanks rickkas7 for dedicating some resources to help me out :smile:

I have compiled successfully and have started my trial as well. It seems to work as expected. Let’s see if I can keep comms up for a few days, too.

Thanks for the insight, AndyW, I’ll keep my eyes open for any development in future release notes.

Looks like that works. Script has been working without fail since I posted last.

Credit to @rickkas7 for the solve. I will now be incorporating this into my full script.

Below is the functioning script modified from that posted above.

Thank you very much! Marked topic as solved.


STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));

unsigned int localPort = 8888;      // local port to listen on

const int UDP_TX_PACKET_MAX_SIZE = 513;

UDP Udp;

char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,
String pBStr = "";

const uint8_t LEDswitchpin = D7;

void setup() {
    IPAddress myAddress(10, 0, 0, 157);
    IPAddress netmask(255,255,255,0);
    IPAddress gateway(10, 0, 0, 1);
    IPAddress dns(8,8,8,8);
    WiFi.setStaticIP(myAddress, netmask, gateway, dns);

    pinMode(LEDswitchpin, OUTPUT);

    WiFi.useStaticIP();

    Udp.begin(localPort);
}

char *p;
char *str;

char udpBuffer[UDP_TX_PACKET_MAX_SIZE];

void loop() {
  //OLD int packetSize = Udp.parsePacket();
  int count = Udp.receivePacket(udpBuffer, UDP_TX_PACKET_MAX_SIZE - 1);
  if(count > 0 /*packetSize*/)
  {
        // read the packet into packetBufffer
    //OLD Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE);
    udpBuffer[count] = 0;
    p = udpBuffer; //OLD packetBuffer;

    String dvc = String(strtok_r(p, ";", &p));
        uint8_t devstate;
        if (dvc == "ll")
        {

          while ((str = strtok_r(p, ";", &p)) != NULL) // delimiter is the semicolon
          {
            pBStr = String(str);
            int pBStr_len = pBStr.length();
            String op = pBStr.substring(0,1);
            if (op == "1") {
              devstate = 1; //ON
            }
            else if (op == "0") {
              devstate = 0; //OFF
            }
          }
          if (devstate == 1) {
            LEDturnON();
          }
          else if (devstate == 0) {
            LEDturnOFF();
          }
        }

    pBStr = "";
    memset(packetBuffer,'\0',UDP_TX_PACKET_MAX_SIZE);
  } else if (count == 0) {
    //no packet
  } else {
    //some type of error
    Udp.begin(localPort);
  } //END OF DECODING COMMAND

}

void LEDturnON() {
    digitalWrite(LEDswitchpin, HIGH);
}

void LEDturnOFF() {
  digitalWrite(LEDswitchpin, LOW);
}

2 Likes