Photon Serial over UDP

Hello all,

It’s been a while, but I’m working on a Photon project. I’m using MaxMSP to send RGB values over serial to arduinos, in turn connected to WS2812b RGB LED strips. I’m using the NeoPixel library.

I got the Photon working fine using serial over USB. But I picked up the Photon hoping I could use UDP to send the data so all I’d have to worry about is power and ditch the USB cables. The MaxMSP patch I use sends out the color values over UDP.

The firmware compiles fine, but it’s not working.

Can anyone take a look at the code related to UDP and tell me if I’m doing something wrong?

Here’s what I have so far:

// MaxMSP WS2812b RGB Lightstrip Controller
//
// This sketch receives RGB values from MaxMSP via the serial bus
// Then it sends those values to WS2812b RGB lightstrips
// The MaxMSP patch allows control of multiple lightstrips
// It also allows for remote control via UDP on multiple computers
//
// david cool 2022
// davidcool.com
//

// This #include statement was automatically added by the Particle IDE.
#include "Particle.h"
#include "neopixel.h"
#include "math.h"

// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN D6
#define NUMPIXELS 192
#define PIXEL_TYPE WS2812B

/* Similar to above, but for an 8-bit gamma-correction table.
   Copy & paste this snippet into a Python REPL to regenerate:
   import math
   gamma=2.6
   for x in range(256):
    print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))),
    if x&15 == 15: print
 */
static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,   1,
    1,   1,   1,   1,   2,   2,   2,   2,   2,   2,   2,   2,   3,   3,   3,   3,
    3,   3,   4,   4,   4,   4,   5,   5,   5,   5,   5,   6,   6,   6,   6,   7,
    7,   7,   8,   8,   8,   9,   9,   9,   10,  10,  10,  11,  11,  11,  12,  12,
    13,  13,  13,  14,  14,  15,  15,  16,  16,  17,  17,  18,  18,  19,  19,  20,
    20,  21,  21,  22,  22,  23,  24,  24,  25,  25,  26,  27,  27,  28,  29,  29,
    30,  31,  31,  32,  33,  34,  34,  35,  36,  37,  38,  38,  39,  40,  41,  42,
    42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,
    58,  59,  60,  61,  62,  63,  64,  65,  66,  68,  69,  70,  71,  72,  73,  75,
    76,  77,  78,  80,  81,  82,  84,  85,  86,  88,  89,  90,  92,  93,  94,  96,
    97,  99,  100, 102, 103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120,
    122, 124, 125, 127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148,
    150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180,
    182, 184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
    218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, 255
};

// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIXEL_PIN, PIXEL_TYPE);

int r,g,b;
String myString;
char recieved;

// UDP Port used for two way communication
unsigned int localPort = 44444;
// An UDP instance to let us send and receive packets over UDP
UDP Udp;

void setup() {
  // Start serial listening for MaxMSP values  
  Serial.begin(115200);
  // start the UDP
  Udp.begin(localPort);
  // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.begin();
}

void loop() {
  // Check if data has been received
  if (Udp.parsePacket() > 0) {
  //if(Serial.available()) { // check to see if there's serial data in the buffer  
    
    recieved = Udp.read();
    myString += recieved;  
    
    if (recieved == 'B') {
      //Serial.print('B');
      //Serial.println(myString);
      int commaIndex = myString.indexOf(',');
      int secondCommaIndex = myString.indexOf(',', commaIndex+1);
      String firstValue = myString.substring(0, commaIndex);
      String secondValue = myString.substring(commaIndex+1, secondCommaIndex);
      String thirdValue = myString.substring(secondCommaIndex+1); 

      r = firstValue.toInt();
      g = secondValue.toInt();
      b = thirdValue.toInt(); 

      // The first NeoPixel in a strand is #0, second is 1, all the way up
      // to the count of pixels minus one.
      for(int i=0; i<NUMPIXELS; i++) { // For each pixel...
        // pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
        // Here we're using a moderately bright green color:
        pixels.setPixelColor(i, pixels.Color(r, g, b));
      }
      pixels.show();   // Send the updated pixel colors to the hardware.     
      myString ="";
    }

    if (recieved == 'C') {
      //Serial.print('C');
      //Serial.println(myString);
      String func = myString.substring(0);
      int f = func.toInt();
      
      if (f == 1) {
        while(1) {
          if(Serial.available()) { break; }
          colorWipe(pixels.Color(255,   0,   0), 50); // Red
          if(Serial.available()) { break; }
          colorWipe(pixels.Color(  0, 255,   0), 50); // Green
          if(Serial.available()) { break; }
          colorWipe(pixels.Color(  0,   0, 255), 50); // Blue
        }
      }
      if (f == 2) {
        while(1) {
          if(Serial.available()) { break; }
          rainbow(10); 
        } 
      }
      if (f == 3) {
        while(1) {
          if(Serial.available()) { break; }
          theaterChase(pixels.Color(127, 127, 127), 50); // White, half brightness
          if(Serial.available()) { break; }
          theaterChase(pixels.Color(127,   0,   0), 50); // Red, half brightness
          if(Serial.available()) { break; }
          theaterChase(pixels.Color(  0,   0, 127), 50); // Blue, half brightn  
        }
      }
      if (f == 4) {
        while(1) {
          if(Serial.available()) { break; }
          theaterChaseRainbow(50);  
        }
      }
      if (f == 5) {
        while(1) {
          if(Serial.available()) { break; }
          CylonBounce(0xff, 0, 0, 4, 10, 50); 
        }
      }
      if (f == 6) {
        while(1) {
          if(Serial.available()) { break; }
          Twinkle(0xff, 0, 0, 10, 100, false);
        }
      }
      if (f == 7) {
        while(1) {
          if(Serial.available()) { break; }
          Sparkle(0xff, 0xff, 0xff, 0); 
        }
      }
      if (f == 8) {
        while(1) {
          if(Serial.available()) { break; }
          BouncingBalls(0xff,0,0, 3);
        }
      }
      if (f == 9) {
        while(1) {
          if(Serial.available()) { break; }
          byte colors[3][3] = { {0xff, 0,0},
                        {0xff, 0xff, 0xff},
                        {0   , 0   , 0xff} };

          BouncingColoredBalls(3, colors);
        }
      }
      if (f == 10) {
        while(1) {
          if(Serial.available()) { break; }
          Fire(55,120,15);
        }
      }
      if (f == 11) {
        while(1) {
          if(Serial.available()) { break; }
          RunningLights(0xff,0xff,0x00, 50);
        }
      }
      if (f == 12) {
        while(1) {
          if(Serial.available()) { break; }
          colorWipe(0x00,0xff,0x00, 50);
          if(Serial.available()) { break; }
          colorWipe(0x00,0x00,0x00, 50);
        }
      }
      if (f == 13) {
        while(1) {
          if(Serial.available()) { break; }
          meteorRain(0xff,0xff,0xff,10, 64, true, 30);
        }
      }
      if (f == 14) {
        while(1) {
          if(Serial.available()) { break; }
          SnowSparkle(0x10, 0x10, 0x10, 20, 200);
        }
      }
      if (f == 15) {
        while(1) {
          if(Serial.available()) { break; }
          Strobe(0xff, 0xff, 0xff, 10, 50, 1000);
        }
      }
      if (f == 16) {
        while(1) {
          if(Serial.available()) { break; }
          NewKITT(0xff, 0, 0, 8, 10, 50);
        }
      }

      pixels.clear();
      pixels.show();
      myString ="";     
    }
  
  } // serial      

} // loop

