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.

This commit is contained in:
Maurice Makaay 2020-07-19 16:49:20 +02:00
parent 4c5ea8c001
commit 02e5cc2e14
14 changed files with 185 additions and 210 deletions

View File

@ -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,8 +44,19 @@ namespace Dough
void App::loop()
{
if (_setupNetworkConnection())
if (!wifi.isConnected())
{
state.setWiFiConnected(false);
state.setWiFiConnected(wifi.connect());
return;
}
if (!mqtt.isConnected())
{
state.setMQTTConnected(false);
state.setMQTTConnected(mqtt.connect());
return;
}
ui.processButtonEvents();
mqtt.procesIncomingsMessages();
@ -62,68 +80,19 @@ namespace Dough
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;
}
}
return connected;
}
void App::clearHistory()
{
case CALIBRATING:
delay(2000);
state.pauseMeasurements();
state.startMeasurements();
break;
case PAUSED:
temperatureSensor.clearHistory();
humiditySensor.clearHistory();
distanceSensor.clearHistory();
break;
default:
// NOOP
break;
}
}
} // namespace Dough

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -106,7 +106,7 @@ namespace Dough
{
_state = UP;
}
else if (_state == UP && buttonIsDown)
else if (_state == UP && buttonIsDown && interval)
{
_state = DOWN;
}

View File

@ -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 <Arduino.h>

View File

@ -2,8 +2,7 @@
namespace Dough
{
UI::UI(
ButtonISR onoffButtonISR,
UI::UI(ButtonISR onoffButtonISR,
ButtonISR setupButtonISR) : onoffButton(ONOFF_BUTTON_PIN, onoffButtonISR),
setupButton(SETUP_BUTTON_PIN, setupButtonISR),
_ledBuiltin(LED_BUILTIN),
@ -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();

View File

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

View File

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

View File

@ -3,29 +3,9 @@
#include <Arduino.h>
#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