Using array notation or similar to create multiple instances of a State Machine

I'm looking for advice on whether there is an available notation in the Arduino IDE that would save me a bunch of copy-paste-modify effort. The Arduino is a MEGA 2560. I'm using the Arduino IDE 1.8.19. Here is a summary of what my code needs to do:

  • Software Overview:
  • The architecture of this code project is a Finite State Machine, and we make use of Jose Rullan's StateMachine library.
  • Note that this library, in turn, makes use of Ivan Seidel's LinkedList library.
  • We'll need seven state machines, one for each inverter.
  • Each FSM has four states and eight transitions, as shown in the diagram, "BPCC State Diagram R2.pdf" (sorry, I cannot upload this file as a new member; however it is simply four boxes representing four states, with a total of 8 arrows representing possible transitions between the states).
  • The state machines and their states are created during Initialization, and the transitions are added within the setup routine.
  • Functions define what happens within each state, and the conditions that trigger transitions; these functions are duplicated for the seven state machines.

[I cannot upload the diagram, being a new user.]

I tried using array notation (e.g., "machine[0]" for the name of the StateMachine instance), and it actually compiled, but didn't appear to be running successfully. This was using the code example provided, which otherwise worked fine with simple names for the multiple machines.

It may be that I need to learn more about using pointers in Arduino code??

Otherwise I guess I will need to write and debug the code for ONE inverter's state machine, then copy and paste the many lines that create and define states and transitions for that machine, then edit the names of the machines and transitions to reflect the six additional inverters.

Just as an example of what I'd like to be able to do, I'm already using array variables for the seven I/O pins and related parameters (e.g., voltages), with each array having seven members, so that I can create a "for" loop with an index that steps from 0 to 6, that can perform the transition testing within a Switch-Case code structure.

Can you share a link to that library ?

Welcome to the forum

To post images etc. you need trust level 1, you can get there by:

  • Entering at least 5 topics
  • Reading at least 30 posts
  • Spend a total of 10 minutes reading posts

Users at trust level 1 can...

  • Use all core Discourse functions; all new user restrictions are removed
  • Send PMs
  • Upload images and attachments

Kind of humorous: I had this in my original message, but deleted it after reading the posting guidelines on this forum, that said something about avoiding putting in links to other sites!
My bad!

Have you considered defining a class for the state machine and creating an array of such classes ?

Thanks for this tip, Bob.
I've browsed way more than that, but failed to create my account on this forum until I finally decided to ask a question!!
Shouldn't take me long to reach trust level 1...

Bob, that sounds like exactly how I should proceed.
I'm learning about classes and instances; kind of new to OO programming, I hate to admit (I'm an older mechanical engineer, with a smattering of simple coding experience over the years).
The StateMachine is already a class created by Rullan's library, if I understand his code correctly. I think what I'm missing is whether I want to create an array of instances of the class, vs creating an array of such classes, as you are suggesting.
I don't suppose you could point me (sorry for the pun) to a source for me to learn the basics of the notation for defining a class and creating the array of classes?

Links to reference code is fine.

Have you tried with just

StateMachine machines[7];

And perform what’s usually done statically in the setup as well

Not sure where you did read that. You can always posts links, it's up to the people to decide if they will follow them.

What a lot of people will not do is leave the forum to download source code (bitbucket, dropbox etc) or download unknown zip files.

Jackson: I was close to that. I tried declaring one of the StateMachines using array notation:

StateMachine machine[0] = StateMachine();

That didn't appear to work (I substituted "machine[0]" everywhere it was previously "machine").

I will try starting with your notation, then defining each member of the array after that.

StateMachine machine[0] = StateMachine();

That would create zero instances of the StateMachine object

If anything you need to use

StateMachine machine[7] = StateMachine();

then refer to them as machine[0], machine[1] etc as you would with any array

Note that I know nothing about the library in question !

Thank you Bob! Your notation suggestion works!
Here is what I have, just for trial purposes:

StateMachine machine[2] = StateMachine();

State* S0 = machine[0].addState(&state0); 
State* S1 = machine[0].addState(&state1);
State* S2 = machine[0].addState(&state2);

//int *machineA;

// StateMachine machine[1] = StateMachine();

State* SA0 = machine[1].addState(&stateA0); 
State* SA1 = machine[1].addState(&stateA1);
State* SA2 = machine[1].addState(&stateA2);


