From 02e5cc2e146e65f5076af6489e456b3629c37125 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 19 Jul 2020 16:49:20 +0200 Subject: [PATCH] Implemented state change handling using a plugin mechanism for the new AppStateController class. This way, I now have a class that handles all state changes, combined with a loosely coupled plugin for it, which takes care of notifying an communicating the state changes. --- src/App/App.cpp | 123 ++++++++++----------------- src/App/App.h | 6 +- src/App/AppStateController.cpp | 14 +++ src/App/AppStateController.h | 2 + src/App/AppStateControllerPlugin.cpp | 62 ++++++++++++++ src/App/AppStateControllerPlugin.h | 24 ++++++ src/App/callbacks.cpp | 18 ++++ src/App/callbacks.h | 2 + src/UI/Button.cpp | 2 +- src/UI/Button.h | 2 +- src/UI/UI.cpp | 22 ++--- src/UI/UI.h | 4 +- src/main.cpp | 92 +------------------- src/main.h | 22 +---- 14 files changed, 185 insertions(+), 210 deletions(-) create mode 100644 src/App/AppStateControllerPlugin.cpp create mode 100644 src/App/AppStateControllerPlugin.h diff --git a/src/App/App.cpp b/src/App/App.cpp index 6600c13..bc0fdd3 100644 --- a/src/App/App.cpp +++ b/src/App/App.cpp @@ -10,9 +10,10 @@ namespace Dough App::App() : config(), ui(onoffButtonInterruptCallback, setupButtonInterruptCallback), - state(&ui), wifi(), mqtt(&wifi, mqttOnConnectCallback, mqttOnMessageCallback), + statePlugin(&ui, &mqtt), + state(&statePlugin), sensorControllerPlugin(&mqtt, &ui), distanceSensor( new DistanceSensor(), &sensorControllerPlugin, @@ -23,10 +24,16 @@ namespace Dough humiditySensor( new HumiditySensor(), &sensorControllerPlugin, HUMIDITY_AVERAGE_STORAGE, HUMIDITY_MEASURE_INTERVAL, MINIMUM_PUBLISH_INTERVAL), - _logger("APP") {} + _logger("APP") + { + ui.onoffButton.onPress(handleOnoffButtonPress); + ui.setupButton.onPress(handleSetupButtonPress); + } void App::setup() { + Dough::Logger::setup(); + state.setup(); ui.setup(); wifi.setup(); mqtt.setup(); @@ -37,93 +44,55 @@ namespace Dough void App::loop() { - if (_setupNetworkConnection()) - { - ui.processButtonEvents(); - mqtt.procesIncomingsMessages(); - - switch (state.get()) - { - case CONFIGURING: - if (config.isOk()) - { - state.startMeasurements(); - } - break; - case MEASURING: - if (config.isOk()) - { - temperatureSensor.loop(); - humiditySensor.loop(); - distanceSensor.loop(); - } - else - { - state.startConfiguration(); - } - break; - } - } - } - - // Check if the device is connected to the WiFi network and the MQTT broker. - // If not, then try to setup the connection. - // Returns true if the connection was established, false otherwise. - bool App::_setupNetworkConnection() - { - static bool connected = false; - if (!wifi.isConnected()) { state.setWiFiConnected(false); - if (connected) - { - _logger.log("s", "ERROR - Connection to WiFi network lost! Reconnecting ..."); - } - else - { - _logger.log("s", "Connecting to the WiFi network ..."); - } - connected = false; - ui.notifyConnectingToWifi(); state.setWiFiConnected(wifi.connect()); + return; } - if (wifi.isConnected() && !mqtt.isConnected()) + if (!mqtt.isConnected()) { - state.setWiFiConnected(true); state.setMQTTConnected(false); - if (connected) + state.setMQTTConnected(mqtt.connect()); + return; + } + + ui.processButtonEvents(); + mqtt.procesIncomingsMessages(); + + switch (state.get()) + { + case CONFIGURING: + if (config.isOk()) { - _logger.log("s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); + state.startMeasurements(); + } + break; + case MEASURING: + if (config.isOk()) + { + temperatureSensor.loop(); + humiditySensor.loop(); + distanceSensor.loop(); } else { - _logger.log("s", "Connecting to the MQTT broker ..."); + state.startConfiguration(); } - connected = false; - ui.notifyConnectingToMQTT(); - state.setMQTTConnected(mqtt.connect()); + break; + case CALIBRATING: + delay(2000); + state.pauseMeasurements(); + state.startMeasurements(); + break; + case PAUSED: + temperatureSensor.clearHistory(); + humiditySensor.clearHistory(); + distanceSensor.clearHistory(); + break; + default: + // NOOP + break; } - if (wifi.isConnected() && mqtt.isConnected()) - { - state.setWiFiConnected(true); - state.setMQTTConnected(true); - if (!connected) - { - _logger.log("s", "Connection to MQTT broker established"); - ui.notifyConnected(); - ui.clearButtonEvents(); - connected = true; - } - } - - return connected; - } - - void App::clearHistory() - { - temperatureSensor.clearHistory(); - humiditySensor.clearHistory(); - distanceSensor.clearHistory(); } } // namespace Dough \ No newline at end of file diff --git a/src/App/App.h b/src/App/App.h index 78e32b6..62bcd90 100644 --- a/src/App/App.h +++ b/src/App/App.h @@ -3,6 +3,7 @@ #include #include "App/AppStateController.h" +#include "App/AppStateControllerPlugin.h" #include "UI/UI.h" #include "App/Configuration.h" #include "App/callbacks.h" @@ -21,9 +22,10 @@ namespace Dough static App *Instance(); Configuration config; UI ui; - AppStateController state; WiFi wifi; MQTT mqtt; + AppStateControllerPlugin statePlugin; + AppStateController state; SensorControllerPlugin sensorControllerPlugin; SensorController distanceSensor; SensorController temperatureSensor; @@ -31,12 +33,10 @@ namespace Dough void setup(); void loop(); - void clearHistory(); private: App(); Logger _logger; - bool _setupNetworkConnection(); }; } diff --git a/src/App/AppStateController.cpp b/src/App/AppStateController.cpp index d6ad2ad..6e85f4d 100644 --- a/src/App/AppStateController.cpp +++ b/src/App/AppStateController.cpp @@ -5,6 +5,12 @@ namespace Dough AppStateController::AppStateController(AppStateControllerPluginBase *plugin) : _logger("STATE"), _plugin(plugin) {} + void AppStateController::setup() + { + // Trigger a state change event, so the plugin can act upon it. + _plugin->onStateChange(get(), INITIALIZING); + } + AppState AppStateController::get() { if (_wifiConnected != CONNECTED) @@ -50,6 +56,14 @@ namespace Dough { if (connected) { + // Trigger a connection established state, so the plugin can act + // upon this specific event. At the end of this method, the status update + // call will push the system to the next state. + if (_mqttConnected != CONNECTED) + { + _appState = CONNECTION_ESTABLISHED; + _plugin->onStateChange(get(), _appState); + } _mqttConnected = CONNECTED; } else diff --git a/src/App/AppStateController.h b/src/App/AppStateController.h index 26cd681..29dcb22 100644 --- a/src/App/AppStateController.h +++ b/src/App/AppStateController.h @@ -13,6 +13,7 @@ namespace Dough CONNECTING_WIFI, MQTT_CONNECTION_LOST, CONNECTING_MQTT, + CONNECTION_ESTABLISHED, CONFIGURING, MEASURING, CALIBRATING, @@ -46,6 +47,7 @@ namespace Dough { public: AppStateController(AppStateControllerPluginBase *plugin); + void setup(); void setWiFiConnected(bool connected); void setMQTTConnected(bool connected); void startConfiguration(); diff --git a/src/App/AppStateControllerPlugin.cpp b/src/App/AppStateControllerPlugin.cpp new file mode 100644 index 0000000..a801656 --- /dev/null +++ b/src/App/AppStateControllerPlugin.cpp @@ -0,0 +1,62 @@ +#include "App/AppStateControllerPlugin.h" + +namespace Dough +{ + AppStateControllerPlugin::AppStateControllerPlugin(UI *ui, MQTT *mqtt) : _ui(ui), + _mqtt(mqtt), + _logger("STATE") {} + + void AppStateControllerPlugin::onStateChange(AppState oldState, AppState newState) + { + switch (newState) + { + case INITIALIZING: + _logger.log("s", "Initializing the device"); + break; + case CONNECTING_WIFI: + _logger.log("s", "Connecting to the WiFi network"); + _ui->notifyConnectingToWifi(); + break; + case WIFI_CONNECTION_LOST: + _logger.log("s", "ERROR - Connection to WiFi network lost! Reconnecting"); + _ui->notifyConnectingToWifi(); + break; + case CONNECTING_MQTT: + _logger.log("s", "Connecting to the MQTT broker"); + _ui->notifyConnectingToMQTT(); + break; + case MQTT_CONNECTION_LOST: + _logger.log("s", "ERROR - Connection to the MQTT broker lost! Reconnecting"); + _ui->notifyConnectingToMQTT(); + break; + case CONNECTION_ESTABLISHED: + _logger.log("s", "Connection to MQTT broker established"); + _ui->notifyConnected(); + _ui->clearButtonEvents(); + _mqtt->publish("state", "connected"); + break; + case CONFIGURING: + _logger.log("s", "Waiting for configuration"); + _ui->notifyWaitingForConfiguration(); + _mqtt->publish("state", "configuring"); + break; + case MEASURING: + _logger.log("s", "Starting measurements"); + _ui->notifyMeasurementsActive(); + _mqtt->publish("state", "measuring"); + break; + case PAUSED: + _logger.log("s", "Pausing measurements"); + _ui->notifyMeasurementsPaused(); + _mqtt->publish("state", "paused"); + break; + case CALIBRATING: + _logger.log("s", "Requested device calibration"); + _ui->notifyCalibrating(); + _mqtt->publish("state", "calibrating"); + break; + default: + _logger.log("sisi", "Unhandled state transfer: ", oldState, " -> ", newState); + } + } +} // namespace Dough diff --git a/src/App/AppStateControllerPlugin.h b/src/App/AppStateControllerPlugin.h new file mode 100644 index 0000000..804bd64 --- /dev/null +++ b/src/App/AppStateControllerPlugin.h @@ -0,0 +1,24 @@ +#ifndef DOUGH_APPSTATECONTROLLERPLUGIN_H +#define DOUGH_APPSTATECONTROLLERPLUGIN_H + +#include +#include "App/AppStateController.h" +#include "Network/MQTT.h" +#include "UI/Logger.h" +#include "UI/UI.h" + +namespace Dough +{ + class AppStateControllerPlugin : public AppStateControllerPluginBase + { + public: + AppStateControllerPlugin(UI *ui, MQTT *mqtt); + virtual void onStateChange(AppState oldState, AppState newState); + private: + UI *_ui; + MQTT *_mqtt; + Logger _logger; + }; +} // namespace Dough + +#endif \ No newline at end of file diff --git a/src/App/callbacks.cpp b/src/App/callbacks.cpp index c2ddd3c..811fdbc 100644 --- a/src/App/callbacks.cpp +++ b/src/App/callbacks.cpp @@ -37,6 +37,24 @@ void setupButtonInterruptCallback() Dough::App::Instance()->ui.setupButton.handleButtonState(); } +void handleOnoffButtonPress() +{ + auto app = Dough::App::Instance(); + if (app->state.get() == Dough::MEASURING) + { + app->state.pauseMeasurements(); + } + else if (app->state.get() == Dough::PAUSED) + { + app->state.resumeMeasurements(); + } +} + +void handleSetupButtonPress() +{ + Dough::App::Instance()->state.startCalibration(); +} + // This callback is called when the TC4 timer from the UI code hits an overflow // interrupt. It is defined outside the Dough namespace, because TC4_Handler is // a hard-coded, root namespace function name. diff --git a/src/App/callbacks.h b/src/App/callbacks.h index 45a6a75..f7ef98f 100644 --- a/src/App/callbacks.h +++ b/src/App/callbacks.h @@ -17,5 +17,7 @@ void mqttOnMessageCallback(String &topic, String &payload); // Callbacks from the Dough::UI module. void onoffButtonInterruptCallback(); void setupButtonInterruptCallback(); +void handleOnoffButtonPress(); +void handleSetupButtonPress(); #endif \ No newline at end of file diff --git a/src/UI/Button.cpp b/src/UI/Button.cpp index c89ab30..575a6dd 100644 --- a/src/UI/Button.cpp +++ b/src/UI/Button.cpp @@ -106,7 +106,7 @@ namespace Dough { _state = UP; } - else if (_state == UP && buttonIsDown) + else if (_state == UP && buttonIsDown && interval) { _state = DOWN; } diff --git a/src/UI/Button.h b/src/UI/Button.h index f1eabb4..c0daf06 100644 --- a/src/UI/Button.h +++ b/src/UI/Button.h @@ -1,7 +1,7 @@ #ifndef DOUGH_BUTTON_H #define DOUGH_BUTTON_H -#define BUTTON_DEBOUNCE_DELAY 10 +#define BUTTON_DEBOUNCE_DELAY 50 #define BUTTON_LONGPRESS_DELAY 500 #include diff --git a/src/UI/UI.cpp b/src/UI/UI.cpp index 90bd1e5..41fbdeb 100644 --- a/src/UI/UI.cpp +++ b/src/UI/UI.cpp @@ -2,14 +2,13 @@ namespace Dough { - UI::UI( - ButtonISR onoffButtonISR, - ButtonISR setupButtonISR) : onoffButton(ONOFF_BUTTON_PIN, onoffButtonISR), - setupButton(SETUP_BUTTON_PIN, setupButtonISR), - _ledBuiltin(LED_BUILTIN), - _led1(LED1_PIN), - _led2(LED2_PIN), - _led3(LED3_PIN) {} + UI::UI(ButtonISR onoffButtonISR, + ButtonISR setupButtonISR) : onoffButton(ONOFF_BUTTON_PIN, onoffButtonISR), + setupButton(SETUP_BUTTON_PIN, setupButtonISR), + _ledBuiltin(LED_BUILTIN), + _led1(LED1_PIN), + _led2(LED2_PIN), + _led3(LED3_PIN) {} void UI::setup() { @@ -112,13 +111,6 @@ namespace Dough _led3.loop(); } - void UI::onStateChange(AppState oldState, AppState newState) - { - Serial.print(oldState); // TODO - Serial.print(" to "); - Serial.println(newState); // TODO - } - void UI::notifyConnectingToWifi() { _led1.blink()->slow(); diff --git a/src/UI/UI.h b/src/UI/UI.h index 1e38616..a90dd3e 100644 --- a/src/UI/UI.h +++ b/src/UI/UI.h @@ -3,7 +3,6 @@ #include #include -#include "App/AppStateController.h" #include "UI/Button.h" #include "UI/LED.h" #include "config.h" @@ -12,7 +11,7 @@ namespace Dough { // This class groups all user interface functionality: serial logging, // LEDs and buttons. - class UI : public AppStateControllerPluginBase + class UI { public: UI(ButtonISR onoffButtonISR, ButtonISR setupButtonISR); @@ -24,7 +23,6 @@ namespace Dough void updateLEDs(); void resume(); void suspend(); - virtual void onStateChange(AppState oldState, AppState newState); void notifyConnectingToWifi(); void notifyConnectingToMQTT(); void notifyConnected(); diff --git a/src/main.cpp b/src/main.cpp index 1a31126..f544580 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,99 +1,13 @@ #include "main.h" -DoughBoyState state = CONFIGURING; Dough::Logger logger("MAIN"); void setup() { - Dough::Logger::setup(); - logger.log("s", "Initializing device"); - auto app = Dough::App::Instance(); - app->setup(); - app->ui.onoffButton.onPress(handleOnoffButtonPress); - app->ui.setupButton.onPress(handleSetupButtonPress); - logger.log("s", "Initialization completed, starting device"); + Dough::App::Instance()->setup(); } void loop() { - auto app = Dough::App::Instance(); - app->loop(); - - // if (state == CONFIGURING) - // { - // if (app->config.isOk()) - // { - // setStateToMeasuring(); - // } - // } - // else if (state == MEASURING && !app->config.isOk()) - // { - // setStateToConfiguring(); - // } - // else if (state == MEASURING) - // { - // app->measure(); - // } - // else if (state == CALIBRATING) - // { - // delay(3000); - // setStateToPaused(); - // } - // else if (state == PAUSED) - // { - // app->clearHistory(); - // } -} - -void handleOnoffButtonPress() -{ - if (state == MEASURING) - { - setStateToPaused(); - } - else if (state == PAUSED) - { - setStateToMeasuring(); - } -} - -void handleSetupButtonPress() -{ - setStateToCalibrating(); -} - -void setStateToConfiguring() -{ - auto app = Dough::App::Instance(); - logger.log("s", "Waiting for configuration ..."); - state = CONFIGURING; - app->ui.notifyWaitingForConfiguration(); - app->mqtt.publish("state", "configuring"); -} - -void setStateToMeasuring() -{ - auto app = Dough::App::Instance(); - logger.log("s", "Starting measurements"); - state = MEASURING; - app->ui.notifyMeasurementsActive(); - app->mqtt.publish("state", "measuring"); -} - -void setStateToPaused() -{ - auto app = Dough::App::Instance(); - logger.log("s", "Pausing measurements"); - state = PAUSED; - app->ui.notifyMeasurementsPaused(); - app->mqtt.publish("state", "paused"); -} - -void setStateToCalibrating() -{ - auto app = Dough::App::Instance(); - logger.log("s", "Requested device calibration"); - state = CALIBRATING; - app->ui.notifyCalibrating(); - app->mqtt.publish("state", "calibrating"); -} + Dough::App::Instance()->loop(); +} \ No newline at end of file diff --git a/src/main.h b/src/main.h index 3a5b3b0..f3738ff 100644 --- a/src/main.h +++ b/src/main.h @@ -3,29 +3,9 @@ #include #include "App/App.h" -#include "Sensors/HighLevel/TemperatureSensor.h" -#include "Sensors/HighLevel/HumiditySensor.h" -#include "Sensors/HighLevel/DistanceSensor.h" -#include "Network/WiFi.h" -#include "Network/MQTT.h" +#include "App/AppStateController.h" #include "UI/Button.h" #include "UI/UI.h" #include "config.h" -typedef enum -{ - CONFIGURING, - MEASURING, - PAUSED, - CALIBRATING -} DoughBoyState; - -void handleOnoffButtonPress(); -void handleSetupButtonPress(); - -void setStateToConfiguring(); -void setStateToMeasuring(); -void setStateToPaused(); -void setStateToCalibrating(); - #endif