Further cleanup. Most noticably is moving all physical LED logic into the Dough::UI class, providing app-state-dependent logical functions for letting the LEDs represent a given application state.
This commit is contained in:
parent
52b33de7aa
commit
fd1363dd45
|
@ -9,6 +9,7 @@ namespace Dough
|
|||
}
|
||||
|
||||
App::App() : config(),
|
||||
ui(),
|
||||
wifi(),
|
||||
mqtt(
|
||||
&wifi,
|
||||
|
@ -19,26 +20,30 @@ namespace Dough
|
|||
"temperature",
|
||||
TemperatureSensor::Instance(),
|
||||
TEMPERATURE_AVERAGE_STORAGE,
|
||||
TEMPERATURE_MEASURE_INTERVAL,
|
||||
MINIMUM_PUBLISH_INTERVAL),
|
||||
TEMPERATURE_MEASURE_INTERVAL, sensorOnMeasureCallback,
|
||||
MINIMUM_PUBLISH_INTERVAL, sensorOnPublishCallback),
|
||||
humiditySensor(
|
||||
&mqtt,
|
||||
"humidity",
|
||||
HumiditySensor::Instance(),
|
||||
HUMIDITY_AVERAGE_STORAGE,
|
||||
HUMIDITY_MEASURE_INTERVAL,
|
||||
MINIMUM_PUBLISH_INTERVAL),
|
||||
HUMIDITY_MEASURE_INTERVAL, sensorOnMeasureCallback,
|
||||
MINIMUM_PUBLISH_INTERVAL, sensorOnPublishCallback),
|
||||
distanceSensor(
|
||||
&mqtt,
|
||||
"distance",
|
||||
DistanceSensor::Instance(),
|
||||
DISTANCE_AVERAGE_STORAGE,
|
||||
DISTANCE_MEASURE_INTERVAL,
|
||||
MINIMUM_PUBLISH_INTERVAL),
|
||||
DISTANCE_MEASURE_INTERVAL, sensorOnMeasureCallback,
|
||||
MINIMUM_PUBLISH_INTERVAL, sensorOnPublishCallback),
|
||||
_logger("APP") {}
|
||||
|
||||
void App::setup()
|
||||
{
|
||||
ui.setup();
|
||||
ui.onoffButton.onInterrupt(::onoffButtonInterruptCallback);
|
||||
ui.setupButton.onInterrupt(::setupButtonInterruptCallback);
|
||||
|
||||
wifi.setup();
|
||||
mqtt.setup();
|
||||
temperatureSensor.setup();
|
||||
|
@ -50,9 +55,14 @@ namespace Dough
|
|||
{
|
||||
if (config.isOk())
|
||||
{
|
||||
// Get measurements from the sensors. Suspend the user interface
|
||||
// interrupts in the meanwhile, to not disturb the timing-sensitive
|
||||
// sensor readings.
|
||||
ui.suspend();
|
||||
temperatureSensor.loop();
|
||||
humiditySensor.loop();
|
||||
distanceSensor.loop();
|
||||
ui.resume();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,3 +100,32 @@ void mqttOnMessageCallback(String &topic, String &payload)
|
|||
callbackLogger.log("ss", "ERROR - Unhandled MQTT message, topic = ", topic.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void onoffButtonInterruptCallback()
|
||||
{
|
||||
Dough::App::Instance()->ui.onoffButton.handleButtonState();
|
||||
}
|
||||
|
||||
void setupButtonInterruptCallback()
|
||||
{
|
||||
Dough::App::Instance()->ui.setupButton.handleButtonState();
|
||||
}
|
||||
|
||||
void sensorOnMeasureCallback()
|
||||
{
|
||||
Dough::App::Instance()->ui.notifySensorActivity();
|
||||
}
|
||||
|
||||
void sensorOnPublishCallback()
|
||||
{
|
||||
Dough::App::Instance()->ui.notifyNetworkActivity();
|
||||
}
|
||||
|
||||
// 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.
|
||||
void TC4_Handler()
|
||||
{
|
||||
Dough::App::Instance()->ui.updateLEDs();
|
||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
||||
}
|
|
@ -16,6 +16,7 @@ namespace Dough
|
|||
public:
|
||||
static App *Instance();
|
||||
Configuration config;
|
||||
UI ui;
|
||||
WiFi wifi;
|
||||
MQTT mqtt;
|
||||
SensorController temperatureSensor;
|
||||
|
@ -35,5 +36,9 @@ namespace Dough
|
|||
// Callback functions that need to live in the global namespace.
|
||||
void mqttOnConnectCallback(Dough::MQTT *mqtt);
|
||||
void mqttOnMessageCallback(String &topic, String &payload);
|
||||
void onoffButtonInterruptCallback();
|
||||
void setupButtonInterruptCallback();
|
||||
void sensorOnMeasureCallback();
|
||||
void sensorOnPublishCallback();
|
||||
|
||||
#endif
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
namespace Dough
|
||||
{
|
||||
Configuration::Configuration() : _logger("CONFIG")
|
||||
{
|
||||
// _mqtt = MQTT::Instance();
|
||||
}
|
||||
Configuration::Configuration() : _logger("CONFIG") {}
|
||||
|
||||
void Configuration::setup()
|
||||
{
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
#define DOUGH_CONFIG_H
|
||||
|
||||
#include <Arduino.h>
|
||||
//#include "App/App.h"
|
||||
#include "Network/MQTT.h"
|
||||
#include "UI/Logger.h"
|
||||
#include "Sensors/LowLevel/SensorHCSR04.h"
|
||||
|
||||
|
@ -18,8 +16,6 @@ namespace Dough
|
|||
public:
|
||||
Configuration();
|
||||
void setup();
|
||||
static void handleMqttConnect(MQTT *mqtt);
|
||||
static void handleMqttMessage(String &key, String &value);
|
||||
void setContainerHeight(int height);
|
||||
unsigned int getContainerHeight();
|
||||
void setTemperatureOffset(int offset);
|
||||
|
@ -27,7 +23,6 @@ namespace Dough
|
|||
bool isOk();
|
||||
|
||||
private:
|
||||
MQTT *_mqtt;
|
||||
Logger _logger;
|
||||
unsigned int _containerHeight;
|
||||
bool _containerHeightSet;
|
||||
|
|
|
@ -9,15 +9,18 @@ namespace Dough
|
|||
SensorBase *sensor,
|
||||
unsigned int storageSize,
|
||||
unsigned int minimumMeasureTime,
|
||||
unsigned int minimumPublishTime)
|
||||
SensorControllerCallback onMeasure,
|
||||
unsigned int minimumPublishTime,
|
||||
SensorControllerCallback onPublish)
|
||||
{
|
||||
_mqtt = mqtt;
|
||||
_mqttKey = mqttKey;
|
||||
_sensor = sensor;
|
||||
_storageSize = storageSize;
|
||||
_minimumMeasureTime = minimumMeasureTime;
|
||||
_onMeasure = onMeasure;
|
||||
_minimumPublishTime = minimumPublishTime;
|
||||
_ui = UI::Instance();
|
||||
_onPublish = onPublish;
|
||||
}
|
||||
|
||||
void SensorController::setup()
|
||||
|
@ -42,10 +45,12 @@ namespace Dough
|
|||
{
|
||||
if (_mustMeasure())
|
||||
{
|
||||
_onMeasure();
|
||||
_measure();
|
||||
}
|
||||
if (_mustPublish())
|
||||
{
|
||||
_onPublish();
|
||||
_publish();
|
||||
}
|
||||
}
|
||||
|
@ -69,19 +74,7 @@ namespace Dough
|
|||
{
|
||||
_lastMeasuredAt = millis();
|
||||
|
||||
// Quickly dip the LED to indicate that a measurement has started.
|
||||
// This is done synchroneously, because we suspend the timer interrupts
|
||||
// in the upcoming code.
|
||||
_ui->led3.off();
|
||||
delay(50);
|
||||
_ui->led3.on();
|
||||
|
||||
// Read a measurement from the sensor. Suspend the user interface
|
||||
// interrupts in the meanwhile, to not disturb the timing-sensitive
|
||||
// sensor readings.
|
||||
_ui->suspend();
|
||||
_store(_sensor->read());
|
||||
_ui->resume();
|
||||
}
|
||||
|
||||
bool SensorController::_mustPublish()
|
||||
|
@ -139,14 +132,6 @@ namespace Dough
|
|||
|
||||
void SensorController::_publish()
|
||||
{
|
||||
// Quickly dip the LED to indicate that a publish has started.
|
||||
// This is done synchroneously, because the upcoming code is too
|
||||
// fast normally to register a LED going off and on again during
|
||||
// the operation.
|
||||
_ui->led1.off();
|
||||
delay(50);
|
||||
_ui->led1.on();
|
||||
|
||||
auto average = getAverage();
|
||||
auto last = getLast();
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
namespace Dough
|
||||
{
|
||||
typedef void (*SensorControllerCallback)();
|
||||
|
||||
// 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
|
||||
|
@ -32,16 +34,24 @@ namespace Dough
|
|||
// @param minimumMeasureTime
|
||||
// The number of seconds after which to read the next measurement
|
||||
// from the sensor.
|
||||
// @param onMeasure
|
||||
// A callback function that is called right before a measurement
|
||||
// is taken.
|
||||
// @param minimumPublishTime
|
||||
// The number of seconds after which to forcibly publish measurements
|
||||
// to MQTT, even when no significant changes to measurements were seen.
|
||||
// @param onPublish
|
||||
// A callback function that is called right before a measurement
|
||||
// is published.
|
||||
SensorController(
|
||||
MQTT *mqtt,
|
||||
const char *mqttKey,
|
||||
SensorBase *sensor,
|
||||
unsigned int storageSize,
|
||||
unsigned int minimumMeasureTime,
|
||||
unsigned int minimumPublishTime);
|
||||
SensorControllerCallback onMeasure,
|
||||
unsigned int minimumPublishTime,
|
||||
SensorControllerCallback onPublish);
|
||||
void setup();
|
||||
void loop();
|
||||
Measurement getLast();
|
||||
|
@ -50,7 +60,6 @@ namespace Dough
|
|||
|
||||
private:
|
||||
MQTT *_mqtt;
|
||||
UI *_ui;
|
||||
const char *_mqttKey;
|
||||
char *_mqttAverageKey;
|
||||
SensorBase *_sensor;
|
||||
|
@ -60,10 +69,12 @@ namespace Dough
|
|||
unsigned int _averageCount = 0;
|
||||
unsigned int _index = 0;
|
||||
bool _mustMeasure();
|
||||
SensorControllerCallback _onMeasure;
|
||||
void _measure();
|
||||
unsigned int _minimumMeasureTime;
|
||||
unsigned long _lastMeasuredAt = 0;
|
||||
bool _mustPublish();
|
||||
SensorControllerCallback _onPublish ;
|
||||
void _publish();
|
||||
unsigned int _minimumPublishTime;
|
||||
unsigned long _lastPublishedAt = 0;
|
||||
|
|
108
src/UI/UI.cpp
108
src/UI/UI.cpp
|
@ -2,17 +2,6 @@
|
|||
|
||||
namespace Dough
|
||||
{
|
||||
UI *UI::_instance = nullptr;
|
||||
|
||||
UI *UI::Instance()
|
||||
{
|
||||
if (UI::_instance == nullptr)
|
||||
{
|
||||
UI::_instance = new UI();
|
||||
}
|
||||
return UI::_instance;
|
||||
}
|
||||
|
||||
UI::UI() : onoffButton(ONOFF_BUTTON_PIN),
|
||||
setupButton(SETUP_BUTTON_PIN),
|
||||
ledBuiltin(LED_BUILTIN),
|
||||
|
@ -24,9 +13,7 @@ namespace Dough
|
|||
{
|
||||
// Setup the buttons.
|
||||
onoffButton.setup();
|
||||
onoffButton.onInterrupt(UI::onoffButtonISR);
|
||||
setupButton.setup();
|
||||
setupButton.onInterrupt(UI::setupButtonISR);
|
||||
|
||||
// Setup the LEDs.
|
||||
ledBuiltin.setup();
|
||||
|
@ -47,16 +34,6 @@ namespace Dough
|
|||
resume();
|
||||
}
|
||||
|
||||
void UI::onoffButtonISR()
|
||||
{
|
||||
UI::Instance()->onoffButton.handleButtonState();
|
||||
}
|
||||
|
||||
void UI::setupButtonISR()
|
||||
{
|
||||
UI::Instance()->setupButton.handleButtonState();
|
||||
}
|
||||
|
||||
// Setup a timer interrupt for updating the GUI. Unfortunately, the standard
|
||||
// libraries that I can find for this, are not equipped to work for the
|
||||
// Arduino Nano 33 IOT architecture. Luckily, documentation and various
|
||||
|
@ -91,13 +68,6 @@ namespace Dough
|
|||
; // Wait for synchronization
|
||||
}
|
||||
|
||||
// Disables the TC4 interrupts, suspending timed async updates to
|
||||
// the user interface.
|
||||
void UI::suspend()
|
||||
{
|
||||
NVIC_DisableIRQ(TC4_IRQn);
|
||||
}
|
||||
|
||||
// Enables the TC4 interrupts in the Nested Vector InterruptController (NVIC),
|
||||
// starting timed async updates for the user interface.
|
||||
void UI::resume()
|
||||
|
@ -106,6 +76,13 @@ namespace Dough
|
|||
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
|
||||
}
|
||||
|
||||
// Disables the TC4 interrupts, suspending timed async updates to
|
||||
// the user interface.
|
||||
void UI::suspend()
|
||||
{
|
||||
NVIC_DisableIRQ(TC4_IRQn);
|
||||
}
|
||||
|
||||
// Fire pending button events.
|
||||
void UI::processButtonEvents()
|
||||
{
|
||||
|
@ -125,7 +102,7 @@ namespace Dough
|
|||
// the timer interrupt code from above. The timer interrupt based invocation
|
||||
// makes it possible to do LED updates, while the device is busy doing
|
||||
// something else.
|
||||
void UI::updatedLEDs()
|
||||
void UI::updateLEDs()
|
||||
{
|
||||
ledBuiltin.loop();
|
||||
led1.loop();
|
||||
|
@ -133,7 +110,65 @@ namespace Dough
|
|||
led3.loop();
|
||||
}
|
||||
|
||||
// Flash all LEDs, one at a time.
|
||||
void UI::notifyConnectingToWifi()
|
||||
{
|
||||
led1.blink()->slow();
|
||||
led2.off();
|
||||
led3.off();
|
||||
}
|
||||
|
||||
void UI::notifyConnectingToMQTT()
|
||||
{
|
||||
led1.blink()->fast();
|
||||
led2.off();
|
||||
led3.off();
|
||||
}
|
||||
|
||||
void UI::notifyWaitingForConfiguration()
|
||||
{
|
||||
led1.on();
|
||||
led2.blink()->slow();
|
||||
led3.off();
|
||||
}
|
||||
|
||||
void UI::notifyCalibrating()
|
||||
{
|
||||
led1.on();
|
||||
led2.blink()->fast();
|
||||
led3.off();
|
||||
}
|
||||
|
||||
void UI::notifyMeasurementsActive()
|
||||
{
|
||||
led1.on();
|
||||
led2.on();
|
||||
led3.on();
|
||||
}
|
||||
|
||||
void UI::notifyMeasurementsPaused()
|
||||
{
|
||||
led1.on();
|
||||
led2.on();
|
||||
led3.pulse();
|
||||
}
|
||||
|
||||
void UI::notifySensorActivity()
|
||||
{
|
||||
led3.off();
|
||||
delay(50);
|
||||
led3.on();
|
||||
}
|
||||
|
||||
void UI::notifyNetworkActivity()
|
||||
{
|
||||
led1.off();
|
||||
delay(50);
|
||||
led1.on();
|
||||
}
|
||||
|
||||
// Flash all LEDs, one at a time in a synchroneous manner, making
|
||||
// this work when the UI timer interrupt is inactive. This is used
|
||||
// as a "Hey, I'm awake!" signal from the device after booting up.
|
||||
void UI::_flash_all_leds()
|
||||
{
|
||||
ledBuiltin.on();
|
||||
|
@ -150,12 +185,3 @@ namespace Dough
|
|||
led3.off();
|
||||
}
|
||||
} // namespace Dough
|
||||
|
||||
// This callback is called when the TC4 timer hits an overflow interrupt.
|
||||
// Defined outside the Dough namespace, because TC4_Handler is a hard-coded
|
||||
// root namespace function name.
|
||||
void TC4_Handler()
|
||||
{
|
||||
Dough::UI::Instance()->updatedLEDs();
|
||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
||||
}
|
||||
|
|
15
src/UI/UI.h
15
src/UI/UI.h
|
@ -14,7 +14,7 @@ namespace Dough
|
|||
class UI
|
||||
{
|
||||
public:
|
||||
static UI *Instance();
|
||||
UI();
|
||||
void setup();
|
||||
static void onoffButtonISR();
|
||||
static void setupButtonISR();
|
||||
|
@ -26,16 +26,23 @@ public:
|
|||
LED led3;
|
||||
void processButtonEvents();
|
||||
void clearButtonEvents();
|
||||
void updatedLEDs();
|
||||
void updateLEDs();
|
||||
void resume();
|
||||
void suspend();
|
||||
void notifyConnectingToWifi();
|
||||
void notifyConnectingToMQTT();
|
||||
void notifyWaitingForConfiguration();
|
||||
void notifyCalibrating();
|
||||
void notifyMeasurementsActive();
|
||||
void notifyMeasurementsPaused();
|
||||
void notifySensorActivity();
|
||||
void notifyNetworkActivity();
|
||||
|
||||
private:
|
||||
UI();
|
||||
void _setupTimerInterrupt();
|
||||
static UI *_instance;
|
||||
void _flash_all_leds();
|
||||
};
|
||||
}
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
60
src/main.cpp
60
src/main.cpp
|
@ -1,6 +1,7 @@
|
|||
#include "main.h"
|
||||
|
||||
// TOOD: implement the calibration logic
|
||||
// TODO: don't make sensors instances
|
||||
// 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;
|
||||
|
@ -10,20 +11,18 @@ void setup()
|
|||
{
|
||||
Dough::Logger::setup();
|
||||
logger.log("s", "Initializing device");
|
||||
Dough::App::Instance()->setup();
|
||||
auto ui = Dough::UI::Instance();
|
||||
ui->setup();
|
||||
ui->onoffButton.onPress(handleOnoffButtonPress);
|
||||
ui->setupButton.onPress(handleSetupButtonPress);
|
||||
auto app = Dough::App::Instance();
|
||||
app->setup();
|
||||
app->ui.onoffButton.onPress(handleOnoffButtonPress);
|
||||
app->ui.setupButton.onPress(handleSetupButtonPress);
|
||||
logger.log("s", "Initialization completed, starting device");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
auto app = Dough::App::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
|
||||
ui->processButtonEvents();
|
||||
app->ui.processButtonEvents();
|
||||
|
||||
if (!setupNetworkConnection())
|
||||
{
|
||||
|
@ -66,7 +65,6 @@ bool setupNetworkConnection()
|
|||
static auto connectionState = CONNECTING_WIFI;
|
||||
|
||||
auto app = Dough::App::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
|
||||
if (!app->wifi.isConnected())
|
||||
{
|
||||
|
@ -79,9 +77,7 @@ bool setupNetworkConnection()
|
|||
logger.log("s", "Connecting to the WiFi network ...");
|
||||
}
|
||||
connectionState = CONNECTING_WIFI;
|
||||
ui->led1.blink()->slow();
|
||||
ui->led2.off();
|
||||
ui->led3.off();
|
||||
app->ui.notifyConnectingToWifi();
|
||||
app->wifi.connect();
|
||||
}
|
||||
if (app->wifi.isConnected() && !app->mqtt.isConnected())
|
||||
|
@ -95,9 +91,7 @@ bool setupNetworkConnection()
|
|||
logger.log("s", "Connecting to the MQTT broker ...");
|
||||
}
|
||||
connectionState = CONNECTING_MQTT;
|
||||
ui->led1.blink()->fast();
|
||||
ui->led2.off();
|
||||
ui->led3.off();
|
||||
app->ui.notifyConnectingToMQTT();
|
||||
app->mqtt.connect();
|
||||
delay(1000); // purely costmetic, to make the faster LED blinking noticable
|
||||
}
|
||||
|
@ -106,10 +100,8 @@ bool setupNetworkConnection()
|
|||
if (connectionState != CONNECTED)
|
||||
{
|
||||
logger.log("s", "Connection to MQTT broker established");
|
||||
ui->led1.on();
|
||||
ui->led2.off();
|
||||
ui->led3.off();
|
||||
ui->clearButtonEvents();
|
||||
app->ui.notifyConnected();
|
||||
app->ui.clearButtonEvents();
|
||||
connectionState = CONNECTED;
|
||||
setStateToConfiguring();
|
||||
}
|
||||
|
@ -137,44 +129,36 @@ void handleSetupButtonPress()
|
|||
|
||||
void setStateToConfiguring()
|
||||
{
|
||||
auto ui = Dough::UI::Instance();
|
||||
auto app = Dough::App::Instance();
|
||||
logger.log("s", "Waiting for configuration ...");
|
||||
state = CONFIGURING;
|
||||
ui->led1.on();
|
||||
ui->led2.blink()->fast();
|
||||
ui->led3.off();
|
||||
Dough::App::Instance()->mqtt.publish("state", "configuring");
|
||||
app->ui.notifyWaitingForConfiguration();
|
||||
app->mqtt.publish("state", "configuring");
|
||||
}
|
||||
|
||||
void setStateToMeasuring()
|
||||
{
|
||||
auto ui = Dough::UI::Instance();
|
||||
auto app = Dough::App::Instance();
|
||||
logger.log("s", "Starting measurements");
|
||||
state = MEASURING;
|
||||
ui->led1.on();
|
||||
ui->led2.on();
|
||||
ui->led3.on();
|
||||
Dough::App::Instance()->mqtt.publish("state", "measuring");
|
||||
app->ui.notifyMeasurementsActive();
|
||||
app->mqtt.publish("state", "measuring");
|
||||
}
|
||||
|
||||
void setStateToPaused()
|
||||
{
|
||||
auto ui = Dough::UI::Instance();
|
||||
auto app = Dough::App::Instance();
|
||||
logger.log("s", "Pausing measurements");
|
||||
state = PAUSED;
|
||||
ui->led1.on();
|
||||
ui->led2.on();
|
||||
ui->led3.pulse();
|
||||
Dough::App::Instance()->mqtt.publish("state", "paused");
|
||||
app->ui.notifyMeasurementsPaused();
|
||||
app->mqtt.publish("state", "paused");
|
||||
}
|
||||
|
||||
void setStateToCalibrating()
|
||||
{
|
||||
auto ui = Dough::UI::Instance();
|
||||
auto app = Dough::App::Instance();
|
||||
logger.log("s", "Requested device calibration");
|
||||
state = CALIBRATING;
|
||||
ui->led1.on();
|
||||
ui->led2.blink()->slow();
|
||||
ui->led3.off();
|
||||
Dough::App::Instance()->mqtt.publish("state", "calibrating");
|
||||
app->ui.notifyCalibrating();
|
||||
app->mqtt.publish("state", "calibrating");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue