Statemachine compiling but not working

Hello, everyone.

I've playing with a programmable LED strip and since I have a 3D printer I printed my own version of the Nano Leaf and run the strip through it, it looks great.

The thing is, I don't want the same effect to run all the time and don't want them on a timer either, so I thought about using a touch sensor (TTP223) to change between one effect and the next.

After a lot of playing around it seems that the only way to get this to work (or at least the only I'e found) is using a State Machine to keep an eye on the button state and change the effect when it detects a HIGH signal.

The program compiles without errors and the Serial Monitor shows when the button is touched and also to which effect it moved... but the LEDs are frozen from the start of the program. Basically, the program fails to call the corresponding effect.

Each effect works perfectly on its own, I tested this by commenting or uncommenting the corresponding function within the loop.

I can't figure it out.
I have the main code and each effect on different tabs within the IDE (it seems more organized this way). I don't think you can upload different tabs here so I'll paste the whole code together and I'll separate each TAB with a comment.

#include <FastLED.h>
#include <Arduino.h>

#define NUM_LEDS 151
#define LED_PIN 3
#define BUTT_PIN 5
#define BRIGHTNESS 255
#define LED_TYPE WS2811
#define COLOR_ORDER GRB

CRGB leds[NUM_LEDS];

#define UPDATES_PER_SECOND 60

CRGBPalette16 currentPalette(CRGB::Black);
CRGBPalette16 targetPalette(PartyColors_p);

void changeEffect();

void setup() {

  delay(3000);  // power-up safety delay
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
  Serial.begin(9600);
}

void loop() {

  fadeAnimationWrapper();
  //cylon();
  //sparkles();

  //changeEffect();
}

void changeEffect() {
  enum class effectState : uint8_t {
    sparkles,
    fadeAnimationWrapper,
    cylon,
  };

  static effectState currEffect = effectState::sparkles;
  static effectState prevEffect = effectState::sparkles;
  static int buttonState = LOW;
  static int prevButtonState = LOW;

  buttonState = digitalRead(BUTT_PIN);

  if (buttonState != prevButtonState) {
    if (buttonState == HIGH) {
      Serial.println("Button Pressed!");

      switch (currEffect) {
        case effectState::sparkles:
          currEffect = effectState::fadeAnimationWrapper;
          Serial.println("Fade Running!!");
          break;
        case effectState::fadeAnimationWrapper:
          currEffect = effectState::cylon;
          Serial.println("Cylon Running!!");
          break;
        case effectState::cylon:
          currEffect = effectState::sparkles;
          Serial.println("Sparkles Running!!");
          break;
      }
      prevEffect = currEffect;
    }
    prevButtonState = buttonState;
  }
}

//                    END OF TAB 1 - MAIN CODE
//                    START OF TAB 2 - CYLON EFFECT

#include <FastLED.h>

#define CLOCK_PIN 13

void fadeall() { for(int i = 0; i < NUM_LEDS; i++) { leds[i].nscale8(250); } }

void cylon() { 
	static uint8_t hue = 0;
	Serial.print("x");
	// First slide the led in one direction
	for(int i = 0; i < NUM_LEDS; i++) {
		// Set the i'th led to red 
		leds[i] = CHSV(hue++, 255, 255);
		// Show the leds
		FastLED.show(); 
		// now that we've shown the leds, reset the i'th led to black
		// leds[i] = CRGB::Black;
		fadeall();
		// Wait a little bit before we loop around and do it again
		delay(10);
	}
	
	// Now go in the other direction.  
	for(int i = (NUM_LEDS)-1; i >= 0; i--) {
		// Set the i'th led to red 
		leds[i] = CHSV(hue++, 255, 255);
		// Show the leds
		FastLED.show();
		// now that we've shown the leds, reset the i'th led to black
		// leds[i] = CRGB::Black;
		fadeall();
		// Wait a little bit before we loop around and do it again
		delay(10);
	}
}

//                    END OF TAB 2 - CYLON EFFECT
//                    START OF TAB 3 - FADE ANIMATION EFFECT

void fadeAnimationWrapper() {
  fadeAnimation(random(256), random(256), random(256));
}

void fadeAnimation(int red, int green, int blue) {
  float r, g, b;
  uint32_t startTime = millis();
  uint32_t totalTime = 1000; // total time for fade in and out, in milliseconds

  // Define array of durations for fade in and fade out
  uint32_t fadeInDurations[] = {600, 800, 1000, 1200, 1600, 2000, 2500};
  uint32_t fadeOutDurations[] = {600, 800, 1000, 1200, 1600, 2000, 2500};
  int numFadeInDurations = sizeof(fadeInDurations) / sizeof(fadeInDurations[0]);
  int numFadeOutDurations = sizeof(fadeOutDurations) / sizeof(fadeOutDurations[0]);

  // Randomly select duration for fade in and fade out
  uint32_t fadeInTime = fadeInDurations[random(numFadeInDurations)];
  uint32_t fadeOutTime = fadeOutDurations[random(numFadeOutDurations)];

  // FADE IN
  while (millis() - startTime <= fadeInTime) {
    float progress = (float)(millis() - startTime) / (float)fadeInTime;
    r = progress * red;
    g = progress * green;
    b = progress * blue;
    fill_solid(leds, NUM_LEDS, CRGB(r, g, b));
    FastLED.show();
  }

  // FADE OUT
  startTime = millis(); // reset start time for fade out
  while (millis() - startTime <= fadeOutTime) {
    float progress = 1.0 - ((float)(millis() - startTime) / (float)fadeOutTime);
    r = progress * red;
    g = progress * green;
    b = progress * blue;
    fill_solid(leds, NUM_LEDS, CRGB(r, g, b));
    FastLED.show();
  }
}

//                    END OF TAB 3 - FADE ANIMATION EFECT
//                    START OF TAB 4 - SPARKLES EFFECT

void sparkles()
{
  ChangePalettePeriodically();

  uint8_t maxChanges = random(48); // values 0-48 (Max colors at play)
  nblendPaletteTowardPalette( currentPalette, targetPalette, maxChanges);

  static uint8_t startIndex = 0;
  startIndex = startIndex + 1; /* motion speed */
  FillLEDsFromPaletteColors( startIndex);

  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
}

void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
  uint8_t brightness = 255;
  
  for( int i = 0; i < NUM_LEDS; i++) {
    leds[i] = ColorFromPalette( currentPalette, colorIndex + sin8(i*16), brightness);
    colorIndex += 3;
  }
}


