Backup work on making the application state a separate class, so I can keep all state knowledge in one place.

This commit is contained in:
Maurice Makaay 2020-07-19 01:14:28 +02:00
parent f20ff5a4bb
commit 4c5ea8c001
9 changed files with 293 additions and 109 deletions

View File

@ -10,6 +10,7 @@ namespace Dough
App::App() : config(), App::App() : config(),
ui(onoffButtonInterruptCallback, setupButtonInterruptCallback), ui(onoffButtonInterruptCallback, setupButtonInterruptCallback),
state(&ui),
wifi(), wifi(),
mqtt(&wifi, mqttOnConnectCallback, mqttOnMessageCallback), mqtt(&wifi, mqttOnConnectCallback, mqttOnMessageCallback),
sensorControllerPlugin(&mqtt, &ui), sensorControllerPlugin(&mqtt, &ui),
@ -34,17 +35,89 @@ namespace Dough
distanceSensor.setup(); 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. return connected;
temperatureSensor.loop();
humiditySensor.loop();
distanceSensor.loop();
} }
void App::clearHistory() void App::clearHistory()

View File

@ -2,6 +2,7 @@
#define DOUGH_APP_H #define DOUGH_APP_H
#include <Arduino.h> #include <Arduino.h>
#include "App/AppStateController.h"
#include "UI/UI.h" #include "UI/UI.h"
#include "App/Configuration.h" #include "App/Configuration.h"
#include "App/callbacks.h" #include "App/callbacks.h"
@ -20,6 +21,7 @@ namespace Dough
static App *Instance(); static App *Instance();
Configuration config; Configuration config;
UI ui; UI ui;
AppStateController state;
WiFi wifi; WiFi wifi;
MQTT mqtt; MQTT mqtt;
SensorControllerPlugin sensorControllerPlugin; SensorControllerPlugin sensorControllerPlugin;
@ -28,12 +30,13 @@ namespace Dough
SensorController humiditySensor; SensorController humiditySensor;
void setup(); void setup();
void measure(); void loop();
void clearHistory(); void clearHistory();
private: private:
App(); App();
Logger _logger; Logger _logger;
bool _setupNetworkConnection();
}; };
} }

View File

@ -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

View File

@ -0,0 +1,70 @@
#ifndef DOUGH_STATEMACHINE_H
#define DOUGH_STATEMACHINE_H
#include <Arduino.h>
#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

View File

@ -11,7 +11,7 @@ namespace Dough
// This class is used to store measurements for a sensor and to keep // This class is used to store measurements for a sensor and to keep
// track of running totals for handling average computations. // track of running totals for handling average computations.
// It also provides functionality to decide when to read a measurement // 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). // changes occur or when the last publish was too long ago).
class SensorController; class SensorController;

View File

@ -112,6 +112,13 @@ namespace Dough
_led3.loop(); _led3.loop();
} }
void UI::onStateChange(AppState oldState, AppState newState)
{
Serial.print(oldState); // TODO
Serial.print(" to ");
Serial.println(newState); // TODO
}
void UI::notifyConnectingToWifi() void UI::notifyConnectingToWifi()
{ {
_led1.blink()->slow(); _led1.blink()->slow();

View File

@ -3,6 +3,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <stdarg.h> #include <stdarg.h>
#include "App/AppStateController.h"
#include "UI/Button.h" #include "UI/Button.h"
#include "UI/LED.h" #include "UI/LED.h"
#include "config.h" #include "config.h"
@ -11,7 +12,7 @@ namespace Dough
{ {
// This class groups all user interface functionality: serial logging, // This class groups all user interface functionality: serial logging,
// LEDs and buttons. // LEDs and buttons.
class UI class UI : public AppStateControllerPluginBase
{ {
public: public:
UI(ButtonISR onoffButtonISR, ButtonISR setupButtonISR); UI(ButtonISR onoffButtonISR, ButtonISR setupButtonISR);
@ -23,6 +24,7 @@ namespace Dough
void updateLEDs(); void updateLEDs();
void resume(); void resume();
void suspend(); void suspend();
virtual void onStateChange(AppState oldState, AppState newState);
void notifyConnectingToWifi(); void notifyConnectingToWifi();
void notifyConnectingToMQTT(); void notifyConnectingToMQTT();
void notifyConnected(); void notifyConnected();

View File

@ -1,9 +1,5 @@
#include "main.h" #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; DoughBoyState state = CONFIGURING;
Dough::Logger logger("MAIN"); Dough::Logger logger("MAIN");
@ -21,93 +17,32 @@ void setup()
void loop() void loop()
{ {
auto app = Dough::App::Instance(); auto app = Dough::App::Instance();
app->loop();
app->ui.processButtonEvents();
if (!setupNetworkConnection())
{
return;
}
app->mqtt.procesIncomingsMessages(); // if (state == CONFIGURING)
// {
if (state == CONFIGURING) // if (app->config.isOk())
{ // {
if (app->config.isOk()) // setStateToMeasuring();
{ // }
setStateToMeasuring(); // }
} // else if (state == MEASURING && !app->config.isOk())
} // {
else if (state == MEASURING && !app->config.isOk()) // setStateToConfiguring();
{ // }
setStateToConfiguring(); // else if (state == MEASURING)
} // {
else if (state == MEASURING) // app->measure();
{ // }
app->measure(); // else if (state == CALIBRATING)
} // {
else if (state == CALIBRATING) // delay(3000);
{ // setStateToPaused();
delay(3000); // }
setStateToPaused(); // else if (state == PAUSED)
} // {
else if (state == PAUSED) // app->clearHistory();
{ // }
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;
} }
void handleOnoffButtonPress() void handleOnoffButtonPress()

View File

@ -12,13 +12,6 @@
#include "UI/UI.h" #include "UI/UI.h"
#include "config.h" #include "config.h"
typedef enum
{
CONNECTING_WIFI,
CONNECTING_MQTT,
CONNECTED
} DoughBoyConnectionState;
typedef enum typedef enum
{ {
CONFIGURING, CONFIGURING,
@ -27,10 +20,9 @@ typedef enum
CALIBRATING CALIBRATING
} DoughBoyState; } DoughBoyState;
bool setupNetworkConnection();
void handleMqttMessage(String &topic, String &payload);
void handleOnoffButtonPress(); void handleOnoffButtonPress();
void handleSetupButtonPress(); void handleSetupButtonPress();
void setStateToConfiguring(); void setStateToConfiguring();
void setStateToMeasuring(); void setStateToMeasuring();
void setStateToPaused(); void setStateToPaused();