// Some functions of our own for creating animated effects -----------------

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<pixels.numPixels(); i++) { // For each pixel in strip...
    if(Serial.available()) {
      break;
    }
    pixels.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    pixels.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
  for(int a=0; a<10; a++) {  // Repeat 10 times...
    if(Serial.available()) {
      break;
    }
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      if(Serial.available()) {
        break;
      }
      pixels.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<pixels.numPixels(); c += 3) {
        if(Serial.available()) {
          break;
        }
        pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      pixels.show(); // Update strip with new contents
      delay(wait);  // Pause for a moment
    }
  }
}

// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<pixels.numPixels(); i++) {
      pixels.setPixelColor(i, Wheel((i+j) & 255));
    }
    pixels.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.

// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
  int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  for(int a=0; a<30; a++) {  // Repeat 30 times...
    if(Serial.available()) {
      break;
    }
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      if(Serial.available()) {
        break;
      }
      pixels.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<pixels.numPixels(); c += 3) {
        if(Serial.available()) {
          break;
        }
        // hue of pixel 'c' is offset by an amount to make one full
        // revolution of the color wheel (range 65536) along the length
        // of the strip (strip.numPixels() steps):
        int      hue   = firstPixelHue + c * 65536L / pixels.numPixels();
        uint32_t color = gamma32(ColorHSV(hue,255,255)); // hue -> RGB
        pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      pixels.show();                // Update strip with new contents
      delay(wait);                 // Pause for a moment
      firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    }
  }
}

//Cylon bounce effect
void CylonBounce(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay){

  for(int i = 0; i < NUMPIXELS-EyeSize-2; i++) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }

  delay(ReturnDelay);

  for(int i = NUMPIXELS-EyeSize-2; i > 0; i--) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }
 
  delay(ReturnDelay);
}

// Twinkle effect
void Twinkle(byte red, byte green, byte blue, int Count, int SpeedDelay, boolean OnlyOne) {
  int c = (red << 16) + (green << 8) + blue;
  colorAll(c, 30);
 
  for (int i=0; i<Count; i++) {
     if(Serial.available()) { break; }
     pixels.setPixelColor(random(NUMPIXELS),red,green,blue);
     pixels.show();
     delay(SpeedDelay);
     if(OnlyOne) {
       if(Serial.available()) { break; }
       int c = (red << 16) + (green << 8) + blue;
       colorAll(c, 30);
     }
   }
 
  delay(SpeedDelay);
}

// sparkle
void Sparkle(byte red, byte green, byte blue, int SpeedDelay) {
  int Pixel = random(NUMPIXELS);
  pixels.setPixelColor(Pixel,red,green,blue);
  pixels.show();
  delay(SpeedDelay);
  pixels.setPixelColor(Pixel,0,0,0);
}

// bouncing balls
void BouncingBalls(byte red, byte green, byte blue, int BallCount) {
  float Gravity = -9.81;
  int StartHeight = 1;
 
  float Height[BallCount];
  float ImpactVelocityStart = sqrt( -2 * Gravity * StartHeight );
  float ImpactVelocity[BallCount];
  float TimeSinceLastBounce[BallCount];
  int   Position[BallCount];
  long  ClockTimeSinceLastBounce[BallCount];
  float Dampening[BallCount];
 
  for (int i = 0 ; i < BallCount ; i++) {  
    ClockTimeSinceLastBounce[i] = millis();
    Height[i] = StartHeight;
    Position[i] = 0;
    ImpactVelocity[i] = ImpactVelocityStart;
    TimeSinceLastBounce[i] = 0;
    Dampening[i] = 0.90 - float(i)/pow(BallCount,2);
  }

  while (true) {
    if(Serial.available()) { break; }
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      TimeSinceLastBounce[i] =  millis() - ClockTimeSinceLastBounce[i];
      Height[i] = 0.5 * Gravity * pow( TimeSinceLastBounce[i]/1000 , 2.0 ) + ImpactVelocity[i] * TimeSinceLastBounce[i]/1000;
 
      if ( Height[i] < 0 ) {                      
        Height[i] = 0;
        ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i];
        ClockTimeSinceLastBounce[i] = millis();
 
        if ( ImpactVelocity[i] < 0.01 ) {
          ImpactVelocity[i] = ImpactVelocityStart;
        }
      }
      Position[i] = round( Height[i] * (NUMPIXELS - 1) / StartHeight);
    }
 
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(Position[i],red,green,blue);
    }
   
    pixels.show();
    int c = (0 << 16) + (0 << 8) + 0;
    colorAll(c, 30);
  }
}

// meteor rain
void meteorRain(byte red, byte green, byte blue, byte meteorSize, byte meteorTrailDecay, boolean meteorRandomDecay, int SpeedDelay) {  
  int c = (0 << 16) + (0 << 8) + 0;
  colorAll(c, 30);
 
  for(int i = 0; i < NUMPIXELS+NUMPIXELS; i++) {
    if(Serial.available()) { break; }
   
    // fade brightness all LEDs one step
    for(int j=0; j<NUMPIXELS; j++) {
      if(Serial.available()) { break; }
      if( (!meteorRandomDecay) || (random(10)>5) ) {
        fadeToBlack(j, meteorTrailDecay );        
      }
    }
   
    // draw meteor
    for(int j = 0; j < meteorSize; j++) {
      if(Serial.available()) { break; }
      if( ( i-j <NUMPIXELS) && (i-j>=0) ) {
        if(Serial.available()) { break; }
        pixels.setPixelColor(i-j, red, green, blue);
      }
    }
   
    pixels.show();
    delay(SpeedDelay);
  }
}

void fadeToBlack(int ledNo, byte fadeValue) {
    // NeoPixel
    uint32_t oldColor;
    uint8_t r, g, b;
    int value;
   
    oldColor = pixels.getPixelColor(ledNo);
    r = (oldColor & 0x00ff0000UL) >> 16;
    g = (oldColor & 0x0000ff00UL) >> 8;
    b = (oldColor & 0x000000ffUL);

    r=(r<=10)? 0 : (int) r-(r*fadeValue/256);
    g=(g<=10)? 0 : (int) g-(g*fadeValue/256);
    b=(b<=10)? 0 : (int) b-(b*fadeValue/256);
   
    pixels.setPixelColor(ledNo, r,g,b);
}

// fire
void Fire(int Cooling, int Sparking, int SpeedDelay) {
  static byte heat[NUMPIXELS];
  int cooldown;
 
  // Step 1.  Cool down every cell a little
  for( int i = 0; i < NUMPIXELS; i++) {
    if(Serial.available()) { break; }
    cooldown = random(0, ((Cooling * 10) / NUMPIXELS) + 2);
   
    if(cooldown>heat[i]) {
      heat[i]=0;
    } else {
      heat[i]=heat[i]-cooldown;
    }
  }
 
  // Step 2.  Heat from each cell drifts 'up' and diffuses a little
  for( int k= NUMPIXELS - 1; k >= 2; k--) {
    if(Serial.available()) { break; }
    heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
  }
   
  // Step 3.  Randomly ignite new 'sparks' near the bottom
  if( random(255) < Sparking ) {
    int y = random(7);
    heat[y] = heat[y] + random(160,255);
    //heat[y] = random(160,255);
  }

  // Step 4.  Convert heat to LED colors
  for( int j = 0; j < NUMPIXELS; j++) {
    if(Serial.available()) { break; }
    setPixelHeatColor(j, heat[j] );
  }

  pixels.show();
  delay(SpeedDelay);
}

void setPixelHeatColor (int Pixel, byte temperature) {
  // Scale 'heat' down from 0-255 to 0-191
  byte t192 = round((temperature/255.0)*191);
 
  // calculate ramp up from
  byte heatramp = t192 & 0x3F; // 0..63
  heatramp <<= 2; // scale up to 0..252
 
  // figure out which third of the spectrum we're in:
  if( t192 > 0x80) {                     // hottest
    pixels.setPixelColor(Pixel, 255, 255, heatramp);
  } else if( t192 > 0x40 ) {             // middle
    pixels.setPixelColor(Pixel, 255, heatramp, 0);
  } else {                               // coolest
    pixels.setPixelColor(Pixel, heatramp, 0, 0);
  }
}

