Photon and FastLED: Fill delay?

It’s a little hard for me to classify this post, so troubleshooting it is. I’ve got a photon driving a strip of LEDs and pre-programmed a bunch of patterns into it for different times of the year that use the FastLED library to display. Up until today I’ve just been running color palettes for the most part, but I thought I’d add a new function where the strip could be set to any color, based on the touched area of an image, by way of a canvas color check using the MIT app inventor.

The Android app itself gets the hex color value based on where a finger has touched the image. I parse it in the app to send a string to the Photon formatted as “0x000000”. The Photon then takes that string, converts it to a hex value and feeds it to the fill_solid function of FastLED before displaying it to the strip.

I’m getting somewhat inconsistent results though. The colors it’s able to display (or that it understands) seem very limited and while in this particular mode (vs the other patterns I’ve laid out) it’s SUPER slow to respond when I try to change to another color. It’s as if there’s some other function or loop running that’s causing it to choke. I’m fairly certain this is nothing Photon specific, but the Particle community has been a good source of all around help for this kind of stuff, so I thought I’d ask. Here’s the entirety of my code, the gist of which is this: My app sends a changeMode command to tell it to use the fill_solid function based on whatever currentColor is set. Then the app sends a changeColor command to update that currentColor. Also, there’s a twinkle, but that’s workin fine.

    // This #include statement was automatically added by the Particle IDE.
    #include <FastLED.h>
    #include "Particle.h"

    FASTLED_USING_NAMESPACE;

    #define LED_PIN     4
    #define COLOR_ORDER GRB
    #define CHIPSET     WS2811
    #define NUM_LEDS    301 //88 for testing, 300 for prodction
    #define BRIGHTNESS  50
    #define FRAMES_PER_SECOND 30

    bool gReverseDirection = false;
    unsigned long previousMillis = 0;
    unsigned long currentMillis;
    unsigned long twinkleMillis = 0;
    int eepValue;
    int eepAddr = 2;
    int mode;
    int maxShells = round(NUM_LEDS / 30);
    int lifespan = 30;
    long totalModes = 5;
    int changeToColor(String newColor);
    uint32_t currentColor=0x000000;

    NSFastLED::CRGB leds[NUM_LEDS];
    CRGBPalette16 gPal;

    void runColor(String currentColor);
    void runRainbow();
    void twinkle();
    void fireworks();
    void runPalette(NSFastLED::CRGB* ledarray, uint16_t numleds, const CRGBPalette16& gCurrentPalette);


    class FireworkShell {
        int stringPos;    //origin of the blast
        int age;          //how long the blast has been on the string
        int maxLife;      //how old this one can get
        uint8_t hue;       //color of the shell

      public:
        FireworkShell(int xPos, int howOld, int deathAge, uint8_t shellColor) {
          stringPos = xPos;
          age = howOld;
          maxLife = deathAge;
          hue = shellColor;
        }

        void Update() {

          if (age < maxLife) {
            int newPosR = stringPos + age;
            int newPosL = stringPos - age;
            if (newPosR < NUM_LEDS) leds[newPosR] +=  CHSV(hue, 255, 255);
            if (newPosL >= 0) leds[newPosL] +=  CHSV(hue, 255, 255);

            if (age > 0) {


              int tailR = newPosR - 1;
              int tailL = newPosL + 1;
              if (tailR > 0 && tailR < NUM_LEDS) {
                leds[tailR] = CHSV(hue + 40, 255, random(50, 200));
              }
              if (tailL > 0 && tailL < NUM_LEDS) {
                leds[tailL] = CHSV(hue + 40, 255,  random(50, 200));
              }



            }

          }
          if (age < maxLife + 20) {
            age++;
          } else {
            age = 0;
            hue = random(0, 213);
            maxLife = random(5, lifespan);
            stringPos =  random(0, NUM_LEDS);
            //Serial.println(maxLife);
          }
        }
    };

    FireworkShell shells[] = {
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan), random(0, 255)),
      FireworkShell(random(0, NUM_LEDS), 0, random(5, lifespan),  random(0, 255))
    };


    void setup() {
      delay(3000); // sanity delay
      FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
      FastLED.setBrightness( BRIGHTNESS );
      //FastLED.setMaxPowerInVoltsAndMilliamps(5, 1500);
      randomSeed(analogRead(0));
      //Serial.begin(9600);
      
      Particle.variable("mode",mode);
      Particle.function("updateMode", modeChange);
      
      Particle.function("changeColor", changeToColor);
      Particle.variable("newColor", currentColor);
        
        EEPROM.get(eepAddr, eepValue);
        if(eepValue == 0xFFFF) {
            // EEPROM was empty -> initialize value
            mode = 0;
        }else{
            mode = eepValue;
        }
      
      //mode = 0;
    }


    extern const TProgmemRGBGradientPalettePtr gGradientPalettes[];

    void loop()
    {
      currentMillis = millis();
      //Particle.publish("Mode is ",  String (mode));

      switch (mode) {

        case 0:
          runRainbow();
         twinkle();
          break;

        case 1:
          //runColor(CRGB::Blue);
          runPalette(leds, NUM_LEDS,  gGradientPalettes[0]);
          twinkle();
          break;

        case 2:
          runPalette(leds, NUM_LEDS,  gGradientPalettes[1]);
          twinkle();
          break;
          
        case 3:
          runPalette(leds, NUM_LEDS,  gGradientPalettes[2]);
          twinkle();
          break;
          
        case 4:
          runPalette(leds, NUM_LEDS,  gGradientPalettes[3]);
          twinkle();
          break;
          
        case 5:
          runPalette(leds, NUM_LEDS,  gGradientPalettes[4]);
          twinkle();
          break;
          
        case 6:
            fireworks();
            break;
            
        case 7:
           runPalette(leds, NUM_LEDS,  gGradientPalettes[5]);
          twinkle();
          
        case 8:
           runPalette(leds, NUM_LEDS,  gGradientPalettes[6]);
          twinkle();
          
        case 9:
           runPalette(leds, NUM_LEDS,  gGradientPalettes[7]);
          twinkle();
          
        case 10:
            runColor(currentColor);
            twinkle();
            break;
      }
    }


    int changeToColor(String newColor){
        Particle.publish("Color received is ",  String (newColor));
        currentColor = strtol(newColor, NULL, 16);
        Particle.publish("Hex received is ",  currentColor);
    }

    int modeChange(String newMode){
        int modeNum = newMode.toInt();
        mode = modeNum;
        EEPROM.put(eepAddr, modeNum);
    }




    void runColor(uint32_t currentColor) {
      if (currentMillis - previousMillis >= 200) {
        previousMillis = currentMillis;
        fill_solid(leds, NUM_LEDS, currentColor);
      }
    }

    void runRainbow() {
      if (currentMillis - previousMillis >= 30) {
        previousMillis = currentMillis;
        static uint8_t hue = 0;
        fill_rainbow(leds, NUM_LEDS, hue++);
        FastLED.show();
      }
    }

    void twinkle() {
      //create random twinkle
      int rp = random(500,2000);
      if (currentMillis - twinkleMillis >= rp) {
        twinkleMillis = currentMillis;
        int pixel = random(NUM_LEDS);
        leds[random(NUM_LEDS)] = CRGB::White;
        FastLED.show();
      }
    }

    void fireworks() {
     EVERY_N_MILLISECONDS(30){
        fadeToBlackBy(leds, NUM_LEDS, 30);
        Serial.println(maxShells);

        for (int i = 0; i < maxShells; i++) {
          shells[i].Update();
        }
        FastLED.show();
      };
    }


    void runPalette(NSFastLED::CRGB* ledarray, uint16_t numleds, const CRGBPalette16& gCurrentPalette)
    {
      if (currentMillis - previousMillis >= 30) {
        previousMillis = currentMillis;
        static uint8_t startindex = 0;
        startindex--;
        fill_palette( ledarray, numleds, startindex, (256 / NUM_LEDS) + 1, gCurrentPalette, BRIGHTNESS, LINEARBLEND);
        FastLED.show();
      }
    }

    DEFINE_GRADIENT_PALETTE( holly_gp) {
      0,    0,  255,  0,  //green
      48,   0,  255,  0,  //green
      49,   255,  0,  0,  //red
      64,   255,  0,  0,  //red
      65,   0,  255,  0,  //green
      114,   0,  255,  0,  //green
      115,   255,  0,  0,  //red
      118,   255,  0,  0,  //red
      119,   0,  255,  0,  //green
      168,  0,  255,  0,  //green
      169,  255,  0,  0,  //red
      184,  255,  0,  0,  //red
      185,  0,  255,  0,  //green
      234,  0,  255,  0,  //green
      235,  255,  0,  0,  //red
      255,  255,  0,  0   //red
    };

    DEFINE_GRADIENT_PALETTE( candycane_gp) {
      0 , 128, 128, 128,  //white
      32 , 128, 128, 128,  //white
      33 , 255, 0, 0,  //red
      66 , 255, 0, 0,  //red
      67 , 128, 128, 128,  //white
      100 , 128, 128, 128,  //white
      101 , 255, 0, 0,  //red
      134 , 255, 0, 0,  //red
      135 , 128, 128, 128,  //white
      168 , 128, 128, 128,  //white
      169 , 255, 0, 0,  //red
      202 , 255, 0, 0,  //red
      203 , 128, 128, 128,  //white
      236 , 128, 128, 128,  //white
      237 , 255, 0, 0,  //red
      255 , 255, 0, 0  //red
    };

    DEFINE_GRADIENT_PALETTE( snowynight_gp) {
      0, 163, 182, 199,
      41,  188, 192, 200,
      117,  117, 157, 240,
      204,  117, 224, 240,
      255,  163, 182, 199
    };

    DEFINE_GRADIENT_PALETTE( silvergold_gp) {
        0, 237,121,  1,
       25, 237,121,  1,
       51, 229,149,  1,
       76, 222,178,  1,
      102, 237,215, 59,
      127, 255,255,255,
      153, 118,164,188,
      178,  46,101,145,
      204,  19, 60, 95,
      229,   4, 31, 56,
      255,   4, 31, 56
      };

    // Gradient palette "es_autumn_04_gp", originally from
    // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_04.png.index.html
    // converted for FastLED with gammas (2.6, 2.2, 2.5)
    // Size: 20 bytes of program space.

    DEFINE_GRADIENT_PALETTE( autumn_gp ) {
        0,  71,  135,  0,
      101,  88,  1,  0,
      165, 210, 22,  1,
      234, 255,166, 42,
      255, 255,166, 42
      };
      
      // Gradient palette "grand_old_flag_gp", originally from
    // http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/grand-old-flag.png.index.html
    // converted for FastLED with gammas (2.6, 2.2, 2.5)
    // Size: 128 bytes of program space.

    DEFINE_GRADIENT_PALETTE( grand_old_flag_gp ) {
        0,   1,  2,105,
       94,   1,  2,105,
       99, 199,  1,  7,
      104, 199,  1,  7,
      109, 255,255,255,
      117, 255,255,255,
      122, 199,  1,  7,
      124, 199,  1,  7,
      130, 255,255,255,
      137, 255,255,255,
      145, 199,  1,  7,
      150, 199,  1,  7,
      155, 255,255,255,
      160, 255,255,255,
      165, 199,  1,  7,
      170, 199,  1,  7,
      175, 255,255,255,
      181, 255,255,255,
      188, 199,  1,  7,
      191, 199,  1,  7,
      198, 255,255,255,
      204, 255,255,255,
      209, 199,  1,  7,
      211, 199,  1,  7,
      219, 255,255,255,
      224, 255,255,255,
      229, 199,  1,  7,
      234, 199,  1,  7,
      239, 255,255,255,
      244, 255,255,255,
      249, 199,  1,  7,
      255, 199,  1,  7
      };
      
      // Gradient palette "christmas_candy_gp", originally from
    // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ocal/tn/christmas-candy.png.index.html
    // converted for FastLED with gammas (2.6, 2.2, 2.5)
    // Size: 44 bytes of program space.

    DEFINE_GRADIENT_PALETTE( christmas_candy_gp ) {
        0, 255,255,255,
       25, 255,  0,  0,
       51, 255,255,255,
       76,   0, 55,  0,
      102, 255,255,255,
      127, 255,  0,  0,
      153, 255,255,255,
      178,   0, 55,  0,
      204, 255,255,255,
      229, 255,  0,  0,
      255, 255,255,255
      };

    // Gradient palette "green_purple_gp", originally from
    // http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/green-purple.png.index.html
    // converted for FastLED with gammas (2.6, 2.2, 2.5)
    // Size: 260 bytes of program space.

    DEFINE_GRADIENT_PALETTE( green_purple_gp ) {
          0, 255, 155,  0,
         40, 255, 155,  0,
         49,   0,   0,  0,
         96,   0,   0,  0,
        107,   0, 254,  0,
        179,   0, 254,  0,
        183, 203,   0,255,
        255, 203,   0,255
        };




    const TProgmemRGBGradientPalettePtr gGradientPalettes[] = {
      holly_gp,
      candycane_gp,
      snowynight_gp,
      silvergold_gp,
      autumn_gp,
      grand_old_flag_gp,
      christmas_candy_gp,
      green_purple_gp,
    };

