Assistance with UDP Broadcast [Solved]

Continuing the discussion from Broadcast data to Multiple Cores:

So, I am dredging up this old topic, because I have searched and searched and cannot find what i am doing wrong.

Basically, I have built a set of under-counter LED controllers for the kitchen. They work well, I am using my home automation controller to issue Spark.function() commands (Lua).

So, I now want to use the installed LED device to issue commands to a child device, so... enter Spark.publish() and his friendly cousin Spark.subscribe() . They work brilliantly (bad pun) with a single exception... the approx. one to two seconds it takes for Spark.publish() to make it's way back to the child device, creating a little lag time (which I know is no big deal) but I'd like to minimize that.

Enter UDP... so I have been trying to take advantage of the lightning fast UDP protocol to transmit the new dim value from the Parent to the Child device. When it works, well it works like magic, almost no perceivable delay. But, It isn't stable. So I am posting my code for possible assistance from the forum members who know about this, which clearly I don't.

Here is the Parent Device code:


#include "Bounce2.h"
#include <application.h>

#define EEPROM_STATE_LOCATION 0
#define FADE_DELAY 20
#define POWER_PIN 2
#define FADE_UP_PIN 3
#define FADE_DOWN_PIN 4

#define DEBUG_ON  // Comment out to supress serial output

#define USING_UDP