// running lights
void RunningLights(byte red, byte green, byte blue, int WaveDelay) {
  int Position=0;
 
  for(int j=0; j<NUMPIXELS*2; j++)
  {
      if(Serial.available()) { break; }
      Position++; // = 0; //Position + Rate;
      for(int i=0; i<NUMPIXELS; i++) {
        if(Serial.available()) { break; }
        // sine wave, 3 offset waves make a rainbow!
        //float level = sin(i+Position) * 127 + 128;
        //setPixel(i,level,0,0);
        //float level = sin(i+Position) * 127 + 128;
        pixels.setPixelColor(i,((sin(i+Position) * 127 + 128)/255)*red,
                   ((sin(i+Position) * 127 + 128)/255)*green,
                   ((sin(i+Position) * 127 + 128)/255)*blue);
      }
     
      pixels.show();
      delay(WaveDelay);
  }
}

// color wipe
void colorWipe(byte red, byte green, byte blue, int SpeedDelay) {
  for(uint16_t i=0; i<NUMPIXELS; i++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i, red, green, blue);
      pixels.show();
      delay(SpeedDelay);
  }
}

// bouncing colored balls
void BouncingColoredBalls(int BallCount, byte colors[][3]) {
  float Gravity = -9.81;
  int StartHeight = 1;
 
  float Height[BallCount];
  float ImpactVelocityStart = sqrt( -2 * Gravity * StartHeight );
  float ImpactVelocity[BallCount];
  float TimeSinceLastBounce[BallCount];
  int   Position[BallCount];
  long  ClockTimeSinceLastBounce[BallCount];
  float Dampening[BallCount];
 
  for (int i = 0 ; i < BallCount ; i++) {
    if(Serial.available()) { break; }  
    ClockTimeSinceLastBounce[i] = millis();
    Height[i] = StartHeight;
    Position[i] = 0;
    ImpactVelocity[i] = ImpactVelocityStart;
    TimeSinceLastBounce[i] = 0;
    Dampening[i] = 0.90 - float(i)/pow(BallCount,2);
  }

  while (true) {
    if(Serial.available()) { break; }
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      TimeSinceLastBounce[i] =  millis() - ClockTimeSinceLastBounce[i];
      Height[i] = 0.5 * Gravity * pow( TimeSinceLastBounce[i]/1000 , 2.0 ) + ImpactVelocity[i] * TimeSinceLastBounce[i]/1000;
 
      if ( Height[i] < 0 ) { 
        if(Serial.available()) { break; }                     
        Height[i] = 0;
        ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i];
        ClockTimeSinceLastBounce[i] = millis();
 
        if ( ImpactVelocity[i] < 0.01 ) {
          if(Serial.available()) { break; }
          ImpactVelocity[i] = ImpactVelocityStart;
        }
      }
      Position[i] = round( Height[i] * (NUMPIXELS - 1) / StartHeight);
    }
 
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(Position[i],colors[i][0],colors[i][1],colors[i][2]);
    }
   
    pixels.show();
    int c = (0 << 16) + (0 << 8) + 0;
    colorAll(c, 30);
  }
}

// snow sparkle
void SnowSparkle(byte red, byte green, byte blue, int SparkleDelay, int SpeedDelay) {
  int c = (red << 16) + (green << 8) + blue;
  colorAll(c, 30);
 
  int Pixel = random(NUMPIXELS);
  pixels.setPixelColor(Pixel,0xff,0xff,0xff);
  pixels.show();
  delay(SparkleDelay);
  pixels.setPixelColor(Pixel,red,green,blue);
  pixels.show();
  delay(SpeedDelay);
}

// strobe
void Strobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause){
  for(int j = 0; j < StrobeCount; j++) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.show();
    delay(FlashDelay);
    colorAll(c, 30);
    pixels.show();
    delay(FlashDelay);
  }
 
 delay(EndPause);
}

