Finite State Machine - Best practice for Particle

Nice! A collection of shared web IDE working examples would be great for users to pick and chose from :wink:

2 Likes

That’s a good idea. I’ll put a finite state machine guide on the list of FAQs/Tutorials to write.

9 Likes

Great @rickkas7. It would be great to also include links to articles and other libraries like Arduino-FSM and possibly others.

3 Likes

@chipmic,

Yes, that's right. Each state has its own function, instead of a big one with all FSM in one. I prefer to deal with small functions, fitting one page. The example I gave does not illustrate this, but in reality an FSM state often needs another switch or a series of if's to handle events.

Need for repeating code in both approaches is the same. Although with the single switch approach one may use fall through cases, but this would make the switch even harder to follow and maintain. Use of functions is, perhaps, the most common and the oldest technique for avoiding code duplication.

Nowadays, compilers generate pretty good instructions. I never had a situation when I would blame switch for poor performance. Nevertheless, call to a function pointer in most cases is more efficient than a switch with numerous cases.
So, if there is an approach that gives better structured code and better performance - I use it

1 Like

Thank you for the explanations. I just started using Finite State Machines last year and realize I have a lot to learn in making better use of them.

@rickkas7,

Thank you for the explanation but I learned c++ by starting with Arduino. The syntax you are using such as “int DS24821WireReadByte::loop()” does not look like the code I am used to writing.

That said, I want to learn and continue to improve my programming. Is there a resource you could point me to (website, book, online course) that could help me better understand what you have written?

Thanks,

Chip

If this doesn's seem familiar, it might be because you never had to write C++ classes and split them in header (.h) and implementation (.cpp).
But it's no real magic there.
If you'd implement the class as one you could write

class someClass {
  public:
    someClass() { }
    int loop() {
      // do something
    }
}

But once you split implementation and definition you'd do it this way

// header file someClass.h
class someClass {
  public:
    someClass();
    int loop();
}

// implementation file someClass.cpp
#include "someClass.h"

someClass::someClass() { }

int someClass::loop() {
      // do something
}

So that syntax with the double colon (::) just specifies to which class definition this method implementation belongs.
You could have multiple classes defined and implemented in one set of files and each of these classes might feature a loop() method, so you need to be explicit about which one you are implementing at any given instance.

4 Likes

One more thing I thought I would share. I often want to put my device into “verbose mode” for monitoring and troubleshooting. I was struggling with an efficient way to report state transitions in this mode since, as discussed above, some states execute once and some execute repeatedly. I cam up with this approach and wanted to see if 1) someone can tell me if they have a better idea or 2) folks can use this in their projects.

code snippet:

// Definitions
enum State { INITIALIZATION_STATE, ERROR_STATE, IDLE_STATE, SLEEPING_STATE, NAPPING_STATE, LOW_BATTERY_STATE, REPORTING_STATE, RESP_WAIT_STATE };
State state = INITIALIZATION_STATE;
char oldState[14] = "Init";
char currentState[11];

// In main loop - for example in my napping state
  case NAPPING_STATE: {  // This state puts the device in low power mode quickly
      strcpy(currentState,"Napping");
      if (verboseMode && strcmp(currentState,oldState) != 0) publishStateTransition();
      strcpy(oldState, currentState);
      if (Particle.connected()) Cellular.off();                         // If connected, we need to disconned and power down the modem
      digitalWrite(blueLED,LOW);                                        // Turn off the LED
      watchdogISR();                                                    // Pet the watchdog
      int secondsToHour = (60*(60 - Time.minute()));                    // Time till the top of the hour
      System.sleep(intPin, RISING, secondsToHour);                      // Sensor will wake us with an interrupt or timeout at the hour
      state = IDLE_STATE;                                               // Back to the IDLE_STATE after a nap
    } break;

// Supporting functions
bool meterParticlePublish(void)
{
  if(millis() - lastPublish >= publishFrequency) return 1;
  else return 0;
}

void publishStateTransition(void)
{
  char stateTransitionString[40];
  snprintf(stateTransitionString, sizeof(stateTransitionString), "From %s to %s", oldState,currentState);
  waitUntil(meterParticlePublish);
  Particle.publish("State Transition",stateTransitionString);
  lastPublish = millis();
}

This approach meters the publishes - since it is verbose mode there are other messages to be metered too. But it gives me a clear indication of state transitions.

Anyway, I hope this is helpful and, as always, open to any suggestions.

Thanks, Chip

I think you’re making it more complicated than it needs to be by making oldState and currentState strings. If you type them as State, you can compare them with ==, and assign them with =.

2 Likes

@ric,

Agree and that was my first approach. However, I could not figure out how to get the state names into my Particle.publish(). If I tried including them as %s in my sprintf, I got random characters. Is there a way around this?

Thanks, Chip

I do it by making an array of the state names, and using state to index into that array.

char stateNames[8][21] = {"INITIALIZATION_STATE", "ERROR_STATE", "IDLE_STATE", "SLEEPING_STATE", "NAPPING_STATE", "LOW_BATTERY_STATE", "REPORTING_STATE", "RESP_WAIT_STATE" };
...
...
char stateTransitionString[40];
snprintf(stateTransitionString, sizeof(stateTransitionString), "From %s to %s", stateNames[oldState], stateNames[currentState]);
waitUntil(meterParticlePublish);
Particle.publish("State Transition", stateTransitionString);
3 Likes

