Using an event handler with a class-based Finite State Machine

I am implementing a class-based FSM on a Boron that wakes up at set intervals to sample sensor input and react accordingly. I am using the example in application note AN010 (AN010 Finite State Machines | Datasheets | Particle)

The FSM works well. My problem arises when I try to move some event handler code from a case-based FSM to my current system.

Specifically, in AN029 (AN029 Wake publish sleep example | Datasheets | Particle) there is a nice example of a firmwareUpdate state, in which the system checks whether new firmware is available; if so, it allows time to install.

The abbreviated code from AN029 is as follows:

unsigned long stateTime;
bool firmwareUpdateInProgress = false;

// forward declaration
void firmwareUpdateHandler(system_event_t event, int param); // forward declaration

void setup() {
 
    System.on(firmware_update, firmwareUpdateHandler);

    // It's only necessary to turn cellular on and connect to the cloud. Stepping up
    // one layer at a time with Cellular.connect() and wait for Cellular.ready() can
    // be done but there's little advantage to doing so.
    Cellular.on();
    Particle.connect();
    stateTime = millis();
}

void loop() {
    switch(state) {
// different state functions here...

case STATE_FIRMWARE_UPDATE:
            if (!firmwareUpdateInProgress) {
                Log.info("firmware update completed");
                state = STATE_SLEEP;
            }
            else
            if (millis() - stateTime >= firmwareUpdateMaxTime.count()) {
                Log.info("firmware update timed out");
                state = STATE_SLEEP;
            }
            break;
    }
}

//...and now the handler
void firmwareUpdateHandler(system_event_t event, int param) {
    switch(param) {
        case firmware_update_begin:
            firmwareUpdateInProgress = true;
            break;

        case firmware_update_complete:
        case firmware_update_failed:
            firmwareUpdateInProgress = false;
            break;
    }
}

Now I try to translate this to a class-based approach:

A. Header file (edited for brevity; full file upon request)

#ifndef __MAINSTATEMACHINE_H
#define __MAINSTATEMACHINE_H

#include "Particle.h"

class MainStateMachine {
public:
    MainStateMachine();
    virtual ~MainStateMachine();

    void setup();
    void loop();

    MainStateMachine &withConnectMaxTime(std::chrono::milliseconds connectMaxTime) { this->connectMaxTime = connectMaxTime; return *this;};
    
    MainStateMachine &withFirmwareUpdateMaxTime(std::chrono::milliseconds firmwareUpdateMaxTime) { this->firmwareUpdateMaxTime = firmwareUpdateMaxTime; return *this;};

protected:

    // Maximum amount of time to wait for a user firmware download in milliseconds
    // before giving up and just going back to sleep
    std::chrono::milliseconds firmwareUpdateMaxTime = 5min;
    
    volatile bool firmwareUpdateInProgress = false;
    
    // forward declarations
    void firmwareUpdateHandler(system_event_t event, int param); 
    void stateSleep();
    
    unsigned long stateTime;
    
	std::function<void(MainStateMachine&)> stateHandler = 0;

};

#endif /* __MAINSTATEMACHINE_H */
  1. The main cpp file
#include "MainStateMachine.h"

static Logger logStatus("app.msm");

MainStateMachine::MainStateMachine() {
}

MainStateMachine::~MainStateMachine() {
}

void MainStateMachine::setup() {
    logStatus.info("MainStateMachine::setup()");

    System.on(firmware_update, MainStateMachine::firmwareUpdateHandler);

    Cellular.on();
    Particle.connect();
    stateTime = millis();
    
    stateHandler = &MainStateMachine::stateWaitConnected;
}

void MainStateMachine::loop() {
    if (stateHandler) {
        stateHandler(*this);
    }
}

void MainStateMachine::stateFirmwareUpdate() {
    if (!firmwareUpdateInProgress) {
        Log.info("firmware update completed");
        stateHandler = &MainStateMachine::stateSleep;
        }
        else
        if (millis() - stateTime >= firmwareUpdateMaxTime.count()) {
            Log.info("firmware update timed out");
            stateHandler = &MainStateMachine::stateSleep;
            }
}

void MainStateMachine::stateSleep() {

    // This is the equivalent to:
    // System.sleep(WKP, RISING, SLEEP_NETWORK_STANDBY);

    SystemSleepConfiguration config;
    // different approaches to sleep, depending on conditions
    switch (conditions) {
        case NIGHTTIME:
            config.mode(SystemSleepMode::ULTRA_LOW_POWER)
            .gpio(photocellPin, RISING)
            .duration(nightSleepTime*1000);
            logStatus.info("going to nighttime sleep for %ld seconds at %d:%d", (long) nightSleepTime, Time.hour(), Time.minute());
            break;
        case DAYTIME:
            config.mode(SystemSleepMode::STOP)
            .gpio(digitalRainPin, FALLING)
            .duration(daySleepTime*1000);
            logStatus.info("going to daytime sleep for %ld seconds at %d:%d", (long) daySleepTime, Time.hour(), Time.minute());
            break;
        case RAINING:
            config.mode(SystemSleepMode::STOP)
            .duration(rainSleepTime*1000);
            logStatus.info("going to raining sleep for %ld seconds at %d:%d", (long) rainSleepTime, Time.hour(), Time.minute());
            break;
        default:
            logStatus.info("Error: failed to set a sleep time");
    }
    

    SystemSleepResult result = System.sleep(config);

    logStatus.info("woke from sleep");

    stateHandler = &MainStateMachine::stateWaitConnected;
    stateTime = millis();
}

void MainStateMachine::firmwareUpdateHandler(system_event_t event, int param) {
    
    switch(param) {
        case firmware_update_begin:
            firmwareUpdateInProgress = true;
            break;

        case firmware_update_complete:
        case firmware_update_failed:
            firmwareUpdateInProgress = false;
            break;
     }
}

When I try to compile, I receive the error message:

MainStateMachine.cpp:29:50: error: invalid use of non-static member function ‘void MainStateMachine::firmwareUpdateHandler(system_event_t, int)’
29 | System.on(firmware_update, MainStateMachine::firmwareUpdateHandler);

Clearly I do not understand how to implement an event handler in C++

I would appreciate any help

David

Solved:

I moved the event handler out of the class, and made it static void.

Then the code compiled properly