Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: FSM globals and state persistance #12

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/FSM.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class FSM {
std::queue<FSMEvent> eventqueue; //!< Queue to store FSMEvents. ATTENTION: THIS IS NOT THREAD SAFE ON ITS OWN!
std::shared_ptr<FSMGlobals> globals; //!< Global FSM state data

const char* NVS_NAMESPACE = "effsm"; //!< Namespace under which the FSM stores persisted data in non-volatile storage (NVS)

/**
* @brief Retrieves the next FSMEvent from the queue in a non-blocking fashion.
*
Expand All @@ -70,6 +72,18 @@ class FSM {
*/
~FSM();

/**
* @brief Resumes the FSM to the last state according to NVS data
*/
void resume();

/**
* @brief Performs a transition to the given next state
*
* @param next The state to transition to.
*/
void transition(std::unique_ptr<FSMState> next);

/**
* @brief Retrieves the tick rate of this FSM
*
Expand Down Expand Up @@ -102,6 +116,17 @@ class FSM {
*/
void handle(unsigned int num_events);

/**
* @brief Presists the current globals state of this FSM to the NVS partition
*/
void persistGlobals();

/**
* @brief Loads the globals state from the NVS partition and recovers it into current
* globals FSM state
*/
void restoreGlobals();

};

#endif /* FSM_H_ */
8 changes: 8 additions & 0 deletions include/FSMGlobals.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@

#include <Arduino.h>