// new KITT
void NewKITT(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay){
  RightToLeft(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  LeftToRight(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  OutsideToCenter(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  CenterToOutside(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  LeftToRight(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  RightToLeft(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  OutsideToCenter(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  CenterToOutside(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
}

void CenterToOutside(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i =((NUMPIXELS-EyeSize)/2); i>=0; i--) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
   
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
   
    pixels.setPixelColor(NUMPIXELS-i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(NUMPIXELS-i-j, red, green, blue);
    }
    pixels.setPixelColor(NUMPIXELS-i-EyeSize-1, red/10, green/10, blue/10);
   
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

void OutsideToCenter(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i = 0; i<=((NUMPIXELS-EyeSize)/2); i++) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
   
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
   
    pixels.setPixelColor(NUMPIXELS-i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(NUMPIXELS-i-j, red, green, blue);
    }
    pixels.setPixelColor(NUMPIXELS-i-EyeSize-1, red/10, green/10, blue/10);
   
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

void LeftToRight(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i = 0; i < NUMPIXELS-EyeSize-2; i++) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

void RightToLeft(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i = NUMPIXELS-EyeSize-2; i > 0; i--) {
    if(Serial.available()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

// Set all pixels in the strip to a solid color, then wait (ms)
void colorAll(uint32_t c, uint8_t wait) {
  uint16_t i;
  
  for(i=0; i<pixels.numPixels(); i++) {
    pixels.setPixelColor(i, c);
  }
  pixels.show();
  delay(wait);
}

// A 32-bit variant of gamma8() that applies the same function
// to all components of a packed RGB or WRGB value.
uint32_t gamma32(uint32_t x) {
  uint8_t *y = (uint8_t *)&x;
  // All four bytes of a 32-bit value are filtered even if RGB (not WRGB),
  // to avoid a bunch of shifting and masking that would be necessary for
  // properly handling different endianisms (and each byte is a fairly
  // trivial operation, so it might not even be wasting cycles vs a check
  // and branch for the RGB case). In theory this might cause trouble *if*
  // someone's storing information in the unused most significant byte
  // of an RGB value, but this seems exceedingly rare and if it's
  // encountered in reality they can mask values going in or coming out.
  for (uint8_t i = 0; i < 4; i++)
    y[i] = gamma8(y[i]);
  return x; // Packed 32-bit return
}

uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {

  uint8_t r, g, b;

  // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
  // 0 is not the start of pure red, but the midpoint...a few values above
  // zero and a few below 65536 all yield pure red (similarly, 32768 is the
  // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
  // each for red, green, blue) really only allows for 1530 distinct hues
  // (not 1536, more on that below), but the full unsigned 16-bit type was
  // chosen for hue so that one's code can easily handle a contiguous color
  // wheel by allowing hue to roll over in either direction.
  hue = (hue * 1530L + 32768) / 65536;
  // Because red is centered on the rollover point (the +32768 above,
  // essentially a fixed-point +0.5), the above actually yields 0 to 1530,
  // where 0 and 1530 would yield the same thing. Rather than apply a
  // costly modulo operator, 1530 is handled as a special case below.

  // So you'd think that the color "hexcone" (the thing that ramps from
  // pure red, to pure yellow, to pure green and so forth back to red,
  // yielding six slices), and with each color component having 256
  // possible values (0-255), might have 1536 possible items (6*256),
  // but in reality there's 1530. This is because the last element in
  // each 256-element slice is equal to the first element of the next
  // slice, and keeping those in there this would create small
  // discontinuities in the color wheel. So the last element of each
  // slice is dropped...we regard only elements 0-254, with item 255
  // being picked up as element 0 of the next slice. Like this:
  // Red to not-quite-pure-yellow is:        255,   0, 0 to 255, 254,   0
  // Pure yellow to not-quite-pure-green is: 255, 255, 0 to   1, 255,   0
  // Pure green to not-quite-pure-cyan is:     0, 255, 0 to   0, 255, 254
  // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
  // the constants below are not the multiples of 256 you might expect.

  // Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
  if (hue < 510) { // Red to Green-1
    b = 0;
    if (hue < 255) { //   Red to Yellow-1
      r = 255;
      g = hue;       //     g = 0 to 254
    } else {         //   Yellow to Green-1
      r = 510 - hue; //     r = 255 to 1
      g = 255;
    }
  } else if (hue < 1020) { // Green to Blue-1
    r = 0;
    if (hue < 765) { //   Green to Cyan-1
      g = 255;
      b = hue - 510;  //     b = 0 to 254
    } else {          //   Cyan to Blue-1
      g = 1020 - hue; //     g = 255 to 1
      b = 255;
    }
  } else if (hue < 1530) { // Blue to Red-1
    g = 0;
    if (hue < 1275) { //   Blue to Magenta-1
      r = hue - 1020; //     r = 0 to 254
      b = 255;
    } else { //   Magenta to Red-1
      r = 255;
      b = 1530 - hue; //     b = 255 to 1
    }
  } else { // Last 0.5 Red (quicker than % operator)
    r = 255;
    g = b = 0;
  }

  // Apply saturation and value to R,G,B, pack into 32-bit result:
  uint32_t v1 = 1 + val;  // 1 to 256; allows >>8 instead of /255
  uint16_t s1 = 1 + sat;  // 1 to 256; same reason
  uint8_t s2 = 255 - sat; // 255 to 0
  return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
         (((((g * s1) >> 8) + s2) * v1) & 0xff00) |
         (((((b * s1) >> 8) + s2) * v1) >> 8);
}

static uint8_t    gamma8(uint8_t x) {
  return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
}

You are only ever reading one byte from the received UDP packet after that you are still looking at Serial.available() and potentially locking your code flow in an infinite loop (while(1)) because you are not sending any data via USB Serial anymore.

BTW, while(1) should always have a definitive exit condition (e.g. a timeout).

I'd also rewrite this construct

this way

const uint32_t msTIMEOUT = 5000;
...
  uint32_t msTimeout = millis();
  while(!Serial.available() && millis() - msTimeout < msTIMEOUT) {
    switch(f) {
      case ###:
        // do stuff for ###
        break;
      case ***:
        // do stuff for ***
        break;
      ...
      default:
         // catch any unexpected case 
        break;
    }
  }

You will see this will shorten your code considerably and also make it more manageable (i.e. when you need to change the common logic).

1 Like

@ScruffR
Thanks! I put in some serial responses to see what was coming out last night.

Only the first value ever comes out as you said. So I’m looking at UDP Receive Packet now. But I’m still confused at how to implement this. Are there any examples of how to receive values and break them out into variables?

From MaxMSP I’m taking R G & B values (0-255) and formatting them this way:

sprintf symout %ld,%ld,%ldB

Then I’m breaking them out again on the Particle side when I hit the “B”.

From looking at Udp.read() there’s no way to increment to the next value is there?

And yeah, I forgot about those while(1) loops! The serial active was breaking them out before, I need to change them to UDP now. They weren’t affecting the program execution though, because I wasn’t selecting/activating those functions on the MaxMSP side of things.

Many thanks for the detailed review/help!

The most important fact is that Udp.read() will only give you one single byte.
Since we don't know how you used to read the incoming data via USB Serial, I would be guessing that you used Serial.readString() or something like that that does read multiple bytes in one call.

Consequently you'd need do something similar.
For that you can take advantage of the fact that Udp.parsePacket() tells you the amount of bytes received and that can be used with Udp.read(buf, len) to read the entire packet at once.

You may also want to have a look at Udp.receivePacket()

@ScruffR
I was using this:

if(Serial.available()) { // check to see if there's serial data in the buffer  
    
    recieved = Serial.read();
    myString += recieved;  
    
    if (recieved == 'B') {
      //Serial.print('B');
      //Serial.println(myString);
      int commaIndex = myString.indexOf(',');
      int secondCommaIndex = myString.indexOf(',', commaIndex+1);
      String firstValue = myString.substring(0, commaIndex);
      String secondValue = myString.substring(commaIndex+1, secondCommaIndex);
      String thirdValue = myString.substring(secondCommaIndex+1); 

      r = firstValue.toInt();
      g = secondValue.toInt();
      b = thirdValue.toInt(); 

      // The first NeoPixel in a strand is #0, second is 1, all the way up
      // to the count of pixels minus one.
      for(int i=0; i<NUMPIXELS; i++) { // For each pixel...
        // pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
        // Here we're using a moderately bright green color:
        pixels.setPixelColor(i, pixels.Color(r, g, b));
      }
      pixels.show();   // Send the updated pixel colors to the hardware.     
      myString ="";
    }

As serial was available it would read each byte and add it to myString until it hit the “B” terminator, then split them out into variables.

I’m confused at how to do this with UPD.read() or recievePacket().

I replaced Serial.read() with UPD.read(), but it doesn’t increment, I think because it’s coming in as a complete packet, not byte by byte like serial?

If it’s the complete “xxx,xxx,xxxB” string coming through, I need to be able to parse out each xxx number taking into account each one could be x, xx, or xxx.

I have to step out for an hour or two, when I get back I’ll keep hacking at this a bit.

Thanks again!

Since UDP can also easily transport binary data, you could even get away without encoding/decoding the data into string and back entirely.

However, with a string-packet I’d use sscanf() to parse the data received via Udp.receivePacket().

I also avoid String wherever possible and rather deal with good old character arrays.

Okay, I got this working… Well, mostly… :slight_smile: It’s not very elegant though. I couldn’t sort out how to do this without a String… Which I know is the worst because it eats up tons of space, etc… BUT, it works…

On the MaxMSP side, I make a 32bit integer from the 3 RGB values:

expr ($i1 << 16) + ($i2 << 8) + $i3

I then do:

sprintf symout %s%ld

The %s is the option of which fork to run on the Photon, the %d is the 32bit int of RGB.

On the particle side it looks like this:

// This #include statement was automatically added by the Particle IDE.
#include "Particle.h"
#include "neopixel.h"
#include "math.h"

// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN D6
#define NUMPIXELS 192
#define PIXEL_TYPE WS2812B

/* Similar to above, but for an 8-bit gamma-correction table.
   Copy & paste this snippet into a Python REPL to regenerate:
   import math
   gamma=2.6
   for x in range(256):
    print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))),
    if x&15 == 15: print
 */
static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,   1,
    1,   1,   1,   1,   2,   2,   2,   2,   2,   2,   2,   2,   3,   3,   3,   3,
    3,   3,   4,   4,   4,   4,   5,   5,   5,   5,   5,   6,   6,   6,   6,   7,
    7,   7,   8,   8,   8,   9,   9,   9,   10,  10,  10,  11,  11,  11,  12,  12,
    13,  13,  13,  14,  14,  15,  15,  16,  16,  17,  17,  18,  18,  19,  19,  20,
    20,  21,  21,  22,  22,  23,  24,  24,  25,  25,  26,  27,  27,  28,  29,  29,
    30,  31,  31,  32,  33,  34,  34,  35,  36,  37,  38,  38,  39,  40,  41,  42,
    42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,
    58,  59,  60,  61,  62,  63,  64,  65,  66,  68,  69,  70,  71,  72,  73,  75,
    76,  77,  78,  80,  81,  82,  84,  85,  86,  88,  89,  90,  92,  93,  94,  96,
    97,  99,  100, 102, 103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120,
    122, 124, 125, 127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148,
    150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180,
    182, 184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
    218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, 255
};

// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIXEL_PIN, PIXEL_TYPE);

// A UDP instance to let us send and receive packets over UDP
UDP Udp;
// UDP Port used for two way communication
unsigned int localPort = 44444;
int r,g,b;

void setup() {
  // Start serial listening for MaxMSP values  
  Serial.begin(115200);
  // start the UDP
  Udp.begin(localPort);
  // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.begin();
}

void loop() {
    char message[128];
    String the_num = "";
    char option;
    uint32_t num32;
    int f;
    
    int count = Udp.receivePacket((byte*)message, 127);
    if (count == 0) {
      option = message[0];
      if (option == 'B') {
        int x = 1;
        while (x < 11) {
          the_num += message[x];
          x++;
        }
        num32 = the_num.toInt();
        //Serial.println(num32);
    
        r = num32 >> 16;
        //Serial.print("red: ");
        //Serial.println(r);
        g = num32 >> 8;
        //Serial.print("green: ");
        //Serial.println(g);
        b = num32;
        //Serial.print("blue: ");
        //Serial.println(b);
        // The first NeoPixel in a strand is #0, second is 1, all the way up
        // to the count of pixels minus one.
        for(int i=0; i<NUMPIXELS; i++) { // For each pixel...
            // pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
            // Here we're using a moderately bright green color:
            pixels.setPixelColor(i, pixels.Color(r, g, b));
        }
        pixels.show();   // Send the updated pixel colors to the hardware.     
      } // end option B
      
      if (option == 'C') {
          int x = 1;
          while (x < 5) {
              the_num += message[x];
              x++;
          }
          f = the_num.toInt();
          
          if (f == 1) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              colorWipe(pixels.Color(255,   0,   0), 50); // Red
              if(Serial.available() || Udp.parsePacket()) { break; }
              colorWipe(pixels.Color(  0, 255,   0), 50); // Green
              if(Serial.available() || Udp.parsePacket()) { break; }
              colorWipe(pixels.Color(  0,   0, 255), 50); // Blue
            }
          }
          if (f == 2) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              rainbow(10); 
            } 
          }
          if (f == 3) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              theaterChase(pixels.Color(127, 127, 127), 50); // White, half brightness
              if(Serial.available() || Udp.parsePacket()) { break; }
              theaterChase(pixels.Color(127,   0,   0), 50); // Red, half brightness
              if(Serial.available() || Udp.parsePacket()) { break; }
              theaterChase(pixels.Color(  0,   0, 127), 50); // Blue, half brightn  
            }
          }
          if (f == 4) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              theaterChaseRainbow(50);  
            }
          }
          if (f == 5) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              CylonBounce(0xff, 0, 0, 4, 10, 50); 
            }
          }
          if (f == 6) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              Twinkle(0xff, 0, 0, 10, 100, false);
            }
          }
          if (f == 7) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              Sparkle(0xff, 0xff, 0xff, 0); 
            }
          }
          if (f == 8) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              BouncingBalls(0xff,0,0, 3);
            }
          }
          if (f == 9) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              byte colors[3][3] = { {0xff, 0,0},
                            {0xff, 0xff, 0xff},
                            {0   , 0   , 0xff} };
    
              BouncingColoredBalls(3, colors);
            }
          }
          if (f == 10) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              Fire(55,120,15);
            }
          }
          if (f == 11) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              RunningLights(0xff,0xff,0x00, 50);
            }
          }
          if (f == 12) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              colorWipe(0x00,0xff,0x00, 50);
              if(Serial.available() || Udp.parsePacket()) { break; }
              colorWipe(0x00,0x00,0x00, 50);
            }
          }
          if (f == 13) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              meteorRain(0xff,0xff,0xff,10, 64, true, 30);
            }
          }
          if (f == 14) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              SnowSparkle(0x10, 0x10, 0x10, 20, 200);
            }
          }
          if (f == 15) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              Strobe(0xff, 0xff, 0xff, 10, 50, 1000);
            }
          }
          if (f == 16) {
            while(1) {
              if(Serial.available() || Udp.parsePacket()) { break; }
              NewKITT(0xff, 0, 0, 8, 10, 50);
            }
          }
    
          pixels.clear();
          pixels.show();
        } // end option c  
      } // end if
    } // end loop