void ChangePalettePeriodically()
{
  uint8_t secondHand = (millis() / 1000) % 60;
  
  switch(secondHand) {
    case 0:
      targetPalette = RainbowColors_p;
      break;
    case 10:
      targetPalette = CRGBPalette16( CHSV(HUE_GREEN, 255, 255), CHSV(HUE_PURPLE, 255, 255), CRGB::Black, CRGB::Black, CHSV(HUE_PURPLE, 255, 255), CHSV(HUE_GREEN, 255, 255), CRGB::Black, CRGB::Black, CHSV(HUE_PURPLE, 255, 255), CHSV(HUE_GREEN, 255, 255), CRGB::Black, CRGB::Black, CHSV(HUE_PURPLE, 255, 255), CHSV(HUE_GREEN, 255, 255), CRGB::Black, CRGB::Black );
      break;
    case 20:
      targetPalette = CRGBPalette16( CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White, CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White, CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White, CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White );
      break;
    case 30:
      targetPalette = LavaColors_p;
      break;
    case 40:
      targetPalette = CloudColors_p;
      break;
    case 50:
      targetPalette = PartyColors_p;
      break;
  }
}

Thank you!!

your animations contain for(;;){}s and while(){}s so they are blocking the code from getting back to the digitalRead(BUTT_PIN);

There's some discussion here:

The basic trick is to disassemble the loops within the animations into their own state machines. For example, take apart your cylon() animation's for(int i...) loops into state variables (static int i; static bool upNotDown;?) so you can do its delay(10) like BlinkWithoutDelay rewrite the rest of the stuff in the for() inside an quick if().

buttons are typically connected between the pin and ground, the pin configured as INPUT_PULLUP to use the internal pullup resistor which pulls the pin HIGH and when pressed, the button pulls the pin LOW.

a button press can be recognized by detecting a change in state and becoming LOW

the thing that makes the effect work is: calling the function
when you tested your code - you really did a function-CALL

void loop() {
  //fadeAnimationWrapper();
  cylon(); // <<<<<<< THIS line of code is the function-CALL which makes the microcontroller EXECUTE the lines of code inside function cylon()
  //sparkles();

  //changeEffect();
}
void loop() {
  //fadeAnimationWrapper();
  //cylon();   
sparkles(); // <<<<<<< THIS line of code is the function-CALL which makes the microcontroller EXECUTE the lines of code inside function sparkles()

  //changeEffect();
}

