How to call a separate FSM or state from a separate FSM

Afternoon all,
Following my recent post regarding my Traffic Lights setup, I’ve taken on the advice and got my head round FSM, which resulted in following code;
/

//define states
//State order changed at R2
const int S_WAIT_CAR_SIDE_ROAD_CHECK = 0;
const int S_WAIT_CAR_SIDE_ROAD_DELAY = 1;
const int S_MAIN_ROAD_SLOW = 2;
const int S_WAIT_MAIN_ROAD_SLOW_DELAY = 3;
const int S_MAIN_ROAD_STOP = 4;
const int S_WAIT_MAIN_ROAD_STOP_DELAY = 5;
const int S_SIDE_ROAD_READY = 6;
const int S_WAIT_SIDE_ROAD_READY_DELAY = 7;
const int S_SIDE_ROAD_GO = 8;
const int S_WAIT_SIDE_ROAD_GO_DELAY = 9;
const int S_SIDE_ROAD_SLOW = 10;
const int S_WAIT_SIDE_ROAD_SLOW_DELAY = 11;
const int S_SIDE_ROAD_STOP = 12;
const int S_WAIT_SIDE_ROAD_STOP_DELAY = 13;
const int S_MAIN_ROAD_READY = 14;
const int S_WAIT_MAIN_ROAD_READY_DELAY = 15;
const int S_MAIN_ROAD_GO = 16;

//define output pins
const int MAIN_ROAD_RED = 11; // TL1 pins
const int MAIN_ROAD_AMBER = 10;
const int MAIN_ROAD_GREEN = 9;
const int SIDE_ROAD_RED = 8; // TL2 pins
const int SIDE_ROAD_AMBER = 7;
const int SIDE_ROAD_GREEN = 6;

//define input pins
const int SR_SENSORY_PIN = 13;

static unsigned long ts;

//--------------------------------------------------------------------------------

void setup() {

//set output pins
pinMode(MAIN_ROAD_RED, OUTPUT);
pinMode(MAIN_ROAD_AMBER, OUTPUT);
pinMode(MAIN_ROAD_GREEN, OUTPUT);
pinMode(SIDE_ROAD_RED, OUTPUT);
pinMode(SIDE_ROAD_AMBER, OUTPUT);
pinMode(SIDE_ROAD_GREEN, OUTPUT);

//set output pins
//Added at R2
pinMode(SR_SENSORY_PIN, INPUT);

//inital traffic lights state
MainRoadGo();
SideRoadStop(); 
}

//--------------------------------------------------------------------------------