void setup() {
  Serial.begin(9600);
  pinMode(LED,OUTPUT);
  pinMode(3, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  randomSeed(A0);
  Serial.println("Doing setup");
 
  S0->addTransition(&transitionS0,S0);    // Transition to itself (see transition logic for details)
  S1->addTransition(&transitionS1S2,S2);  // S1 transition to S2
  S0->addTransition(&transitionS0S1,S1);    // S0 transition to S1
  S2->addTransition(&transitionS2S0,S0);  // S2 transition to S0

  SA0->addTransition(&transitionSA0,SA0);    // Transition to itself (see transition logic for details)
  SA1->addTransition(&transitionSA1SA2,SA2);  // SA1 transition to SA2

}
void state0(){
  Serial.println("State 0");
  if(machine[0].executeOnce){
    Serial.println("Execute Once");
    digitalWrite(LED,!digitalRead(LED));
  }
}
void state1(){
  Serial.println("State 1");
  if(machine[0].executeOnce){
    Serial.println("Execute Once");
    digitalWrite(LED,!digitalRead(LED));
  }
}
void state2(){
  Serial.println("State 2");
  if(machine[0].executeOnce){
    Serial.println("Execute Once");
    digitalWrite(LED,!digitalRead(LED));
  }
}
bool transitionS0(){
  Serial.println("Testing for transitionS0");
  if(digitalRead(3) == HIGH){
    return true;
  }
  return false;
}
bool transitionS1S2(){
  if(digitalRead(5) == HIGH){
    return true;
  }
  return false;
}

bool transitionS0S1(){
  if(digitalRead(3) == LOW){
    return true;
  }
  return false;
}

bool transitionS2S0(){
  if(digitalRead(5) == LOW){
    return true;
  }
  return false;
}

void stateA0(){
  Serial.println("StateA 0");
  if(machine[1].executeOnce){
    Serial.println("Execute Once A");
    digitalWrite(LED,!digitalRead(LED));
  }
}
void stateA1(){
  Serial.println("StateA 1");
  if(machine[1].executeOnce){
    Serial.println("Execute Once A");
    digitalWrite(LED,!digitalRead(LED));
  }
}
void stateA2(){
  Serial.println("StateA 2");
  if(machine[1].executeOnce){
    Serial.println("Execute Once A");
    digitalWrite(LED,!digitalRead(LED));
  }
}
bool transitionSA0(){
  if(digitalRead(3) == HIGH){
    return true;
  }
  return false;
}
bool transitionSA1SA2(){
  if(digitalRead(5) == HIGH){
    return true;
  }
  return false;
}


void loop() {
  machine[0].run();
  Serial.println("Running machine[0]");
  delay(STATE_DELAY);
  machine[1].run();
  Serial.println("Running machine[1]");
  delay(STATE_DELAY);
}

My next challenge will be how to refer to the four states and eight transitions, which pertain to each machine, with a similar array notation. As you can see in the code above, I just used unique names, e.g., state1 and state2 for one machine and stateA1 and stateA2 for the second machine. Since these are functions I'm defining (both the states and transitions are defined functions), can I similarly create an array of "state1" functions?

BPCC State Diagram R2.pdf (64.1 KB)

OK here is that diagram (I've reached trust level 1).

The = StateMachine() notation is superfluous. Just use:

StateMachine machine[2];

Why do you need an array of states and transitions ?

The states and transitions belong to their own instance of the state machine object and I would expect them to be private to that instance

If that is the case then each instance can use the same state and transition names without any danger of a change of state in one affecting any other state machine

gfvalvo: you are correct; I changed the code to delete the part you said is superfluous; still works fine.
The original line from Rullan's example code (in his github) is this:

StateMachine machine = StateMachine();

Is the "= StateMachine()" superfluous in his code as well?
I'm trying to understand the difference; declaring an array vs declaring a single instance of a class.

Yes.

In this case it's pretty straightforward:
This creates a single instance named machine

StateMachine machine;

This creates and array of 5 instances named manchines:

StateMachine machines[5];

Each state is associated with a function. If I try to associate the same state to multiple instances of "machine", of course it throws a compiler error: redefinition of 'State* S0'. Thus:

StateMachine machine[2];

State* S0 = machine[0].addState(&state0); 
State* S1 = machine[0].addState(&state1);
State* S2 = machine[0].addState(&state2);
State* S0 = machine[1].addState(&state0); 
State* S1 = machine[1].addState(&state1);
State* S2 = machine[1].addState(&state2);

Do I need to find a way to add the states to the StateMachine class, before creating the seven instances of it? It's not obvious to me how I could do that.

I'm also missing something here, regarding how to define the states and transitions, which are functions called by code in the StateMachine.h library, to handle the multiple instances of machines. If they could take an argument, we could pass them the machine array number, and their functions could use that to test the correct parameter array member.

Thank you gfvalvo; that's perfectly clear now.
I had copied that line from Jose's example code, and it rather confused me as well.

Here is the code from StateMachine.h:

#include <LinkedList.h>
#include "State.h"

#ifndef _STATEMACHINE_H
#define _STATEMACHINE_H

class StateMachine
{
  public:
    // Methods
    
    StateMachine();
    ~StateMachine();
    void init();
    void run();

    // When a state is added we pass the function that represents 
    // that state logic
    State* addState(void (*functionPointer)());
    State* transitionTo(State* s);
    int transitionTo(int i);
	
    // Attributes
    LinkedList<State*> *stateList;
	  bool executeOnce = true; 	//Indicates that a transition to a different state has occurred
    int currentState = -1;	//Indicates the current state number
};

And then from the referenced State.h:

#include <LinkedList.h>

#ifndef _STATE_H
#define _STATE_H

/*
 * Transition is a structure that holds the address of 
 * a function that evaluates whether or not not transition
 * from the current state and the number of the state to transition to
 */
struct Transition{
  bool (*conditionFunction)();
  int stateNumber;
};

/*
 * State represents a state in the statemachine. 
 * It consists mainly of the address of the function
 * that contains the state logic and a collection of transitions 
 * to other states.
 */
class State{
  public:
    State();
    ~State();

	void addTransition(bool (*c)(), State* s);
    void addTransition(bool (*c)(), int stateNumber);
    int evalTransitions();
    int execute();
    int setTransition(int index, int stateNumber);	//Can now dynamically set the transition
	
    // stateLogic is the pointer to the function
    // that represents the state logic
    void (*stateLogic)();
    LinkedList<struct Transition*> *transitions;
	int index;
};
#endif

There are also .cpp files for both of these .h files; here is state.cpp:

#include "State.h"

State::State(){
  transitions = new LinkedList<struct Transition*>();
};

State::~State(){};

/*
 * Adds a transition structure to the list of transitions
 * for this state.
 * Params:
 * conditionFunction is the address of a function that will be evaluated
 * to determine if the transition occurs
 * state is the state to transition to
 */
void State::addTransition(bool (*conditionFunction)(), State* s){
  struct Transition* t = new Transition{conditionFunction,s->index};
  transitions->add(t);
}

/*
 * Adds a transition structure to the list of transitions
 * for this state.
 * Params:
 * conditionFunction is the address of a function that will be evaluated
 * to determine if the transition occurs
 * stateNumber is the number of the state to transition to
 */
void State::addTransition(bool (*conditionFunction)(), int stateNumber){
  struct Transition* t = new Transition{conditionFunction,stateNumber};
  transitions->add(t);
}

/*
 * Evals all transitions sequentially until one of them is true.
 * Returns:
 * The stateNumber of the transition that evaluates to true
 * -1 if none evaluate to true ===> Returning index now instead to avoid confusion between first run and no transitions
 */
int State::evalTransitions(){
  if(transitions->size() == 0) return index;
  bool result = false;
  
  for(int i=0;i<transitions->size();i++){
    result = transitions->get(i)->conditionFunction();
    if(result == true){
      return transitions->get(i)->stateNumber;
    }
  }
  return index;
}

/*
 * Execute runs the stateLogic and then evaluates
 * all available transitions. The transition that
 * returns true is returned.
 */
int State::execute(){
  stateLogic();
  return evalTransitions();
}

/*
 * Method to dynamically set a transition
 */
int State::setTransition(int index, int stateNo){
	if(transitions->size() == 0) return -1;
	transitions->get(index)->stateNumber = stateNo;
	return stateNo;
}

Bob, if this is getting too far into the weeds, perhaps I should forget about using this StateMachine library, and instead I can study the several tutorials available teaching how to implement state machines using switch/case nomenclature. It might be a lot simpler to do that using array variables, rather than dealing with the class structure within this library.