diff --git a/src/App/App.cpp b/src/App/App.cpp index ad86f40..4dae346 100644 --- a/src/App/App.cpp +++ b/src/App/App.cpp @@ -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(); } } @@ -89,4 +99,33 @@ 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. } \ No newline at end of file diff --git a/src/App/App.h b/src/App/App.h index 7597307..709318f 100644 --- a/src/App/App.h +++ b/src/App/App.h @@ -16,6 +16,7 @@ namespace Dough public: static App *Instance(); Configuration config; + UI ui; WiFi wifi; MQTT mqtt; SensorController temperatureSensor; @@ -33,7 +34,11 @@ namespace Dough } // Callback functions that need to live in the global namespace. -void mqttOnConnectCallback(Dough::MQTT* mqtt); +void mqttOnConnectCallback(Dough::MQTT *mqtt); void mqttOnMessageCallback(String &topic, String &payload); +void onoffButtonInterruptCallback(); +void setupButtonInterruptCallback(); +void sensorOnMeasureCallback(); +void sensorOnPublishCallback(); #endif \ No newline at end of file diff --git a/src/App/Configuration.cpp b/src/App/Configuration.cpp index 4bb9217..fbd0ba8 100644 --- a/src/App/Configuration.cpp +++ b/src/App/Configuration.cpp @@ -2,10 +2,7 @@ namespace Dough { - Configuration::Configuration() : _logger("CONFIG") - { -// _mqtt = MQTT::Instance(); - } + Configuration::Configuration() : _logger("CONFIG") {} void Configuration::setup() { diff --git a/src/App/Configuration.h b/src/App/Configuration.h index 01c7a63..e4d0e32 100644 --- a/src/App/Configuration.h +++ b/src/App/Configuration.h @@ -2,8 +2,6 @@ #define DOUGH_CONFIG_H #include -//#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; diff --git a/src/Data/SensorController.cpp b/src/Data/SensorController.cpp index 3c0abc2..8dcacdb 100644 --- a/src/Data/SensorController.cpp +++ b/src/Data/SensorController.cpp @@ -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(); diff --git a/src/Data/SensorController.h b/src/Data/SensorController.h index 830a2d6..316790c 100644 --- a/src/Data/SensorController.h +++ b/src/Data/SensorController.h @@ -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; diff --git a/src/UI/UI.cpp b/src/UI/UI.cpp index 7ae62e8..4afbd18 100644 --- a/src/UI/UI.cpp +++ b/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(); @@ -149,13 +184,4 @@ namespace Dough delay(100); 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. -} +} // namespace Dough \ No newline at end of file diff --git a/src/UI/UI.h b/src/UI/UI.h index cdebc44..bfb9bd4 100644 --- a/src/UI/UI.h +++ b/src/UI/UI.h @@ -9,33 +9,40 @@ namespace Dough { -// This class groups all user interface functionality: serial logging, -// LEDs and buttons. -class UI -{ -public: - static UI *Instance(); - void setup(); - static void onoffButtonISR(); - static void setupButtonISR(); - Button onoffButton; - Button setupButton; - LED ledBuiltin; - LED led1; - LED led2; - LED led3; - void processButtonEvents(); - void clearButtonEvents(); - void updatedLEDs(); - void resume(); - void suspend(); + // This class groups all user interface functionality: serial logging, + // LEDs and buttons. + class UI + { + public: + UI(); + void setup(); + static void onoffButtonISR(); + static void setupButtonISR(); + Button onoffButton; + Button setupButton; + LED ledBuiltin; + LED led1; + LED led2; + LED led3; + void processButtonEvents(); + void clearButtonEvents(); + 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(); -}; -} + private: + void _setupTimerInterrupt(); + static UI *_instance; + void _flash_all_leds(); + }; +} // namespace Dough #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e7d5113..da71482 100644 --- a/src/main.cpp +++ b/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"); }