In the code you have posted there are no function-CALLs for cylon(), sparkles(), etc.

You are a victim of the bad habit of "professional" code-writers to use very similar names for completely different things. (shame on all these "professional" code-writers!!")

As long as you do not know the differences between what do the code-lines

1. defining an enumeration

  enum class effectState : uint8_t {
    sparkles,
    fadeAnimationWrapper,
    cylon,
  };

and the

2. declaring a function

 
void cylon() {
  static uint8_t hue = 0;
  Serial.print("x");
  // First slide the led in one direction
  for (int i = 0; i < NUM_LEDS; i++) {
.......
    fadeall();
    // Wait a little bit before we loop around and do it again
    delay(10);
  }
}

and a

3. function-CALL of a function

void loop() {
...
  cylon(); // <<<< this is the function-CALL for function cylon()
...

You experience what you have experienced.

In addition to this:
as long as the animations run in inner-loops
these functions are blocking

For making the code responsive to a button-press all the time there are two ways:

the ugly way:
adding code that reads in the IO-pin in

  • each and every for-loop
  • each and every while-loop

the elegant way
restructuring / re-designing the code that the single - one and only loop that exists is

void loop()

and all functions work in a quickly jump-in / jump-out-manner

void loop is the most outerside loop

If some lines of code shall be executed only at certain conditions these lines of code
are inside a conditional-statement beeing a

  • switch case
    or
  • if-condition

So there are some new things to learn for you.

I remembered that I started a tutorial about non-blocking coding. I haven't finished it yet.
But as a first part it still makes sense to publish it in the introductional tutorial section

best regards Stefan

2 Likes

@DaveX thank you for your reply. Yes I'm aware that the for loops block the rest of the code and I tried to change them into something else wherever I could, and maybe I am wrong but I was under the impression that the state machine structure was supposed to allow the effect to run its own loop because the effect function itself was being called from the loop() on the main code (Tab 1).
I don't know if it helps to understand, but I didn't write the effects, one is from the examples and the other two I got from a youtube video I think, nv. the point is that each effect was a complete sketch with setup() and loop() functions and they worked on their own, what I did was copy the code and edit/delete/standardized bits and pieces to make it work as a sub element of the larger code.
As per your comment on the button, it apparently works just fine and it's not being blocked, but since the LEDs are not lightning up from the start, button or not, upon powering on the Sparkles animation should start automatically regardless of the button state, so if the animation had started and THEN having an unresponsive button yes, I'd say it's being blocked, as of now, since apparently the function is not being called properly and the LEDs not lightning up, it might be why the button does actually respond, because there's no function running to block it.
So regardless of the button being blocked or not, shouldn't at least the first animation start running? because that's the main issue at the moment, not the button, it's the fact that upon powering up the Sparkles animation should start and it isn't I think it's the first thing to address.

@gcjr Thank you for your input.
The TTP223 has 3 connections; GND, VCC and Signal. GND and VCC connect directly to the GND and 5v pins on the board and the Signal goes directly to any digital pin on the board. Out of the box, it returns a LOW signal when not active and a HIGH signal when activated. By way of shorting some connectors on the back of the sensor you can change it from a momentary switch to a toggle and make it wither NO or NC.

@StefanL38 Hello thank you for your reply, there's certainly a lot of info there for me to digest.

couple of things, though...
I know what I did to test the effects was a function call (I actually wrote it in the initial explanation, albeit just a bit different ("the program fails to call the corresponding effect")).
I do anyway appreciate you being thorough.

you say there's no function calls for the effects but there is (or so I thought??)...

void changeEffect() {
  enum class effectState : uint8_t {
    sparkles,
    fadeAnimationWrapper,
    cylon,
  };

This is supposed to be it, calling the effects by way of the State Machine I was trying to make.

I' a little confused by this line "You are a victim of the bad habit of "professional" code-writers to use very similar names for completely different things. (shame on all these "professional" code-writers!!")"

For one, I don't understand the bad habit you mention, I believe you if you say I'm a victim of it but I don't see where I used similar names to do different things (it'd be great if you show me what I did wrong so I don't do it again).
And just so you know (probably you already figured it out, though) I'm not even a rookie when it comes to coding. I took this as a hobby just over a year ago and haven't really had time to really learn. Also I'm a hand on kind of person and prefer to learn by doing and just make it up as I go along. And yes, I now full well that's not even close to being the best way of going about it, but for the time being, it works.

Yes, I also had come to the realization that there an ugly way and an elegant way, and I was working towards the latter, I tried arrays and other stuff before stumbling upon the State Machine. I think I've been working on this for a month or so.

I also tried switch cases but something was off, can't remember what it was now but it seemed promising, if you think that might be better than FSM then I shall take your advice.

That jump in / jump out comment goes right over my head, don't have a clue what you mean.

The problem I think is exacerbated by the fact that each effect (at least some) have a very complex and advanced code that may not work of it doesn't have its own loops and whiles and ifs within??

Anywho, lots of info!! you clearly know your sh**.
Thanks, man. Any advice, tip, or random line to code to speed me on my way would be appreciated.

Here's a modified sim of your code:

// for https://forum.arduino.cc/t/statemachine-compiling-but-not-working/1126324

#include <FastLED.h>
#include <Arduino.h>

#define NUM_LEDS 151
#define LED_PIN 3
#define BUTT_PIN 5
#define BRIGHTNESS 255
#define LED_TYPE WS2811
#define COLOR_ORDER GRB

CRGB leds[NUM_LEDS];

#define UPDATES_PER_SECOND 60

CRGBPalette16 currentPalette(CRGB::Black);
CRGBPalette16 targetPalette(PartyColors_p);

void changeEffect();

void setup() {

  delay(3000);  // power-up safety delay
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
  Serial.begin(9600);
}

void loop() {

  //fadeAnimationWrapper();
  //cylon();
  //sparkles();

  changeEffect();
}

void changeEffect() {
  enum class effectState : uint8_t {
    sparkles,
    fadeAnimationWrapper,
    cylon,
  };

  static effectState currEffect = effectState::sparkles;
  static effectState prevEffect = effectState::sparkles;
  static int buttonState = LOW;
  static int prevButtonState = LOW;

  buttonState = digitalRead(BUTT_PIN);

  if (buttonState != prevButtonState) {
    if (buttonState == HIGH) {
      Serial.println("Button Pressed!");

      switch (currEffect) {
        case effectState::sparkles:
          currEffect = effectState::fadeAnimationWrapper;
          Serial.println("Fade Running!!");
          break;
        case effectState::fadeAnimationWrapper:
          currEffect = effectState::cylon;
          Serial.println("Cylon Running!!");
          break;
        case effectState::cylon:
          currEffect = effectState::sparkles;
          Serial.println("Sparkles Running!!");
          break;
      }
      prevEffect = currEffect;
    }
    prevButtonState = buttonState;
  }
  switch (currEffect) {
    case effectState::sparkles:
      sparkles();
      break;
    case effectState::fadeAnimationWrapper:
      fadeAnimationWrapper();
      break;
    case effectState::cylon:
      cylon();
      break;
  }
}

//                    END OF TAB 1 - MAIN CODE
//                    START OF TAB 2 - CYLON EFFECT

#include <FastLED.h>

#define CLOCK_PIN 13

void fadeall() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].nscale8(250);
  }
}