void loop() {

  //defines starting state

//Initial state changed at R2
static int state = S_WAIT_CAR_SIDE_ROAD_CHECK;
 
//--------------------------------------------------------------------------------
   switch (state)
  {
//--------------------------------------------------------------------------------
    case S_WAIT_CAR_SIDE_ROAD_CHECK:

      if ( digitalRead (SR_SENSORY_PIN) == HIGH) {
        state = S_WAIT_CAR_SIDE_ROAD_DELAY;
      }
 
      break;
//--------------------------------------------------------------------------------
    case S_WAIT_CAR_SIDE_ROAD_DELAY:
    
      // If five seconds have passed, then move on to the next state.
      if (millis() > ts + 15000)
      {
        state = S_MAIN_ROAD_SLOW;
      }
 
      break;
//--------------------------------------------------------------------------------
    case S_MAIN_ROAD_SLOW:

      MainRoadSlow();
     
      state = S_WAIT_MAIN_ROAD_SLOW_DELAY;  // Move to the next state
       
      break;
//--------------------------------------------------------------------------------
    case S_WAIT_MAIN_ROAD_SLOW_DELAY:
    
      // If five seconds have passed, then move on to the next state.
      if (millis() > ts + 2000)
      {
        state = S_MAIN_ROAD_STOP;
      }

      break;
//--------------------------------------------------------------------------------    
    case S_MAIN_ROAD_STOP:

      MainRoadStop();
 
      state = S_WAIT_MAIN_ROAD_STOP_DELAY;
 
      break;
//--------------------------------------------------------------------------------
    case S_WAIT_MAIN_ROAD_STOP_DELAY:

      if (millis() > ts + 1000)
      {
        state = S_SIDE_ROAD_READY;
      }
 
      break;
//-------------------------------------------------------------------------------- 
    case S_SIDE_ROAD_READY:

      SideRoadReady();
  
      state = S_WAIT_SIDE_ROAD_READY_DELAY;
 
      break;
//-------------------------------------------------------------------------------- 
    case S_WAIT_SIDE_ROAD_READY_DELAY:

      if (millis() > ts + 2000)
      {
        state = S_SIDE_ROAD_GO;
      }

      break;
//--------------------------------------------------------------------------------
    case S_SIDE_ROAD_GO:

      SideRoadGo();
  
      state = S_WAIT_SIDE_ROAD_GO_DELAY;
 
      break;
//-------------------------------------------------------------------------------- 
    case S_WAIT_SIDE_ROAD_GO_DELAY:

      if (millis() > ts + 15000)
      {
        state = S_SIDE_ROAD_SLOW;
      }
 
      break;
//--------------------------------------------------------------------------------
    case S_SIDE_ROAD_SLOW:

      SideRoadSlow();
  
      state = S_WAIT_SIDE_ROAD_SLOW_DELAY;
 
      break;
//-------------------------------------------------------------------------------- 
    case S_WAIT_SIDE_ROAD_SLOW_DELAY:

      if (millis() > ts + 2000)
      {
        state = S_SIDE_ROAD_STOP;
      }
 
      break;
//--------------------------------------------------------------------------------
    case S_SIDE_ROAD_STOP:

      SideRoadStop();
  
      state = S_WAIT_SIDE_ROAD_STOP_DELAY;
 
      break;
//-------------------------------------------------------------------------------- 
    case S_WAIT_SIDE_ROAD_STOP_DELAY:

      if (millis() > ts + 1000)
      {
        state = S_MAIN_ROAD_READY;
      }
 
      break;
//--------------------------------------------------------------------------------
    case S_MAIN_ROAD_READY:

      MainRoadReady();
  
      state = S_WAIT_MAIN_ROAD_READY_DELAY;
 
      break;
//-------------------------------------------------------------------------------- 
    case S_WAIT_MAIN_ROAD_READY_DELAY:

      if (millis() > ts + 2000)
      {
        state = S_MAIN_ROAD_GO;
      }

      break;
//--------------------------------------------------------------------------------
//State moved from 0 to 16 in R2    
    case S_MAIN_ROAD_GO:

      MainRoadGo();
      
      state = S_WAIT_CAR_SIDE_ROAD_CHECK;
 
      break;

//--------------------------------------------------------------------------------      
  } // end of switch
}

void MainRoadGo(){
  digitalWrite( MAIN_ROAD_RED, LOW);
  digitalWrite( MAIN_ROAD_AMBER, LOW);
  digitalWrite( MAIN_ROAD_GREEN, HIGH);
  ts = millis();  // Remember the current time
}

Please note, I have only put one function (void MainRoadGo()) at the end as the post length was > 9000 characters, all functions refereed to in my sketch follow the exact same format but with slightly different outputs depending on which LEDs I want on/off.

This is based on the guide from this website;

The philosophy is as follows, the traffic lights system is on a main road, where the light is always green until a car approaches the traffic lights from the side street, for now I’m simulating are car pulling up at the side street traffic lights using a push button. The idea is that if a car is detected at TL2, the car waits for 5 seconds before the sequence changes to let that car out. The sketch then returns back to the main road lights being green until another car at the side street is detected.

I’m happy with the above sketch to execute out the above system, but I want to introduce a new push button, that sets all lights to red and flashes another LED. I understand that I can add more states to this FSM but I feel it will start to become unorganised.

I have read on a few forums and guides that it’s possible to call a state from another FSM, which I feel would be the best way to go, however, all I’ve read is that it is possible to do this but can’t actually find an example that does this (or maybe I have and I just don’t understand what I’m reading).
I don’t feel that in my code I’ve given my FSM a name or identity so I’m struggling to understand how I would call another FSM without knowing how to refer to it, or what function to use to call it?

I guess my questions are;

Is it possible to call on a separate FSM within the same sketch, or have I missed something?
If so, how would I call a FSM, or a particular state within another FSM?
Do I need to name my existing FSM?

Any help is appreciated.

That would be a design flaw in my opinion as you are really talking about the same system. you need to structure your FSM to cover all cases

I have read on a few forums and guides that it's possible to call a state from another FSM,

You cannot "call" a State Machine but you could have two or more running at the same time.

understand that I can add more states to this FSM but I feel it will start to become unorganised.

Surely a new state is the right way to go. Even if you were to implement a second FSM to hand the red lights and flashing LED you would need to add another state to the current FSM to handle the fact that it is now responding to a second FSM. You only need one more state to handle what you describe.

