diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1adb8c5..f1561db 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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: diff --git a/firmware/main_mcu/lib/app_lib/src/DebounceFsm.cpp b/firmware/main_mcu/lib/app_lib/src/DebounceFsm.cpp new file mode 100644 index 0000000..2dcde47 --- /dev/null +++ b/firmware/main_mcu/lib/app_lib/src/DebounceFsm.cpp @@ -0,0 +1,55 @@ +#include "DebounceFsm.h" + +DebounceFsm::DebounceFsm(Clock &clock, + GpioInput &input, + unsigned int debounce_millis, + const bool active_value, + const std::function &on_activate, + const std::function &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 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); + } +} diff --git a/firmware/main_mcu/lib/app_lib/src/DebounceFsm.h b/firmware/main_mcu/lib/app_lib/src/DebounceFsm.h new file mode 100644 index 0000000..1ac91fa --- /dev/null +++ b/firmware/main_mcu/lib/app_lib/src/DebounceFsm.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#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 &on_activate, + const std::function &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 callback, + bool to_pin_value, + State &previous_state, + State &next_state); + + virtual void onEntry() override; + virtual void step() override; + + private: + DebounceFsm &fsm; + std::function 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; +}; diff --git a/firmware/main_mcu/lib/generic_lib/ArduinoGpio.cpp b/firmware/main_mcu/lib/generic_lib/ArduinoGpio.cpp new file mode 100644 index 0000000..2e53d1c --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/ArduinoGpio.cpp @@ -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 diff --git a/firmware/main_mcu/lib/generic_lib/src/ArduinoClock.cpp b/firmware/main_mcu/lib/generic_lib/src/ArduinoClock.cpp new file mode 100644 index 0000000..e757cbd --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/ArduinoClock.cpp @@ -0,0 +1,13 @@ +#include "ArduinoClock.h" + +#ifdef ARDUINO + +#include "Arduino.h" + +ArduinoClock::ArduinoClock() {} + +uint32_t ArduinoClock::getMillis() { + return getMillis(); +} + +#endif diff --git a/firmware/main_mcu/lib/generic_lib/src/ArduinoClock.h b/firmware/main_mcu/lib/generic_lib/src/ArduinoClock.h new file mode 100644 index 0000000..0c56227 --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/ArduinoClock.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Clock.h" +#ifdef ARDUINO + +class ArduinoClock : public Clock { + public: + ArduinoClock(); + uint32_t getMillis() override; +}; + +#endif diff --git a/firmware/main_mcu/lib/generic_lib/src/ArduinoGpio.cpp b/firmware/main_mcu/lib/generic_lib/src/ArduinoGpio.cpp new file mode 100644 index 0000000..2e53d1c --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/ArduinoGpio.cpp @@ -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 diff --git a/firmware/main_mcu/lib/generic_lib/src/ArduinoGpio.h b/firmware/main_mcu/lib/generic_lib/src/ArduinoGpio.h new file mode 100644 index 0000000..ac85f07 --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/ArduinoGpio.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Gpio.h" +#include + +#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 diff --git a/firmware/main_mcu/lib/generic_lib/src/Clock.h b/firmware/main_mcu/lib/generic_lib/src/Clock.h new file mode 100644 index 0000000..d499ba1 --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/Clock.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +class Clock { + public: + virtual uint32_t getMillis() = 0; +}; diff --git a/firmware/main_mcu/lib/generic_lib/src/Fsm.cpp b/firmware/main_mcu/lib/generic_lib/src/Fsm.cpp new file mode 100644 index 0000000..3b0a0d5 --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/Fsm.cpp @@ -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(); +} diff --git a/firmware/main_mcu/lib/generic_lib/src/Fsm.h b/firmware/main_mcu/lib/generic_lib/src/Fsm.h new file mode 100644 index 0000000..31af14b --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/Fsm.h @@ -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; +}; diff --git a/firmware/main_mcu/lib/generic_lib/src/Gpio.h b/firmware/main_mcu/lib/generic_lib/src/Gpio.h new file mode 100644 index 0000000..1e52a0e --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/Gpio.h @@ -0,0 +1,11 @@ +#pragma once + +class GpioInput { + public: + virtual bool read() = 0; +}; + +class GpioOutput { + public: + virtual void write(bool value) = 0; +}; diff --git a/firmware/main_mcu/lib/generic_lib/src/Timer.h b/firmware/main_mcu/lib/generic_lib/src/Timer.h new file mode 100644 index 0000000..7c39026 --- /dev/null +++ b/firmware/main_mcu/lib/generic_lib/src/Timer.h @@ -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; + +}; diff --git a/firmware/main_mcu/platformio.ini b/firmware/main_mcu/platformio.ini index 7ee9eeb..5ebc16b 100644 --- a/firmware/main_mcu/platformio.ini +++ b/firmware/main_mcu/platformio.ini @@ -9,29 +9,54 @@ ; 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/> - - - - - - - - - ;; Don't compile until the user adds their own config + +<*> + -<.git/> + -<.svn/> + - + - + - + - - [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 diff --git a/firmware/main_mcu/src/ButtonApplication.cpp b/firmware/main_mcu/src/ButtonApplication.cpp new file mode 100644 index 0000000..973839c --- /dev/null +++ b/firmware/main_mcu/src/ButtonApplication.cpp @@ -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}; + +}; diff --git a/firmware/main_mcu/src/main.cpp b/firmware/main_mcu/src/main.cpp index 687a42a..a948d39 100644 --- a/firmware/main_mcu/src/main.cpp +++ b/firmware/main_mcu/src/main.cpp @@ -3,9 +3,12 @@ #include #include +#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() { } diff --git a/firmware/main_mcu/src/timer.h b/firmware/main_mcu/src/timer.h new file mode 100644 index 0000000..e69de29 diff --git a/firmware/main_mcu/test/test_local/DebounceFsmTest.cpp b/firmware/main_mcu/test/test_local/DebounceFsmTest.cpp new file mode 100644 index 0000000..ac9d1d4 --- /dev/null +++ b/firmware/main_mcu/test/test_local/DebounceFsmTest.cpp @@ -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(); +}