void cylon() {
  static uint8_t hue = 0;
  Serial.print("x");
  // First slide the led in one direction
  for (int i = 0; i < NUM_LEDS; i++) {
    // Set the i'th led to red
    leds[i] = CHSV(hue++, 255, 255);
    // Show the leds
    FastLED.show();
    // now that we've shown the leds, reset the i'th led to black
    // leds[i] = CRGB::Black;
    fadeall();
    // Wait a little bit before we loop around and do it again
    delay(10);
  }

  // Now go in the other direction.
  for (int i = (NUM_LEDS) - 1; i >= 0; i--) {
    // Set the i'th led to red
    leds[i] = CHSV(hue++, 255, 255);
    // Show the leds
    FastLED.show();
    // now that we've shown the leds, reset the i'th led to black
    // leds[i] = CRGB::Black;
    fadeall();
    // Wait a little bit before we loop around and do it again
    delay(10);
  }
}

//                    END OF TAB 2 - CYLON EFFECT
//                    START OF TAB 3 - FADE ANIMATION EFFECT

void fadeAnimationWrapper() {
  Serial.print('F');
  fadeAnimation(random(256), random(256), random(256));
}

void fadeAnimation(int red, int green, int blue) {
  float r, g, b;
  uint32_t startTime = millis();
  uint32_t totalTime = 1000; // total time for fade in and out, in milliseconds

  // Define array of durations for fade in and fade out
  uint32_t fadeInDurations[] = {600, 800, 1000, 1200, 1600, 2000, 2500};
  uint32_t fadeOutDurations[] = {600, 800, 1000, 1200, 1600, 2000, 2500};
  int numFadeInDurations = sizeof(fadeInDurations) / sizeof(fadeInDurations[0]);
  int numFadeOutDurations = sizeof(fadeOutDurations) / sizeof(fadeOutDurations[0]);

  // Randomly select duration for fade in and fade out
  uint32_t fadeInTime = fadeInDurations[random(numFadeInDurations)];
  uint32_t fadeOutTime = fadeOutDurations[random(numFadeOutDurations)];

  // FADE IN
  while (millis() - startTime <= fadeInTime) {
    float progress = (float)(millis() - startTime) / (float)fadeInTime;
    r = progress * red;
    g = progress * green;
    b = progress * blue;
    fill_solid(leds, NUM_LEDS, CRGB(r, g, b));
    FastLED.show();
  }

  // FADE OUT
  startTime = millis(); // reset start time for fade out
  while (millis() - startTime <= fadeOutTime) {
    float progress = 1.0 - ((float)(millis() - startTime) / (float)fadeOutTime);
    r = progress * red;
    g = progress * green;
    b = progress * blue;
    fill_solid(leds, NUM_LEDS, CRGB(r, g, b));
    FastLED.show();
  }
}

