Progress towards a better application FSM, testing

This commit is contained in:
2021-09-06 23:18:43 -04:00
parent 983cd1423e
commit 7e59cd49e8
18 changed files with 462 additions and 17 deletions

View File

@@ -13,7 +13,8 @@ main_mcu:
script:
- cd firmware/main_mcu/
- cp src/config-example.h src/config.h
- pio run
- pio run -e button-debug -e button-release -e button-debug -e button-timing
- pio test -e native
lowpower_mcu:
stage: build
script:

View File

@@ -0,0 +1,55 @@
#include "DebounceFsm.h"
DebounceFsm::DebounceFsm(Clock &clock,
GpioInput &input,
unsigned int debounce_millis,
const bool active_value,
const std::function<void()> &on_activate,
const std::function<void()> &on_deactivate) :
Fsm(inactive),
timer(Timer(clock)),
input(input),
debounce_millis(debounce_millis),
inactive(*this, to_active, !active_value),
active(*this, to_inactive, active_value),
to_inactive(*this, on_deactivate, !active_value, active, inactive),
to_active(*this, on_activate, active_value, inactive, active)
{}
DebounceFsm::SteadyState::SteadyState(DebounceFsm &fsm, State &next_state, bool steadyPinValue) :
fsm(fsm),
next_state(next_state),
steadyPinValue(steadyPinValue)
{}
void DebounceFsm::SteadyState::step() {
if (fsm.input.read() != steadyPinValue) {
fsm.transition(next_state);
}
}
DebounceFsm::Debouncing::Debouncing(DebounceFsm &fsm,
std::function<void()> callback,
bool to_pin_value,
State &previous_state,
State &next_state) :
fsm(fsm),
callback(callback),
to_pin_value(to_pin_value),
previous_state(previous_state),
next_state(next_state)
{}
void DebounceFsm::Debouncing::onEntry() {
fsm.timer.start();
}
void DebounceFsm::Debouncing::step() {
if (fsm.input.read() != to_pin_value) {
fsm.transition(previous_state);
} else if (fsm.timer.getRuntime() >= fsm.debounce_millis) {
callback();
fsm.transition(next_state);
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <functional>
#include "Fsm.h"
#include "Timer.h"
#include "Gpio.h"
class DebounceFsm : public Fsm {
public:
DebounceFsm(Clock &clock,
GpioInput &input,
unsigned int debouce_millis,
const bool active_value,
const std::function<void()> &on_activate,
const std::function<void()> &on_deactivate);
private:
class SteadyState : public State {
public:
SteadyState(DebounceFsm &fsm, State &next_state, bool steadyPinValue);
virtual void step() override;
private:
DebounceFsm &fsm;
State &next_state;
const bool steadyPinValue;
};
class Debouncing : public State {
public:
Debouncing(DebounceFsm &fsm,
std::function<void()> callback,
bool to_pin_value,
State &previous_state,
State &next_state);
virtual void onEntry() override;
virtual void step() override;
private:
DebounceFsm &fsm;
std::function<void()> callback;
const int to_pin_value;
State &previous_state;
State &next_state;
};
Timer timer;
GpioInput &input;
const unsigned int debounce_millis;
SteadyState inactive;
SteadyState active;
Debouncing to_inactive;
Debouncing to_active;
};

View File

@@ -0,0 +1,23 @@
#include "ArduinoGpio.h"
#ifdef ARDUINO
#include "Arduino.h"
ArduinoGpioInput::ArduinoGpioInput(uint8_t pin) :
pin(pin)
{}
bool ArduinoGpioInput::read() {
return digitalRead(pin);
}
ArduinoGpioOutput::ArduinoGpioOutput(uint8_t pin) :
pin(pin)
{}
void ArduinoGpioOutput::write(bool value) {
digitalWrite(pin, value);
}
#endif

View File

@@ -0,0 +1,13 @@
#include "ArduinoClock.h"
#ifdef ARDUINO
#include "Arduino.h"
ArduinoClock::ArduinoClock() {}
uint32_t ArduinoClock::getMillis() {
return getMillis();
}
#endif

View File

@@ -0,0 +1,12 @@
#pragma once
#include "Clock.h"
#ifdef ARDUINO
class ArduinoClock : public Clock {
public:
ArduinoClock();
uint32_t getMillis() override;
};
#endif

View File

@@ -0,0 +1,23 @@
#include "ArduinoGpio.h"
#ifdef ARDUINO
#include "Arduino.h"
ArduinoGpioInput::ArduinoGpioInput(uint8_t pin) :
pin(pin)
{}
bool ArduinoGpioInput::read() {
return digitalRead(pin);
}
ArduinoGpioOutput::ArduinoGpioOutput(uint8_t pin) :
pin(pin)
{}
void ArduinoGpioOutput::write(bool value) {
digitalWrite(pin, value);
}
#endif

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Gpio.h"
#include <stdint.h>
#ifdef ARDUINO
class ArduinoGpioInput : public GpioInput {
public:
ArduinoGpioInput(uint8_t pin_number);
virtual bool read() override;
private:
const uint8_t pin;
};
class ArduinoGpioOutput : public GpioOutput {
public:
ArduinoGpioOutput(uint8_t pin_number);
virtual void write(bool value) override;
private:
const uint8_t pin;
};
#endif

View File

@@ -0,0 +1,8 @@
#pragma once
#include <stdint.h>
class Clock {
public:
virtual uint32_t getMillis() = 0;
};

View File

@@ -0,0 +1,15 @@
#include "Fsm.h"
Fsm::Fsm(State &initialState) :
current_state(&initialState)
{}
void Fsm::transition(State &state) {
current_state->onExit();
current_state = &state;
current_state->onEntry();
}
void Fsm::step() {
current_state->step();
}

View File

@@ -0,0 +1,28 @@
#pragma once
class State {
public:
virtual void step() {};
virtual void onEntry() {};
virtual void onExit() {};
};
class Transition {
public:
Transition(State &toState) {}
virtual bool evalutate() = 0;
};
class Fsm {
protected:
Fsm(State &initialState);
void transition(State &state);
public:
void step();
private:
State *current_state;
};

View File

@@ -0,0 +1,11 @@
#pragma once
class GpioInput {
public:
virtual bool read() = 0;
};
class GpioOutput {
public:
virtual void write(bool value) = 0;
};

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Clock.h"
class Timer {
public:
Timer(Clock &clock)
: clock(clock)
, start_time(0)
{}
void start() {
start_time = clock.getMillis();
}
uint32_t getRuntime() {
return start_time - clock.getMillis();
}
private:
Clock &clock;
uint32_t start_time;
};

View File

@@ -9,15 +9,7 @@
; https://docs.platformio.org/page/projectconf.html
[env]
platform = espressif8266
framework = arduino
board = esp12e
lib_deps =
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.2
upload_speed = 115200
src_filter =
;; Default filter
+<*>
-<.git/>
-<.svn/>
@@ -25,13 +17,46 @@ src_filter =
-<examples/>
-<test/>
-<tests/>
;; Don't compile until the user adds their own config
-<src/config-example.h>
[env:button-release]
platform = espressif8266
framework = arduino
board = esp12e
lib_ldf_mode = deep+ ;; Required for Platformio to identify some transitive dependencies of AutoConnect
lib_deps =
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.2
hieromon/AutoConnect@^1.2.2
upload_speed = 115200
[env:button-timing]
platform = espressif8266
framework = arduino
board = esp12e
lib_ldf_mode = deep+ ;; Required for Platformio to identify some transitive dependencies of AutoConnect
lib_deps =
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.2
hieromon/AutoConnect@^1.2.2
upload_speed = 115200
build_flags = -D DEBUG_SKETCH_TIMING
[env:button-debug]
platform = espressif8266
framework = arduino
board = esp12e
lib_ldf_mode = deep+ ;; Required for Platformio to identify some transitive dependencies of AutoConnect
lib_deps =
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.2
hieromon/AutoConnect@^1.2.2
upload_speed = 115200
build_flags = -D DEBUG_SKETCH
[env:native]
platform = native
lib_compat_mode = off ;; Otherwise, the private libs aren't included.
build_flags = -pthread ;; Required for googletest
lib_deps =
google/googletest @ ^1.10.0

View File

@@ -0,0 +1,23 @@
#include "Fsm.h"
class ButtonApplication : Fsm {
class ReadConfigValuesState : public State {
public:
ReadConfigValuesState(ButtonApplication &fsm) :
fsm(fsm)
{}
void onEntry() override {
}
void step() override {
}
private:
ButtonApplication &fsm;
} read_config_values_state {*this};
};

View File

@@ -3,9 +3,12 @@
#include <PubSubClient.h>
#include <Wire.h>
#include "ArduinoGpio.h"
#include "config.h"
#include "mqtt.h"
#include "util.h"
#include "DebounceFsm.h"
#include "ArduinoClock.h"
#define BTN_PIN D0
#define RED_LED_PIN 4
@@ -17,6 +20,10 @@
static WiFiClient wifiClient;
static PubSubClient mqttClient;
static ArduinoClock main_clock;
static ArduinoGpioInput button_input(BTN_PIN);
static DebounceFsm button_fsm(main_clock, button_input, 50, true, [](){}, [](){});
void blink(int pin, int times, int onMillis, int offMillis) {
// for (int i = 0; i < times; i++) {
// digitalWrite(pin, HIGH);
@@ -111,6 +118,23 @@ void setup() {
ESP.deepSleep(0, RFMode::RF_NO_CAL);
}
enum ApplicationState {
READ_CONFIG_VALUES,
INIT_WIFI_CONFIG_SERVER,
INIT_WIFI_CLIENT,
INIT_MQTT,
RUN_WIFI_CONFIG_SERVER,
READ_SENSOR,
PUBLISH_MQTT,
SLEEP,
} app_state;
enum SensorState {
INIT_BUS,
REQUESTING_STATUS,
DONE,
} sensor_state;
void loop() {
}

View File

View File

@@ -0,0 +1,79 @@
#include "DebounceFsm.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
using ::testing::Test;
using ::testing::Return;
class MockClock : public Clock {
public:
MOCK_METHOD(uint32_t, getMillis, ());
};
class MockGpioInput : public GpioInput {
public:
MOCK_METHOD(bool, read, ());
};
class DebounceFsmTest : public Test {
protected:
DebounceFsmTest() :
clock(),
gpioInput(),
activated(false),
deactivated(false)
{}
~DebounceFsmTest() {}
static const unsigned int DEBOUNCE_MILLIS = 100;
static const bool ACTIVE_VALUE = true;
MockClock clock;
MockGpioInput gpioInput;
bool activated;
bool deactivated;
void SetUp() override {
fsm = new DebounceFsm(
clock,
gpioInput,
DEBOUNCE_MILLIS,
ACTIVE_VALUE,
[this](){activated = true;},
[this](){deactivated = true;});
ON_CALL(clock, getMillis()).WillByDefault(Return(0));
ON_CALL(gpioInput, read()).WillByDefault(Return(!ACTIVE_VALUE));
activated = false;
deactivated = false;
}
void TearDown() override {
fsm = nullptr;
}
DebounceFsm *fsm;
};
TEST_F(DebounceFsmTest, Noop) {
EXPECT_CALL(clock, getMillis()).WillOnce(Return(0));
EXPECT_CALL(gpioInput, read()).WillOnce(Return(ACTIVE_VALUE));
fsm->step();
EXPECT_EQ(false, activated);
EXPECT_EQ(false, deactivated);
EXPECT_CALL(clock, getMillis()).WillOnce(Return(DEBOUNCE_MILLIS));
EXPECT_CALL(gpioInput, read()).WillOnce(Return(ACTIVE_VALUE));
fsm->step();
EXPECT_EQ(true, activated);
EXPECT_EQ(false, deactivated);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}