/**
* @brief Internal data structure used by the FSM to allows carrying data over
* between states and allows it to be persisted to the non-volatile storage (NVS).
*
* @warning If you want your data to be persisted to NVS, you need to add it to
* FSM::persistGlobals() and FSM::restoreGlobals() respectively.
*/
typedef struct {
const char* lastRememberedState = "DisplayPrideFlag"; //!< Name of the state that should be resumed upon reboot
uint8_t menuMainPointerIdx = 0; //!< MenuMain: Index of the menu cursor
uint8_t prideFlagModeIdx = 0; //!< DisplayPrideFlag: Mode selector
uint8_t animationModeIdx = 0; //!< DisplayAnimation: Mode selector
Expand Down
17 changes: 17 additions & 0 deletions include/FSMState.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,35 @@ class FSMState {
protected:

std::shared_ptr<FSMGlobals> globals; //!< Pointer to global FSM state variables
bool is_globals_dirty; //!< Marks globals as dirty, causing it to be persisted to NVS

public:
/**
* @brief Sets the reference on the global FSM data struct
*/
void attachGlobals(std::shared_ptr<FSMGlobals> globals);

/**
* @brief Determines, if the globals struct was modified and requires
* to be persisted to NVS by the FSM controller
*/
bool isGlobalsDirty();

/**
* @brief Provides access to the name of this state
*
* @return Name of this state
*/
virtual const char* getName();

/**
* @brief If true, the FSM will persist this state and resume to it after
* reboot, if no other transition to another rememberable state happened since
*
* @return True, if the FSM should remember this state and resume to it upon reboot
*/
virtual bool shouldBeRemembered();

/**
* @brief Provides access to the tick rate of this state
*
Expand Down Expand Up @@ -128,6 +143,7 @@ struct DisplayPrideFlag : public FSMState {
unsigned int switchdelay_ms = 5000;

virtual const char* getName() override;
virtual bool shouldBeRemembered() override;
virtual const unsigned int getTickRateMs() override;

virtual void entry() override;
Expand All @@ -143,6 +159,7 @@ struct DisplayAnimation : public FSMState {
uint32_t tick = 0;

virtual const char* getName() override;
virtual bool shouldBeRemembered() override;
virtual const unsigned int getTickRateMs() override;

virtual void entry() override;
Expand Down
79 changes: 72 additions & 7 deletions src/FSM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include <Arduino.h>
#include <Preferences.h>

#include <EFLogging.h>

Expand All @@ -38,13 +39,54 @@ FSM::FSM(unsigned int tickrate_ms)
this->globals = std::make_shared<FSMGlobals>();
this->state = std::make_unique<DisplayPrideFlag>();
this->state->attachGlobals(this->globals);
this->state->entry();
}

FSM::~FSM() {
this->state->exit();
}

void FSM::resume() {
this->restoreGlobals();

if (strcmp(this->globals->lastRememberedState, "DisplayPrideFlag") == 0) {
this->transition(std::make_unique<DisplayPrideFlag>());
return;
}

if (strcmp(this->globals->lastRememberedState, "DisplayAnimation") == 0) {
this->transition(std::make_unique<DisplayAnimation>());
return;
}

LOGF_WARNING("(FSM) Failed to resume to unknown state: %s\r\n", this->globals->lastRememberedState);
this->transition(std::make_unique<DisplayPrideFlag>());
}

void FSM::transition(std::unique_ptr<FSMState> next) {
if (next == nullptr) {
LOG_WARNING("(FSM) Failed to transition to null state. Aborting.");
return;
}

// State exit
LOGF_INFO("(FSM) Transition %s -> %s\r\n", this->state->getName(), next->getName());
this->state->exit();

// Persist globals if state dirtied it or next state wants to be persisted
if (next->shouldBeRemembered()) {
this->globals->lastRememberedState = next->getName();
}
if (this->state->isGlobalsDirty() || next->shouldBeRemembered()) {
this->persistGlobals();
}

// Transition to next state
this->state = std::move(next);
this->state->attachGlobals(this->globals);
this->state_last_run = 0;
this->state->entry();
}

unsigned int FSM::getTickRateMs() {
return this->tickrate_ms;
}
Expand Down Expand Up @@ -147,12 +189,35 @@ void FSM::handle(unsigned int num_events) {

// Handle state transition
if (next != nullptr) {
LOGF_INFO("(FSM) Transition %s -> %s\r\n", this->state->getName(), next->getName());
this->state->exit();
this->state = std::move(next);
this->state->attachGlobals(this->globals);
this->state_last_run = 0;
this->state->entry();
this->transition(move(next));
}
}
}

void FSM::persistGlobals() {
Preferences pref;
pref.begin(this->NVS_NAMESPACE, false);
LOGF_INFO("(FSM) Persisting FSM state data to NVS area: %s\r\n", this->NVS_NAMESPACE);
pref.clear();
LOG_DEBUG("(FSM) -> Clear storage area");
pref.putString("resumeState", this->globals->lastRememberedState);
LOGF_DEBUG("(FSM) -> resumeState = %s\r\n", this->globals->lastRememberedState);
pref.putUInt("prideFlagMode", this->globals->prideFlagModeIdx);
LOGF_DEBUG("(FSM) -> prideFlagMode = %d\r\n", this->globals->prideFlagModeIdx);
pref.putUInt("animationMode", this->globals->animationModeIdx);
LOGF_DEBUG("(FSM) -> animationMode = %d\r\n", this->globals->animationModeIdx);
pref.end();
}

void FSM::restoreGlobals() {
Preferences pref;
pref.begin(this->NVS_NAMESPACE, true);
LOGF_INFO("(FSM) Restored FSM state data from NVS area: %s\r\n", this->NVS_NAMESPACE);
this->globals->lastRememberedState = pref.getString("resumeState", "DisplayPrideFlag").c_str();
LOGF_DEBUG("(FSM) -> resumeState = %s\r\n", this->globals->lastRememberedState);
this->globals->prideFlagModeIdx = pref.getUInt("prideFlagMode", 0);
LOGF_DEBUG("(FSM) -> prideFlagMode = %d\r\n", this->globals->prideFlagModeIdx);
this->globals->animationModeIdx = pref.getUInt("animationMode", 0);
LOGF_DEBUG("(FSM) -> animationMode = %d\r\n", this->globals->animationModeIdx);
pref.end();
}
11 changes: 3 additions & 8 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ void setup() {
EFTouch.attachInterruptOnRelease(EFTouchZone::Nose, isr_noseRelease);
EFTouch.attachInterruptOnShortpress(EFTouchZone::Nose, isr_noseShortpress);
EFTouch.attachInterruptOnLongpress(EFTouchZone::Nose, isr_noseLongpress);

// Get FSM going
fsm.resume();
}

/**
Expand Down Expand Up @@ -215,14 +218,6 @@ void loop() {
task_fsm_handle = millis() + fsm.getTickRateMs();
}

// Task: Blink LED
// if (task_blinkled < millis()) {
// EFLed.setDragonEarTop(blinkled_state ? CRGB::Green : CRGB::Black);
// blinkled_state = !blinkled_state;

// task_blinkled = millis() + 1000;
// }

// Task: Battery checks
if (task_battery < millis()) {
pwrstate = EFBoard.updatePowerState();
Expand Down
5 changes: 5 additions & 0 deletions src/states/DisplayAnimation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const char* DisplayAnimation::getName() {
return "DisplayAnimation";
}

bool DisplayAnimation::shouldBeRemembered() {
return true;
}

const unsigned int DisplayAnimation::getTickRateMs() {
return animations[this->globals->animationModeIdx % DISPLAY_ANIMATION_NUM_TOTAL].tickrate;
}
Expand All @@ -68,6 +72,7 @@ void DisplayAnimation::run() {

std::unique_ptr<FSMState> DisplayAnimation::touchEventFingerprintRelease() {
this->globals->animationModeIdx++;
this->is_globals_dirty = true;
this->tick = 0;
EFLed.clear();

Expand Down
4 changes: 4 additions & 0 deletions src/states/DisplayPrideFlag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const char* DisplayPrideFlag::getName() {
return "DisplayPrideFlag";
}

bool DisplayPrideFlag::shouldBeRemembered() {
return true;
}

const unsigned int DisplayPrideFlag::getTickRateMs() {
return 20;
}
Expand Down
8 changes: 8 additions & 0 deletions src/states/FSMState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ void FSMState::attachGlobals(std::shared_ptr<FSMGlobals> globals) {
this->globals = std::move(globals);
}

bool FSMState::isGlobalsDirty() {
return this->is_globals_dirty;
}

bool FSMState::shouldBeRemembered() {
return false;
}

const char* FSMState::getName() {
return "FSMState";
}
Expand Down
3 changes: 3 additions & 0 deletions src/states/menu/MenuPrideFlagSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ void MenuPrideFlagSelector::exit() {

std::unique_ptr<FSMState> MenuPrideFlagSelector::touchEventFingerprintRelease() {
this->globals->prideFlagModeIdx = (this->globals->prideFlagModeIdx + 1) % 13;
this->is_globals_dirty = true;

this->prideFlagDisplayRunner->entry();
this->prideFlagDisplayRunner->switchdelay_ms = 1000;

return nullptr;
}

Expand Down
Loading