//                    END OF TAB 3 - FADE ANIMATION EFECT
//                    START OF TAB 4 - SPARKLES EFFECT

void sparkles()
{
  Serial.print('s');
  ChangePalettePeriodically();

  uint8_t maxChanges = random(48); // values 0-48 (Max colors at play)
  nblendPaletteTowardPalette( currentPalette, targetPalette, maxChanges);

  static uint8_t startIndex = 0;
  startIndex = startIndex + 1; /* motion speed */
  FillLEDsFromPaletteColors( startIndex);

  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
}

void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
  uint8_t brightness = 255;

  for ( int i = 0; i < NUM_LEDS; i++) {
    leds[i] = ColorFromPalette( currentPalette, colorIndex + sin8(i * 16), brightness);
    colorIndex += 3;
  }
}


void ChangePalettePeriodically()
{
  uint8_t secondHand = (millis() / 1000) % 60;

  switch (secondHand) {
    case 0:
      targetPalette = RainbowColors_p;
      break;
    case 10:
      targetPalette = CRGBPalette16( CHSV(HUE_GREEN, 255, 255), CHSV(HUE_PURPLE, 255, 255), CRGB::Black, CRGB::Black, CHSV(HUE_PURPLE, 255, 255), CHSV(HUE_GREEN, 255, 255), CRGB::Black, CRGB::Black, CHSV(HUE_PURPLE, 255, 255), CHSV(HUE_GREEN, 255, 255), CRGB::Black, CRGB::Black, CHSV(HUE_PURPLE, 255, 255), CHSV(HUE_GREEN, 255, 255), CRGB::Black, CRGB::Black );
      break;
    case 20:
      targetPalette = CRGBPalette16( CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White, CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White, CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White, CRGB::Black, CRGB::Black, CRGB::Black, CRGB::White );
      break;
    case 30:
      targetPalette = LavaColors_p;
      break;
    case 40:
      targetPalette = CloudColors_p;
      break;
    case 50:
      targetPalette = PartyColors_p;
      break;
  }
}

Beyond enabling the changeEffect() function in loop, the main change I added was an additional switch-case to actually call the functions based on the state. I also added a couple Serial.print()s within the animation functions so you could see when they get focus (and block.)

As it was, depending on the commenting in loop() it would either run a cycle of each animation, or just one. The changeEffect() didn't seem to have a mechanism to act on the changes, and did nothing without a button change.

With this chunk added to changeEffect() to call the active effect, it seems to be more functional.

  switch (currEffect) {
    case effectState::sparkles:
      sparkles();
      break;
    case effectState::fadeAnimationWrapper:
      fadeAnimationWrapper();
      break;
    case effectState::cylon:
      cylon();
      break;
  }

When the code compiles, it runs as written. If it doesn't work as expected, the description of your expectations wasn't communicated to the compiler.

as already explained you could modify all effects and get rid of for / while loops with delays.

a quick search brought me to:

which shows you the basic concept with Adafruits Neopixel effects.
I bet this was already done for fastled effects by others ...

