Using the contents of a string (char array) as a variable ?

I've used the FiniteStateMachien library a few times very successfully, with triggers in each state to move on to the next one. In a new application it would be very helpful to be able to set up a list of states to progress through, which may involve using the same state several different times. ideally I'd like to be able to set up a list of states in the order I want them and then increment through.

I'm not an experienced programmer, and despite having read articles about pointers, I simply can't get this to work. Adding asterisks as I thought might be required changed the error message but the nextState() function has always brought a compiler error.

Here is a brief example of the kind of thing I'm trying, which has exactly the same error as my larger code.

void nextState(){
  runState ++;
  mainStateMachine.transitionTo(runningOrder[runState])
}

#include <FiniteStateMachine.h>
#include <LiquidCrystal_I2C.h> // LCD library

char command;
char* runningOrder[]= { "startTest",
  "waitForSafety",
  "actuateNest",
  "waitForUp",
  "actuateNest",
  "mainNoOp"};
  
const int numPar = 8; // number of parameters in a command string
int parameters[numPar];
  
  //pin assignments
const byte sendHeadUp = 22;
const byte headIsUp = 23;
const byte safetyIsOk = 25;
const byte itemDetected = 27;

boolean endComm;
byte runState;

  
State mainNoOp = State(mainNoOpEnter,mainNoOpUpdate,mainNoOpExit);  //no operation
State startTest = State(startTestEnter,startTestUpdate,startTestExit);
State waitForSafety = State(waitForSafetyUpdate);
State waitForUp = State(waitForUpEnter,waitForUpUpdate,waitForUpExit);
State error = State(errorEnter,errorUpdate,errorExit);
  
FSM mainStateMachine = FSM(mainNoOp); //initialize state machine, start in state: mainNoOp

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup(){

  pinMode(headIsUp,INPUT);
  pinMode(safetyIsOk,INPUT);
  pinMode(itemDetected,INPUT);
  pinMode(sendHeadUp,OUTPUT);

  Serial.begin(115200,SERIAL_8N1);   // serial used for PC comms through USB
  lcd.init();                      // initialize the lcd & turn on backlight
  lcd.backlight();
  lcd.clear();
  lcd.print("Hello !");
  delay(1000);
}
void loop(){
  mainStateMachine.update(); // this line makes the state machine 'tick' for timer reaedings
} 
  
void mainNoOpEnter(){
  command = '0';
  memset(parameters,0,sizeof(parameters)/sizeof(int));
  digitalWrite(sendHeadUp,LOW);
}
void mainNoOpUpdate(){
  readIncoming();
  if (endComm) {
    if (command ='s') mainStateMachine.transitionTo(startTest);
    else mainStateMachine.transitionTo(error);
  }
}
void mainNoOpExit(){
  command='0';
  endComm=!endComm; // reset end of communication string flag
}
void startTestEnter(){
  runState = 1; // reset running state
}
void startTestUpdate(){
  if (digitalRead(itemDetected)==LOW) nextState(); // wait for item detected sensor
}
void startTestExit(){
  
}
void waitForSafetyUpdate(){
  if (digitalRead(safetyIsOk)==LOW) nextState(); // wait until safe to run
}
void waitForUpEnter(){
  digitalWrite(sendHeadUp,HIGH);  
}
void waitForUpUpdate(){
  if (digitalRead(headIsUp) ==LOW) nextState(); // when head is raised, return to noOp
}
void waitForUpExit(){
  
}
void errorEnter(){
  lcd.clear();
  lcd.print("Error");
  delay(1000);
}
void errorUpdate(){
  //things to do if error occurred
}
void errorExit(){
}

  
void nextState(){
  runState ++;
  mainStateMachine.transitionTo(runningOrder[runState])
}

void readIncoming(){
  switch (readPCSerial){
  case 1:
    if (Serial.available()>0) readPCSerial = 2; // something arrived from PC
    break;
  case 2:
    if (Serial.read() == '*'){ // check for correct start command
      memset(parameters,0,sizeof(parameters)/sizeof(int)); // zero all parameters
      commsEnd = millis() + (commsTime);
      readPCSerial = 3;
    }
    else{
      readPCSerial =99; // error state
      while (Serial.available()) Serial.read();
    }
    break;
  case 3:
    while(Serial.available()<2){ // wait for minimum size acceptable message to be available - blocking while waiting is no big deal here
      if ((long)(millis() - commsEnd) >=0){ // jump out of waiting loop if insufficient data received in time - should comms retry ?
        commsFailed = true;
        readPCSerial =99;
        errorState = 1; // 1=comms timeout
        failWhy=1;
        break;
      }
    }
    if (readPCSerial !=99) readPCSerial=4;
    break;
  case 4:
    {
      command = Serial.read(); // read command letter - NEED TO CHECK IS VALID
      int i=0;
      endComm = false; // detect end of command string
      while(!endComm && !commsFailed){ // keep looping to read all info          
        if (Serial.available()>0){ // check serial data available
          incomingChar = Serial.read(); // read next part
          if (incomingChar >= 48 && incomingChar <= 57){ // if is numeric
            parameters[i] = parameters[i] * 10;
            parameters[i] = parameters[i] + (incomingChar -'0');
          }
          else if (incomingChar == ',') i++;  // check for comma separator and increment to next parameter
          else if (incomingChar == '\r'){
            endComm = true; // check for CR at end of command string and end loop
            if (i=0) checkRequest=true; // if no parameters, is a request for a status update
            else checkRequest=false;
          }
          //else {}//ERROR CONDITION
          if ((long)(millis() - commsEnd) >=0){ // jump out of waiting loop if insufficient data received in time - should comms retry ?
            commsFailed = true;
            errorState = 1;
            failWhy=1;
            break;
          } // end of if comms timeout
        }// end of if avaialble
      } // end of while(!endComm) loop
      break;
    }
  case 99: 
    //things to do if comms error from PC
    break;
  }
}

