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(),
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()

View File

@ -2,6 +2,7 @@
#define DOUGH_APP_H
#include <Arduino.h>
#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();
};
}

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
// 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;

View File

@ -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();

View File

@ -3,6 +3,7 @@
#include <Arduino.h>
#include <stdarg.h>
#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();

View File

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

View File

@ -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();