@StefanL38 means that even though your enums and the functions have the same names, they are different things. Essentially effectState::sparkles boils down to mean the number zero as an ID number, while the sparkles() is the function that actually does than animation.

This code translates between the two:

no this part

 {
  enum class effectState : uint8_t {
    sparkles,
    fadeAnimationWrapper,
    cylon,
  };

just defines names for ID-numbers
compiler whenever you come across "sparkles" use ID-number 0
compiler whenever you come across "fadeAnimationWrapper" use ID-number 1
compiler whenever you come across "cylon" use ID-number 2

these enumerations are something completely different from
void sparkles()
void fadeAnimationWrapper()
void cylon()

the enumeration should have a names like

 {
  enum class effectState : uint8_t {
    sparklesMode,
    fadeAnimationWrapperMode,
    cylonMode,
  };

sparklesMode ,
fadeAnimationWrapperMode ,
cylonMode ,

to make very clear it is something different than the function sparkles**()**;
This is the bad habit that makes it hard for beginners to see what is what.

the code that does execute a function is only the function-CALL

sparkles() ;
fadeAnimationWrapper() ;
cylon() ;

best regards Stefan

Don't know about others but I have done it as well.

// Multiple patterns in a state machine format
// using the FastLED library
// by Mike Cook 2017

#include "FastLED.h"

// first set up the parameters to use in the pattern calling
unsigned long patternInterval [] = { 500, 40, 20, 200, 5 }; // how often each pattern updates
unsigned long lastUpdate [5] ;  // for millis() when last update occurred
boolean patternEnabled [] = {true,true,false,true,true}; // should the pattern be called at all
byte patternState[5]; // state machine variable for patterns - this initialises them to zero

// now set up the LEDs to use
#define NUM_LEDS 64
#define DATA_PIN 3
#define CLOCK_PIN 13


CRGB leds[NUM_LEDS];

// Constants for patterns
// for Fire2012
#define COOLING  20
#define SPARKING 50
#define COLOR_ORDER BGR

// now set up the array of pointers to each pattern
void (*patternPtrs[5])(int index,byte state); //the array of pattern pointers

void setup() {
  //initialises the array of pattern pointers
  patternPtrs[0] = blinkOne; 
  patternPtrs[1] = cylon;
  patternPtrs[2] = fire;
  patternPtrs[3] = colorWipe;
  patternPtrs[4] = rainbowCycle;
  //initialises the FastLED driver you want
  //FastLED.addLeds<APA102,leds, NUM_LEDS); // 13 clock  and 11 data
  FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, BGR>(leds, NUM_LEDS); // 13 clock  and 11 data
  FastLED.setBrightness(8);
}

void loop() {
  for(int i = 0; i<5; i++) { // go through all the patterns and see if it is time to call one
    if(patternEnabled[i] && millis() - lastUpdate[i] > patternInterval[i]){
      lastUpdate[i] = millis();
      callPatterns(i, patternState[i]);
    }
  }
}

void callPatterns(int index, byte state) {
  (*patternPtrs[index])(index,state); //calls the pattern at the index of `index` in the array
}

// These are the pattern functions written as a state machine
// this is the Blink program in FastLED's example folder
void blinkOne(int index,byte state) {
  if(state == 0){
    leds[3] = CRGB::Blue;
    FastLED.show();
    patternState[index] = 1; // move on the state machine for the next call
  }
  if(state == 1){
     leds[3] = CRGB::Black;
     FastLED.show();
     patternState[index] = 0;
   }
  }

// this is the Cylon program in FastLED's example folder
// we will use LEDs 8 to 15 to show this
void cylon(int index,byte state) {
  static int i = 8; // replaces the loop index
  if(state == 0){
    leds[i] = CRGB::Red;
    FastLED.show();
    patternState[index] = 1; // move on the state machine for the next call
  }
   if(state == 1){
    // now that we've shown the leds, reset the i'th led to black
    leds[i] = CRGB::Black;
    i++; // increment what was the loop variable
    if(i >= 16){ // we have finished one direction
     patternState[index] = 2;
     i--;
    }
    else {
    patternState[index] = 0;
    }
   }
   // Now go in the other direction only green
   if(state == 2){
     leds[i] = CRGB::Green;
    FastLED.show();
    patternState[index] = 3; // move on the state machine for the next call
   }
  if(state == 3){
    // now that we've shown the leds, reset the i'th led to black
    leds[i] = CRGB::Black;
    i--; // decrement what was the loop variable
    if(i < 8){ // we have finished the return, go back to the start
     patternState[index] = 0;
     i= 8; // ready to start again
    }
    else {
    patternState[index] = 2;
    } 
    // note that this could be better implemented but it has been written like this to keep it close to the original example
    // so you can see what changes have been made 
  }
}

// this is the Fire2012 program in FastLED's example folder
void fire(int index,byte state) {
// using LEDs 16 to 32
// Array of temperature readings at each simulation cell
  const byte startLED = 16; // first LED in section
  const byte numLEDs = 16;
  static byte heat[numLEDs];

  random16_add_entropy( random());
  // Step 1.  Cool down every cell a little
    for( int i = 0; i < numLEDs; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
    }
  
    // Step 2.  Heat from each cell drifts 'up' and diffuses a little
    for( int k= numLEDs - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
    }
    
    // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int y = random8(7);
      heat[y] = qadd8( heat[y], random8(160,255) );
    }

    // Step 4.  Map from heat cells to LED colors
    for( int j = 0; j < numLEDs; j++) {
        leds[j+startLED] = HeatColor( heat[j]); // transfer heat array to LEDs
    }
    FastLED.show(); // display this frame
  }

//colorWipe  modified from Adafruit example to make it a state machine
// uses LEDs 32 to 40
void colorWipe(int index,byte state) {
  static int i =0; // used as state variable
  static byte firstLED = 32;
  static byte numLEDs = 8;
    leds[i+firstLED] = CRGB::Yellow;
    FastLED.show();
    i++;
  if(i >= numLEDs){
    i = 0;
    for(int j;j<numLEDs; j++) leds[j+firstLED] = CRGB::Black; // blank out strip
  }
}

 // rainbowCycle modified from Adafruit example to make it a state machine
 // uses LEDs 48 to 64
void rainbowCycle(int index,byte state) {
  static uint16_t j=0; // used as state variable
  static byte firstLED = 48;
  static byte numLEDs = 16;
    for(int i=0; i< numLEDs; i++) {
      leds[i+firstLED].setHSV( (((i * 256 / numLEDs) + j) & 255), 255, 255);
    }
    FastLED.show();
    j++;
    if(j >= 256*5) j=0;
}


2 Likes

nice to see you well back again :wink:

Still the right hand is not fully working yet but I am managing to get round it for Forum work, thanks :smiley:

1 Like

This actually worked! not perfectly, the code still gets blocked a little bit but I'm able to switch between the effects. Switching out of Sparkles works 100% because itself is a very short (fast) loop. Switching out of FadeAnimation I think works if I'm touching the sensor at the end of the fade to black (the end of the loop) and for Cylon is a bit tricky, I only tested a few cycles but it doesn't seem to respond as good as the others.

So I guess my next step is just restructuring each effect and remove the fors and whiles and ifs so the code doesn't block?

And those measly 10 lines of switch case was all that was missing for the thing to work?
Amazing.. o me any way, for you is probably pedestrian AF haha.

Thanks a lot Dave!!!! I'll post an update later when I have the thing up on the wall and working with a link to a video and thingiverse for the panels in case someone else want to build it and hopefully improve upon it.

@StefanL38 Thanks a lot too for all the info, definitely going to check it out in more detail, as you said, much for me to learn.

Look good! definitely going to try it and see how it works wit my setup.

wait, what happened to you, if I may ask?

very good feedback / question to guide to that explanations that you need.
I have started to write a tutorial about non-blocking code here

to be very immodest:
you are the ideal candidate for this tutorial because you are an expert on beginner questions.

your code for the cylon-animation
has two for-loops

void cylon() { 
  for(int i = 0; i < NUM_LEDS; i++) {
    // serveral lines of code that work with variable i
    // ...

  // Now go in the other direction.  
  for(int i = (NUM_LEDS)-1; i >= 0; i--) {
     // ..

whenever you call your function cylon()

the microcontroller execute these lines of code inside function cylon()
and as there are two for-loops that count up from 0 to 151

the microcontroller stays inside function cylon()
for 151 + 151 = 302 iterations
each iteration with a delay(10);
which means the time between

  • entering function cylon()
    and
  • leaving function cylon()needs
    302 * 10 milliseconds = 3020 milliseconds = 3 seconds

as a time-table
00:10,000 entering function cylon()
00:10,001 first for-loops starts
00:11,500 first for-loop has finished
00:11,501 second for-loop starts
00:13,000 second for-loop finished
00:13,001 leaving function cylon()

00:13,002 checking for button-press

00:13,003 entering function cylon()
00:13,004 first for-loops starts
00:14,505 first for-loop has finished
00:14,506 second for-loop starts
00:16,007 second for-loop finished
00:16,008 leaving function cylon()

00:16,009 checking for button-press
.....

the main lines

00:10,000 jumpin-in = entering function cylon()
00:13,001 jump-out = leaving function cylon()

00:13,003 jump-in entering function cylon()
00:16,008 jump-out leaving function cylon()

The time between jump-in and jump-out is 3 seconds.
that is the opposite of quickly. It is slow(and therefore blocking)

the non-blocking solution is to code something like this.
This is more or less pseudo-code because some details are missing
This pseudo.code focues on the basic principle

void loop() {
  // loop does what its name say LOOPING

  // check if cylon shall be active
  if (cylonMode == true) {
    cylonQuickIn_QuickOut(); // quickly jump-in / quickly jump-out
  }
}
void cylonQuickIn_quickOut() {

  // check if LEDs shall run forward
  if (cylonforward == true) {
    i++; // this counts variable i up
    OneCylonStep(); // do the details of the LEDs

    // check if last LED is reached
    if (i > NUM_LEDS) {
      // when last LED is reached       
      cylonforward = false; // change direction
      cylonbackward = true; 
      i = 151;
    }
  }

  // check if LEDs shall run backwards
  if (cylonbackward == true) {
    i--;
    OneCylonStep();
    if (i > NUM_LEDS) {
      cylonforward = true;
      cylonbackward = false;
      i = 0;
    }
  }
}

function OneCylonStep() does what its name says:
proceed the cylonAnimation one single step
and then jump-out of function OneCylonStep()
again jump-out of function cylonQuickIn_quickOut()

This means as a time table

00:10,000 enter function cylonQuickIn_quickOut()
00:10,002 leave function cylonQuickIn_quickOut()

00:10,003 check button

00:10,004 enter function cylonQuickIn_quickOut()
00:10,006 leave function cylonQuickIn_quickOut()

00:10,007 check button

00:10,008 enter function cylonQuickIn_quickOut()
00:10,010 leave function cylonQuickIn_quickOut()
....
quickly jump-in quickly jump-out (within a few milliseconds
still counting up / counting down variable i to switch the LEDs on/off for the cylon-effect.

by the way do you know why the effect is called "cylon"?

best regards Stefan

The button reliability is much improved if you add:

pinMode(BUTT_PIN,INPUT_PULLUP);

...since otherwise the input is floating, and may not read as HIGH at the exact nanosecond when an animation relinquishes control. Also, since INPUT_PULLUP makes the normal pin state HIGH, testing for LOW would make it respond to the press rather than the release.

The 10 lines could be just a more measlely three or four, if you save a reference to the active function while you are processing the other state transitions:

void changeEffect() {
  enum class effectState : uint8_t {
    sparkles,
    fadeAnimationWrapper,
    cylon,
  };

  static effectState currEffect = effectState::sparkles;
  static effectState prevEffect = effectState::sparkles;
  static int buttonState = LOW;
  static int prevButtonState = LOW;
  static auto activeEffectFunction = sparkles;

  buttonState = digitalRead(BUTT_PIN);

  if (buttonState != prevButtonState) {
    if (buttonState == HIGH) {
      Serial.println("Button Pressed!");

      switch (currEffect) {
        case effectState::sparkles:
          activeEffectFunction = fadeAnimationWrapper;
          currEffect = effectState::fadeAnimationWrapper;
          Serial.println("Fade Running!!");
          break;
        case effectState::fadeAnimationWrapper:
          activeEffectFunction = cylon;
          currEffect = effectState::cylon;
          Serial.println("Cylon Running!!");
          break;
        case effectState::cylon:
          activeEffectFunction = sparkles;
          currEffect = effectState::sparkles;
          Serial.println("Sparkles Running!!");
          break;
      }
      prevEffect = currEffect;
    }
    prevButtonState = buttonState;
  }
  activeEffectFunction();  // this cedes control to the active animation
}

There are a lot of ways to do it, and that isn't really the clearest way. @StefanL38's suggestion about careful naming is important to keep things from getting confused.

If you move to adding more animations, it might be nice to store their names & references in an array, then you can cycle through them with arithmetic, rather than all the repetitive names.

I had a stroke about 2 weeks ago and I have lost control over my right hand. The rest of me is fine.