#ifdef DEBUG_ON
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define SERIAL_START(x) Serial.begin(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define SERIAL_START(x)
#endif

const int ledPin = A6;
const int buttonPin[] = {POWER_PIN, FADE_UP_PIN, FADE_DOWN_PIN};
int oldValue[3];

int currentLevel = 0;

Bounce debouncer[3] = Bounce();

#ifdef USING_UDP
  IPAddress slaveIP(192, 168, 1, 255);
  unsigned int localPort = 2222;
  UDP Udp;
#endif

extern char* itoa(int a, char* buffer, unsigned char radix);

//
void setup()
{
  SERIAL_START(115200);
  pinMode(ledPin, OUTPUT);
  Spark.function("setDimLevel" , incomingDimCommand);
  Spark.variable("currentLevel", &currentLevel, INT);
#ifdef USING_UDP
  Udp.begin(localPort);
#endif
  DEBUG_PRINTLN("Retreiving saved dim level...");
  int storedLevel = EEPROM.read(EEPROM_STATE_LOCATION);
  fadeToLevel(storedLevel);
  currentLevel = storedLevel;
  Spark.publish("newDimValue", String(currentLevel));
  for (int i = 0; i < 3; i++)
  {
    debouncer[i].attach(buttonPin[i]);
    debouncer[i].interval(5);
  }
}
//
void loop()
{
#ifdef USING_UDP
  for (int i = 0; i < 3; i++)
  {
    debouncer[i].update();
    int value = debouncer[i].read();
    if (value != oldValue[i])
    {
      int fadeTarget = 0;
      switch (value)
      {
        case 0:
          fadeTarget = currentLevel? 0 : 100;
          break;
        case 1:
          fadeTarget = min(currentLevel + 10, 100);
          break;
        case 2:
          fadeTarget = max(currentLevel - 10, 0);
          break;
      }
      sendFadeTargetAsUDP(value);
      //Spark.publish("newDimValue", String(fadeTarget));// debugging
      fadeToLevel(fadeTarget);
      oldValue[i] = value;
    }
  }
#endif
}
//
void fadeToLevel(int targetLevel)
{
  int delta = (targetLevel - currentLevel) < 0 ? -1 : 1;
  while ( currentLevel != targetLevel )
  {
    currentLevel += delta;
    analogWrite( ledPin, (int)(currentLevel / 100.0 * 255) );
    delay(FADE_DELAY);
  }
}
//
int incomingDimCommand(String params)
{
  int value = params.toInt();
  value = constrain(value, 0, 100);
#ifdef USING_UDP
  sendFadeTargetAsUDP(value);
#else
  Spark.publish("newDimValue", String(value));
#endif
  fadeToLevel(value);
  DEBUG_PRINT("Set incoming dim level to");
  DEBUG_PRINTLN(value);
  EEPROM.write(EEPROM_STATE_LOCATION, currentLevel);
  return value;
}
//
#ifdef USING_UDP
void sendFadeTargetAsUDP(int slaveTarget)
{
  DEBUG_PRINTLN("sending message via UDP");
  char string1[4] = "";
  itoa(slaveTarget, string1, 10); // 10 radix;
  Udp.beginPacket(slaveIP, localPort);
  Udp.write(string1);
  Udp.write('/0');
  Udp.endPacket();
}
#endif

and the child:


#define EEPROM_STATE_LOCATION 0
#define FADE_DELAY 20
#define UDP_TX_PACKET_MAX_SIZE 25

//#define DEBUG_ON  // Comment out to supress serial output

#define USING_UDP

#ifdef DEBUG_ON

#define DEBUG_PRINT(x) Serial.print(x)#define
#define DEBUG_PRINT2(x,y) Serial.print(x,y)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define SERIAL_START(x) Serial.begin(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINT2(x,y)
#define DEBUG_PRINTLN(x)
#define SERIAL_START(x)
#endif

const int ledPin = D0;

int currentLevel = 0;

#ifdef USING_UDP
  unsigned int localPort = 2222;
  UDP udp;
#endif

//
void setup()
{
  SERIAL_START(115200);
  DEBUG_PRINTLN("Retreiving saved dim level...");
  int storedLevel = EEPROM.read(EEPROM_STATE_LOCATION);
  fadeToLevel(storedLevel);
  currentLevel = storedLevel;
  pinMode(D0, OUTPUT);
#ifdef USING_UDP
  udp.begin(localPort);
#else
  Spark.function("setDimLevel" , incomingDimCommand);
  Spark.variable("currentLevel", &currentLevel, INT);
  Spark.subscribe("newDimValue", masterRequest, "<REDACTED>");
  Spark.publish("dim_slave", String(currentLevel));
#endif
}
//
void loop()
{
#ifdef USING_UDP
  int packetSize = udp.parsePacket();
  if (packetSize)
  {
    // read the packet into packetBufffer
    char packetBuffer[UDP_TX_PACKET_MAX_SIZE] = "";
    udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    DEBUG_PRINTLN("Contents:");
    DEBUG_PRINTLN(packetBuffer);
    int udpValue = atoi(packetBuffer);
    fadeToLevel(udpValue);
  }
#endif
}
//
void fadeToLevel(int targetLevel)
{
  int delta = ( targetLevel - currentLevel ) < 0 ? -1 : 1;
  while ( currentLevel != targetLevel )
  {
    currentLevel += delta;
    analogWrite(ledPin, (int)(currentLevel / 100.0 * 255) );
    delay(FADE_DELAY);
    Spark.process();
  }
  EEPROM.write(EEPROM_STATE_LOCATION, currentLevel);
}
//
int incomingDimCommand(String params)
{
  int value = params.toInt();
  value = constrain(value, 0, 100);
  fadeToLevel(value);
  DEBUG_PRINT("Set incoming dim level to");
  DEBUG_PRINTLN(value);
  return value;
}

#ifdef USING_UDP

#else
void masterRequest(const char *event, const char *data)
{

  String params = String(data);
  int value = params.toInt();
  value = constrain(value, 0, 100);
  fadeToLevel(value);
  Spark.publish("dim_slave_Subscribe", String(currentLevel));// test
  DEBUG_PRINT("Set incoming dim level to");
  DEBUG_PRINTLN(value);
}
#endif

When you say “unstable” can you be more specific ?

Does one or both of the parent or child crash & restart ?

Does it work, but just not reliably, without either parent or child appearing to reset or hang ?

1 Like

Hey Andy,

thanks and good questions… the Parent crashes and restarts. sometimes it seems to work after a restart, other times, no.

Is the (parent) Core’s UDP buffer overloading?

Jim

Hi @BulldogLowell

On the Core, the sender receives their own broadcast packets back so you need to be ready for that and read all the broadcast data so that buffers don’t overflow.

So you should add code like if (UDP.parsePacket()) {UDP.read(junkArray, packetSize);} in loop.

1 Like

Ja, that makes sense! I'll try that, thanks.

Ah - you might be falling foul of the old “you hear your own broadcasts” problem.

Try specifying a different port to listen to when you call udp.begin() - by default it listens on the same port it transmits on.

That way the parent won’t hear it’s own messages.

I know that this should not be a problem, but buffers are scarce in the CC3000 and if it ties some up with the received broadcasts, that can cause problems.

So the idea is to call udp.begin() on the parent with a port number that is different than the one you use in udp.beginPacket() and that the children will be listening on.

Just an idea, let us know if it helps at all. UDP is kind of borked on the core, because the CC3000 doesn’t preserve packet boundaries, as long as you code for that, you can survive, but you should be coding with that fact in mind. I believe that the photon does things correctly, however.

2 Likes

Thanks @AndyW & @bko

I took @bko’s method and added a bit of code to process away the UDP message to the the Parent, and it works nearly flawlessly, almost imperceivable delay in the fading between the two devices. So, I’m going to have to do more of this!!!

Working Parent Code in case anyone comes across this issue:


#include "Bounce2.h"
#include <application.h>

#define EEPROM_STATE_LOCATION 0
#define FADE_DELAY 20
#define POWER_PIN 2
#define FADE_UP_PIN 3
#define FADE_DOWN_PIN 4

#define DEBUG_ON  // Comment out to supress serial output

#define USING_UDP  //  Comment out to switch to Spark.publish() & Spark.subscribe()

#ifdef DEBUG_ON
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define SERIAL_START(x) Serial.begin(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define SERIAL_START(x)
#endif

const int ledPin = A6;
const int buttonPin[] = {POWER_PIN, FADE_UP_PIN, FADE_DOWN_PIN};
int oldValue[3];

int currentLevel = 0;

Bounce debouncer[3] = Bounce();

#ifdef USING_UDP
  #define UDP_TX_PACKET_MAX_SIZE 25
  IPAddress slaveIP(192, 168, 1, 255);
  unsigned int localPort = 2222;
  UDP Udp;
#endif

extern char* itoa(int a, char* buffer, unsigned char radix);

//
void setup()
{
  SERIAL_START(115200);
  pinMode(ledPin, OUTPUT);
  Spark.function("setDimLevel" , incomingDimCommand);
  Spark.variable("currentLevel", &currentLevel, INT);
#ifdef USING_UDP
  Udp.begin(localPort);
#endif
  DEBUG_PRINTLN("Retreiving saved dim level...");
  int storedLevel = EEPROM.read(EEPROM_STATE_LOCATION);
  fadeToLevel(storedLevel);
  currentLevel = storedLevel;
  Spark.publish("newDimValue", String(currentLevel));
  for (int i = 0; i < 3; i++)
  {
    debouncer[i].attach(buttonPin[i]);
    debouncer[i].interval(5);
  }
}
//
void loop()
{
#ifdef USING_UDP
  if (Udp.parsePacket())
  {
    // read the packet into packetBufffer
    char packetBuffer[UDP_TX_PACKET_MAX_SIZE] = "";
    Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    DEBUG_PRINT("UDP bounceback Contents:");
    DEBUG_PRINTLN(packetBuffer);
  }
#endif
  for (int i = 0; i < 3; i++)
  {
    debouncer[i].update();
    int value = debouncer[i].read();
    if (value != oldValue[i])
    {
      int fadeTarget = 0;
      switch (value)
      {
        case 0:
          fadeTarget = currentLevel? 0 : 100;
          break;
        case 1:
          fadeTarget = min(currentLevel + 10, 100);
          break;
        case 2:
          fadeTarget = max(currentLevel - 10, 0);
          break;
      }
#ifdef USING_UDP
      sendFadeTargetAsUDP(value);
#else
      Spark.publish("newDimValue", String(fadeTarget));// debugging
#endif
      fadeToLevel(fadeTarget);
      oldValue[i] = value;
    }
  }
}
//
void fadeToLevel(int targetLevel)
{
  int delta = (targetLevel - currentLevel) < 0 ? -1 : 1;
  while ( currentLevel != targetLevel )
  {
    currentLevel += delta;
    analogWrite( ledPin, (int)(currentLevel / 100.0 * 255) );
    delay(FADE_DELAY);
  }
}
//
int incomingDimCommand(String params)
{
  int value = params.toInt();
  value = constrain(value, 0, 100);
#ifdef USING_UDP
  sendFadeTargetAsUDP(value);
#else
  Spark.publish("newDimValue", String(value));
#endif
  fadeToLevel(value);
  DEBUG_PRINT("I just set incoming dim level to ");
  DEBUG_PRINTLN(value);
  EEPROM.write(EEPROM_STATE_LOCATION, currentLevel);
  return value;
}
//
#ifdef USING_UDP
void sendFadeTargetAsUDP(int slaveTarget)
{
  DEBUG_PRINT("sending message via UDP, message = ");
  char string1[4] = "";
  itoa(slaveTarget, string1, 10); // 10 radix;
  DEBUG_PRINTLN(string1);
  Udp.beginPacket(slaveIP, 2222);
  Udp.write(string1);
  Udp.endPacket();
}
#endif

and the Child:

#define EEPROM_STATE_LOCATION 0
#define FADE_DELAY 20
#define UDP_TX_PACKET_MAX_SIZE 25

#define DEBUG_ON   // Comment out to supress serial output

#define USING_UDP  // Comment out to switch to Spark.publish() & Spark.subscribe()

#ifdef DEBUG_ON
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINT2(x,y) Serial.print(x,y)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define SERIAL_START(x) Serial.begin(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINT2(x,y)
#define DEBUG_PRINTLN(x)
#define SERIAL_START(x)
#endif

const int ledPin = D0;

int currentLevel = 0;

#ifdef USING_UDP
  unsigned int localPort = 2222;
  UDP udp;
#endif

//
void setup()
{
  SERIAL_START(115200);
  DEBUG_PRINTLN("Retreiving saved dim level...");
  int storedLevel = EEPROM.read(EEPROM_STATE_LOCATION);
  fadeToLevel(storedLevel);
  currentLevel = storedLevel;
  pinMode(D0, OUTPUT);
#ifdef USING_UDP
  udp.begin(localPort);
#else
  Spark.function("setDimLevel" , incomingDimCommand);
  Spark.variable("currentLevel", &currentLevel, INT);
  Spark.subscribe("newDimValue", masterRequest, "<REDACTED>");
  Spark.publish("dim_slave", String(currentLevel));
#endif
}
//
void loop()
{
#ifdef USING_UDP
  if (udp.parsePacket())
  {
    char packetBuffer[UDP_TX_PACKET_MAX_SIZE] = "";
    udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    DEBUG_PRINTLN("Contents:");
    DEBUG_PRINTLN(packetBuffer);
    int udpValue = atoi(packetBuffer);
    fadeToLevel(udpValue);
  }
#endif
}
//
void fadeToLevel(int targetLevel)
{
  int delta = ( targetLevel - currentLevel ) < 0 ? -1 : 1;
  while ( currentLevel != targetLevel )
  {
    currentLevel += delta;
    analogWrite(ledPin, (int)(currentLevel / 100.0 * 255) );
    delay(FADE_DELAY);
  }
  EEPROM.write(EEPROM_STATE_LOCATION, currentLevel);
}
//
int incomingDimCommand(String params)
{
  int value = params.toInt();
  value = constrain(value, 0, 100);
  fadeToLevel(value);
  DEBUG_PRINT("Set incoming dim level to");
  DEBUG_PRINTLN(value);
  return value;
}

#ifdef USING_UDP

#else
void masterRequest(const char *event, const char *data)
{

  String params = String(data);
  int value = params.toInt();
  value = constrain(value, 0, 100);
  fadeToLevel(value);
  Spark.publish("dim_slave_Subscribe", String(currentLevel));// test
  DEBUG_PRINT("Set incoming dim level to");
  DEBUG_PRINTLN(value);
}
#endif

THANKS!!!

1 Like