Read the new button input in loop() and set the state accordingly. When in the new state turn on the lights and flash the LED. How do you intend to get out of the new state ?

I see you are doing this:

const int S_WAIT_CAR_SIDE_ROAD_CHECK = 0;
const int S_WAIT_CAR_SIDE_ROAD_DELAY = 1;
const int S_MAIN_ROAD_SLOW = 2;
const int S_WAIT_MAIN_ROAD_SLOW_DELAY = 3;
const int S_MAIN_ROAD_STOP = 4;
const int S_WAIT_MAIN_ROAD_STOP_DELAY = 5;
const int S_SIDE_ROAD_READY = 6;
const int S_WAIT_SIDE_ROAD_READY_DELAY = 7;
const int S_SIDE_ROAD_GO = 8;
const int S_WAIT_SIDE_ROAD_GO_DELAY = 9;
const int S_SIDE_ROAD_SLOW = 10;
const int S_WAIT_SIDE_ROAD_SLOW_DELAY = 11;
const int S_SIDE_ROAD_STOP = 12;
const int S_WAIT_SIDE_ROAD_STOP_DELAY = 13;
const int S_MAIN_ROAD_READY = 14;
const int S_WAIT_MAIN_ROAD_READY_DELAY = 15;
const int S_MAIN_ROAD_GO = 16;

this 'problem' was considered and made easier with the Enum Resource, but you may find this link easier to understand.

In the abstract, a state machine has a collection of possible states, some information that represents the current state and some code to manage it.

In your case, items such as S_WAIT_CAR_SIDE_ROAD_CHECK represent possible states, your state information (surprise) is called state and the loop and MainRoadGo functions comprise your management code.

You can replicate all of that with new names to make a second FSM. Consider a home automation system - it might have a water heater and HVAC system to control, each of which has it's own set of states, a state variable and a set of functions that control it.

Let's assume that the gas supply to the home is limited so that only one system can run at a time and that we prefer a warm home over hot water. The HVAC controller can tell the water heater to go on standby while it is running the heat. Crudely, this would be by direct manipulation of the water heater's state variable. Better would be for the water heater to have a function the HVAC can call so it can manage the standby change cleanly.

It might be clearer if you create a new function called ControlTrafficLights containing everything you currently have in loop. Call it from loop. Call the controller function for your second FSM from loop too.

Clearly in my home example, multiple controller functions make sense. They are largely independant except for my contrived example. For you though, the second controller will be manipulating the same outputs and acting on the same state. I can't see that it is a good candidate for a second FSM, you just need another state.

You should represent things in a diagram, may be something like this

once you have a picture like this with event based transitions it's pretty trivial to implement and enrich... (like for example adding some memory to say you can't trigger the small road to green if it has been green in the past minute for example)

I posted a small tutorial on FSM in french here, may be through google translate that can help you get some sense on how to handle such a diagram with a sensor (button) and timing events - that's exactly what I cover in the tutorial.

J-M-L:
That would be a design flaw in my opinion as you are really talking about the same system. you need to structure your FSM to cover all cases

Having rethought about this I agree with you.

UKHeliBob:
You cannot "call" a State Machine but you could have two or more running at the same time.
Surely a new state is the right way to go. Even if you were to implement a second FSM to hand the red lights and flashing LED you would need to add another state to the current FSM to handle the fact that it is now responding to a second FSM. You only need one more state to handle what you describe.

Read the new button input in loop() and set the state accordingly. When in the new state turn on the lights and flash the LED. How do you intend to get out of the new state ?

I've implemented new states to cover my changes rather than a separate FSM after rethinking this, but I could not work out or find an example of how to do this with only adding one more state. I had to add 4 new states;

case S_EM_ON:
      digitalWrite(EM_RED, HIGH);
      ts = millis();
      lcd.clear();
      lcd.print("EM ON"); 
      state = S_WAIT_EM_ON_DELAY;
      break; 
//--------------------------------------------------------------------------------        
    case S_WAIT_EM_ON_DELAY:
      if (millis() > ts + 200)
      {
        ts=millis();
        state = S_EM_OFF;
      }

      break;
//-------------------------------------------------------------------------------- 
//New state added in R3
    case S_EM_OFF:
      
      digitalWrite(EM_RED, LOW);
      state = S_WAIT_EM_OFF_DELAY;
      ts = millis();
      lcd.clear();
      lcd.print("EM OFF");
      break; 