// Some functions of our own for creating animated effects -----------------

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<pixels.numPixels(); i++) { // For each pixel in strip...
    if(Serial.available() || Udp.parsePacket()) {
      break;
    }
    pixels.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    pixels.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
  for(int a=0; a<10; a++) {  // Repeat 10 times...
    if(Serial.available() || Udp.parsePacket()) {
      break;
    }
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      if(Serial.available() || Udp.parsePacket()) {
        break;
      }
      pixels.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<pixels.numPixels(); c += 3) {
        if(Serial.available() || Udp.parsePacket()) {
          break;
        }
        pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      pixels.show(); // Update strip with new contents
      delay(wait);  // Pause for a moment
    }
  }
}

// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<pixels.numPixels(); i++) {
      pixels.setPixelColor(i, Wheel((i+j) & 255));
    }
    pixels.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.

// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
  int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  for(int a=0; a<30; a++) {  // Repeat 30 times...
    if(Serial.available() || Udp.parsePacket()) {
      break;
    }
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      if(Serial.available() || Udp.parsePacket()) {
        break;
      }
      pixels.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<pixels.numPixels(); c += 3) {
        if(Serial.available() || Udp.parsePacket()) {
          break;
        }
        // hue of pixel 'c' is offset by an amount to make one full
        // revolution of the color wheel (range 65536) along the length
        // of the strip (strip.numPixels() steps):
        int      hue   = firstPixelHue + c * 65536L / pixels.numPixels();
        uint32_t color = gamma32(ColorHSV(hue,255,255)); // hue -> RGB
        pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      pixels.show();                // Update strip with new contents
      delay(wait);                 // Pause for a moment
      firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    }
  }
}

//Cylon bounce effect
void CylonBounce(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay){

  for(int i = 0; i < NUMPIXELS-EyeSize-2; i++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }

  delay(ReturnDelay);

  for(int i = NUMPIXELS-EyeSize-2; i > 0; i--) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }
 
  delay(ReturnDelay);
}

// Twinkle effect
void Twinkle(byte red, byte green, byte blue, int Count, int SpeedDelay, boolean OnlyOne) {
  int c = (red << 16) + (green << 8) + blue;
  colorAll(c, 30);
 
  for (int i=0; i<Count; i++) {
     if(Serial.available()) { break; }
     pixels.setPixelColor(random(NUMPIXELS),red,green,blue);
     pixels.show();
     delay(SpeedDelay);
     if(OnlyOne) {
       if(Serial.available() || Udp.parsePacket()) { break; }
       int c = (red << 16) + (green << 8) + blue;
       colorAll(c, 30);
     }
   }
 
  delay(SpeedDelay);
}

// sparkle
void Sparkle(byte red, byte green, byte blue, int SpeedDelay) {
  int Pixel = random(NUMPIXELS);
  pixels.setPixelColor(Pixel,red,green,blue);
  pixels.show();
  delay(SpeedDelay);
  pixels.setPixelColor(Pixel,0,0,0);
}