Does the parsed color value fit the one you expect?
I’d not publish twice in changeToColor() but do something like this

  int changeToColor(String newColor){
    char txt[128];
    currentColor = strtol(newColor, NULL, 16);

    snprintf(txt, sizeof(txt), "%s (0x%06x)", (const char*)newColor, currentColor);    
    Particle.publish("ColorInfo", txt, PRIVATE);

    return currentColor; // added on Ric's hint
  }

And for mode 10 (constant color) a one-shot should do, or not?

1 Like

Also, you should be returning ant int from your Particle.functions.

2 Likes

I’m just now getting back to this. Thanks for the responses.

I think the colors I’m getting match up, but I kept getting frustrated when it wasn’t changing as I was swiping around the app, so I’m not entirely sure. The resulting hex codes I publish check out when I plug them into an online generator though.

Couple questions: My C++ is still on the below-novice side. I’m reading some reference material that talks about snprintf and buffering, but could you briefly explain what it’s doing here and why I should include it in the future?

Also, you asked about mode 10 being a one shot, but I’m not sure what you’re asking. Would you mind elaborating?

Actually, I think I understand what you meant by one shot. I’m re-running it ever 200 milis. Yeah, you’re right, I effectively have it running constantly so that when one of the twinkles fires, it re-fills the strip after the twinkle, otherwise the single LED that twinkles stays white, so I’ll probably have to rethink that function for the solids (or remove it).

