Storing multiple FastLED patterns to be called by serial monitor

Hi there!

First off, I will put it out there that I am very new to programming in general and need some help being pointed in the right direction with my project.

Putting it simply, I am trying to build a Bluetooth speaker with addressable LEDs inside that can be controlled via a Bluetooth app.

My idea was to have each FastLED pattern (lighting pre-set) stored as a function that can be called using a Switch Case. For example, if the app sent the number 1 over serial Bluetooth then the microcontroller would read this number and play the case associated with the number one on loop until another value was given.

My problem is that after every cycle of the loop void my integer that gets its value from the serial input returns to 0 no matter what, meaning that each lighting preset only plays once.

I thought I could get around this by calling the same function again within the function itself, but something tells me that this is a big no no as that just creates an infinite loop that cant be broken.

For this project I am using an ESP32 connected to 144 WS2812b leds on a strip and am using the FastLED library to write the lighting patterns.

Here is a little test project I made just to try and get my head around making these presets loop and to give you an idea where I my brain was going with this. (I know its not using Bluetooth, I have just been using the serial monitor to input values to keep things simple until I can overcome this hurdle.)

#include <FastLED.h>

#define NUM_LEDS 144
#define LED_PIN 15


CRGB leds[NUM_LEDS];
 
 void setup() {
  
  Serial.begin(9600);
  
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
  
  fill_solid(leds, NUM_LEDS, CRGB::Black); //This was just to make sure all the Leds are cleared (there is propably a better way to do so)
  FastLED.show();
  
}

void loop() {
  
  int val = Serial.parseInt(); //Reads incoming number from serial monitor and sets 'val' accordingly.

  switch (val)   //Switch Case used to switch between different fastLED presets depending on what number is received in the serial monitor.
    case 1:{
      RedBlink();
      break;
      }
}


void RedBlink() {
  
  fill_solid(leds, NUM_LEDS, CRGB::Red);   //Turn all Leds on to Red
  FastLED.show();
  delay(100);
  fill_solid(leds, NUM_LEDS, CRGB::Black);  //Turn all Leds off
  FastLED.show();
  delay(100);

  int val = Serial.parseInt();  //read serial monitor and set val accordingly
  bool NewData; //create new boolean

  if (val==1){  
    bool NewData = false;  //if the serial monitor is still = to 1 then no new input has been received, set NewData bool to flase.
  }
  else if (val!=1) {
    bool NewData = true;  //if the serial monitor does not = 1 then a new input has been received, set NewData bool to true.
  }

  if (NewData == false){  //if NewData is flase then start the funtion from the top (looping the FastLed effect over and over until a new input is received)
    RedBlink();
  }

  else if (NewData == true) {  // if NewData is true then a new input has been received so break the loop
    
  }  
}

Please could someone point me in the right direction of how to store multiple FastLED lighting presets that can be called on loop until a new preset is called using the Serial Monitor.

I am really struggling to find a way around it as my knowledge on the matter isn't great enough and feel like I am trying to solve this problem in the completely wrong way. If there are much better ways to do this as I'm sure its been done many times before then please let me know.

I hope that I have given enough detail on the project at hand and thank you for any help in advance!

Start by only reading from Serial if data is available() and by setting the Serial monitor Line ending to "No line ending" to prevent any extraneous characters such as Carriage Return or Linefeed being sent

Also if you want this to be reactive you need the animations to not use delay and you call the function repetitively from the loop() and it takes a new step when needed

So, where did the value go? Did you zero it out or something? Surely you saved it somewhere, but where?
Paul

here is may be a code structure you could explore:

// type definition for our function pointer
typedef bool (*animPtr) (bool);

animPtr currentAnimation = nullptr;


bool animation1(bool forceStart) {
  static bool animationComplete = false;
  static byte x = 0;
  static unsigned long lastFrameTime = 0;
  const unsigned long animationPeriod = 500; // 2Hz

  if (forceStart) {
    // reset context here
    x = 0;
    animationComplete = forceStart;
  }

  if (forceStart || (millis() - lastFrameTime > animationPeriod)) {
    // here perform one step
    Serial.println(x);

    // prepare for next step
    x++;

    // record the time
    lastFrameTime = millis();

    // check if animation is done
    animationComplete = (x > 30);
  }
  return animationComplete;
}

bool animation2(bool forceStart) {
  static bool animationComplete = false;
  static byte x = 0;
  static unsigned long lastFrameTime = 0;
  const unsigned long animationPeriod = 1000; // 1Hz

  if (forceStart) {
    // reset context here
    x = 0;
    animationComplete = forceStart;
  }

  if (forceStart || (millis() - lastFrameTime > animationPeriod)) {
    // here perform one step
    Serial.println(x);

    // prepare for next step
    x += 2;

    // record the time
    lastFrameTime = millis();

    // check if animation is done
    animationComplete = (x > 30);
  }
  return animationComplete;
}

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (Serial.available()) {
    char r = Serial.read();
    switch (r) {
      case '\r':
      case '\n': break; // ignore those
      case '1': currentAnimation = animation1; currentAnimation(true); break;
      case '2': currentAnimation = animation2; currentAnimation(true); break;
      default: currentAnimation = nullptr; break;
    }
  }

  // run the current animation until it says it's done
  if (currentAnimation != nullptr)
    if (currentAnimation(false))
      currentAnimation = nullptr;

}

you can open the Serial monitor and type 1 or 2 and the code will call repetitively animation1() or animation2() until the animation is complete.

because nothing is blocking you can type commands whenever you want and the functions, through the use of millis() control when it's time to take the next step.

Here is some simple code that reads from Serial with minimal delays
(from by tutorial on Arduino Software Solutions )
Does not matter what type of line ending or none you have set.
@J-M-L solution is very good for single char cmds and may be all you need. But if want to extend beyond that then this code will read a whole line of input.
Check out the tutorial above for examples of parsing the input.

String input;

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(2); // at 9600 get about 1char/1mS so when reading user input from Arduino IDE monitor can set this short timeout
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  Serial.println(F("SerialReadString.ino"));
  input.reserve(80); // expected line size
}

void handleInput() {
  if (Serial.available()) { // skip readString timeout if nothing to read
    input = Serial.readString();
    input.trim(); // strip off any leading/trailing space and \r \n
    if (input.length() > 0) {  // got some input handle it
      Serial.print(F(" got a some input '")); Serial.print(input); Serial.println("'");
     // handle the input here and set globals as needed.
      // that is set mode 1 or 2 depending on the input    
    }
  }
}

void loop() {
 handleInput();  // one task
 runAnimation(); // another task  // looks at the mode and runs accordingly
}

Then combine this with a tasked based coding approach
multitaskingDiagramSmall

For non-blocking millis delays see my tutorial on How to write Timers and Delays in Arduino which includes a led sequencing example