// bouncing balls
void BouncingBalls(byte red, byte green, byte blue, int BallCount) {
  float Gravity = -9.81;
  int StartHeight = 1;
 
  float Height[BallCount];
  float ImpactVelocityStart = sqrt( -2 * Gravity * StartHeight );
  float ImpactVelocity[BallCount];
  float TimeSinceLastBounce[BallCount];
  int   Position[BallCount];
  long  ClockTimeSinceLastBounce[BallCount];
  float Dampening[BallCount];
 
  for (int i = 0 ; i < BallCount ; i++) {  
    ClockTimeSinceLastBounce[i] = millis();
    Height[i] = StartHeight;
    Position[i] = 0;
    ImpactVelocity[i] = ImpactVelocityStart;
    TimeSinceLastBounce[i] = 0;
    Dampening[i] = 0.90 - float(i)/pow(BallCount,2);
  }

  while (true) {
    if(Serial.available()) { break; }
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      TimeSinceLastBounce[i] =  millis() - ClockTimeSinceLastBounce[i];
      Height[i] = 0.5 * Gravity * pow( TimeSinceLastBounce[i]/1000 , 2.0 ) + ImpactVelocity[i] * TimeSinceLastBounce[i]/1000;
 
      if ( Height[i] < 0 ) {                      
        Height[i] = 0;
        ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i];
        ClockTimeSinceLastBounce[i] = millis();
 
        if ( ImpactVelocity[i] < 0.01 ) {
          ImpactVelocity[i] = ImpactVelocityStart;
        }
      }
      Position[i] = round( Height[i] * (NUMPIXELS - 1) / StartHeight);
    }
 
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(Position[i],red,green,blue);
    }
   
    pixels.show();
    int c = (0 << 16) + (0 << 8) + 0;
    colorAll(c, 30);
  }
}

// meteor rain
void meteorRain(byte red, byte green, byte blue, byte meteorSize, byte meteorTrailDecay, boolean meteorRandomDecay, int SpeedDelay) {  
  int c = (0 << 16) + (0 << 8) + 0;
  colorAll(c, 30);
 
  for(int i = 0; i < NUMPIXELS+NUMPIXELS; i++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
   
    // fade brightness all LEDs one step
    for(int j=0; j<NUMPIXELS; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      if( (!meteorRandomDecay) || (random(10)>5) ) {
        fadeToBlack(j, meteorTrailDecay );        
      }
    }
   
    // draw meteor
    for(int j = 0; j < meteorSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      if( ( i-j <NUMPIXELS) && (i-j>=0) ) {
        if(Serial.available() || Udp.parsePacket()) { break; }
        pixels.setPixelColor(i-j, red, green, blue);
      }
    }
   
    pixels.show();
    delay(SpeedDelay);
  }
}

void fadeToBlack(int ledNo, byte fadeValue) {
    // NeoPixel
    uint32_t oldColor;
    uint8_t r, g, b;
    int value;
   
    oldColor = pixels.getPixelColor(ledNo);
    r = (oldColor & 0x00ff0000UL) >> 16;
    g = (oldColor & 0x0000ff00UL) >> 8;
    b = (oldColor & 0x000000ffUL);

    r=(r<=10)? 0 : (int) r-(r*fadeValue/256);
    g=(g<=10)? 0 : (int) g-(g*fadeValue/256);
    b=(b<=10)? 0 : (int) b-(b*fadeValue/256);
   
    pixels.setPixelColor(ledNo, r,g,b);
}

// fire
void Fire(int Cooling, int Sparking, int SpeedDelay) {
  static byte heat[NUMPIXELS];
  int cooldown;
 
  // Step 1.  Cool down every cell a little
  for( int i = 0; i < NUMPIXELS; i++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    cooldown = random(0, ((Cooling * 10) / NUMPIXELS) + 2);
   
    if(cooldown>heat[i]) {
      heat[i]=0;
    } else {
      heat[i]=heat[i]-cooldown;
    }
  }
 
  // Step 2.  Heat from each cell drifts 'up' and diffuses a little
  for( int k= NUMPIXELS - 1; k >= 2; k--) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
  }
   
  // Step 3.  Randomly ignite new 'sparks' near the bottom
  if( random(255) < Sparking ) {
    int y = random(7);
    heat[y] = heat[y] + random(160,255);
    //heat[y] = random(160,255);
  }

  // Step 4.  Convert heat to LED colors
  for( int j = 0; j < NUMPIXELS; j++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    setPixelHeatColor(j, heat[j] );
  }

  pixels.show();
  delay(SpeedDelay);
}

void setPixelHeatColor (int Pixel, byte temperature) {
  // Scale 'heat' down from 0-255 to 0-191
  byte t192 = round((temperature/255.0)*191);
 
  // calculate ramp up from
  byte heatramp = t192 & 0x3F; // 0..63
  heatramp <<= 2; // scale up to 0..252
 
  // figure out which third of the spectrum we're in:
  if( t192 > 0x80) {                     // hottest
    pixels.setPixelColor(Pixel, 255, 255, heatramp);
  } else if( t192 > 0x40 ) {             // middle
    pixels.setPixelColor(Pixel, 255, heatramp, 0);
  } else {                               // coolest
    pixels.setPixelColor(Pixel, heatramp, 0, 0);
  }
}

// running lights
void RunningLights(byte red, byte green, byte blue, int WaveDelay) {
  int Position=0;
 
  for(int j=0; j<NUMPIXELS*2; j++)
  {
      if(Serial.available() || Udp.parsePacket()) { break; }
      Position++; // = 0; //Position + Rate;
      for(int i=0; i<NUMPIXELS; i++) {
        if(Serial.available() || Udp.parsePacket()) { break; }
        // sine wave, 3 offset waves make a rainbow!
        //float level = sin(i+Position) * 127 + 128;
        //setPixel(i,level,0,0);
        //float level = sin(i+Position) * 127 + 128;
        pixels.setPixelColor(i,((sin(i+Position) * 127 + 128)/255)*red,
                   ((sin(i+Position) * 127 + 128)/255)*green,
                   ((sin(i+Position) * 127 + 128)/255)*blue);
      }
     
      pixels.show();
      delay(WaveDelay);
  }
}

// color wipe
void colorWipe(byte red, byte green, byte blue, int SpeedDelay) {
  for(uint16_t i=0; i<NUMPIXELS; i++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i, red, green, blue);
      pixels.show();
      delay(SpeedDelay);
  }
}

// bouncing colored balls
void BouncingColoredBalls(int BallCount, byte colors[][3]) {
  float Gravity = -9.81;
  int StartHeight = 1;
 
  float Height[BallCount];
  float ImpactVelocityStart = sqrt( -2 * Gravity * StartHeight );
  float ImpactVelocity[BallCount];
  float TimeSinceLastBounce[BallCount];
  int   Position[BallCount];
  long  ClockTimeSinceLastBounce[BallCount];
  float Dampening[BallCount];
 
  for (int i = 0 ; i < BallCount ; i++) {
    if(Serial.available()) { break; }  
    ClockTimeSinceLastBounce[i] = millis();
    Height[i] = StartHeight;
    Position[i] = 0;
    ImpactVelocity[i] = ImpactVelocityStart;
    TimeSinceLastBounce[i] = 0;
    Dampening[i] = 0.90 - float(i)/pow(BallCount,2);
  }

  while (true) {
    if(Serial.available()) { break; }
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      TimeSinceLastBounce[i] =  millis() - ClockTimeSinceLastBounce[i];
      Height[i] = 0.5 * Gravity * pow( TimeSinceLastBounce[i]/1000 , 2.0 ) + ImpactVelocity[i] * TimeSinceLastBounce[i]/1000;
 
      if ( Height[i] < 0 ) { 
        if(Serial.available()) { break; }                     
        Height[i] = 0;
        ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i];
        ClockTimeSinceLastBounce[i] = millis();
 
        if ( ImpactVelocity[i] < 0.01 ) {
          if(Serial.available()) { break; }
          ImpactVelocity[i] = ImpactVelocityStart;
        }
      }
      Position[i] = round( Height[i] * (NUMPIXELS - 1) / StartHeight);
    }
 
    for (int i = 0 ; i < BallCount ; i++) {
      if(Serial.available()) { break; }
      pixels.setPixelColor(Position[i],colors[i][0],colors[i][1],colors[i][2]);
    }
   
    pixels.show();
    int c = (0 << 16) + (0 << 8) + 0;
    colorAll(c, 30);
  }
}