At any rate, when I swapped out your function for mine, it seems to work great. Is this because I was publishing a varaible twice every time I changed the color? To me that seems odd, but, as I said, I’m still learning.

snprintf() is a function that can generate formatted (hence the f) strings from a format string and a dynamic list of "value" variables and place the result in a character array (buffer).
On small/embedded controllers this is the prefered way (over String objects) since it doesn't do any dynamic memory allocation which may cause heap fragmentation which may eventually lead to crashes.
Also incorporating all your values in one string helps reducing the count of publish instructions in order to not hit the rate limit for allowed publishes per second.
Additionally the snprintf() (compared to sprintf()) adds a boundary check to avoid corrupting other data in case your string doesn't fit into the provided buffer.

In your loop() with mode == 10 you unconditionally execute runColor() even when currentColor hasn't changed. This doesn't really make sense and just slows down your whole code.
For a "one-shot" approach you'd use some extra variable to check whether you've already set the color or you need to refresh and only execute runColor() when a refresh due.

For your twinkle() function you may want to remember the last "twinkle position" to set that specific pixel pack to its normal value before setting the new twinkle instead of unconditionally clearing the whole strip for the sake of only one pixel.

The corruption of data during your original function may be caused by a shared buffer in the system, hence it's considered poor practice to Particle.publish() inside another Particle.xxx() handler/callback and if you must, backup your essential data on function entry and publish only once near the end of the function.

2 Likes

Truth is, the only reason I had the publish there in the first place was to make sure I was sending the right hex value to the code, by sending it to the console and reviewing it, so publishing is ultimately unnecessary.

Thanks for the guidance, and the mini-lesson. I really wish other forums’ users had the kind of responsiveness, knowledge and willingness to help as this one does, particularly you, ScruffR.

2 Likes