It reports 'no matching function for call to 'FiniteStateMachine::transitionTo(char*&)'

I like the clarity that the FiniteStateMachine library brings, but it would be a pain to have to effectively duplicate states so that I can use them in different parts of the sequence. It would also be nice to be able to adjust the sequence by simply editing one list, rather than chasing through the entire code.

I'm sure this is a clear example of a gap in my knowledge, and there are several different ways to achieve the result. I'm open to ideas !

Sorry - omitted to add :

Arduino IDE version 1.0.5

Board is Arduino Mega 2560.

It reports 'no matching function for call to 'FiniteStateMachine::transitionTo(char*&)'

The function wants the address of a function to call, not the name of a function. Names are converted to addresses as part of the compilation process. The name is not known at run time, so the function can't be called by name.

Do some research on function pointers. You need an array of function pointers, not an array of names.

You can read more on complex data definition and Purdum's Right-Left Rule at:

http://jdurrett.ba.ttu.edu/3345/handouts/RL-rule.html

An array of pointers to functions needs to be written as Paul suggested. He's a short demo of how the syntax it works.

void (*funcPtr[])() = {func1, func2, func3};   // Define an array of pointers to function all of which return void

void func1() {
  Serial.println("Function 1");
}

void func2() {
  Serial.println("Function 2");
}
void func3() {
  Serial.println("Function 3");
}

void setup() {
  Serial.begin(115200);
  for (int i = 0; i < 3; i++) 
    (*funcPtr[i])();
}

void loop() {
}

Thanks very much for the very quick and helpful responses. I think I understand some more now, and will take a look at creating an array of function pointers following these suggestions.

Tim

I've worked on a variety of function pointer approaches to this, and I'm not getting anywhere ?

When using the FiniteStateMachine library, transitions to the next state are called by mainStateMachine.transitionTo(RequiredNextState). When I've previously used this library without trying to be clever with it, RequiredNextState is not a function that is defined in the sketch. The states that the finite state machine uses are created like this State RequiredNextState = state (RequiredNextStateEnter,RequiredNextStateUpdate,RequiredNextStateExit);

The three functions named (RequiredNextStateEnter, RequiredNextStateUpdate, RequiredNextStateExit) are defined in the sketch in the normal way. The Enter function is completed once on entering the state, the Update function is repeated until a transition occurs, and the Exit function is completed once on exiting to the next state.

Defining a function such as void RequiredNextState(){} causes a compiler error 'RequiredNextState' redeclared as different kind of symbol.

I can provide more details of the library if it would help.

FiniteStateMachine library cpp and h files attached.

FiniteStateMachine.cpp (2.93 KB)

FiniteStateMachine.h (3.02 KB)

Using a switch to select the next state compiles as expected, and is the method used in the library examples. It is clumsy though, and is a pain to edit to change the running order. The switch is in the nextState() function at the end of the code.

#include <FiniteStateMachine.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // LCD library

char command;

const int numPar = 8; // number of parameters in a command string
int parameters[numPar];
  
  //pin assignments
const byte sendHeadUp = 22;
const byte headIsUp = 23;
const byte safetyIsOk = 25;
const byte itemDetected = 27;

boolean endComm,commsEnd,commsFailed,checkRequest;
byte runState;
byte errorState;
byte failWhy =0;
byte readPCSerial=0;
int commsTime = 2000;
  
State mainNoOp = State(mainNoOpEnter,mainNoOpUpdate,mainNoOpExit);  //no operation
State startTest = State(startTestEnter,startTestUpdate,startTestExit);
State waitForSafety = State(waitForSafetyUpdate);
State waitForUp = State(waitForUpEnter,waitForUpUpdate,waitForUpExit);
State error = State(errorEnter,errorUpdate,errorExit);
  