// snow sparkle
void SnowSparkle(byte red, byte green, byte blue, int SparkleDelay, int SpeedDelay) {
  int c = (red << 16) + (green << 8) + blue;
  colorAll(c, 30);
 
  int Pixel = random(NUMPIXELS);
  pixels.setPixelColor(Pixel,0xff,0xff,0xff);
  pixels.show();
  delay(SparkleDelay);
  pixels.setPixelColor(Pixel,red,green,blue);
  pixels.show();
  delay(SpeedDelay);
}

// strobe
void Strobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause){
  for(int j = 0; j < StrobeCount; j++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.show();
    delay(FlashDelay);
    colorAll(c, 30);
    pixels.show();
    delay(FlashDelay);
  }
 
 delay(EndPause);
}

// new KITT
void NewKITT(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay){
  RightToLeft(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  LeftToRight(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  OutsideToCenter(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  CenterToOutside(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  LeftToRight(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  RightToLeft(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  OutsideToCenter(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
  CenterToOutside(red, green, blue, EyeSize, SpeedDelay, ReturnDelay);
}

void CenterToOutside(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i =((NUMPIXELS-EyeSize)/2); i>=0; i--) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
   
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
   
    pixels.setPixelColor(NUMPIXELS-i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(NUMPIXELS-i-j, red, green, blue);
    }
    pixels.setPixelColor(NUMPIXELS-i-EyeSize-1, red/10, green/10, blue/10);
   
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

void OutsideToCenter(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i = 0; i<=((NUMPIXELS-EyeSize)/2); i++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
   
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
   
    pixels.setPixelColor(NUMPIXELS-i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(NUMPIXELS-i-j, red, green, blue);
    }
    pixels.setPixelColor(NUMPIXELS-i-EyeSize-1, red/10, green/10, blue/10);
   
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

void LeftToRight(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i = 0; i < NUMPIXELS-EyeSize-2; i++) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

void RightToLeft(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) {
  for(int i = NUMPIXELS-EyeSize-2; i > 0; i--) {
    if(Serial.available() || Udp.parsePacket()) { break; }
    int c = (red << 16) + (green << 8) + blue;
    colorAll(c, 30);
    pixels.setPixelColor(i, red/10, green/10, blue/10);
    for(int j = 1; j <= EyeSize; j++) {
      if(Serial.available() || Udp.parsePacket()) { break; }
      pixels.setPixelColor(i+j, red, green, blue);
    }
    pixels.setPixelColor(i+EyeSize+1, red/10, green/10, blue/10);
    pixels.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

// Set all pixels in the strip to a solid color, then wait (ms)
void colorAll(uint32_t c, uint8_t wait) {
  uint16_t i;
  
  for(i=0; i<pixels.numPixels(); i++) {
    pixels.setPixelColor(i, c);
  }
  pixels.show();
  delay(wait);
}

// A 32-bit variant of gamma8() that applies the same function
// to all components of a packed RGB or WRGB value.
uint32_t gamma32(uint32_t x) {
  uint8_t *y = (uint8_t *)&x;
  // All four bytes of a 32-bit value are filtered even if RGB (not WRGB),
  // to avoid a bunch of shifting and masking that would be necessary for
  // properly handling different endianisms (and each byte is a fairly
  // trivial operation, so it might not even be wasting cycles vs a check
  // and branch for the RGB case). In theory this might cause trouble *if*
  // someone's storing information in the unused most significant byte
  // of an RGB value, but this seems exceedingly rare and if it's
  // encountered in reality they can mask values going in or coming out.
  for (uint8_t i = 0; i < 4; i++)
    y[i] = gamma8(y[i]);
  return x; // Packed 32-bit return
}

uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {

  uint8_t r, g, b;

  // Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
  if (hue < 510) { // Red to Green-1
    b = 0;
    if (hue < 255) { //   Red to Yellow-1
      r = 255;
      g = hue;       //     g = 0 to 254
    } else {         //   Yellow to Green-1
      r = 510 - hue; //     r = 255 to 1
      g = 255;
    }
  } else if (hue < 1020) { // Green to Blue-1
    r = 0;
    if (hue < 765) { //   Green to Cyan-1
      g = 255;
      b = hue - 510;  //     b = 0 to 254
    } else {          //   Cyan to Blue-1
      g = 1020 - hue; //     g = 255 to 1
      b = 255;
    }
  } else if (hue < 1530) { // Blue to Red-1
    g = 0;
    if (hue < 1275) { //   Blue to Magenta-1
      r = hue - 1020; //     r = 0 to 254
      b = 255;
    } else { //   Magenta to Red-1
      r = 255;
      b = 1530 - hue; //     b = 255 to 1
    }
  } else { // Last 0.5 Red (quicker than % operator)
    r = 255;
    g = b = 0;
  }

  // Apply saturation and value to R,G,B, pack into 32-bit result:
  uint32_t v1 = 1 + val;  // 1 to 256; allows >>8 instead of /255
  uint16_t s1 = 1 + sat;  // 1 to 256; same reason
  uint8_t s2 = 255 - sat; // 255 to 0
  return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
         (((((g * s1) >> 8) + s2) * v1) & 0xff00) |
         (((((b * s1) >> 8) + s2) * v1) >> 8);
}

static uint8_t    gamma8(uint8_t x) {
  return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
}

I’m having two problems now:

  1. When running the LED animation functions on the Photon, the execution time seems to be much faster than an Arduino Pro MIcro. Is there a way to slow it down?
  2. I have those while(1) loops and the animation functions have a lot of for loops… When I’m in max and want to switch to a new function or continue sending live RGB values from Max, I need a way to break out the the while/for loops… I was using:
if(Serial.available()) { break; }

I would just put that in each while / for statement in all the functions… It worked perfectly. When you’d select a new option in Max, the lag before switching was only milliseconds.

I tried adding this to account for data coming in over UDP now:

if(Serial.available() || Udp.parsePacket()) { break; }

But this didn’t work as expected… If I just add it to the while(1) loops, it works… But you have to keep clicking a new option and if get the timing right, the new option will run. But if I add it to all the individual functions, it seems like the break just takes you back into the while and restarts the particular function, but you never completely break out…

Any ideas?

Many thanks!!!

You'd have to add some delay between each step of an animation.
There are many ways to do it but I'd use an FSM and/or SoftwareTimers.
This way you already would implicitly address your second question as you'd never even had any loops to break.

That would also help decouple the animation playing and the command reading/parsing.

BTW, regarding your binary data.
I've never "programmed" a MaxMSP, so I've no idea what's possible there or not, but I'd rather surprised if you couldn't package binary data with it somehow (what language are you using?)

I'd put the option selector (%s) into as leading byte into your 32bit value.
With that you could do something like that in your Photon code

enum FIELDS{ OPTION, R, G, B };
uint8_t data[4];
...
  if(sizeof(data) == Udp.receivePacket(data, sizeof(data))) { // when we got a full packet
    select(data[OPTION]) {
      case 'B':
        // deal with B
        break;
      case 'C': 
        // deal with C
        // colors would be accessed via
        r = data[R];
        g = data[G];
        b = data[B];
        break;
      default: 
        // deal with invalid option
        break;
    }

Here you can get an idea how this could be done

I've modified some of the (simpler) animations to illustrate the non-blocking paradigm.
Each of these animations is only visited for on single update and yields back control immediately. The animation sequence is bound to a step variable that allows it to advance on next visit.

I also dropped your 'C' option and its related f value and rather took the first byte of the 32bit value to carry all the info that's needed (values 1..16 and 'B').

2 Likes

That’s great. I’m sorry I can’t help you, but I have to say, this project is really great!

@ScruffR
I just saw that example you posted, many thanks! :slight_smile:
I’ll have a look this afternoon and/or tomorrow and try and implement it.
Cheers.

1 Like

@Mikeli
Thanks! I’ll share all the project files on Github after I sort everything out.

1 Like

@ScruffR

I had a quick look at what you shared:
first look

I added in some serial.prints to see variables.
I also moved “SOLIDCOLOR” to the top and elimanted the “B”, now just using 1 as the option for MaxMSP direct input, and 2-17 for the Photon functions. I wasn’t sure how to deal with packing the “B” into that 32bit expression, so I just used a number to make it easier.

On the Max side I used this to format what’s sent over UDP:

expr ($i1 << 24) + ($i2 << 16) + ($i3 << 8) + $i4
sprintf symout %ld

It creates one 32bit integer of all 4 values (option = $i1, r = $i2, g = $i3, b = $i4).

On the particle side in the serial monitor, I do see the correct values for “options” as long as they don’t exceed 9… 10-17 only output 1… Only the one byte is coming through.

But the RGB values get values that are mostly static and not correct.

I have to study your code more to understand what’s happening, but don’t I need to shift those values back out into variables first?
Like:

o = num32 >> 24;
r = num32 >> 16;
g = num32 >> 8;
b = num32;

It seems like I’m missing something.

I’m going to keep working on this and touch back.

This would suggest that your $i1 is a string and not a binary number. Any value in the range 0..255 is one byte.
This would also explain why your RGB values are not correct either.

Nope, that's the whole point of transferring them binary into individual variables (fields in the struct).
When you have one set of 32 bits in memory you also have four sets of 8 bits without any extra action :wink:


I have modified a few more animations and also added a Particle.function() to test them without the need for a UDP packet being sent.

The command format would be oo:rrr,ggg,bbb

(I have not tested any of this due to the lack of 192 NeoPixels :blush:)

@ScruffR
I just saw you added a modified program. I haven’t taken a look yet.

I did get this working today! Here is my testing code that (mostly) works:

I basically changed SOLIDCOLOR to MAXMSP because those are live values sent from max.
I also changed char to unit8_t for option here:

struct data_t {
  uint8_t option;
  uint8_t r;
  uint8_t g;
  uint8_t b;
} data;

And I had to take out data.option = 0 here:

  switch (data.option) {
    case MAXMSP:
      for(int p = 0; p < NUMPIXELS; pixels.setPixelColor(p++, pixels.Color(data.r, data.g, data.b)));
      pixels.show();        
      //data.option = 0;
      break;

It was causing a fast flicker between color sends… I’m sending on-the-fly color transitions at 30ms intervals from MaxMSP.

The Photon functions “mostly” work. There were some “colorAll” functions missing in the Particle Neopixel library, so I ported over this from the Adafruit repo and updated the function names in the code to fill:

void fill(uint32_t c, uint16_t first, uint16_t count) {
  uint16_t i, end;

  if (first >= NUMPIXELS) {
    return; // If first LED is past end of strip, nothing to do
  }

  // Calculate the index ONE AFTER the last pixel to fill
  if (count == 0) {
    // Fill to end of strip
    end = NUMPIXELS;
  } else {
    // Ensure that the loop won't go past the last pixel
    end = first + count;
    if (end > NUMPIXELS)
      end = NUMPIXELS;
  }

  for (i = first; i < end; i++) {
    pixels.setPixelColor(i, c);
  }
}

I am noticing running some of the animations, they don’t behave normally… The whole strip might have a red fill and the animation runs on top of that color… or they do really strange things… like the theater chase rainbow has a narrow strip of rainbow that moves inside the chase… so to speak…

But I haven’t had any time to even look at those functions or really what your code is doing there yet. I’ll have to study the new code too… It might be one of the ones you haven’t updated yet…

I was focused on getting the data passed properly formatted… And making sure options worked. My solution was this on the MaxMSP side:

http://www.sadam.hu/en/software

It’s a MaxMSP external that let’s you pass groups of bytes directly to UDP… The Max object is called:

sadam.udpSender

I had to group each integer (option, r, g, b) as a list of space separated integers and pass that to the object. Whew… The “normal” udpsend object in Max sends all this extra stuff along…

But my goodness ScruffR, you basically nailed this without access to any of the hardware… BRAVO…

Lunch break, and then I plan to try and sort out the function in the afternoon… Once all is sorted, I’ll post it all with the MaxMsp patches and such for others.

Cheers

1 Like

This might either because I misunderstood the original behaviour of the blocking animation or I just messed up recreating it as non-blocking animation :blush:

@ScruffR
I’ve been working on the MaxMSP side of things Fri/Sat.
I used your last example code as a base and modified just slightly to get the core working. I’ve been studying your code, but it’s challenging for me to sort out the details… I see that it keeps everything looping from the main loop so you don’t have to worry about releasing back, but I’m having trouble figuring out how to modify the remaining functions to get them working properly.

All the functions do what they are supposed to do individually, except for the rainbow theater chaser. I studied that for a bit but couldn’t figure out the problem.
The only problem with the other blocked functions is some don’t release back to the main loop, as expected.

Snow sparkle and fire execute properly without any modification.

Could you take one of the functions and break it down a bit so I can figure out what’s going on? C is not my strong suit so I’m struggling a bit to digest this. But I can see how this opens up so many doors so I’m eager to figure this out! I could have used this solution in so many other projects.

Here’s my current revision:

Thanks for you help on this, it’s been invaluable!!

Also, Here’s a screen shot of the MaxMSP interface so far:

2 Likes

I have now found a strip of SK6812RGBW LEDs and could test the TheaterChaseRainbow animation (at least somewhat as they are not completely compatible).
My code behaves pretty much the same way as your original animation. The only thing that made a major difference between the two version was that my version had the TheaterChase part run the opposite way. So while your's was going against the Rainbow part and mine was going with it the effect of mine wasn't quite what it should have been :blush:

This should do it a bit better.

The idea of that approach is to not see the animation as a continuous action but rather like a film with one frame for any given moment in time. When you jump to any time T you will find one image - irrespective whether you saw T-1 or not.
So the "rendering" functions should create the scene for any given moment and the timer callback animation() acts like a film projector that just requests and displays one frame after the other by passing the frame number (step) into the rendering function.

@ScruffR
Thanks! I did see the + / - change in the code for direction… But this isn’t the problem I’m seeing. Look here:

The top one is the Proton. The strip is red with just one of the segments blinking in rainbow colors (it looks kinda purple in the photo), moving across the strip, segment-by-segment.

The bottom one is the original firmware running on the Arduino. The rainbow effect spans the entire strand (just like the rainbow function) with the chaser segments punctuating the rainbow.

Is this what you were seeing on your strand?

And thanks for the description of what’s happening it helps… I just need to study the code in detail now and see if I can update the other functions.

Cheers

That’s odd. The original (blocking) code does exactly that in my setup too.
Can you maybe resend the Arduino implementation for that animation?

Here’s the Arduino code:

// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
  int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  for(int a=0; a<30; a++) {  // Repeat 30 times...
    if(Serial.available()) {
      break;
    }
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      if(Serial.available()) {
        break;
      }
      pixels.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<pixels.numPixels(); c += 3) {
        if(Serial.available()) {
          break;
        }
        // hue of pixel 'c' is offset by an amount to make one full
        // revolution of the color wheel (range 65536) along the length
        // of the strip (strip.numPixels() steps):
        int      hue   = firstPixelHue + c * 65536L / pixels.numPixels();
        uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); // hue -> RGB
        pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      pixels.show();                // Update strip with new contents
      delay(wait);                 // Pause for a moment
      firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    }
  }
}

I just uploaded this again to be sure this is what I had on the arduino. It produces the rainbow across the whole strip with punctuated chaser.