diff --git a/src/App/App.cpp b/src/App/App.cpp index dff250b..6600c13 100644 --- a/src/App/App.cpp +++ b/src/App/App.cpp @@ -10,6 +10,7 @@ namespace Dough App::App() : config(), ui(onoffButtonInterruptCallback, setupButtonInterruptCallback), + state(&ui), wifi(), mqtt(&wifi, mqttOnConnectCallback, mqttOnMessageCallback), sensorControllerPlugin(&mqtt, &ui), @@ -34,17 +35,89 @@ namespace Dough distanceSensor.setup(); } - void App::measure() + void App::loop() { - if (!config.isOk()) + if (_setupNetworkConnection()) { - return; + 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()); + } + if (wifi.isConnected() && !mqtt.isConnected()) + { + state.setWiFiConnected(true); + state.setMQTTConnected(false); + if (connected) + { + _logger.log("s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); + } + else + { + _logger.log("s", "Connecting to the MQTT broker ..."); + } + connected = false; + ui.notifyConnectingToMQTT(); + state.setMQTTConnected(mqtt.connect()); + } + 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; + } } - // Get measurements from the sensors. - temperatureSensor.loop(); - humiditySensor.loop(); - distanceSensor.loop(); + return connected; } void App::clearHistory() diff --git a/src/App/App.h b/src/App/App.h index c289182..78e32b6 100644 --- a/src/App/App.h +++ b/src/App/App.h @@ -2,6 +2,7 @@ #define DOUGH_APP_H #include +#include "App/AppStateController.h" #include "UI/UI.h" #include "App/Configuration.h" #include "App/callbacks.h" @@ -20,6 +21,7 @@ namespace Dough static App *Instance(); Configuration config; UI ui; + AppStateController state; WiFi wifi; MQTT mqtt; SensorControllerPlugin sensorControllerPlugin; @@ -28,12 +30,13 @@ namespace Dough SensorController humiditySensor; void setup(); - void measure(); + void loop(); void clearHistory(); private: App(); Logger _logger; + bool _setupNetworkConnection(); }; } diff --git a/src/App/AppStateController.cpp b/src/App/AppStateController.cpp new file mode 100644 index 0000000..d6ad2ad --- /dev/null +++ b/src/App/AppStateController.cpp @@ -0,0 +1,102 @@ +#include "App/AppStateController.h" + +namespace Dough +{ + AppStateController::AppStateController(AppStateControllerPluginBase *plugin) : _logger("STATE"), + _plugin(plugin) {} + + AppState AppStateController::get() + { + if (_wifiConnected != CONNECTED) + { + return _wifiConnected == CONNECTION_LOST ? WIFI_CONNECTION_LOST : CONNECTING_WIFI; + } + if (_mqttConnected != CONNECTED) + { + return _mqttConnected == CONNECTION_LOST ? MQTT_CONNECTION_LOST : CONNECTING_MQTT; + } + if (_operationMode == CONFIGURE) + { + return CONFIGURING; + } + if (_operationMode == MEASURE) + { + return _paused ? PAUSED : MEASURING; + ; + } + if (_operationMode == CALIBRATE) + { + return CALIBRATING; + } + _logger.log("s", "get(): Unable to determine state!"); + return INVALID; + } + + void AppStateController::setWiFiConnected(bool connected) + { + if (connected) + { + _wifiConnected = CONNECTED; + } + else + { + _wifiConnected = _wifiConnected == CONNECTED ? CONNECTION_LOST : CONNECTING; + setMQTTConnected(false); + } + _updateState(); + } + + void AppStateController::setMQTTConnected(bool connected) + { + if (connected) + { + _mqttConnected = CONNECTED; + } + else + { + _mqttConnected = _mqttConnected == CONNECTED ? CONNECTION_LOST : CONNECTING; + } + _updateState(); + } + + void AppStateController::startConfiguration() + { + _operationMode = CONFIGURE; + _updateState(); + } + + void AppStateController::startMeasurements() + { + _operationMode = MEASURE; + _updateState(); + } + + void AppStateController::startCalibration() + { + _operationMode = CALIBRATE; + _updateState(); + } + + void AppStateController::pauseMeasurements() + { + _paused = true; + _updateState(); + } + + void AppStateController::resumeMeasurements() + { + _paused = false; + _updateState(); + } + + void AppStateController::_updateState() + { + auto oldState = _appState; + auto newState = get(); + if (newState != oldState) + { + _plugin->onStateChange(oldState, newState); + _appState = newState; + } + } +} // namespace Dough diff --git a/src/App/AppStateController.h b/src/App/AppStateController.h new file mode 100644 index 0000000..26cd681 --- /dev/null +++ b/src/App/AppStateController.h @@ -0,0 +1,70 @@ +#ifndef DOUGH_STATEMACHINE_H +#define DOUGH_STATEMACHINE_H + +#include +#include "UI/Logger.h" + +namespace Dough +{ + typedef enum + { + INITIALIZING, + WIFI_CONNECTION_LOST, + CONNECTING_WIFI, + MQTT_CONNECTION_LOST, + CONNECTING_MQTT, + CONFIGURING, + MEASURING, + CALIBRATING, + PAUSED, + INVALID + } AppState; + + typedef enum + { + CONNECTING, + CONNECTED, + CONNECTION_LOST + } ConnectionState; + + typedef enum + { + CONFIGURE, + MEASURE, + CALIBRATE + } AppOperationMode; + + // This class defines an interface that can be implemented to create a plugin + // for the Dough::AppState class. This plugin can act upon state changes. + class AppStateControllerPluginBase + { + public: + virtual void onStateChange(AppState oldState, AppState newState); + }; + + class AppStateController + { + public: + AppStateController(AppStateControllerPluginBase *plugin); + void setWiFiConnected(bool connected); + void setMQTTConnected(bool connected); + void startConfiguration(); + void startMeasurements(); + void startCalibration(); + void pauseMeasurements(); + void resumeMeasurements(); + AppState get(); + + private: + Logger _logger; + AppStateControllerPluginBase *_plugin; + AppState _appState = INITIALIZING; + ConnectionState _wifiConnected = CONNECTING; + ConnectionState _mqttConnected = CONNECTING; + AppOperationMode _operationMode = CONFIGURE; + bool _paused = false; + void _updateState(); + }; +} // namespace Dough + +#endif \ No newline at end of file diff --git a/src/Sensors/SensorController.h b/src/Sensors/SensorController.h index f9d1cbd..4218536 100644 --- a/src/Sensors/SensorController.h +++ b/src/Sensors/SensorController.h @@ -11,7 +11,7 @@ namespace Dough // This class is used to store measurements for a sensor and to keep // track of running totals for handling average computations. // It also provides functionality to decide when to read a measurement - // from a sensor and when to publish measurements (after significant + // from a sensor and when to publish measurements (after significant // changes occur or when the last publish was too long ago). class SensorController; diff --git a/src/UI/UI.cpp b/src/UI/UI.cpp index 5162e30..90bd1e5 100644 --- a/src/UI/UI.cpp +++ b/src/UI/UI.cpp @@ -112,6 +112,13 @@ 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 a90dd3e..1e38616 100644 --- a/src/UI/UI.h +++ b/src/UI/UI.h @@ -3,6 +3,7 @@ #include #include +#include "App/AppStateController.h" #include "UI/Button.h" #include "UI/LED.h" #include "config.h" @@ -11,7 +12,7 @@ namespace Dough { // This class groups all user interface functionality: serial logging, // LEDs and buttons. - class UI + class UI : public AppStateControllerPluginBase { public: UI(ButtonISR onoffButtonISR, ButtonISR setupButtonISR); @@ -23,6 +24,7 @@ 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 bf689f6..1a31126 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,5 @@ #include "main.h" -// TOOD: implement the calibration logic -// TODO: don't make sensors instances anymore, now they are handled by App as the only singleton -// TODO: see what more stuff can be moved to the UI code. Maybe state to UI state translation ought to be there as well - DoughBoyState state = CONFIGURING; Dough::Logger logger("MAIN"); @@ -21,93 +17,32 @@ void setup() void loop() { auto app = Dough::App::Instance(); - - app->ui.processButtonEvents(); - - if (!setupNetworkConnection()) - { - return; - } + app->loop(); - app->mqtt.procesIncomingsMessages(); - - 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(); - } -} - -// 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 setupNetworkConnection() -{ - static auto connectionState = CONNECTING_WIFI; - - auto app = Dough::App::Instance(); - - if (!app->wifi.isConnected()) - { - if (connectionState == CONNECTED) - { - logger.log("s", "ERROR - Connection to WiFi network lost! Reconnecting ..."); - } - else - { - logger.log("s", "Connecting to the WiFi network ..."); - } - connectionState = CONNECTING_WIFI; - app->ui.notifyConnectingToWifi(); - app->wifi.connect(); - } - if (app->wifi.isConnected() && !app->mqtt.isConnected()) - { - if (connectionState == CONNECTED) - { - logger.log("s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); - } - else - { - logger.log("s", "Connecting to the MQTT broker ..."); - } - connectionState = CONNECTING_MQTT; - app->ui.notifyConnectingToMQTT(); - app->mqtt.connect(); - delay(1000); // purely costmetic, to make the faster LED blinking noticable - } - if (app->wifi.isConnected() && app->mqtt.isConnected()) - { - if (connectionState != CONNECTED) - { - logger.log("s", "Connection to MQTT broker established"); - app->ui.notifyConnected(); - app->ui.clearButtonEvents(); - connectionState = CONNECTED; - setStateToConfiguring(); - } - } - - return connectionState == CONNECTED; + // 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() diff --git a/src/main.h b/src/main.h index 81eb77a..3a5b3b0 100644 --- a/src/main.h +++ b/src/main.h @@ -12,13 +12,6 @@ #include "UI/UI.h" #include "config.h" -typedef enum -{ - CONNECTING_WIFI, - CONNECTING_MQTT, - CONNECTED -} DoughBoyConnectionState; - typedef enum { CONFIGURING, @@ -27,10 +20,9 @@ typedef enum CALIBRATING } DoughBoyState; -bool setupNetworkConnection(); -void handleMqttMessage(String &topic, String &payload); void handleOnoffButtonPress(); void handleSetupButtonPress(); + void setStateToConfiguring(); void setStateToMeasuring(); void setStateToPaused();