Finite State Machine - Best practice for Particle

I was talking with a friend about a common project (AquaMaster) and we had a question that might be interesting to get the communities’ input on as it might be of general interest.

The question is how the program should flow through the main loop when using a finite state machine approach. We both agreed that in this approach, the only code should be the SWITCH / CASE of the state machine. The difference in approach came when we discussed states that would be executed over and over waiting for the right conditions to move to the next state. A good example is the IDLE state.

In one approach, the program flow traverses the SWITCH / CASE multiple times while in IDLE. The pseudo code would look like this:

void loop() {
  switch(state) {
    case IDLE: 
      // Do some things
      // Call some functions
      // Test to see if it is time to go to the next state
      break;
  }
}

The nice about this approach is that Particle.process() is handled each time the main loop is completed. The downside of this approach is that you are executing code outside the assigned state.

The alternative approach is to keep the program execution in the state and only complete the main loop when a change in state is required. This code would look like:

void loop() {
  switch(state) {
    case IDLE: 
      do { 
        // Do some things
        // Call some functions
        Particle.process();  // Required as we don't complete the main loop
      } while (test to see if you should move to the next state)
      break;
  }
}

The advantage here is that you are 100% certain where program execution is and there is no chance of changing state until the requirements to do so are met. The downside (?) is that you need the Particle.process or connection to Particle would be lost.

Does anyone have a point of view as to which is the better approach for a Particle application? Is there another approach to implementing Finite State Machines we should consider?

Thanks,

Chip

1 Like

The first. It’s always best to return from loop() is quickly as possible. While Particle.process() is provided as a way to make sure the cloud functions are processed in an environment where you need to block, it’s just better to avoid blocking.

Otherwise, in every state handler, you need to remember to put a Particle.process() in. If you forget, you could end up losing the cloud connection.

Also, in multithreaded mode, the second method throws off the loop scheduling. Unless you also make a call to yield the current thread, you can end up spinning the CPU unnecessarily.

4 Likes

Additionally with the blocking IDLE state you’d dismiss any change of state that’s not actually caused by the FMS itself (e.g. Particle.subscribe(), Particle.function() callbacks, Software Timers, interrupts, … could cause that to happen) - or you’d have to revisit the (test to see if you should move to the next state) part each time you’d decide to add a reason that’d required the loop to break out.

3 Likes

At the risk of being repetitive, you really want to manage each state as if it were a single program. All of the individual states ought to be optimized for Particle (i.e. making sure that the Particle functions are not blocked and serviced regularly).

Im not sure if this helps you but when using state machines, I try to compact loop() to only manage the program branches (states)… something like this:

enum MyStates {
  IDLE_STATE,
  STATE_ONE,
  STATE_TWO,
  STATE_THREE,
};

MyStates mainState = IDLE_STATE;

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  static MyStates lastState = mainState;

  // react to state changes...
  //
  if (mainState != lastState) {
    switch (mainState) {
      case IDLE_STATE:
        // perform functions to return to IDLE_STATE
        break;
      case STATE_ONE:
        // perform functions to return to STATE_ONE
        break;
      case STATE_TWO:
        // perform functions to return to STATE_TWO
        break;
      case STATE_THREE:
        // perform functions to return to STATE_THREE
        break;
      default:
        //
        break;
    }
    lastState = mainState;
  }

  // manage states...
  //
  switch (mainState) {
    case IDLE_STATE:
      idleState();
      break;
    case STATE_ONE:
      stateOne();
      break;
    case STATE_TWO:
      stateTwo();
      break;
    case STATE_THREE:
      stateThree();
      break;
  }
}

void idleState() {
  // some non-blocking set of function calls in these functions... for example
}

void stateOne() {
  
}

void stateTwo() {
  
}

void stateThree() {
  
}

