[SOLVED] A state machine with momentary switch

Indeed, an AND is what I want there.

There's a traffic light example on WOKWi that might be interesting to look at.

@6v6gt I have the "pedestrian" version working, apart from the "special mode" where only the yellow LED ring is flashing; but first I must remove the redundancies in each case

    case 0:
      Serial.println ("RED");
      interval = interval_RED;
      fill_solid(ledRings[0], numLEDs, hsvRed); //almost the same for every case
      fill_solid(ledRings[1], numLEDs, 0);
      fill_solid(ledRings[2], numLEDs, 0);
      systemState = 1;
      break;

and I began looking into your suggestion using a struct, which appears to be the best candidate to "collect" various variables pertaining to the states of a system.

That would mean I need a struct with those members, I think.

struct stateVariables
{
  byte number;
  char name[10];
  unsigned long interval;
  byte ring1Colour[3];
  byte ring2Colour[3];
  byte ring3Colour[3];
};

stateVariables state[5] =
{
  {0, "RED",        9000, {0, 255, 192},      {0, 0, 0},      {0, 0, 0}},
  {1, "RED_YELLOW", 2000, {0, 255, 192}, {32, 255, 255},      {0, 0, 0}},
  {2, "GREEN",      6000,     {0, 0, 0},      {0, 0, 0}, {80, 255, 168}},
  {3, "YELLOW",     2000,     {0, 0, 0}, {32, 255, 255},      {0, 0, 0}},
  {4, "FLASHING",    700,     {0, 0, 0}, {32, 255, 255},      {0, 0, 0}}
} ;

state[1].ring2Colour[1] //would yield 32, if I understood how a struct works?

Then I create a third function setLEDs() that I call from each case to set the three LED rings accordingly, although this still has some redundancy to be removed.

setLEDs(byte stateNumber)
  for (byte i = 0; i <= 2; i++)
  {
    fill_solid(ledRings[i], numLEDs, state[stateNumber].ring1Colour[i]);
    fill_solid(ledRings[i], numLEDs, state[stateNumber].ring2Colour[i]);
    fill_solid(ledRings[i], numLEDs, state[stateNumber].ring3Colour[i]);
  }
}

For state 4, flashing yellow, I could then have a conditional with millis() timed flashing as part of the aforementioned setLEDs() function.

I know that your and @gcjr versions are more clever and more condensed, but for learning purposes, I better keep with the olde worlde switch/case approach for now and learn more about structs and how to access its members. Please no code, just let me know if I am on the right track.

I looked at your simulation and it works nicely for the basic case.

I think that instead of putting the the arrays of colour values directly into the struct as you are proposing:

byte ring1Colour[3];

In this example to be filled from the first array in here:
{0, "RED", 9000, {0, 255, 192}, {0, 0, 0}, {0, 0, 0}},

You should instead put a pointer to the object you have already created here:

CHSV hsvRed(0, 255, 192); // Red (hue, saturation, value from 0 - 255

So the struct may look something like :

struct stateVariables { 
   byte number; 
   char name[10]; 
   unsigned long interval;
   CHSV * ring1Colour ; 
   CHSV * ring2Colour ; 
   CHSV * ring3Colour ;
 };

and may be initialized by something like:
{0, "RED", 9000, hsvRed, hsvAllOff, hsvAllOff },

I see, thanks. Pointing to a memory location where something already exists seems effective. Now after structs I have to explore pointers also. Very educational, I have to read up on pointers in my C++ book.

I guess it will be a two stage process. (1) Scratching something together which works based on examples and (2) marvelling at how it all actually works in the background.

Haven't dared to venture into pointers yet, but this works, also the flashing. Quite some code and magic numbers have now disappeared, but the function above feels clumsy, with magic numbers 0, 1 and 2 sprinkled vertically and horizontally, and I wonder if that can be done programmatically cleaner; maybe the struct triples need to be ordered differently or I need more struct members.

#include "FastLED.h"

const byte pinSwitch = 8;

const int timeShortPress = 250;
const int timeLongPress = 350;

const byte pinData1 = 10;
const byte pinData2 = 11;
const byte pinData3 = 12;

const byte numLEDs = 16;

CRGB ledRings[3][numLEDs];

struct stateVariables
{
  byte          number;
  char          name[11];
  unsigned long interval;
  byte          ring0Colour[3];
  byte          ring1Colour[3];
  byte          ring2Colour[3];
};