//-------------------------------------------------------------------------------- 
    case S_WAIT_EM_OFF_DELAY:
      if (millis() > EMts + GoTime)
      {
        state = S_SIDE_ROAD_STOP;
      }
      else if (millis() > ts + 200)
      {
        state = S_EM_ON;
      }

      break;
//--------------------------------------------------------------------------------

I'm happy that it works, but it bothers me that I could be doing this more efficiently.

wildbill:
In the abstract, a state machine has a collection of possible states, some information that represents the current state and some code to manage it.

In your case, items such as S_WAIT_CAR_SIDE_ROAD_CHECK represent possible states, your state information (surprise) is called state and the loop and MainRoadGo functions comprise your management code.

You can replicate all of that with new names to make a second FSM. Consider a home automation system - it might have a water heater and HVAC system to control, each of which has it's own set of states, a state variable and a set of functions that control it.

Let's assume that the gas supply to the home is limited so that only one system can run at a time and that we prefer a warm home over hot water. The HVAC controller can tell the water heater to go on standby while it is running the heat. Crudely, this would be by direct manipulation of the water heater's state variable. Better would be for the water heater to have a function the HVAC can call so it can manage the standby change cleanly.

It might be clearer if you create a new function called ControlTrafficLights containing everything you currently have in loop. Call it from loop. Call the controller function for your second FSM from loop too.

Clearly in my home example, multiple controller functions make sense. They are largely independant except for my contrived example. For you though, the second controller will be manipulating the same outputs and acting on the same state. I can't see that it is a good candidate for a second FSM, you just need another state.

Thanks for the information, I've decided to add another state, but at some point I would like to try tie two systems together just so I can understand how to do it.

J-M-L:
You should represent things in a diagram, may be something like this

once you have a picture like this with event based transitions it's pretty trivial to implement and enrich... (like for example adding some memory to say you can't trigger the small road to green if it has been green in the past minute for example)

I posted a small tutorial on FSM in french here, may be through google translate that can help you get some sense on how to handle such a diagram with a sensor (button) and timing events - that's exactly what I cover in the tutorial.

I'll have a read thank you.

Just two more questions if that's OK,

I've added a second push button, but it's only detected in the first state;

    case S_WAIT_CAR_SIDE_ROAD_CHECK:

      if (digitalRead (SR_SENSORY_PIN) == HIGH) {
        ts = millis();
        state = S_WAIT_CAR_SIDE_ROAD_DELAY;
        lcd.clear();
        lcd.print("SR CAR WAITING");}
      //Added at R3
      else if (digitalRead (EM_SENSORY_PIN) == HIGH) {
        ts = millis();
        state = S_WAIT_EM_DELAY;
        lcd.clear();
        lcd.print("**EMERGENCY**");         
      }  
      break;

In my system, the second button is an emergency button and should function at time, would I need to put the following function in every state to acheive this, it seems a tedious way to go around the task;

if (digitalRead (EM_SENSORY_PIN) == HIGH) {
        ts = millis();
        state = S_WAIT_EM_DELAY;
        lcd.clear();
        lcd.print("**EMERGENCY**");         
      }

Should I be looking at using an existing FSM library to make my life easier?

For the emergency state you can put the code that starts it outside the switch statement so it gets checked every time loop is invoked without the need to put it in every state. You will presumably need a case in the switch statement to handle it once you're in the emergency state.

As to FSM libraries, that's tricky. A major problem with FSMs is that people find them hard, especially to begin with. There seems to be a comprehension problem that's difficult to get past, especially when they're presented as an abstract concept. Examples are a way to help there. An FSM library adds an extra layer of complexity that makes all this worse. So while you're getting to grips with the idea, I'd stay away from a library.

In addition, if you're looking for help, an FSM library will reduce the number of people who can immediately assist with your code because many will not be familiar with the library you select. So sure, once you have mastery and no longer really need it, use a library - there are some neat and elegant ones available. Just don't suppose that using one will solve your current problems.

I had to add 4 new states;

OK, doing it with just 1 new state may be ambitious but you don't need 4 new states.

Whatever causes the state to change to S_EM_ON might just as well set up the entry conditions for S_WAIT_EM_ON_DELAY and set the state to that immediately. There is no need to go through the intermediate state.

You would also be wise to read the value of millis() in loop() and use that throughout the FSM as the current value is used at various places. Doing so means that there is one less entry condition to set up for states that need the current value of millis().