@Ric,

Thank you! Your way is simpler. I appreciate the help. I will test this tonight.

Chip

@Ric,

Works like a charm and only requires a single line in each state. Now looks like this:

// Initializations
// State Maching Variables
enum State { INITIALIZATION_STATE, ERROR_STATE, IDLE_STATE, SLEEPING_STATE, NAPPING_STATE, LOW_BATTERY_STATE, REPORTING_STATE, RESP_WAIT_STATE };
char stateNames[8][14] = {"Initialize", "Error", "Idle", "Sleeping", "Napping", "Low Battery", "Reporting", "Response Wait" };
State state = INITIALIZATION_STATE;
State oldState = INITIALIZATION_STATE;

// In the loop
case NAPPING_STATE: {  // This state puts the device in low power mode quickly
      if (verboseMode && state != oldState) publishStateTransition();
      if (Particle.connected()) Cellular.off();                         // If connected, we need to disconned and power down the modem
      digitalWrite(blueLED,LOW);                                        // Turn off the LED
      watchdogISR();                                                    // Pet the watchdog
      int secondsToHour = (60*(60 - Time.minute()));                    // Time till the top of the hour
      System.sleep(intPin, RISING, secondsToHour);                      // Sensor will wake us with an interrupt or timeout at the hour
      state = IDLE_STATE;                                               // Back to the IDLE_STATE after a nap
    } break;

// Supporting Functions
bool meterParticlePublish(void)
{
  if(millis() - lastPublish >= publishFrequency) return 1;
  else return 0;
}

void publishStateTransition(void)
{
  char stateTransitionString[40];
  snprintf(stateTransitionString, sizeof(stateTransitionString), "From %s to %s", stateNames[oldState],stateNames[state]);
  oldState = state;
  waitUntil(meterParticlePublish);
  Particle.publish("State Transition",stateTransitionString);
  lastPublish = millis();
}

Much simpler - thank you!

Chip
1 Like

or save a little space:

const char* stateNames[] = {"INITIALIZATION_STATE", "ERROR_STATE", "IDLE_STATE", "SLEEPING_STATE", "NAPPING_STATE", "LOW_BATTERY_STATE", "REPORTING_STATE", "RESP_WAIT_STATE" };

or you can use the preprocessor's string-izing # operator

#define NAME_OF( varName ) #varName

enum State {
  INITIALIZATION_STATE, 
  ERROR_STATE, 
  IDLE_STATE, 
  SLEEPING_STATE,
  NAPPING_STATE,
  LOW_BATTERY_STATE,
  REPORTING_STATE,
  RESP_WAIT_STATE,
};

const char* stateNames[] = {NAME_OF(INITIALIZATION_STATE), NAME_OF(ERROR_STATE), NAME_OF(IDLE_STATE), NAME_OF(SLEEPING_STATE), NAME_OF(NAPPING_STATE), NAME_OF(LOW_BATTERY_STATE), NAME_OF(REPORTING_STATE), NAME_OF(RESP_WAIT_STATE) };
3 Likes

Thanks @Ric & @BulldogLowell,
I can use a couple of your suggestions to refine my code.

I am using almost the same approach as @chipmc, except I have some code to execute on entering into a new state. However, the difference I have is to have the verbose mode “report” contained in its own function, which I call passing the parameter newState.
My main loop code is something like this:

if State != newState {
   // transition to new state
   switch newState {
      case X:
         report(newState);
   }
}
if State == newState {
   // do steady-state processing
}

I found that by using the report(newState) function, I could localize all the string handling and eliminate a lot of repeating code. Also, if I want to report to Serial, Particle Cloud, etc. then I only have to adjust the code in a single function.

The huge benefit is that my main loop code cleaned up a lot and is much simpler to read without having all the publishing code in it.

As for metering the publishes, I am using @rickkas7’s library “PublishQueueAsyncRK”.

1 Like

With a function-per-state approach you may use GCC’s built-in variable __func__ (please follow this link for details). This eliminates need for mapping states to their names.

1 Like

I got very inspired by the posts here to simplify my state machine approach, and would like to share this.

SerialLogHandler logHandler;

enum States {START, RUN, WAIT};
const char* StateNames[] = {"START", "RUN", "WAIT"};
void (*StateFuncs[]) (void) = {STARTstate, RUNstate, WAITstate};

States mainState = START;
States lastState = mainState;

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

void loop() {
 
    StateFuncs[mainState](); // Runs the current statemachine state function
    if (mainState != lastState) Log.info("%s state",  StateNames[mainState]);
}

void STARTstate() {
    if (mainState != lastState) {/*state changed actions;*/ lastState = mainState;}
    if (Particle.connected()) mainState = RUN;
}

void RUNstate() {
    if (mainState != lastState) {/*state changed actions;*/ lastState = mainState;}
    Log.info("Logging this from %s state and changing to WAIT state",  StateNames[mainState]);
    mainState = WAIT;
}

void WAITstate() {
    if (mainState != lastState) {/*state changed actions;*/ lastState = mainState;}
    // Do I go or do I stay ...
}
4 Likes

There is a Particle application note, AN010 Finite State Machines that includes many of the tips above and shows some of the different options.

5 Likes

Broken link? Thanks!

@gularsen ,

Try this link:

https://docs.particle.io/datasheets/app-notes/an010-finite-state-machines/

Thanks,

Chip

2 Likes