stateVariables states[5] =
{
  {0, "RED",        9000, {0, 255, 192},      {0, 0, 0},      {0, 0, 0}},
  {1, "RED_YELLOW", 2000, {0, 255, 192}, {32, 255, 255},      {0, 0, 0}},
  {2, "GREEN",      6000,     {0, 0, 0},      {0, 0, 0}, {80, 255, 168}},
  {3, "YELLOW",     2000,     {0, 0, 0}, {32, 255, 255},      {0, 0, 0}},
  {4, "FLASHING",    600,     {0, 0, 0}, {32, 255, 255},      {0, 0, 0}}
} ;

byte lastSwitchState = HIGH;

unsigned long timePressed  = 0;
unsigned long timeReleased = 0;

unsigned long interval = 0;

bool normalMode = true;
bool flashingMode = false;

byte systemState = 0;

void setup()
{
  Serial.begin (9600);

  pinMode (pinSwitch, INPUT_PULLUP);

  pinMode(pinData1, OUTPUT);
  pinMode(pinData2, OUTPUT);
  pinMode(pinData3, OUTPUT);

  FastLED.addLeds<NEOPIXEL, pinData1>(ledRings[0], numLEDs);
  FastLED.addLeds<NEOPIXEL, pinData2>(ledRings[1], numLEDs);
  FastLED.addLeds<NEOPIXEL, pinData3>(ledRings[2], numLEDs);

  for (byte i = 0; i < 3; i++)
  {
    fill_solid(ledRings[i], numLEDs, 0);
  }
  FastLED.show();

  delay (3000);
}

void loop()
{
  checkSwitch();
  eventProcessing();
}

void eventProcessing()
{
  static unsigned long timeNow = 0;

  if (millis() - timeNow <= interval && normalMode)
  {
    return;
  }
  else if (!normalMode)
  {
    systemState = 4;
  }

  switch (systemState)
  {
    case 0:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState++;
      break;

    case 1:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState++;
      break;

    case 2:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState++;
      break;

    case 3:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState = 0;
      break;

    case 4:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      break;
  }
  timeNow = millis();
}

void switchRings(byte state)
{
  if (normalMode)
  { 
    fill_solid(ledRings[0], numLEDs, CHSV(states[state].ring0Colour[0], states[state].ring0Colour[1], states[state].ring0Colour[2]));
    fill_solid(ledRings[1], numLEDs, CHSV(states[state].ring1Colour[0], states[state].ring1Colour[1], states[state].ring1Colour[2]));
    fill_solid(ledRings[2], numLEDs, CHSV(states[state].ring2Colour[0], states[state].ring2Colour[1], states[state].ring2Colour[2]));
  }
  else if (!normalMode)
  {
    static unsigned long timeNow = 0;
    if (millis() - timeNow >= states[systemState].interval)
    {
      if (flashingMode == false)
      {
        fill_solid(ledRings[0], numLEDs, 0);
        fill_solid(ledRings[1], numLEDs, CHSV(states[state].ring1Colour[0], states[state].ring1Colour[1], states[state].ring1Colour[2]));
        fill_solid(ledRings[2], numLEDs, 0);
        flashingMode = true;
      }
      else
      {
        fill_solid(ledRings[1], numLEDs, 0);
        flashingMode = false;
      }
      timeNow = millis();
    }
  }
  FastLED.show();
}

void checkSwitch()
{
  byte switchState = digitalRead (pinSwitch);
  if (switchState != lastSwitchState)
  {
    if (switchState == LOW)
    {
      timePressed = millis();
    }
    else
    {
      timeReleased = millis();
      unsigned long pressDuration = timeReleased - timePressed;
      if (pressDuration < timeShortPress)
      {
        Serial.print ("Flashing mode; short press milliseconds "); Serial.println (pressDuration);
        normalMode = false;
      }
      else if (pressDuration > timeLongPress )
      {
        Serial.print ("Normal mode; long press milliseconds "); Serial.println (pressDuration);
        normalMode = true;
        systemState = 0;
      }
    }
    lastSwitchState = switchState;
  }
}

Well done, that is quite an achievement. I have also learned a bit more about how useful WokWi is.
Here is a variant using ready made colour objects (type CHSV) and putting references (&) to them in the struct instead of the repeating the colour values each time. These are de-referenced (*) before use. The result is a bit cleaner.

#include "FastLED.h"

const byte pinSwitch = 8;

const int timeShortPress = 250;
const int timeLongPress = 350;

const byte pinData1 =  10 ;
const byte pinData2 =  11 ;
const byte pinData3 =  12 ;


const byte numLEDs = 16;

