Finite state machine with timer resets

117 views Asked by At

I'll have a real world problem that I try to solve which involves the implementation of a FSM. But I fail to get it right.

My problem: I want to integrate my garage door into my smart home. As it is an apartment block, I cannot tamper with the electronics and my only chance is to send the opening signal via RF. I already got a simple PoC working. The only problem is that I can only ever open the garage door via RF. I cannot get any state. It will auto close after some time. Now I want to add the opening/open/closing times, so that the state of my FSM roughly matches the actual state of the garage door.

The door has four states:

  1. Closed
  2. Opening
  3. Open
  4. Closing

Opening takes 14.5s, it stays open for 40s and closing takes 14s. Now implementing this isn't the hard part (some pseudo code):

loop {
  if current_state.val == target_state.val then return;
  if target_state.val == GARAGE_DOOR_STATE_OPEN AND target_state.updated_ago > GARAGE_DOOR_OPENING_DUR then current_state.val = target_state.val;
  // Somewhat similar for the other states ...
}

But let's say someone decides to close the door via the smart home integration (which doesn't do anything as there is no RF signal for that), how do I still match the state in my FSM and the actual door? So let's say right after opening, close is initiated but the real door will actually stay open for another 40s, before being auto-closed. This means the state machine must actually stay in the closing state, for 40s + 14s (staying open + closing). The same is true when initiating a state change in the middle of an opening state (e.g. closing while the door is opening): This means I need to add the remaining time it takes the door to open to the 40s + 14s of the two follow-up states.

Any idea how to express/implement that in a nice way?

1

There are 1 answers

0
George Profenza On

You could use a basic enum and a switch statement and a variable to keep track of the current state (calling one function at a time based on the state, each updating / transitioning to the next stage based on your required logic).

Here's a really rough example:

// example: door closes automatically after 3 minutes (in millis)
const long AUTO_CLOSE_DURATION = 3 * 60 * 1000;
// assuming duration to open is the same as closing (e.g. 30s)
const long OPEN_CLOSE_DURATION = 30 * 1000;

enum State { CLOSED, OPENING, OPEN, CLOSING };
State currentState = CLOSED;

long lastMillisUpdate;

void setup(){
    lastMillisUpdate = millis();
}

void loop(){
    switch(currentState){
        case CLOSED:    handleClosedState();    break;
        case OPENING:   handleOpeningState();   break;
        case OPEN:      handleOpenState();      break;
        case CLOSING:   handleClosingState();   break;
    }
}

void handleClosedState(){
    //TODO: handle awaiting closing RF input -> switch state
}

void handleOpeningState(){
    // TODO: handle updating opening timer
    long currentMillis = millis();
    if(currentMillis - lastMillisUpdate >= OPEN_CLOSE_DURATION){
        lastMillisUpdate = currentMillis;
        // update state
        state = OPEN;
    }
}

void handleOpenState(){
    //TODO: handle awaiting closing RF input -> switch state
}

void handleClosingState(){
    // TODO: handle updating closing timer
    long currentMillis = millis();
    if(currentMillis - lastMillisUpdate >= OPEN_CLOSE_DURATION){
        lastMillisUpdate = currentMillis;
        // update state
        state = CLOSED;
    }
}

Note the above isn't complete, nor tested. It's more an illustration of handling one state at a time, using switch and millis() to trigger a different state after a set duration.

It might be worth trying to illustrate your state machine described above with a diagram for easier understanding / communication.

It may take some iteration/experimentation to get the logic and timings right. You may want to use Serial to debug and ensure everything works as intended before plugging hardware to control the physical garage door.