I’ve almost always two switch statements when managing states (one for managing the change, one for managing regular program flow. It also makes it easier (for me at least) if you are trying to manage several state machines in the main program flow (i.e. inside loop()) where you may have something like a sensorState and displayState.

3 Likes

How would you avoid blocking (e.g. Particle.subscribe(), Particle.function() callbacks, Software Timers, interrupts, … ) if you have a routine that needs to keep running (I have loop monitoring an analog input looking for a transient so most of the time the code is in that state, reading the analog input, calculating a moving average and comparing the average to a trigger threshold). I am new to the Particle environment but my expectation was that a pin interrupt or a timer interrupt would preempt running code and trigger the ISR unless you explicitly disable interrupts. Is this not the case on the Photon?

I was hoping that having “Particle.process()” would ensure all of the Particle background processes would be serviced. Are there additional calls (in the tight loop looking for a transient condition) that will ensure background processes are serviced?

Thanks!

For measuring an analog value and doing simple calculations, I would use a hardware timer (SparkIntervalTimer). This uses a hardware interrupt and won’t be affecting by blocking system calls. You can safely use analogRead() from an ISR, though you should reduce the sample period.

You can go even farther and use ADC hardware DMA which allows to be captured very precisely at extremely high rates, without interrupt latency, as well.

1 Like

The ADC hardware also supports analog watchdog mode. it can generate an interrupt when an ADC input goes out of range, and can find really small transients, as it can sample up to 2 million samples per second, I think.

2 Likes

Thanks for the SparkIntervalTimer tip. Part of the issue is I am calculating a moving average to reduce noise in the signal (which I could improve if I add an op-amp buffer/low-pass filter but I am trying to do as much as possible in just software) so I can’t use the ADC watchdog mode. I want to add a datapoint to an array, process the array to get an average and then compare the average to the trigger. More processing than I want to do inside an ISR. So I am left running in a closed loop, processing data and watching for a trigger event (which triggers a state change so the system will respond to the transient and report the data). I think I should be able to do it without interrupts but I do not have a feel for what is going on on the other side of the Particle background processes running on the STmicro.

I had been running without Particle.process() and it would take several tries to get the code to flash! It worked WAY better after I added Particle.process(), thanks to a heads up from Chip! I would like to know the best way to play nice with the Particle background tasks while inside the monitoring loop.

The DMA angle does sounds intriguing though. That opens up some frequency analysis options that I had not considered.

@sdtrent, are you this friend @chipmc is talking about?
If not, would you mind if we would seperate your discussion from the OP's?

Otherwise this extra discussion would side-track the original topic which is considered as thread-jacking, which is frowned upon.

1 Like

@ScruffR,

Sorry, I did not have Shane’s Particle ID when I created the thread but he is the friend I was talking about. I reached out to him via email and asked to join the conversation.

Thanks, Chip

3 Likes

One DMA trick I really like is the DMA double buffer technique. The hardware can handle DMA into two buffers, filling one while you process the other. This allows you to do your calculations in the loop thread so you don’t have ISR limitations, and they’re not as timing-sensitive. But the data capture happens simultaneously with very low system overhead.

This example is for audio, but it works the same for any kind of analog sample. The DMA double-buffer is audio3.

2 Likes

Yes, that is me. Sorry, please clip my question to “Can you avoid blocking background particle tasks when locked into a particular case/state in a state machine?” That was the question that led to the start of the thread. The answer might be "You can’t. You MUST exit the switch statement and return to the top of the loop before returning processing to your state case to ensure the particle background tasks function properly.

The question came up because I was taught that execution in a state machine should not leave a state until there is a state transition to send execution elsewhere (working on single threaded, 8-bit microcontrollers). The question is do the Particle background processes keep this approach from working on a Photon without an explicit exit from the switch statement and return to the top of the loop before returning execution to the current state/case each pass through the loop. That is do you have to reenter the case/state every pass through the loop or is there a particle supported way you can stay in state1 while (state == state1)? I thought that calling Particle.process() would allow staying in the state/case but it sounds like there may be additional functions calls needed to ensure reliable function of background particle processes. This is all new territory to me so I am trying to come up to speed before I paint myself into a corner. Again. Thanks!

To recap what’s been said:
You can go both ways, the Particle background tasks don’t prevent you from hanging around in one particular case branch of your “FSM” (as long you make sure the background tasks get their due processing time).
But then you are not actually employing the “pure” FSM approach as it’s intended to be used (hence the double quotes :wink: ).
With the enter-check-execute-leave paradigm you keep the FSM most flexible and won’t need to care about the background tasks.

6 Likes

I prefer the #1 version, and in the IDLE task, you look for conditions that will shift state, and fall through if there’s nothing to do.

This makes it very simple to make various Submodule.process(); calls from within loop() to maintain several state machines at once.

2 Likes

I will vote up #1 version as well. I use a state to manage the transition between states and this makes the code much easier to debug and also better control of code block repetition, this means a large switch() case: statement in the loop() and LOTS of states (60 in my main application), for example;

I have a state called “standby” - it doesn’t matter what this does but it is monitoring for user input.
If a user input is for a function “charging” say then the response to the user input is state = “go_to_charging”, on the next loop() iteration it will perform the go_to_charging function once which prepares for charging and as the last act sets state = “charging”. The loop() then faithfully performs charging function until there is an event such as charging ended, then it might set state = “go_to_standby”.

To handle cloud function responses where there could be blocking activities (writes to SD card and screen, etc.) since these can happen whilst the main thread is doing “charging” say, I have another state called previous_state where in the cloud function handler I save the current state as previous_state then set the state as “web_command_handler”. Then the function on this switch case will perform response to web command on the application thread and then as the last thing set the state = previous_state.

Any waits such as delay() I have avoided by using sub-states where required or having a custom delay() function that can be broken out of when high priority events occur (user input!).

3 Likes

My vote is for #1 as well.

I want to bring up the comment about remaining in a state. I think the earlier comment was actually intending to indicate staying within a block of code associated with a state. That would have been taken as truth in earlier days, but technology and thinking has pushed beyond that now. It is not necessary to stay within a block of code in order to stay within a state.

For example, I have a project being developed this week that has 3 state machines running simultaneously. By not being held within a block of code, the Photon is able to run them all no problem. One state machine is for data acquisition and is triggered by a timer and ends in an idle(done) state; it is somewhat linear but allows to break out frequently between state changes (a large number of states) and be responsive to the other state machines. The two other state machines are control algorithms, controlling two different things but using the same sensor and cloud data.

Similar to, but different from @armor, I use variables to track current_state and next_state and use a switch() case for running code for entry into an new state and another switch() case for running code in steady state. The steady-state code runs once per loop iteration, but has the advantage to no blocking and no delays. It is this steady-state code that allows staying in a state, but not staying stuck in a code block.

With this approach, I think my Photon has enough capacity to also do a second completely separate project that is at the same geographical location. However, this is only possible by going with the option #1. The ultimate deciding factor will be whether or not I can keep the code clean while combining multiple projects…

6 Likes

@cyclin_al,

Excellent point. Finite State Machine “multitasking” or at least being able to carry out distinct missions with a single Photon.

Thank you,

Chip

1 Like

My preferred approach for implementing state machines is using function pointer as the state.
With such implementation there is no need for a switch an each state is handled by its dedicated function, as were suggested earlier in this thread. And the loop becomes as simple as
while( (state = state(event)) );

The tricky part with such approach - declaring a function returning self. This can be implemented with a wrapper structure, for example as in this answer on StackOverflow or, with C++ as on this live example

enum class event_t {
  myEvent  
};

template<typename In>
struct state_t {
    using type = state_t<In>(*)(In);
    type value;
    constexpr state_t<In> operator()(In in) const { 
        return value(in);
    }
    constexpr operator bool() const {
        return value != nullptr;
    }
};

using mystate = state_t<event_t>;

mystate state_init(event_t);
mystate state_next(event_t);
mystate state_last(event_t);

mystate state_init(event_t) {
    std::cout << __func__ << std::endl;
    return { state_next };
}

mystate state_next(event_t) {
    std::cout << __func__ << std::endl;
    return { state_last };
}

mystate state_last(event_t) {
    std::cout << __func__ << std::endl;
    return { nullptr };
}


int main() {
  mystate state = { state_init };
  while( (state = state(event_t::myEvent)) );
}
1 Like

@hutorny,

Very cool approach but, I have a few questions:

  1. Is the main advantage that each state has its own function rather than a block of code in the main loop?
  2. If each state is its own function, then don’t you end up with repeating code? What if there was a code block that was in common between two states (such as the steps to connect to Particle) - would these functions, in turn, call functions?
  3. Is there a downside in using SWITCH / CASE? I know some functions are less efficient on a micro, is this one of them?

Thank you for sharing,

Chip

I find that a case statement gets unwieldy and hard to follow. I prefer to use class member functions. The state variable is declared as a class member like this in class DS24821WireReadByte:

	std::function<int(DS24821WireReadByte&)> stateHandler = &DS24821WireReadByte::startState;

The state handler is called with code like this:

int DS24821WireReadByte::loop() {
	int result = stateHandler(*this);
	if (result != RESULT_WORKING && completion) {
		DEBUG_HIGH(("DS24821WireReadByte completed status=%d value=%02x", result, value));
		completion(*this, result, value);
	}
	return result;
}

And implementation of startState has this definition:

int DS24821WireReadByte::startState() {
...
}

And you transition between states like this:

	stateHandler = &DS24821WireReadByte::waitState;

The advantage here is that the state handler is a class member, so it has access to class member variables.

Anything that’s shared between multiple states can be put in a shared class member function called by all of the state handlers that need it.

3 Likes