Here is an example of what I mean

case S_WAIT_EM_ON_DELAY:
if (millis() - currentTime >= 200)  //CHANGED TO ROLLOVER SAFE CALCULATION
{
  digitalWrite(EM_RED, LOW);  //set up entry conditions for next state
  lcd.clear();
  lcd.print("EM OFF");
  state = S_WAIT_EM_OFF_DELAY;
}
break;
//--------------------------------------------------------------------------------

case S_WAIT_EM_OFF_DELAY:
if (millis() - EMts >= GoTime)
{
  state = S_SIDE_ROAD_STOP;
}
else if (millis() - currentTime >= 200)
{
  state = S_EM_ON;
}

break;

wildbill:
For the emergency state you can put the code that starts it outside the switch statement so it gets checked every time loop is invoked without the need to put it in every state. You will presumably need a case in the switch statement to handle it once you’re in the emergency state.

As to FSM libraries, that’s tricky. A major problem with FSMs is that people find them hard, especially to begin with. There seems to be a comprehension problem that’s difficult to get past, especially when they’re presented as an abstract concept. Examples are a way to help there. An FSM library adds an extra layer of complexity that makes all this worse. So while you’re getting to grips with the idea, I’d stay away from a library.

In addition, if you’re looking for help, an FSM library will reduce the number of people who can immediately assist with your code because many will not be familiar with the library you select. So sure, once you have mastery and no longer really need it, use a library - there are some neat and elegant ones available. Just don’t suppose that using one will solve your current problems.

It never crossed my mind to move to loop(). Once the code was in the state machine, i assumed everything outside the state machine was ignored. Kinda ties in with you saying people find them hard!!

Until I get my head around the FSM alot more, it’s probably best that I leave the libraries alone! Thanks for taking the time to help.

UKHeliBob:
OK, doing it with just 1 new state may be ambitious but you don’t need 4 new states.

Whatever causes the state to change to S_EM_ON might just as well set up the entry conditions for S_WAIT_EM_ON_DELAY and set the state to that immediately. There is no need to go through the intermediate state.

You would also be wise to read the value of millis() in loop() and use that throughout the FSM as the current value is used at various places. Doing so means that there is one less entry condition to set up for states that need the current value of millis().

Here is an example of what I mean

case S_WAIT_EM_ON_DELAY:

if (millis() - currentTime >= 200)  //CHANGED TO ROLLOVER SAFE CALCULATION
{
  digitalWrite(EM_RED, LOW);  //set up entry conditions for next state
  lcd.clear();
  lcd.print(“EM OFF”);
  state = S_WAIT_EM_OFF_DELAY;
}
break;
//--------------------------------------------------------------------------------

case S_WAIT_EM_OFF_DELAY:
if (millis() - EMts >= GoTime)
{
  state = S_SIDE_ROAD_STOP;
}
else if (millis() - currentTime >= 200)
{
  state = S_EM_ON;
}

break;

Sorry, perhaps I took your comment too literally! I can actually implement the principle you’ve shown on across the rest of the code, which could half the number of states!

As I mentioned in my reply to wildbill, I didn’t actually realise I could still use loop() one in the FSM, kinda obvious now it’s been pointed out to me twice!! I Understand the idea of moving millis() to loop() but in your example, i’m not sure where i’d set ‘currentTime’? it’s the timing concept that really trips me up :(.

And thanks for take the time to respond, it’s appreciated.

Check the code I provided in the French tutorial - it should be pretty self explanatory

You can put a state machine inside another state machine. When you're in that "flashing" mode, you can create smaller sub-states that manage the flashing. You could just copy Blink-Without-Delay right into that one case-clause. This also means that you don't have to duplicate the exit conditions (emergency switch no longer switched) into all 4 new states.

You can have two state machines run alongside each other. A robot car might have lots of states related to driving: forwards, backwards, left, right etc. But then you want to put headlights on the car. You don't want to have states like driving-forwards-with-headlights. That just makes it too complex. Create a second state machine with its own state variable to control the headlights. The two state machines may interact: it might refuse to go into the driving-forwards state if it's night time and the headlights aren't on.

I can actually implement the principle you've shown on across the rest of the code, which could half the number of states!

Good. You took the hint. If there is more than one state change that needs the same entry conditions set then don't forget that you can use a function to do it and further cut down the code size.

in your example, i'm not sure where i'd set 'currentTime'?

It would be set from millis() in loop() and used wherever the current value of millis() is needed.