FSM mainStateMachine = FSM(mainNoOp); //initialize state machine, start in state: mainNoOp

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup(){

  pinMode(headIsUp,INPUT);
  pinMode(safetyIsOk,INPUT);
  pinMode(itemDetected,INPUT);
  pinMode(sendHeadUp,OUTPUT);

  Serial.begin(115200,SERIAL_8N1);   // serial used for PC comms through USB
  lcd.init();                      // initialize the lcd & turn on backlight
  lcd.backlight();
  lcd.clear();
  lcd.print("Hello !");
  delay(1000);
}
void loop(){
  mainStateMachine.update(); // this line makes the state machine 'tick' for timer reaedings
} 
  
void mainNoOpEnter(){
  command = '0';
  memset(parameters,0,sizeof(parameters)/sizeof(int));
  digitalWrite(sendHeadUp,LOW);
}
void mainNoOpUpdate(){
  readIncoming();
  if (endComm) {
    if (command ='s') mainStateMachine.transitionTo(startTest);
    else mainStateMachine.transitionTo(error);
  }
}
void mainNoOpExit(){
  command='0';
  endComm=!endComm; // reset end of communication string flag
}
void startTestEnter(){
  runState = 1; // reset running state
}
void startTestUpdate(){
  if (digitalRead(itemDetected)==LOW) nextState(); // wait for item detected sensor
}
void startTestExit(){
  
}
void waitForSafetyUpdate(){
  if (digitalRead(safetyIsOk)==LOW) nextState(); // wait until safe to run
}
void waitForUpEnter(){
  digitalWrite(sendHeadUp,HIGH);  
}
void waitForUpUpdate(){
  if (digitalRead(headIsUp) ==LOW) nextState(); // when head is raised, return to noOp
}
void waitForUpExit(){
  
}
void errorEnter(){
  lcd.clear();
  lcd.print("Error");
  delay(1000);
}
void errorUpdate(){
  //things to do if error occurred
}
void errorExit(){
}

void readIncoming(){
  switch (readPCSerial){
  case 1:
    if (Serial.available()>0) readPCSerial = 2; // something arrived from PC
    break;
  case 2:
    if (Serial.read() == '*'){ // check for correct start command
      memset(parameters,0,sizeof(parameters)/sizeof(int)); // zero all parameters
      commsEnd = millis() + (commsTime);
      readPCSerial = 3;
    }
    else{
      readPCSerial =99; // error state
      while (Serial.available()) Serial.read();
    }
    break;
  case 3:
    while(Serial.available()<2){ // wait for minimum size acceptable message to be available - blocking while waiting is no big deal here
      if ((long)(millis() - commsEnd) >=0){ // jump out of waiting loop if insufficient data received in time - should comms retry ?
        commsFailed = true;
        readPCSerial =99;
        errorState = 1; // 1=comms timeout
        failWhy=1;
        break;
      }
    }
    if (readPCSerial !=99) readPCSerial=4;
    break;
  case 4:
    {
      command = Serial.read(); // read command letter - NEED TO CHECK IS VALID
      int i=0;
      endComm = false; // detect end of command string
      while(!endComm && !commsFailed){ // keep looping to read all info          
        if (Serial.available()>0){ // check serial data available
          char incomingChar = Serial.read(); // read next part
          if (incomingChar >= 48 && incomingChar <= 57){ // if is numeric
            parameters[i] = parameters[i] * 10;
            parameters[i] = parameters[i] + (incomingChar -'0');
          }
          else if (incomingChar == ',') i++;  // check for comma separator and increment to next parameter
          else if (incomingChar == '\r'){
            endComm = true; // check for CR at end of command string and end loop
            if (i=0) checkRequest=true; // if no parameters, is a request for a status update
            else checkRequest=false;
          }
          //else {}//ERROR CONDITION
          if ((long)(millis() - commsEnd) >=0){ // jump out of waiting loop if insufficient data received in time - should comms retry ?
            commsFailed = true;
            errorState = 1;
            failWhy=1;
            break;
          } // end of if comms timeout
        }// end of if avaialble
      } // end of while(!endComm) loop
      break;
    }
  case 99: 
    //things to do if comms error from PC
    break;
  }
}

void nextState(){
  runState ++;
  switch (runState){
      case 0: mainStateMachine.transitionTo(startTest); break;
      case 1: mainStateMachine.transitionTo(waitForSafety); break; 
      case 2: mainStateMachine.transitionTo(waitForUp); break; 
      case 3: mainStateMachine.transitionTo(waitForUp); break; 
      case 4: mainStateMachine.transitionTo(mainNoOp); break;
  }
}

This compiles happily, which is a step forward.

It is clumsy though, and is a pain to edit to change the running order.

So, don't use the FSM library. It does nothing that you can't do yourself.

Thanks. The finitestatemachine library has helped out with providing a structure as i've figured out some projects over the last year or so, but maybe its time to move forward and leave it behind....

TIM