CRGB ledRings[3][numLEDs];
CHSV hsvRed(0, 255, 192); // Red (hue, saturation, value from 0 - 255
CHSV hsvYellow(32, 255, 255); //  
CHSV hsvGreen(80, 255, 168); //  
CHSV hsvBlank(0, 0, 0); //  


struct stateVariables
{
  byte          number;
  char          name[11];
  unsigned long interval;
  CHSV *        ringColour[ 3 ] ; 
};


stateVariables states[5] =
{
  {0, "RED",        9000, {&hsvRed,   &hsvBlank,   &hsvBlank} },
  {1, "RED_YELLOW", 2000, {&hsvRed,   &hsvYellow,  &hsvBlank} },
  {2, "GREEN",      6000, {&hsvBlank, &hsvBlank,   &hsvGreen} },
  {3, "YELLOW",     2000, {&hsvBlank, &hsvYellow,  &hsvBlank} },
  {4, "FLASHING",    600, {&hsvBlank, &hsvYellow,  &hsvBlank} }
} ;


byte lastSwitchState = HIGH;

unsigned long timePressed  = 0;
unsigned long timeReleased = 0;

unsigned long interval = 0;

bool normalMode = true;
bool flashingMode = false;

byte systemState = 0;

void setup()
{
  Serial.begin (9600);

  pinMode (pinSwitch, INPUT_PULLUP);
 
  pinMode(pinData1, OUTPUT);
  pinMode(pinData2, OUTPUT);
  pinMode(pinData3, OUTPUT);

  FastLED.addLeds<NEOPIXEL, pinData1>(ledRings[0], numLEDs);
  FastLED.addLeds<NEOPIXEL, pinData2>(ledRings[1], numLEDs);
  FastLED.addLeds<NEOPIXEL, pinData3>(ledRings[2], numLEDs);


  for (byte i = 0; i < 3; i++)
  {
    fill_solid(ledRings[i], numLEDs, 0);
  }
  FastLED.show();

  delay (3000);
}

void loop()
{
  checkSwitch();
  eventProcessing();
}

void eventProcessing()
{
  static unsigned long timeNow = 0;

  if (millis() - timeNow <= interval && normalMode)
  {
    return;
  }
  else if (!normalMode)
  {
    systemState = 4;
  }

  switch (systemState)
  {
    case 0:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState++;
      break;

    case 1:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState++;
      break;

    case 2:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState++;
      break;

    case 3:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      systemState = 0;
      break;

    case 4:
      Serial.println (states[systemState].name);
      interval = states[systemState].interval;
      switchRings(systemState);
      break;
  }
  timeNow = millis();
}

void switchRings(byte state)
{
  if (normalMode)
  { 
    fill_solid(ledRings[0], numLEDs, *(states[state].ringColour[ 0 ]) ) ;
    fill_solid(ledRings[1], numLEDs, *(states[state].ringColour[ 1 ]) ) ;
    fill_solid(ledRings[2], numLEDs, *(states[state].ringColour[ 2 ]) ) ;
  }
  else if (!normalMode)
  {
    static unsigned long timeNow = 0;
    if (millis() - timeNow >= states[systemState].interval)
    {
      if (flashingMode == true)
      {
        fill_solid(ledRings[0], numLEDs, 0);
        fill_solid(ledRings[1], numLEDs, *(states[state].ringColour[ 1 ]));
        fill_solid(ledRings[2], numLEDs, 0);
        flashingMode = false;
      }
      else
      {
        fill_solid(ledRings[1], numLEDs, 0);
        flashingMode = true;
      }
      timeNow = millis();
    }
  }
  FastLED.show();
}

void checkSwitch()
{
  byte switchState = digitalRead (pinSwitch);
  if (switchState != lastSwitchState)
  {
    if (switchState == LOW)
    {
      timePressed = millis();
    }
    else
    {
      timeReleased = millis();
      unsigned long pressDuration = timeReleased - timePressed;
      if (pressDuration < timeShortPress)
      {
        Serial.print ("Flashing mode; short press milliseconds "); Serial.println (pressDuration);
        normalMode = false;
      }
      else if (pressDuration > timeLongPress )
      {
        Serial.print ("Normal mode; long press milliseconds "); Serial.println (pressDuration);
        normalMode = true;
        systemState = 0;
      }
    }
    lastSwitchState = switchState;
  }
}

WOKWI Basic FSM traffic light with momentary switch.ino - Wokwi Arduino and ESP32 Simulator

Thanks, more redundancy removed, while still easy to understand. Now I really need to open my C++ book and read about pointers. Later I can get into more abstract things like in @gcjr example. I always take one step at a time.

Thought it'd be a good idea to add my state diagram for completeness.