From a31840c479c6d8b7ae7174463508e81560f67a4f Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 12 Jul 2020 22:41:23 +0200 Subject: [PATCH] Completed moving all code into its own Dough namespace, to prevent naming clashes with stuff like WiFi and DHT11. --- src/Data/DataController.cpp | 331 +++++++++++++------------- src/Data/DataController.h | 85 +++---- src/Data/Measurement.cpp | 49 ++-- src/Data/Measurement.h | 29 +-- src/Data/Measurements.cpp | 162 ------------- src/Data/Measurements.h | 66 ----- src/Data/SensorController.cpp | 165 +++++++++++++ src/Data/SensorController.h | 65 +++++ src/Network/DoughWiFi.cpp | 87 ------- src/Network/DoughWiFi.h | 30 --- src/Network/MQTT.cpp | 4 +- src/Network/MQTT.h | 12 +- src/Network/WiFi.cpp | 90 +++++++ src/Network/WiFi.h | 31 +++ src/Sensors/DistanceSensor.cpp | 101 ++++---- src/Sensors/DistanceSensor.h | 37 +-- src/Sensors/HumiditySensor.cpp | 73 +++--- src/Sensors/HumiditySensor.h | 29 +-- src/Sensors/LowLevel/SensorDHT11.cpp | 69 +++--- src/Sensors/LowLevel/SensorDHT11.h | 29 +-- src/Sensors/LowLevel/SensorHCSR04.cpp | 261 ++++++++++---------- src/Sensors/LowLevel/SensorHCSR04.h | 61 ++--- src/Sensors/SensorBase.h | 17 +- src/Sensors/TemperatureSensor.cpp | 73 +++--- src/Sensors/TemperatureSensor.h | 29 +-- src/UI/Button.cpp | 146 ++++++++++++ src/UI/Button.h | 54 +++++ src/UI/DoughButton.cpp | 153 ------------ src/UI/DoughButton.h | 53 ----- src/UI/DoughLED.cpp | 173 -------------- src/UI/DoughLED.h | 58 ----- src/UI/DoughLogger.cpp | 70 ------ src/UI/DoughLogger.h | 28 --- src/UI/DoughUI.cpp | 193 --------------- src/UI/LED.cpp | 176 ++++++++++++++ src/UI/LED.h | 59 +++++ src/UI/Logger.cpp | 73 ++++++ src/UI/Logger.h | 29 +++ src/UI/UI.cpp | 182 ++++++++++++++ src/UI/{DoughUI.h => UI.h} | 35 +-- src/main.cpp | 43 ++-- src/main.h | 6 +- 42 files changed, 1766 insertions(+), 1750 deletions(-) delete mode 100644 src/Data/Measurements.cpp delete mode 100644 src/Data/Measurements.h create mode 100644 src/Data/SensorController.cpp create mode 100644 src/Data/SensorController.h delete mode 100644 src/Network/DoughWiFi.cpp delete mode 100644 src/Network/DoughWiFi.h create mode 100644 src/Network/WiFi.cpp create mode 100644 src/Network/WiFi.h create mode 100644 src/UI/Button.cpp create mode 100644 src/UI/Button.h delete mode 100644 src/UI/DoughButton.cpp delete mode 100644 src/UI/DoughButton.h delete mode 100644 src/UI/DoughLED.cpp delete mode 100644 src/UI/DoughLED.h delete mode 100644 src/UI/DoughLogger.cpp delete mode 100644 src/UI/DoughLogger.h delete mode 100644 src/UI/DoughUI.cpp create mode 100644 src/UI/LED.cpp create mode 100644 src/UI/LED.h create mode 100644 src/UI/Logger.cpp create mode 100644 src/UI/Logger.h create mode 100644 src/UI/UI.cpp rename src/UI/{DoughUI.h => UI.h} (64%) diff --git a/src/Data/DataController.cpp b/src/Data/DataController.cpp index 942cdcb..31301ed 100644 --- a/src/Data/DataController.cpp +++ b/src/Data/DataController.cpp @@ -1,176 +1,177 @@ #include "Data/DataController.h" -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -DataController *DataController::_instance = nullptr; - -DataController *DataController::Instance() +namespace Dough { - if (DataController::_instance == nullptr) + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + DataController *DataController::_instance = nullptr; + + DataController *DataController::Instance() { - DataController::_instance = new DataController(); - } - return DataController::_instance; -} - -DataController::DataController() : _temperatureMeasurements( - "temperature", - TemperatureSensor::Instance(), - TEMPERATURE_AVG_LOOKBACK, - TEMPERATURE_SIGNIFICANT_CHANGE, - PUBLISH_INTERVAL), - _humidityMeasurements( - "humidity", - HumiditySensor::Instance(), - HUMIDITY_AVG_LOOKBACK, - HUMIDITY_SIGNIFICANT_CHANGE, - PUBLISH_INTERVAL), - _distanceMeasurements( - "distance", - DistanceSensor::Instance(), - DISTANCE_AVG_LOOKBACK, - DISTANCE_SIGNIFICANT_CHANGE, - PUBLISH_INTERVAL), - _logger("DATA") -{ - _ui = DoughUI::Instance(); - _mqtt = Dough::MQTT::Instance(); -} - -// ---------------------------------------------------------------------- -// Setup -// ---------------------------------------------------------------------- - -void DataController::setup() -{ - _containerHeight = 0.00; - _containerHeightSet = false; - - Dough::MQTT *mqtt = Dough::MQTT::Instance(); - mqtt->onConnect(DataController::handleMqttConnect); - mqtt->onMessage(DataController::handleMqttMessage); - - _temperatureMeasurements.setup(); - _humidityMeasurements.setup(); - _distanceMeasurements.setup(); -} - -void DataController::handleMqttConnect(Dough::MQTT *mqtt) -{ - mqtt->subscribe("container_height"); -} - -void DataController::handleMqttMessage(String &key, String &payload) -{ - if (key.equals("container_height")) - { - DataController::Instance()->setContainerHeight(payload.toInt()); - } - else - { - DataController::Instance()->_logger.log("sS", "ERROR - Unhandled MQTT message, key = ", key); - } -} - -bool DataController::isConfigured() -{ - return _containerHeightSet; -} - -/** - * Set the container height in mm. This is the distance between the sensor - * and the bottom of the container. It is used to determine the height of - * the starter or dough by subtracting the distance measurement from it. - */ -void DataController::setContainerHeight(int height) -{ - _containerHeightSet = false; - if (height <= HCSR04_MIN_MM) - { - _logger.log("sisis", "ERROR - Container height ", height, - "mm is less than the minimum measuring distance of ", - HCSR04_MIN_MM, "mm"); - return; - } - if (height >= HCSR04_MAX_MM) - { - _logger.log("sisis", "ERROR - Container height ", height, - "mm is more than the maximum measuring distance of ", - HCSR04_MAX_MM, "mm"); - return; - } - _logger.log("sis", "Set container height to ", height, "mm"); - _containerHeight = height; - _containerHeightSet = true; -} - -// ---------------------------------------------------------------------- -// Loop -// ---------------------------------------------------------------------- - -void DataController::loop() -{ - if (isConfigured()) - { - _sample(); - } -} - -void DataController::clearHistory() -{ - _temperatureMeasurements.clearHistory(); - _humidityMeasurements.clearHistory(); - _distanceMeasurements.clearHistory(); - _sampleType = SAMPLE_TEMPERATURE; - _sampleCounter = 0; -} - -void DataController::_sample() -{ - auto now = millis(); - auto delta = now - _lastSample; - auto tick = _lastSample == 0 || delta >= SAMPLE_INTERVAL; - - if (tick) - { - _lastSample = now; - - // Quickly dip the LED to indicate that a measurement is started. - // This is done synchroneously, because we suspend the timer interrupts - // in the upcoming code. - _ui->led3.off(); - delay(50); - _ui->led3.on(); - - // Suspend the UI timer interrupts, to not let these interfere - // with the sensor measurements. - _ui->suspend(); - - // Take a sample. - switch (_sampleType) + if (DataController::_instance == nullptr) { - case SAMPLE_TEMPERATURE: - _temperatureMeasurements.process(); - _sampleType = SAMPLE_HUMIDITY; - break; - case SAMPLE_HUMIDITY: - _humidityMeasurements.process(); - _sampleType = SAMPLE_DISTANCE; - break; - case SAMPLE_DISTANCE: - _distanceMeasurements.process(); - break; + DataController::_instance = new DataController(); } + return DataController::_instance; + } - _ui->resume(); + DataController::DataController() : _temperatureMeasurements( + "temperature", + TemperatureSensor::Instance(), + TEMPERATURE_AVG_LOOKBACK, + TEMPERATURE_SIGNIFICANT_CHANGE, + PUBLISH_INTERVAL), + _humidityMeasurements( + "humidity", + HumiditySensor::Instance(), + HUMIDITY_AVG_LOOKBACK, + HUMIDITY_SIGNIFICANT_CHANGE, + PUBLISH_INTERVAL), + _distanceMeasurements( + "distance", + DistanceSensor::Instance(), + DISTANCE_AVG_LOOKBACK, + DISTANCE_SIGNIFICANT_CHANGE, + PUBLISH_INTERVAL), + _logger("DATA") + { + _ui = UI::Instance(); + _mqtt = MQTT::Instance(); + } - _sampleCounter++; - if (_sampleCounter == SAMPLE_CYCLE_LENGTH) + // ---------------------------------------------------------------------- + // Setup + // ---------------------------------------------------------------------- + + void DataController::setup() + { + _containerHeight = 0.00; + _containerHeightSet = false; + + MQTT *mqtt = MQTT::Instance(); + mqtt->onConnect(DataController::handleMqttConnect); + mqtt->onMessage(DataController::handleMqttMessage); + + _temperatureMeasurements.setup(); + _humidityMeasurements.setup(); + _distanceMeasurements.setup(); + } + + void DataController::handleMqttConnect(MQTT *mqtt) + { + mqtt->subscribe("container_height"); + } + + void DataController::handleMqttMessage(String &key, String &payload) + { + if (key.equals("container_height")) { - _sampleCounter = 0; - _sampleType = SAMPLE_TEMPERATURE; + DataController::Instance()->setContainerHeight(payload.toInt()); + } + else + { + DataController::Instance()->_logger.log("sS", "ERROR - Unhandled MQTT message, key = ", key); } } -} \ No newline at end of file + + bool DataController::isConfigured() + { + return _containerHeightSet; + } + + // Set the container height in mm. This is the distance between the sensor + // and the bottom of the container. It is used to determine the height of + // the starter or dough by subtracting the distance measurement from it. + void DataController::setContainerHeight(int height) + { + _containerHeightSet = false; + if (height <= HCSR04_MIN_MM) + { + _logger.log("sisis", "ERROR - Container height ", height, + "mm is less than the minimum measuring distance of ", + HCSR04_MIN_MM, "mm"); + return; + } + if (height >= HCSR04_MAX_MM) + { + _logger.log("sisis", "ERROR - Container height ", height, + "mm is more than the maximum measuring distance of ", + HCSR04_MAX_MM, "mm"); + return; + } + _logger.log("sis", "Set container height to ", height, "mm"); + _containerHeight = height; + _containerHeightSet = true; + } + + // ---------------------------------------------------------------------- + // Loop + // ---------------------------------------------------------------------- + + void DataController::loop() + { + if (isConfigured()) + { + _sample(); + } + } + + void DataController::clearHistory() + { + _temperatureMeasurements.clearHistory(); + _humidityMeasurements.clearHistory(); + _distanceMeasurements.clearHistory(); + _sampleType = SAMPLE_TEMPERATURE; + _sampleCounter = 0; + } + + void DataController::_sample() + { + auto now = millis(); + auto delta = now - _lastSample; + auto tick = _lastSample == 0 || delta >= SAMPLE_INTERVAL; + + if (tick) + { + _lastSample = now; + + // Quickly dip the LED to indicate that a measurement is started. + // This is done synchroneously, because we suspend the timer interrupts + // in the upcoming code. + _ui->led3.off(); + delay(50); + _ui->led3.on(); + + // Suspend the UI timer interrupts, to not let these interfere + // with the sensor measurements. + _ui->suspend(); + + // Take a sample. + switch (_sampleType) + { + case SAMPLE_TEMPERATURE: + _temperatureMeasurements.process(); + _sampleType = SAMPLE_HUMIDITY; + break; + case SAMPLE_HUMIDITY: + _humidityMeasurements.process(); + _sampleType = SAMPLE_DISTANCE; + break; + case SAMPLE_DISTANCE: + _distanceMeasurements.process(); + break; + } + + _ui->resume(); + + _sampleCounter++; + if (_sampleCounter == SAMPLE_CYCLE_LENGTH) + { + _sampleCounter = 0; + _sampleType = SAMPLE_TEMPERATURE; + } + } + } +} // namespace Dough \ No newline at end of file diff --git a/src/Data/DataController.h b/src/Data/DataController.h index fbae401..aa30b22 100644 --- a/src/Data/DataController.h +++ b/src/Data/DataController.h @@ -28,54 +28,55 @@ #define PUBLISH_INTERVAL 300 #include -#include "Data/Measurements.h" +#include "Data/SensorController.h" #include "Sensors/TemperatureSensor.h" #include "Sensors/HumiditySensor.h" #include "Sensors/DistanceSensor.h" -#include "Network/DoughWiFi.h" +#include "Network/WiFi.h" #include "Network/MQTT.h" -#include "UI/DoughUI.h" -#include "UI/DoughLogger.h" +#include "UI/UI.h" +#include "UI/Logger.h" -typedef enum +namespace Dough { - SAMPLE_TEMPERATURE, - SAMPLE_HUMIDITY, - SAMPLE_DISTANCE -} DoughSampleType; + typedef enum + { + SAMPLE_TEMPERATURE, + SAMPLE_HUMIDITY, + SAMPLE_DISTANCE + } DoughSampleType; -/** - * This class is responsible for handling all things "data". - * It holds the device configuration, collects measurements from sensors, - * gathers statistics on these data, and publishing results to the MQTT broker. - */ -class DataController -{ -public: - static DataController *Instance(); - void setup(); - void loop(); - void clearHistory(); - void setContainerHeight(int height); - bool isConfigured(); - static void handleMqttConnect(Dough::MQTT *mqtt); - static void handleMqttMessage(String &key, String &value); + // This class is responsible for handling all things "data". + // It holds the device configuration, collects measurements from sensors, + // gathers statistics on these data, and publishing results to the MQTT broker. + class DataController + { + public: + static DataController *Instance(); + void setup(); + void loop(); + void clearHistory(); + void setContainerHeight(int height); + bool isConfigured(); + static void handleMqttConnect(MQTT *mqtt); + static void handleMqttMessage(String &key, String &value); -private: - DataController(); - static DataController *_instance; - DoughUI *_ui; - Dough::MQTT *_mqtt; - Measurements _temperatureMeasurements; - Measurements _humidityMeasurements; - Measurements _distanceMeasurements; - DoughLogger _logger; - unsigned long _lastSample = 0; - DoughSampleType _sampleType = SAMPLE_TEMPERATURE; - int _sampleCounter = 0; - int _containerHeight; - bool _containerHeightSet; - void _sample(); -}; + private: + DataController(); + static DataController *_instance; + UI *_ui; + MQTT *_mqtt; + SensorController _temperatureMeasurements; + SensorController _humidityMeasurements; + SensorController _distanceMeasurements; + Logger _logger; + unsigned long _lastSample = 0; + DoughSampleType _sampleType = SAMPLE_TEMPERATURE; + int _sampleCounter = 0; + int _containerHeight; + bool _containerHeightSet; + void _sample(); + }; +} // namespace Dough -#endif +#endif \ No newline at end of file diff --git a/src/Data/Measurement.cpp b/src/Data/Measurement.cpp index 24a164b..3ca35cd 100644 --- a/src/Data/Measurement.cpp +++ b/src/Data/Measurement.cpp @@ -1,29 +1,32 @@ #include "Data/Measurement.h" -Measurement::Measurement() { } - -Measurement Measurement::Failed() +namespace Dough { - Measurement m; - return m; -} + Measurement::Measurement() {} -Measurement Measurement::Value(int value) -{ - Measurement m; - m.ok = true; - m.value = value; - return m; -} + Measurement Measurement::Failed() + { + Measurement m; + return m; + } -void Measurement::clear() -{ - ok = false; - value = 0; -} + Measurement Measurement::Value(int value) + { + Measurement m; + m.ok = true; + m.value = value; + return m; + } -void Measurement::copyTo(Measurement* target) -{ - target->ok = ok; - target->value = value; -} \ No newline at end of file + void Measurement::clear() + { + ok = false; + value = 0; + } + + void Measurement::copyTo(Measurement *target) + { + target->ok = ok; + target->value = value; + } +} // namespace Dough \ No newline at end of file diff --git a/src/Data/Measurement.h b/src/Data/Measurement.h index b5c3df5..58e983c 100644 --- a/src/Data/Measurement.h +++ b/src/Data/Measurement.h @@ -1,20 +1,21 @@ #ifndef DOUGH_DATA_MEASUREMENT_H #define DOUGH_DATA_MEASUREMENT_H -/** - * This class represents a single measurement, which can be either a - * successful (bearing a measurement value) or a failed one. - */ -class Measurement +namespace Dough { -public: - Measurement(); - int value = 0; - bool ok = false; - static Measurement Failed(); - static Measurement Value(int value); - void clear(); - void copyTo(Measurement *target); -}; + // This class represents a single measurement, which can be either a + // successful (bearing a measurement value) or a failed one. + class Measurement + { + public: + Measurement(); + int value = 0; + bool ok = false; + static Measurement Failed(); + static Measurement Value(int value); + void clear(); + void copyTo(Measurement *target); + }; +} // namespace Dough #endif diff --git a/src/Data/Measurements.cpp b/src/Data/Measurements.cpp deleted file mode 100644 index eb3a4c9..0000000 --- a/src/Data/Measurements.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "Data/Measurements.h" -#include "UI/DoughUI.h" - -Measurements::Measurements( - const char *mqttKey, - SensorBase *sensor, - unsigned int storageSize, - unsigned int significantChange, - unsigned int minimumPublishTime) -{ - _mqttKey = mqttKey; - _sensor = sensor; - _storageSize = storageSize; - _significantChange = significantChange; - _minimumPublishTime = minimumPublishTime; - _mqtt = Dough::MQTT::Instance(); -} - -void Measurements::setup() -{ - // Format the key to use for publishing the average (i.e. "/average"). - auto lenAverageKey = strlen(_mqttKey) + 9; // +9 for the "/average\0" suffix - _mqttAverageKey = new char[lenAverageKey]; - snprintf(_mqttAverageKey, lenAverageKey, "%s/average", _mqttKey); - - // Initialize the storage for holding the measurements. - _storage = new Measurement *[_storageSize]; - for (unsigned int i = 0; i < _storageSize; i++) - { - _storage[i] = new Measurement; - } - clearHistory(); -} - -void Measurements::process() -{ - auto m = _sensor->read(); - _store(m); - if (_mustPublish()) - { - _publish(); - } -} - -bool Measurements::_mustPublish() -{ - Measurement lastMeasurement = getLast(); - - // When the measurement failed, then there's no need to publish it. - if (lastMeasurement.ok == false) - { - return false; - } - - // When no data was published before, then this is a great time to do so. - if (_lastPublished.ok == false) - { - return true; - } - - // If the value did not change, only publish when the minimum publishing - // time has passed. - if (_lastPublished.value == lastMeasurement.value) - { - auto now = millis(); - auto delta = now - _lastPublishedAt; - return _lastPublishedAt == 0 || delta >= (_minimumPublishTime * 1000); - } - - // When there is a significant change in the sensor value, then publish. - if (abs(_lastPublished.value - lastMeasurement.value) >= _significantChange) - { - return true; - } - - auto average = getAverage(); - - // When there is a significant change in the average value, then publish. - if (average.ok && abs(_lastPublishedAverage.value - average.value) >= _significantChange) - { - return true; - } - - // When the value changed less than the significant change, but it reached - // the current average value, then publish it, since we might have reached - // a stable value. - if (average.ok && average.value == lastMeasurement.value) - { - return true; - } - - // Well, we're out of options. No reason to publish the data right now. - return false; -} - -void Measurements::_publish() -{ - auto average = getAverage(); - auto last = getLast(); - - _mqtt->publish(_mqttKey, last); - _mqtt->publish(_mqttAverageKey, average); - - _lastPublishedAt = millis(); - average.copyTo(&_lastPublishedAverage); - last.copyTo(&_lastPublished); -} - -void Measurements::_store(Measurement measurement) -{ - measurement.copyTo(_storage[_next()]); - - if (measurement.ok) - { - _averageCount++; - _averageSum += measurement.value; - } -} - -unsigned int Measurements::_next() -{ - _index++; - - // Wrap around at the end of the circular buffer. - if (_index == _storageSize) - { - _index = 0; - } - - // If the new position contains an ok value, update the running totals. - if (_storage[_index]->ok) - { - _averageSum -= _storage[_index]->value; - _averageCount--; - } - - _storage[_index]->clear(); - - return _index; -} - -Measurement Measurements::getLast() -{ - return *_storage[_index]; -} - -Measurement Measurements::getAverage() -{ - return _averageCount > 0 - ? Measurement::Value(round(_averageSum / _averageCount)) - : Measurement::Failed(); -} - -void Measurements::clearHistory() -{ - _averageCount = 0; - _averageSum = 0; - for (unsigned int i = 0; i < _storageSize; i++) - { - _storage[i]->clear(); - } -} diff --git a/src/Data/Measurements.h b/src/Data/Measurements.h deleted file mode 100644 index d2d611d..0000000 --- a/src/Data/Measurements.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef DOUGH_DATA_MEASUREMENTS_H -#define DOUGH_DATA_MEASUREMENTS_H - -#include -#include "Sensors/SensorBase.h" -#include "Data/Measurement.h" -#include "Network/MQTT.h" - -/** - * 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 publish measurements - * to MQTT (after significant changes occur or when the last publish - * was too long ago). - */ -class Measurements -{ -public: - /** - * Create a new Measurements object. - * - * @param sensor - * The sensor to read, implements SensorBase. - * @param storageSize - * Number of measurements to keep track of for computing an average. - * @param significantChange - * Number that describes how much a measurement value needs to change, - * before it is considered significant and must be published to MQTT. - * @param minimumPublishTime - * The number of seconds after which to forcibly publish measurements - * to MQTT, even when no significant changes to measurements were seen. - */ - Measurements( - const char *mqttKey, - SensorBase *sensor, - unsigned int storageSize, - unsigned int significantChange, - unsigned int minimumPublishTime); - void setup(); - void process(); - Measurement getLast(); - Measurement getAverage(); - void clearHistory(); - -private: - Dough::MQTT *_mqtt; - const char *_mqttKey; - char *_mqttAverageKey; - SensorBase *_sensor; - Measurement **_storage; - unsigned int _storageSize; - unsigned int _significantChange; - unsigned int _minimumPublishTime; - int _averageSum = 0; - unsigned int _averageCount = 0; - unsigned int _index = 0; - unsigned long _lastPublishedAt = 0; - Measurement _lastPublished; - Measurement _lastPublishedAverage; - bool _mustPublish(); - void _publish(); - void _store(Measurement measurement); - unsigned int _next(); -}; - -#endif diff --git a/src/Data/SensorController.cpp b/src/Data/SensorController.cpp new file mode 100644 index 0000000..f67b3bc --- /dev/null +++ b/src/Data/SensorController.cpp @@ -0,0 +1,165 @@ +#include "Data/SensorController.h" +#include "UI/UI.h" + +namespace Dough +{ + SensorController::SensorController( + const char *mqttKey, + SensorBase *sensor, + unsigned int storageSize, + unsigned int significantChange, + unsigned int minimumPublishTime) + { + _mqttKey = mqttKey; + _sensor = sensor; + _storageSize = storageSize; + _significantChange = significantChange; + _minimumPublishTime = minimumPublishTime; + _mqtt = MQTT::Instance(); + } + + void SensorController::setup() + { + // Format the key to use for publishing the average (i.e. "/average"). + auto lenAverageKey = strlen(_mqttKey) + 9; // +9 for the "/average\0" suffix + _mqttAverageKey = new char[lenAverageKey]; + snprintf(_mqttAverageKey, lenAverageKey, "%s/average", _mqttKey); + + // Initialize the storage for holding the measurements. + _storage = new Measurement *[_storageSize]; + for (unsigned int i = 0; i < _storageSize; i++) + { + _storage[i] = new Measurement; + } + clearHistory(); + } + + void SensorController::process() + { + auto m = _sensor->read(); + _store(m); + if (_mustPublish()) + { + _publish(); + } + } + + bool SensorController::_mustPublish() + { + Measurement lastMeasurement = getLast(); + + // When the measurement failed, then there's no need to publish it. + if (lastMeasurement.ok == false) + { + return false; + } + + // When no data was published before, then this is a great time to do so. + if (_lastPublished.ok == false) + { + return true; + } + + // If the value did not change, only publish when the minimum publishing + // time has passed. + if (_lastPublished.value == lastMeasurement.value) + { + auto now = millis(); + auto delta = now - _lastPublishedAt; + return _lastPublishedAt == 0 || delta >= (_minimumPublishTime * 1000); + } + + // When there is a significant change in the sensor value, then publish. + if (abs(_lastPublished.value - lastMeasurement.value) >= _significantChange) + { + return true; + } + + auto average = getAverage(); + + // When there is a significant change in the average value, then publish. + if (average.ok && abs(_lastPublishedAverage.value - average.value) >= _significantChange) + { + return true; + } + + // When the value changed less than the significant change, but it reached + // the current average value, then publish it, since we might have reached + // a stable value. + if (average.ok && average.value == lastMeasurement.value) + { + return true; + } + + // Well, we're out of options. No reason to publish the data right now. + return false; + } + + void SensorController::_publish() + { + auto average = getAverage(); + auto last = getLast(); + + _mqtt->publish(_mqttKey, last); + _mqtt->publish(_mqttAverageKey, average); + + _lastPublishedAt = millis(); + average.copyTo(&_lastPublishedAverage); + last.copyTo(&_lastPublished); + } + + void SensorController::_store(Measurement measurement) + { + measurement.copyTo(_storage[_next()]); + + if (measurement.ok) + { + _averageCount++; + _averageSum += measurement.value; + } + } + + unsigned int SensorController::_next() + { + _index++; + + // Wrap around at the end of the circular buffer. + if (_index == _storageSize) + { + _index = 0; + } + + // If the new position contains an ok value, update the running totals. + if (_storage[_index]->ok) + { + _averageSum -= _storage[_index]->value; + _averageCount--; + } + + _storage[_index]->clear(); + + return _index; + } + + Measurement SensorController::getLast() + { + return *_storage[_index]; + } + + Measurement SensorController::getAverage() + { + return _averageCount > 0 + ? Measurement::Value(round(_averageSum / _averageCount)) + : Measurement::Failed(); + } + + void SensorController::clearHistory() + { + _averageCount = 0; + _averageSum = 0; + for (unsigned int i = 0; i < _storageSize; i++) + { + _storage[i]->clear(); + } + } +} // namespace Dough \ No newline at end of file diff --git a/src/Data/SensorController.h b/src/Data/SensorController.h new file mode 100644 index 0000000..855f6c6 --- /dev/null +++ b/src/Data/SensorController.h @@ -0,0 +1,65 @@ +#ifndef DOUGH_DATA_MEASUREMENTS_H +#define DOUGH_DATA_MEASUREMENTS_H + +#include +#include "Sensors/SensorBase.h" +#include "Data/Measurement.h" +#include "Network/MQTT.h" + +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 publish measurements + // to MQTT (after significant changes occur or when the last publish + // was too long ago). + class SensorController + { + public: + // Create a new Measurements object. + // + // @param sensor + // The sensor to read, implements SensorBase. + // @param storageSize + // Number of measurements to keep track of for computing an average. + // @param significantChange + // Number that describes how much a measurement value needs to change, + // before it is considered significant and must be published to MQTT. + // @param minimumPublishTime + // The number of seconds after which to forcibly publish measurements + // to MQTT, even when no significant changes to measurements were seen. + SensorController( + const char *mqttKey, + SensorBase *sensor, + unsigned int storageSize, + unsigned int significantChange, + unsigned int minimumPublishTime); + void setup(); + void process(); + Measurement getLast(); + Measurement getAverage(); + void clearHistory(); + + private: + MQTT *_mqtt; + const char *_mqttKey; + char *_mqttAverageKey; + SensorBase *_sensor; + Measurement **_storage; + unsigned int _storageSize; + unsigned int _significantChange; + unsigned int _minimumPublishTime; + int _averageSum = 0; + unsigned int _averageCount = 0; + unsigned int _index = 0; + unsigned long _lastPublishedAt = 0; + Measurement _lastPublished; + Measurement _lastPublishedAverage; + bool _mustPublish(); + void _publish(); + void _store(Measurement measurement); + unsigned int _next(); + }; +} // namespace Dough + +#endif diff --git a/src/Network/DoughWiFi.cpp b/src/Network/DoughWiFi.cpp deleted file mode 100644 index d6b6a82..0000000 --- a/src/Network/DoughWiFi.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "Network/DoughWiFi.h" - -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -DoughWiFi *DoughWiFi::_instance = nullptr; - -DoughWiFi *DoughWiFi::Instance() -{ - if (DoughWiFi::_instance == nullptr) - { - DoughWiFi::_instance = new DoughWiFi(); - } - return DoughWiFi::_instance; -} - -DoughWiFi::DoughWiFi() : _logger("WIFI") {} - -// ---------------------------------------------------------------------- -// Setup -// ---------------------------------------------------------------------- - -void DoughWiFi::_setMacAddress() -{ - byte mac[6]; - WiFi.macAddress(mac); - snprintf( - _macAddress, sizeof(_macAddress) / sizeof(_macAddress[0]), - "%x:%x:%x:%x:%x:%x", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]); -} - -void DoughWiFi::setup() -{ - _setMacAddress(); - _logger.log("ss", "MAC address = ", getMacAddress()); -} - -// ---------------------------------------------------------------------- -// Loop -// ---------------------------------------------------------------------- - -bool DoughWiFi::isConnected() -{ - return WiFi.status() == WL_CONNECTED; -} - -bool DoughWiFi::connect() -{ - int status = WiFi.status(); - - // Check if a device with a WiFi shield is used. - if (status == WL_NO_SHIELD) - { - _logger.log("s", "ERROR - Device has no WiFi shield"); - delay(5000); - return false; - } - - // Check if the WiFi network is already up. - if (status == WL_CONNECTED) - { - return true; - } - - // Setup the connection to the WiFi network. - _logger.log("ss", "WiFi network = ", WIFI_SSID); - status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - - // Check if the connection attempt was successful. - if (status == WL_CONNECTED) - { - _logger.log("sa", "IP-Address = ", WiFi.localIP()); - _logger.log("sis", "Signal strength = ", WiFi.RSSI(), " dBm"); - return true; - } - else - { - _logger.log("sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")"); - return false; - } -} - -char *DoughWiFi::getMacAddress() -{ - return _macAddress; -} diff --git a/src/Network/DoughWiFi.h b/src/Network/DoughWiFi.h deleted file mode 100644 index 2888a0f..0000000 --- a/src/Network/DoughWiFi.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef DOUGH_NETWORK_H -#define DOUGH_NETWORK_H - -#include -#include "UI/DoughLogger.h" -#include "config.h" - -/** - * This class encapsulates the connection to the WiFi network. - */ -class DoughWiFi -{ -public: - static DoughWiFi *Instance(); - char *getMacAddress(); - void setup(); - void loop(); - bool isConnected(); - bool connect(); - WiFiClient client; - -private: - DoughWiFi(); - static DoughWiFi *_instance; - void _setMacAddress(); - char _macAddress[18]; // max MAC address length + 1 - DoughLogger _logger; -}; - -#endif diff --git a/src/Network/MQTT.cpp b/src/Network/MQTT.cpp index 2b3e8a0..3af39a4 100644 --- a/src/Network/MQTT.cpp +++ b/src/Network/MQTT.cpp @@ -1,4 +1,4 @@ -#include "MQTT.h" +#include "Network/MQTT.h" namespace Dough { @@ -26,7 +26,7 @@ namespace Dough void MQTT::setup() { - DoughWiFi *network = DoughWiFi::Instance(); + WiFi *network = WiFi::Instance(); #ifdef MQTT_DEVICE_ID _mqttDeviceId = MQTT_DEVICE_ID; diff --git a/src/Network/MQTT.h b/src/Network/MQTT.h index f382119..ef69b90 100644 --- a/src/Network/MQTT.h +++ b/src/Network/MQTT.h @@ -3,17 +3,15 @@ #include #include -#include "Network/DoughWiFi.h" +#include "Network/WiFi.h" #include "Data/Measurement.h" -#include "UI/DoughLogger.h" +#include "UI/Logger.h" #include "config.h" namespace Dough { - /** - * This class encapsulates the connection to the MQTT broker. - * MQTT is used to publish measurements and to store configuration data. - */ + // This class encapsulates the connection to the MQTT broker. + // MQTT is used to publish measurements and to store configuration data. class MQTT; typedef void (*MQTTConnectHandler)(MQTT *mqtt); @@ -38,7 +36,7 @@ namespace Dough MQTT(); static MQTT *_instance; MQTTClient _mqttClient; - DoughLogger _logger; + Logger _logger; MQTTConnectHandler _onConnect = nullptr; MQTTClientCallbackSimple _onMessage = nullptr; static void handleMessage(String &topic, String &payload); diff --git a/src/Network/WiFi.cpp b/src/Network/WiFi.cpp new file mode 100644 index 0000000..45b43d3 --- /dev/null +++ b/src/Network/WiFi.cpp @@ -0,0 +1,90 @@ +#include "Network/WiFi.h" + +namespace Dough +{ + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + WiFi *WiFi::_instance = nullptr; + + WiFi *WiFi::Instance() + { + if (WiFi::_instance == nullptr) + { + WiFi::_instance = new WiFi(); + } + return WiFi::_instance; + } + + WiFi::WiFi() : _logger("WIFI") {} + + // ---------------------------------------------------------------------- + // Setup + // ---------------------------------------------------------------------- + + void WiFi::_setMacAddress() + { + byte mac[6]; + ::WiFi.macAddress(mac); + snprintf( + _macAddress, sizeof(_macAddress) / sizeof(_macAddress[0]), + "%x:%x:%x:%x:%x:%x", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]); + } + + void WiFi::setup() + { + _setMacAddress(); + _logger.log("ss", "MAC address = ", getMacAddress()); + } + + // ---------------------------------------------------------------------- + // Loop + // ---------------------------------------------------------------------- + + bool WiFi::isConnected() + { + return ::WiFi.status() == WL_CONNECTED; + } + + bool WiFi::connect() + { + int status = ::WiFi.status(); + + // Check if a device with a WiFi shield is used. + if (status == WL_NO_SHIELD) + { + _logger.log("s", "ERROR - Device has no WiFi shield"); + delay(5000); + return false; + } + + // Check if the WiFi network is already up. + if (status == WL_CONNECTED) + { + return true; + } + + // Setup the connection to the WiFi network. + _logger.log("ss", "WiFi network = ", WIFI_SSID); + status = ::WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + + // Check if the connection attempt was successful. + if (status == WL_CONNECTED) + { + _logger.log("sa", "IP-Address = ", ::WiFi.localIP()); + _logger.log("sis", "Signal strength = ", ::WiFi.RSSI(), " dBm"); + return true; + } + else + { + _logger.log("sis", "ERROR - WiFi connection failed (reason: ", ::WiFi.reasonCode(), ")"); + return false; + } + } + + char *WiFi::getMacAddress() + { + return _macAddress; + } +} // namespace Dough \ No newline at end of file diff --git a/src/Network/WiFi.h b/src/Network/WiFi.h new file mode 100644 index 0000000..b89669a --- /dev/null +++ b/src/Network/WiFi.h @@ -0,0 +1,31 @@ +#ifndef DOUGH_NETWORK_H +#define DOUGH_NETWORK_H + +#include +#include "UI/Logger.h" +#include "config.h" + +namespace Dough +{ + // This class encapsulates the connection to the WiFi network. + class WiFi + { + public: + static WiFi *Instance(); + char *getMacAddress(); + void setup(); + void loop(); + bool isConnected(); + bool connect(); + WiFiClient client; + + private: + WiFi(); + static WiFi *_instance; + void _setMacAddress(); + char _macAddress[18]; // max MAC address length + 1 + Logger _logger; + }; +} // namespace Dough + +#endif \ No newline at end of file diff --git a/src/Sensors/DistanceSensor.cpp b/src/Sensors/DistanceSensor.cpp index 370049f..b152ae4 100644 --- a/src/Sensors/DistanceSensor.cpp +++ b/src/Sensors/DistanceSensor.cpp @@ -1,59 +1,62 @@ #include "DistanceSensor.h" -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -DistanceSensor *DistanceSensor::_instance = nullptr; - -DistanceSensor *DistanceSensor::Instance() +namespace Dough { - if (DistanceSensor::_instance == nullptr) + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + DistanceSensor *DistanceSensor::_instance = nullptr; + + DistanceSensor *DistanceSensor::Instance() { - DistanceSensor::_instance = new DistanceSensor(); + if (DistanceSensor::_instance == nullptr) + { + DistanceSensor::_instance = new DistanceSensor(); + } + return DistanceSensor::_instance; } - return DistanceSensor::_instance; -} -DistanceSensor::DistanceSensor() : _logger("DISTANCE") -{ - _hcsr04 = new SensorHCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN); -} - -// ---------------------------------------------------------------------- -// setup -// ---------------------------------------------------------------------- - -void DistanceSensor::setup() -{ - _hcsr04->setup(); -} - -void DistanceSensor::setTemperature(int temperature) -{ - _hcsr04->setTemperature(temperature); -} - -void DistanceSensor::setHumidity(int humidity) -{ - _hcsr04->setHumidity(humidity); -} - -// ---------------------------------------------------------------------- -// loop -// ---------------------------------------------------------------------- - -Measurement DistanceSensor::read() -{ - int d = _hcsr04->readDistance(); - if (d == -1) + DistanceSensor::DistanceSensor() : _logger("DISTANCE") { - _logger.log("s", "ERROR - Distance measurement failed"); - return Measurement::Failed(); + _hcsr04 = new SensorHCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN); } - else + + // ---------------------------------------------------------------------- + // setup + // ---------------------------------------------------------------------- + + void DistanceSensor::setup() { - _logger.log("sis", "Distance = ", d, "mm"); - return Measurement::Value(d); + _hcsr04->setup(); } -} \ No newline at end of file + + void DistanceSensor::setTemperature(int temperature) + { + _hcsr04->setTemperature(temperature); + } + + void DistanceSensor::setHumidity(int humidity) + { + _hcsr04->setHumidity(humidity); + } + + // ---------------------------------------------------------------------- + // loop + // ---------------------------------------------------------------------- + + Measurement DistanceSensor::read() + { + int d = _hcsr04->readDistance(); + if (d == -1) + { + _logger.log("s", "ERROR - Distance measurement failed"); + return Measurement::Failed(); + } + else + { + _logger.log("sis", "Distance = ", d, "mm"); + return Measurement::Value(d); + } + } +} // namespace Dough \ No newline at end of file diff --git a/src/Sensors/DistanceSensor.h b/src/Sensors/DistanceSensor.h index 418768e..b1193e4 100644 --- a/src/Sensors/DistanceSensor.h +++ b/src/Sensors/DistanceSensor.h @@ -3,27 +3,28 @@ #include "Sensors/SensorBase.h" #include "Sensors/LowLevel/SensorHCSR04.h" -#include "UI/DoughLogger.h" +#include "UI/Logger.h" #include "Data/Measurement.h" #include "config.h" -/** - * This class provides access to the distance sensor in the device. - */ -class DistanceSensor : public SensorBase +namespace Dough { -public: - static DistanceSensor *Instance(); - virtual void setup(); - virtual Measurement read(); - void setTemperature(int temperature); - void setHumidity(int humidity); + // This class provides access to the distance sensor in the device. + class DistanceSensor : public SensorBase + { + public: + static DistanceSensor *Instance(); + virtual void setup(); + virtual Measurement read(); + void setTemperature(int temperature); + void setHumidity(int humidity); -private: - DistanceSensor(); - static DistanceSensor *_instance; - DoughLogger _logger; - SensorHCSR04 *_hcsr04; -}; + private: + DistanceSensor(); + static DistanceSensor *_instance; + Logger _logger; + SensorHCSR04 *_hcsr04; + }; +} // namespace Dough -#endif +#endif \ No newline at end of file diff --git a/src/Sensors/HumiditySensor.cpp b/src/Sensors/HumiditySensor.cpp index 334d34a..ac9f121 100644 --- a/src/Sensors/HumiditySensor.cpp +++ b/src/Sensors/HumiditySensor.cpp @@ -1,47 +1,50 @@ #include "HumiditySensor.h" -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -HumiditySensor *HumiditySensor::_instance = nullptr; - -HumiditySensor *HumiditySensor::Instance() +namespace Dough { - if (HumiditySensor::_instance == nullptr) + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + HumiditySensor *HumiditySensor::_instance = nullptr; + + HumiditySensor *HumiditySensor::Instance() { - HumiditySensor::_instance = new HumiditySensor(); + if (HumiditySensor::_instance == nullptr) + { + HumiditySensor::_instance = new HumiditySensor(); + } + return HumiditySensor::_instance; } - return HumiditySensor::_instance; -} -HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {} + HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {} -// ---------------------------------------------------------------------- -// setup -// ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // setup + // ---------------------------------------------------------------------- -void HumiditySensor::setup() -{ - SensorDHT11::Instance()->begin(); -} - -// ---------------------------------------------------------------------- -// loop -// ---------------------------------------------------------------------- - -Measurement HumiditySensor::read() -{ - float t = SensorDHT11::Instance()->readHumidity(); - if (t == -1) + void HumiditySensor::setup() { - _logger.log("s", "ERROR - Humidity measurement failed"); - return Measurement::Failed(); + SensorDHT11::Instance()->begin(); } - else + + // ---------------------------------------------------------------------- + // loop + // ---------------------------------------------------------------------- + + Measurement HumiditySensor::read() { - _logger.log("sis", "Humidity = ", int(t), "%"); - DistanceSensor::Instance()->setHumidity(int(t)); - return Measurement::Value(int(t)); + float t = SensorDHT11::Instance()->readHumidity(); + if (t == -1) + { + _logger.log("s", "ERROR - Humidity measurement failed"); + return Measurement::Failed(); + } + else + { + _logger.log("sis", "Humidity = ", int(t), "%"); + DistanceSensor::Instance()->setHumidity(int(t)); + return Measurement::Value(int(t)); + } } -} \ No newline at end of file +} // namespace Dough \ No newline at end of file diff --git a/src/Sensors/HumiditySensor.h b/src/Sensors/HumiditySensor.h index d6234aa..26eda65 100644 --- a/src/Sensors/HumiditySensor.h +++ b/src/Sensors/HumiditySensor.h @@ -3,25 +3,26 @@ #include "Sensors/SensorBase.h" #include "Sensors/LowLevel/SensorDHT11.h" -#include "UI/DoughLogger.h" +#include "UI/Logger.h" #include "Data/Measurement.h" #include "Sensors/DistanceSensor.h" #include "config.h" -/** - * This class provides access to the humidity sensor in the device. - */ -class HumiditySensor : public SensorBase +namespace Dough { -public: - static HumiditySensor *Instance(); - virtual void setup(); - virtual Measurement read(); + // This class provides access to the humidity sensor in the device. + class HumiditySensor : public SensorBase + { + public: + static HumiditySensor *Instance(); + virtual void setup(); + virtual Measurement read(); -private: - HumiditySensor(); - static HumiditySensor *_instance; - DoughLogger _logger; -}; + private: + HumiditySensor(); + static HumiditySensor *_instance; + Logger _logger; + }; +} // namespace Dough #endif \ No newline at end of file diff --git a/src/Sensors/LowLevel/SensorDHT11.cpp b/src/Sensors/LowLevel/SensorDHT11.cpp index cd67b5e..f6789fd 100644 --- a/src/Sensors/LowLevel/SensorDHT11.cpp +++ b/src/Sensors/LowLevel/SensorDHT11.cpp @@ -1,44 +1,47 @@ #include "Sensors/LowLevel/SensorDHT11.h" -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -SensorDHT11 *SensorDHT11::_instance = nullptr; - -SensorDHT11 *SensorDHT11::Instance() +namespace Dough { - if (SensorDHT11::_instance == nullptr) + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + SensorDHT11 *SensorDHT11::_instance = nullptr; + + SensorDHT11 *SensorDHT11::Instance() { - SensorDHT11::_instance = new SensorDHT11(); + if (SensorDHT11::_instance == nullptr) + { + SensorDHT11::_instance = new SensorDHT11(); + } + return SensorDHT11::_instance; } - return SensorDHT11::_instance; -} -SensorDHT11::SensorDHT11() -{ - _dht = new DHT(DHT11_DATA_PIN, DHT11); -} + SensorDHT11::SensorDHT11() + { + _dht = new DHT(DHT11_DATA_PIN, DHT11); + } -// ---------------------------------------------------------------------- -// setup -// ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // setup + // ---------------------------------------------------------------------- -void SensorDHT11::begin() -{ - _dht->begin(); -} + void SensorDHT11::begin() + { + _dht->begin(); + } -// ---------------------------------------------------------------------- -// loop -// ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // loop + // ---------------------------------------------------------------------- -float SensorDHT11::readHumidity() -{ - return _dht->readHumidity(); -} + float SensorDHT11::readHumidity() + { + return _dht->readHumidity(); + } -float SensorDHT11::readTemperature() -{ - return _dht->readTemperature(); -} \ No newline at end of file + float SensorDHT11::readTemperature() + { + return _dht->readTemperature(); + } +} // namespace Dough \ No newline at end of file diff --git a/src/Sensors/LowLevel/SensorDHT11.h b/src/Sensors/LowLevel/SensorDHT11.h index 199bee1..dbe07a8 100644 --- a/src/Sensors/LowLevel/SensorDHT11.h +++ b/src/Sensors/LowLevel/SensorDHT11.h @@ -4,21 +4,22 @@ #include #include "config.h" -/** - * This class provides access to the DHT11 sensor in the device. - */ -class SensorDHT11 +namespace Dough { -public: - static SensorDHT11 *Instance(); - void begin(); - float readTemperature(); - float readHumidity(); + // This class provides access to the DHT11 sensor in the device. + class SensorDHT11 + { + public: + static SensorDHT11 *Instance(); + void begin(); + float readTemperature(); + float readHumidity(); -private: - SensorDHT11(); - static SensorDHT11 *_instance; - DHT *_dht; -}; + private: + SensorDHT11(); + static SensorDHT11 *_instance; + DHT *_dht; + }; +} // namespace Dough #endif \ No newline at end of file diff --git a/src/Sensors/LowLevel/SensorHCSR04.cpp b/src/Sensors/LowLevel/SensorHCSR04.cpp index a58203b..4eab504 100644 --- a/src/Sensors/LowLevel/SensorHCSR04.cpp +++ b/src/Sensors/LowLevel/SensorHCSR04.cpp @@ -1,155 +1,152 @@ #include "Sensors/LowLevel/SensorHCSR04.h" -SensorHCSR04::SensorHCSR04(int triggerPin, int echoPin) : _logger("HCSR04") +namespace Dough { - _triggerPin = triggerPin; - _echoPin = echoPin; - _temperature = HCSR04_INIT_TEMPERATURE; - _humidity = HCSR04_INIT_HUMIDITY; - #ifndef HCSR04_DEBUG - _logger.suspend(); - #endif -} - -void SensorHCSR04::setup() -{ - _logger.log("sisi", "Setup output pin ", _triggerPin, " and input pin ", _echoPin); - pinMode(_triggerPin, OUTPUT); - pinMode(_echoPin, INPUT); -} - -void SensorHCSR04::setTemperature(int temperature) -{ - _logger.log("sis", "Set temperature to ", temperature, "°C"); - _temperature = temperature; -} - -void SensorHCSR04::setHumidity(int humidity) -{ - _logger.log("sis", "Set humidity to ", humidity, "%"); - _humidity = humidity; -} - -/** - * Get a distance reading. - * When reading the distance fails, -1 is returned. - * Otherwise the distance in mm. - */ -int SensorHCSR04::readDistance() -{ - _setSpeedOfSound(); - _setEchoTimeout(); - _takeSamples(); - if (_haveEnoughSamples()) + SensorHCSR04::SensorHCSR04(int triggerPin, int echoPin) : _logger("HCSR04") { - _sortSamples(); - return _computeAverage(); + _triggerPin = triggerPin; + _echoPin = echoPin; + _temperature = HCSR04_INIT_TEMPERATURE; + _humidity = HCSR04_INIT_HUMIDITY; +#ifndef HCSR04_DEBUG + _logger.suspend(); +#endif } - return -1; -} -/** - * Sets the speed of sound in mm/Ms, depending on the temperature - * and relative humidity. I derived this formula from a YouTube - * video about the HC-SR04: https://youtu.be/6F1B_N6LuKw?t=1548 - */ -void SensorHCSR04::_setSpeedOfSound() -{ - _speedOfSound = - 0.3314 + - (0.000606 * _temperature) + - (0.0000124 * _humidity); - _logger.log("sfs", "Speed of sound = ", _speedOfSound, "mm/Ms"); -} - -void SensorHCSR04::_setEchoTimeout() -{ - _echoTimeout = HCSR04_MAX_MM * 2 / _speedOfSound; - _logger.log("sfs", "Echo timeout = ", _echoTimeout, "Ms"); -} - -void SensorHCSR04::_takeSamples() -{ - _successfulSamples = 0; - for (int i = 0; i < HCSR04_SAMPLES_TAKE; i++) + void SensorHCSR04::setup() { - // Because I notice some repeating patterns in timings when doing - // a tight loop here, I add some random waits to get a better spread - // of sample values. - if (i > 0) - { - delay(HCSR04_SAMPLE_WAIT + random(HCSR04_SAMPLE_WAIT_SPREAD)); - } - int distance = _takeSample(); - if (distance != -1) - { - _samples[i] = distance; - _successfulSamples++; - } + _logger.log("sisi", "Setup output pin ", _triggerPin, " and input pin ", _echoPin); + pinMode(_triggerPin, OUTPUT); + pinMode(_echoPin, INPUT); } -} -bool SensorHCSR04::_haveEnoughSamples() -{ - return _successfulSamples >= HCSR04_SAMPLES_USE; -} - -int SensorHCSR04::_takeSample() -{ - // Send 10μs trigger to ask sensor for a measurement. - digitalWrite(HCSR04_TRIG_PIN, LOW); - delayMicroseconds(2); - digitalWrite(HCSR04_TRIG_PIN, HIGH); - delayMicroseconds(10); - digitalWrite(HCSR04_TRIG_PIN, LOW); - - // Measure the length of echo signal. - unsigned long durationMicroSec = pulseIn(HCSR04_ECHO_PIN, HIGH, _echoTimeout); - - // Compute the distance, based on the echo signal length. - double distance = durationMicroSec / 2.0 * _speedOfSound; - _logger.log("sfs", "Sample result = ", distance, "mm"); - if (distance < HCSR04_MIN_MM || distance >= HCSR04_MAX_MM) + void SensorHCSR04::setTemperature(int temperature) { + _logger.log("sis", "Set temperature to ", temperature, "°C"); + _temperature = temperature; + } + + void SensorHCSR04::setHumidity(int humidity) + { + _logger.log("sis", "Set humidity to ", humidity, "%"); + _humidity = humidity; + } + + // Get a distance reading. + // When reading the distance fails, -1 is returned. + // Otherwise the distance in mm. + int SensorHCSR04::readDistance() + { + _setSpeedOfSound(); + _setEchoTimeout(); + _takeSamples(); + if (_haveEnoughSamples()) + { + _sortSamples(); + return _computeAverage(); + } return -1; } - else - { - return distance; - } -} -void SensorHCSR04::_sortSamples() -{ - int holder, x, y; - for (x = 0; x < _successfulSamples; x++) + // Sets the speed of sound in mm/Ms, depending on the temperature + // and relative humidity. I derived this formula from a YouTube + // video about the HC-SR04: https://youtu.be/6F1B_N6LuKw?t=1548 + void SensorHCSR04::_setSpeedOfSound() { - for (y = 0; y < _successfulSamples - 1; y++) + _speedOfSound = + 0.3314 + + (0.000606 * _temperature) + + (0.0000124 * _humidity); + _logger.log("sfs", "Speed of sound = ", _speedOfSound, "mm/Ms"); + } + + void SensorHCSR04::_setEchoTimeout() + { + _echoTimeout = HCSR04_MAX_MM * 2 / _speedOfSound; + _logger.log("sfs", "Echo timeout = ", _echoTimeout, "Ms"); + } + + void SensorHCSR04::_takeSamples() + { + _successfulSamples = 0; + for (int i = 0; i < HCSR04_SAMPLES_TAKE; i++) { - if (_samples[y] > _samples[y + 1]) + // Because I notice some repeating patterns in timings when doing + // a tight loop here, I add some random waits to get a better spread + // of sample values. + if (i > 0) { - holder = _samples[y + 1]; - _samples[y + 1] = _samples[y]; - _samples[y] = holder; + delay(HCSR04_SAMPLE_WAIT + random(HCSR04_SAMPLE_WAIT_SPREAD)); + } + int distance = _takeSample(); + if (distance != -1) + { + _samples[i] = distance; + _successfulSamples++; } } } -} -/** - * Compute the average of the samples. To get rid of measuring extremes, - * only a subset of measurements from the middle are used. - * When not enough samples were collected in the previous steps, then - * NAN is returned. - */ -int SensorHCSR04::_computeAverage() -{ - float sum = 0; - int offset = (_successfulSamples - HCSR04_SAMPLES_USE) / 2; - for (int i = 0; i < HCSR04_SAMPLES_USE; i++) + bool SensorHCSR04::_haveEnoughSamples() { - sum += _samples[i + offset]; + return _successfulSamples >= HCSR04_SAMPLES_USE; } - return round(sum / HCSR04_SAMPLES_USE); -} + int SensorHCSR04::_takeSample() + { + // Send 10μs trigger to ask sensor for a measurement. + digitalWrite(HCSR04_TRIG_PIN, LOW); + delayMicroseconds(2); + digitalWrite(HCSR04_TRIG_PIN, HIGH); + delayMicroseconds(10); + digitalWrite(HCSR04_TRIG_PIN, LOW); + + // Measure the length of echo signal. + unsigned long durationMicroSec = pulseIn(HCSR04_ECHO_PIN, HIGH, _echoTimeout); + + // Compute the distance, based on the echo signal length. + double distance = durationMicroSec / 2.0 * _speedOfSound; + _logger.log("sfs", "Sample result = ", distance, "mm"); + if (distance < HCSR04_MIN_MM || distance >= HCSR04_MAX_MM) + { + return -1; + } + else + { + return distance; + } + } + + void SensorHCSR04::_sortSamples() + { + int holder, x, y; + for (x = 0; x < _successfulSamples; x++) + { + for (y = 0; y < _successfulSamples - 1; y++) + { + if (_samples[y] > _samples[y + 1]) + { + holder = _samples[y + 1]; + _samples[y + 1] = _samples[y]; + _samples[y] = holder; + } + } + } + } + + // Compute the average of the samples. To get rid of measuring extremes, + // only a subset of measurements from the middle are used. + // When not enough samples were collected in the previous steps, then + // NAN is returned. + int SensorHCSR04::_computeAverage() + { + float sum = 0; + int offset = (_successfulSamples - HCSR04_SAMPLES_USE) / 2; + for (int i = 0; i < HCSR04_SAMPLES_USE; i++) + { + sum += _samples[i + offset]; + } + + return round(sum / HCSR04_SAMPLES_USE); + } +} // namespace Dough \ No newline at end of file diff --git a/src/Sensors/LowLevel/SensorHCSR04.h b/src/Sensors/LowLevel/SensorHCSR04.h index 42c941f..db43483 100644 --- a/src/Sensors/LowLevel/SensorHCSR04.h +++ b/src/Sensors/LowLevel/SensorHCSR04.h @@ -29,38 +29,39 @@ #undef HCSR04_DEBUG #include -#include "UI/DoughLogger.h" +#include "UI/Logger.h" #include "config.h" -/** - * This class is used to get a distance reading from an HCSR04 sensor. - */ -class SensorHCSR04 +namespace Dough { -public: - SensorHCSR04(int triggerPin, int echoPin); - void setup(); - void setTemperature(int temperature); - void setHumidity(int humidity); - int readDistance(); + // This class is used to get a distance reading from an HCSR04 sensor. + class SensorHCSR04 + { + public: + SensorHCSR04(int triggerPin, int echoPin); + void setup(); + void setTemperature(int temperature); + void setHumidity(int humidity); + int readDistance(); -private: - DoughLogger _logger; - int _triggerPin; - int _echoPin; - int _humidity; - int _temperature; - void _setSpeedOfSound(); - float _speedOfSound; - void _setEchoTimeout(); - int _echoTimeout; - float _samples[HCSR04_SAMPLES_TAKE]; - void _takeSamples(); - bool _haveEnoughSamples(); - int _takeSample(); - int _successfulSamples; - void _sortSamples(); - int _computeAverage(); -}; + private: + Logger _logger; + int _triggerPin; + int _echoPin; + int _humidity; + int _temperature; + void _setSpeedOfSound(); + float _speedOfSound; + void _setEchoTimeout(); + int _echoTimeout; + float _samples[HCSR04_SAMPLES_TAKE]; + void _takeSamples(); + bool _haveEnoughSamples(); + int _takeSample(); + int _successfulSamples; + void _sortSamples(); + int _computeAverage(); + }; +} // namespace Dough -#endif +#endif \ No newline at end of file diff --git a/src/Sensors/SensorBase.h b/src/Sensors/SensorBase.h index 5e5134a..b45b98b 100644 --- a/src/Sensors/SensorBase.h +++ b/src/Sensors/SensorBase.h @@ -3,14 +3,15 @@ #include "Data/Measurement.h" -/** - * This interface is implemented by all sensors. - */ -class SensorBase +namespace Dough { -public: - virtual void setup(); - virtual Measurement read(); -}; + // This interface is implemented by all sensors. + class SensorBase + { + public: + virtual void setup(); + virtual Measurement read(); + }; +} #endif \ No newline at end of file diff --git a/src/Sensors/TemperatureSensor.cpp b/src/Sensors/TemperatureSensor.cpp index c71ec11..e807088 100644 --- a/src/Sensors/TemperatureSensor.cpp +++ b/src/Sensors/TemperatureSensor.cpp @@ -1,47 +1,50 @@ #include "TemperatureSensor.h" -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -TemperatureSensor *TemperatureSensor::_instance = nullptr; - -TemperatureSensor *TemperatureSensor::Instance() +namespace Dough { - if (TemperatureSensor::_instance == nullptr) + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + TemperatureSensor *TemperatureSensor::_instance = nullptr; + + TemperatureSensor *TemperatureSensor::Instance() { - TemperatureSensor::_instance = new TemperatureSensor(); + if (TemperatureSensor::_instance == nullptr) + { + TemperatureSensor::_instance = new TemperatureSensor(); + } + return TemperatureSensor::_instance; } - return TemperatureSensor::_instance; -} -TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {} + TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {} -// ---------------------------------------------------------------------- -// setup -// ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // setup + // ---------------------------------------------------------------------- -void TemperatureSensor::setup() -{ - SensorDHT11::Instance()->begin(); -} - -// ---------------------------------------------------------------------- -// loop -// ---------------------------------------------------------------------- - -Measurement TemperatureSensor::read() -{ - float t = SensorDHT11::Instance()->readTemperature(); - if (isnan(t)) + void TemperatureSensor::setup() { - _logger.log("s", "ERROR - Temperature measurement failed"); - return Measurement::Failed(); + SensorDHT11::Instance()->begin(); } - else + + // ---------------------------------------------------------------------- + // loop + // ---------------------------------------------------------------------- + + Measurement TemperatureSensor::read() { - _logger.log("sis", "Temperature = ", int(t), "°C"); - DistanceSensor::Instance()->setTemperature(int(t)); - return Measurement::Value(int(t)); + float t = SensorDHT11::Instance()->readTemperature(); + if (isnan(t)) + { + _logger.log("s", "ERROR - Temperature measurement failed"); + return Measurement::Failed(); + } + else + { + _logger.log("sis", "Temperature = ", int(t), "°C"); + DistanceSensor::Instance()->setTemperature(int(t)); + return Measurement::Value(int(t)); + } } -} \ No newline at end of file +} // namespace Dough \ No newline at end of file diff --git a/src/Sensors/TemperatureSensor.h b/src/Sensors/TemperatureSensor.h index b629f86..8e3a61b 100644 --- a/src/Sensors/TemperatureSensor.h +++ b/src/Sensors/TemperatureSensor.h @@ -3,25 +3,26 @@ #include "Sensors/SensorBase.h" #include "Sensors/LowLevel/SensorDHT11.h" -#include "UI/DoughLogger.h" +#include "UI/Logger.h" #include "Data/Measurement.h" #include "Sensors/DistanceSensor.h" #include "config.h" -/** - * This class provides access to the temperature sensor in the device. - */ -class TemperatureSensor : public SensorBase +namespace Dough { -public: - static TemperatureSensor *Instance(); - virtual void setup(); - virtual Measurement read(); + // This class provides access to the temperature sensor in the device. + class TemperatureSensor : public SensorBase + { + public: + static TemperatureSensor *Instance(); + virtual void setup(); + virtual Measurement read(); -private: - TemperatureSensor(); - static TemperatureSensor *_instance; - DoughLogger _logger; -}; + private: + TemperatureSensor(); + static TemperatureSensor *_instance; + Logger _logger; + }; +} // namespace Dough #endif \ No newline at end of file diff --git a/src/UI/Button.cpp b/src/UI/Button.cpp new file mode 100644 index 0000000..f4cd316 --- /dev/null +++ b/src/UI/Button.cpp @@ -0,0 +1,146 @@ +#include "Button.h" + +namespace Dough +{ + // Constructor for a button instance. + // As a necessary evil, because of the way attachinterrupt() works in + // Arduino, construction needs a bit of extra work to get the button + // working. An interrupt service routine (ISR) function must be created + // and linked to the button to get the interrupts working. Pattern: + // + // // Construct the button instance. + // Button myButton(MYBUTTON_PIN); + // + // // A function for handling interrupts. + // void myButtonISR() { + // myButton.handleButtonState(); + // } + // + // // Linking the function ot button interrupts. + // myButton.onInterrupt(myButtonISR); + Button::Button(int pin) + { + _pin = pin; + } + + void Button::setup() + { + pinMode(_pin, INPUT_PULLUP); + } + + // Assign an interrupt service routine (ISR) for handling button + // interrupts. The provided isr should relay interrupts to the + // handleButtonState() method of this class (see constructor docs). + void Button::onInterrupt(ButtonHandler isr) + { + attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE); + } + + // Assign an event handler for short and long button presses. + // When specific handlers for long and/or short presses are + // configured as well, those have precedence over this one. + void Button::onPress(ButtonHandler handler) + { + _pressHandler = handler; + } + + // Assign an event handler for long button presses. + void Button::onLongPress(ButtonHandler handler) + { + _longPressHandler = handler; + } + + // Assign an event handler for short button presses. + void Button::onShortPress(ButtonHandler handler) + { + _shortPressHandler = handler; + } + + void Button::loop() + { + handleButtonState(); + if (_state == UP_AFTER_SHORT) + { + if (_shortPressHandler != nullptr) + { + _shortPressHandler(); + } + else if (_pressHandler != nullptr) + { + _pressHandler(); + } + _state = READY_FOR_NEXT_PRESS; + } + else if (_state == DOWN_LONG || _state == UP_AFTER_LONG) + { + if (_longPressHandler != nullptr) + { + _longPressHandler(); + } + else if (_pressHandler != nullptr) + { + _pressHandler(); + } + _state = READY_FOR_NEXT_PRESS; + } + else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr) + { + if (_pressHandler != nullptr) + { + _pressHandler(); + } + _state = READY_FOR_NEXT_PRESS; + } + } + + void Button::clearEvents() + { + _state = READY_FOR_NEXT_PRESS; + } + + void Button::handleButtonState() + { + bool buttonIsDown = digitalRead(_pin) == 0; + bool buttonIsUp = !buttonIsDown; + + // When the button state has changed since the last time, then + // start the debounce timer. + if (buttonIsDown != _debounceState) + { + _debounceTimer = millis(); + _debounceState = buttonIsDown; + } + + unsigned long interval = (millis() - _debounceTimer); + + // Only when the last state change has been stable for longer than the + // configured debounce delay, then we accept the current state as + // a stabilized button state. + if (interval < BUTTON_DEBOUNCE_DELAY) + { + return; + } + + // Handle button state changes. + if (_state == READY_FOR_NEXT_PRESS && buttonIsUp) + { + _state = UP; + } + else if (_state == UP && buttonIsDown) + { + _state = DOWN; + } + else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY) + { + _state = DOWN_LONG; + } + else if (_state == DOWN && buttonIsUp) + { + _state = UP_AFTER_SHORT; + } + else if (_state == DOWN_LONG && buttonIsUp) + { + _state = UP_AFTER_LONG; + } + } +} // namespace Dough \ No newline at end of file diff --git a/src/UI/Button.h b/src/UI/Button.h new file mode 100644 index 0000000..bb986f8 --- /dev/null +++ b/src/UI/Button.h @@ -0,0 +1,54 @@ +#ifndef DOUGH_BUTTON_H +#define DOUGH_BUTTON_H + +#define BUTTON_DEBOUNCE_DELAY 50 +#define BUTTON_LONGPRESS_DELAY 1000 + +#include + +namespace Dough +{ + typedef enum + { + UP, + DOWN, + DOWN_LONG, + UP_AFTER_LONG, + UP_AFTER_SHORT, + READY_FOR_NEXT_PRESS + } ButtonState; + + typedef void (*ButtonHandler)(); + + // This class provides a simple interface for handling button presses. + // Only a few events are supported: + // + // - short press: a button up event, will not trigger after long press + // - long press: when a button is held down for a while + // - press: this is a button down event, which will only trigger if + // short press and long press events aren't used + class Button + { + public: + Button(int pin); + void setup(); + void loop(); + void onInterrupt(ButtonHandler isr); + void onPress(ButtonHandler handler); + void onShortPress(ButtonHandler handler); + void onLongPress(ButtonHandler handler); + void clearEvents(); + void handleButtonState(); + + private: + int _pin; + ButtonHandler _pressHandler = nullptr; + ButtonHandler _shortPressHandler = nullptr; + ButtonHandler _longPressHandler = nullptr; + bool _debounceState = false; + unsigned long _debounceTimer = 0; + ButtonState _state = UP; + }; +} // namespace Dough + +#endif \ No newline at end of file diff --git a/src/UI/DoughButton.cpp b/src/UI/DoughButton.cpp deleted file mode 100644 index e755564..0000000 --- a/src/UI/DoughButton.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "DoughButton.h" - -/** - * Constructor for a button instance. - * As a necessary evil, because of the way attachinterrupt() works in - * Arduino, construction needs a bit of extra work to get the button - * working. An interrupt service routine (ISR) function must be created - * and linked to the button to get the interrupts working. Pattern: - * - * // Construct the button instance. - * DoughButton myButton(MYBUTTON_PIN); - * - * // A function for handling interrupts. - * void myButtonISR() { - * myButton.handleButtonState(); - * } - * - * // Linking the function ot button interrupts. - * myButton.onInterrupt(myButtonISR); - */ -DoughButton::DoughButton(int pin) -{ - _pin = pin; -} - -void DoughButton::setup() -{ - pinMode(_pin, INPUT_PULLUP); -} - -/** - * Assign an interrupt service routine (ISR) for handling button - * interrupts. The provided isr should relay interrupts to the - * handleButtonState() method of this class (see constructor docs). - */ -void DoughButton::onInterrupt(DoughButtonHandler isr) -{ - attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE); -} - -/** - * Assign an event handler for short and long button presses. - * When specific handlers for long and/or short presses are - * configured as well, those have precedence over this one. - */ -void DoughButton::onPress(DoughButtonHandler handler) -{ - _pressHandler = handler; -} - -/** - * Assign an event handler for long button presses. - */ -void DoughButton::onLongPress(DoughButtonHandler handler) -{ - _longPressHandler = handler; -} - -/** - * Assign an event handler for short button presses. - */ -void DoughButton::onShortPress(DoughButtonHandler handler) -{ - _shortPressHandler = handler; -} - -void DoughButton::loop() -{ - handleButtonState(); - if (_state == UP_AFTER_SHORT) - { - if (_shortPressHandler != nullptr) - { - _shortPressHandler(); - } - else if (_pressHandler != nullptr) - { - _pressHandler(); - } - _state = READY_FOR_NEXT_PRESS; - } - else if (_state == DOWN_LONG || _state == UP_AFTER_LONG) - { - if (_longPressHandler != nullptr) - { - _longPressHandler(); - } - else if (_pressHandler != nullptr) - { - _pressHandler(); - } - _state = READY_FOR_NEXT_PRESS; - } - else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr) - { - if (_pressHandler != nullptr) - { - _pressHandler(); - } - _state = READY_FOR_NEXT_PRESS; - } -} - -void DoughButton::clearEvents() -{ - _state = READY_FOR_NEXT_PRESS; -} - -void DoughButton::handleButtonState() -{ - bool buttonIsDown = digitalRead(_pin) == 0; - bool buttonIsUp = !buttonIsDown; - - // When the button state has changed since the last time, then - // start the debounce timer. - if (buttonIsDown != _debounceState) - { - _debounceTimer = millis(); - _debounceState = buttonIsDown; - } - - unsigned long interval = (millis() - _debounceTimer); - - // Only when the last state change has been stable for longer than the - // configured debounce delay, then we accept the current state as - // a stabilized button state. - if (interval < BUTTON_DEBOUNCE_DELAY) - { - return; - } - - // Handle button state changes. - if (_state == READY_FOR_NEXT_PRESS && buttonIsUp) - { - _state = UP; - } - else if (_state == UP && buttonIsDown) - { - _state = DOWN; - } - else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY) - { - _state = DOWN_LONG; - } - else if (_state == DOWN && buttonIsUp) - { - _state = UP_AFTER_SHORT; - } - else if (_state == DOWN_LONG && buttonIsUp) - { - _state = UP_AFTER_LONG; - } -} diff --git a/src/UI/DoughButton.h b/src/UI/DoughButton.h deleted file mode 100644 index c699aa1..0000000 --- a/src/UI/DoughButton.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef DOUGH_BUTTON_H -#define DOUGH_BUTTON_H - -#define BUTTON_DEBOUNCE_DELAY 50 -#define BUTTON_LONGPRESS_DELAY 1000 - -#include - -typedef enum -{ - UP, - DOWN, - DOWN_LONG, - UP_AFTER_LONG, - UP_AFTER_SHORT, - READY_FOR_NEXT_PRESS -} DoughButtonState; - -typedef void (*DoughButtonHandler)(); - -/** - * This class provides a simple interface for handling button presses. - * Only a few events are supported: - * - * - short press: a button up event, will not trigger after long press - * - long press: when a button is held down for a while - * - press: this is a button down event, which will only trigger if - * short press and long press events aren't used - */ -class DoughButton -{ -public: - DoughButton(int pin); - void setup(); - void loop(); - void onInterrupt(DoughButtonHandler isr); - void onPress(DoughButtonHandler handler); - void onShortPress(DoughButtonHandler handler); - void onLongPress(DoughButtonHandler handler); - void clearEvents(); - void handleButtonState(); - -private: - int _pin; - DoughButtonHandler _pressHandler = nullptr; - DoughButtonHandler _shortPressHandler = nullptr; - DoughButtonHandler _longPressHandler = nullptr; - bool _debounceState = false; - unsigned long _debounceTimer = 0; - DoughButtonState _state = UP; -}; - -#endif diff --git a/src/UI/DoughLED.cpp b/src/UI/DoughLED.cpp deleted file mode 100644 index ada3dc8..0000000 --- a/src/UI/DoughLED.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "DoughLED.h" - -DoughLED::DoughLED(int pin) -{ - _pin = pin; -} - -void DoughLED::setup() -{ - pinMode(_pin, OUTPUT); - _state = OFF; - _setPin(LOW); -} - -void DoughLED::loop() -{ - unsigned long now = millis(); - bool tick = (now - _timer) > _time; - - if (_state == FLASH) - { - if (tick) - { - _setPin(LOW); - _state = OFF; - } - } - else if (_state == DIP) - { - if (tick) - { - _setPin(HIGH); - _state = ON; - } - } - else if (_state == BLINK_ON) - { - if (_blinkStep == _blinkOnStep) - { - _setPin(HIGH); - } - if (tick) - { - _setPin(LOW); - _state = BLINK_OFF; - _timer = now; - } - } - else if (_state == BLINK_OFF) - { - if (tick) - { - _state = BLINK_ON; - _timer = now; - _blinkStep++; - if (_blinkStep > _blinkOfSteps) - { - _blinkStep = 1; - } - } - } - else if (_state == PULSE) - { - if (tick) - { - _timer = now; - _time = 1; - _brightness += _pulseStep; - if (_brightness <= 0) - { - _time = 200; - _brightness = 0; - _pulseStep = -_pulseStep; - } - else if (_brightness >= 100) - { - _brightness = 100; - _pulseStep = -_pulseStep; - } - } - analogWrite(_pin, _brightness); - } - else if (_state == OFF) - { - _setPin(LOW); - } - else if (_state == ON) - { - _setPin(HIGH); - } -} - -void DoughLED::_setPin(int high_or_low) -{ - _pinState = high_or_low; - analogWrite(_pin, _pinState == LOW ? 0 : 255); -} - -void DoughLED::on() -{ - _state = ON; - loop(); -} - -void DoughLED::off() -{ - _state = OFF; - loop(); -} - -DoughLED *DoughLED::flash() -{ - _setPin(HIGH); - _state = FLASH; - _timer = millis(); - _time = LED_TRANSITION_TIME_DEFAULT; - loop(); - return this; -} - -DoughLED *DoughLED::blink() -{ - return blink(1, 1); -} - -DoughLED *DoughLED::dip() -{ - _setPin(LOW); - _state = DIP; - _timer = millis(); - _time = LED_TRANSITION_TIME_DEFAULT; - loop(); - return this; -} - -DoughLED *DoughLED::blink(int onStep, int ofSteps) -{ - _blinkOnStep = onStep; - _blinkOfSteps = ofSteps; - _blinkStep = 1; - _state = BLINK_ON; - _time = LED_TRANSITION_TIME_DEFAULT; - loop(); - return this; -} - -void DoughLED::pulse() -{ - _state = PULSE; - _brightness = 0; - _pulseStep = +8; - _time = 1; -} - -void DoughLED::slow() -{ - _time = LED_TRANSITION_TIME_SLOW; -} - -void DoughLED::fast() -{ - _time = LED_TRANSITION_TIME_FAST; -} - -bool DoughLED::isOn() -{ - return _pinState == HIGH; -} - -bool DoughLED::isOff() -{ - return _pinState == LOW; -} diff --git a/src/UI/DoughLED.h b/src/UI/DoughLED.h deleted file mode 100644 index 70eea63..0000000 --- a/src/UI/DoughLED.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef DOUGH_LED_H -#define DOUGH_LED_H - -// Delay times for blinking, flashing and dipping. -#define LED_TRANSITION_TIME_SLOW 400 -#define LED_TRANSITION_TIME_DEFAULT 250 -#define LED_TRANSITION_TIME_FAST 100 - -#include - -typedef enum -{ - ON, - OFF, - BLINK_ON, - BLINK_OFF, - FLASH, - DIP, - PULSE -} DoughLEDState; - -/** - * This class provides a set of basic LED lighting patterns, which can - * be used in an async way. - */ -class DoughLED -{ -public: - DoughLED(int pin); - void setup(); - void loop(); - void on(); - void off(); - DoughLED *blink(); - DoughLED *blink(int onStep, int ofSteps); - DoughLED *flash(); - DoughLED *dip(); - void pulse(); - void slow(); - void fast(); - bool isOn(); - bool isOff(); - -private: - int _pin; - int _pinState = LOW; - DoughLEDState _state = OFF; - void _setPin(int high_or_low); - unsigned long _timer; - unsigned int _time; - int _blinkOnStep; - int _blinkOfSteps; - int _blinkStep; - int _brightness; - int _pulseStep; -}; - -#endif diff --git a/src/UI/DoughLogger.cpp b/src/UI/DoughLogger.cpp deleted file mode 100644 index 75f204f..0000000 --- a/src/UI/DoughLogger.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "DoughLogger.h" - -DoughLogger::DoughLogger(const char *section) -{ - _section = section; -} - -void DoughLogger::suspend() -{ - _suspended = true; -} - -void DoughLogger::resume() -{ - _suspended = false; -} - -void DoughLogger::log(const char *fmt, ...) -{ - if (_suspended) - { - return; - } - - char buf[LOGGER_PREFIX_BUFLEN]; - snprintf(buf, sizeof(buf) / sizeof(buf[0]), LOGGER_PREFIX_FORMAT, _section); - Serial.print(buf); - - va_list args; - va_start(args, fmt); - - while (*fmt != '\0') - { - if (*fmt == 'i') - { - int i = va_arg(args, int); - Serial.print(i); - } - else if (*fmt == 'f') - { - float f = va_arg(args, double); - Serial.print(f); - } - else if (*fmt == 'a') - { - IPAddress a = va_arg(args, IPAddress); - Serial.print(a); - } - else if (*fmt == 's') - { - const char *s = va_arg(args, const char *); - Serial.print(s); - } - else if (*fmt == 'S') - { - String S = va_arg(args, String); - Serial.print(S); - } - else - { - Serial.print(""); - } - fmt++; - } - va_end(args); - - Serial.println(""); -} diff --git a/src/UI/DoughLogger.h b/src/UI/DoughLogger.h deleted file mode 100644 index f8e6b99..0000000 --- a/src/UI/DoughLogger.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef DOUGH_LOGGER_H -#define DOUGH_LOGGER_H - -#define LOGGER_PREFIX_BUFLEN 16 -#define LOGGER_PREFIX_FORMAT "%11s | " - -#include -#include -#include - -/** - * This class provides an interface for logging data in a structured - * manner to the serial console. - */ -class DoughLogger -{ -public: - DoughLogger(const char *section); - void log(const char *fmt, ...); - void suspend(); - void resume(); - -private: - const char *_section; - bool _suspended = false; -}; - -#endif diff --git a/src/UI/DoughUI.cpp b/src/UI/DoughUI.cpp deleted file mode 100644 index 58cd72f..0000000 --- a/src/UI/DoughUI.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "DoughUI.h" - -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -DoughUI *DoughUI::_instance = nullptr; - -DoughUI *DoughUI::Instance() -{ - if (DoughUI::_instance == nullptr) - { - DoughUI::_instance = new DoughUI(); - } - return DoughUI::_instance; -} - -DoughUI::DoughUI() : onoffButton(ONOFF_BUTTON_PIN), - setupButton(SETUP_BUTTON_PIN), - ledBuiltin(LED_BUILTIN), - led1(LED1_PIN), - led2(LED2_PIN), - led3(LED3_PIN) {} - -// ---------------------------------------------------------------------- -// Setup -// ---------------------------------------------------------------------- - -void DoughUI::setup() -{ - // Setup the serial port, used for logging. - Serial.begin(LOG_BAUDRATE); -#ifdef LOG_WAIT_SERIAL - while (!Serial) - { - // wait for serial port to connect. Needed for native USB. - } -#endif - - // Setup the buttons. - onoffButton.setup(); - onoffButton.onInterrupt(DoughUI::onoffButtonISR); - setupButton.setup(); - setupButton.onInterrupt(DoughUI::setupButtonISR); - - // Setup the LEDs. - ledBuiltin.setup(); - led1.setup(); - led2.setup(); - led3.setup(); - - // Setup a timer interrupt that is used to update the - // user interface (a.k.a. "LEDs") in parallel to other activities. - // This allows for example to have a flashing LED, during the - // wifi connection setup. - _setupTimerInterrupt(); - - // Notify the user that we're on a roll! - _flash_all_leds(); -} - -void DoughUI::onoffButtonISR() -{ - DoughUI::Instance()->onoffButton.handleButtonState(); -} - -void DoughUI::setupButtonISR() -{ - DoughUI::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 - * helpful threads on the internet helped me piece the following code together. - */ -void DoughUI::_setupTimerInterrupt() -{ - REG_GCLK_GENDIV = GCLK_GENDIV_DIV(200) | // Use divider (32kHz/200 = 160Hz) - GCLK_GENDIV_ID(4); // for Generic Clock GCLK4 - while (GCLK->STATUS.bit.SYNCBUSY) - ; // Wait for synchronization - - REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW - GCLK_GENCTRL_GENEN | // and enable the clock - GCLK_GENCTRL_SRC_OSC32K | // using the 32kHz clock source as input - GCLK_GENCTRL_ID(4); // for Generic Clock GCLK4 - while (GCLK->STATUS.bit.SYNCBUSY) - ; // Wait for synchronization - - REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable timer - GCLK_CLKCTRL_GEN_GCLK4 | // using Generic Clock GCLK4 as input - GCLK_CLKCTRL_ID_TC4_TC5; // and feed its output to TC4 and TC5 - while (GCLK->STATUS.bit.SYNCBUSY) - ; // Wait for synchronization - - REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV8 | // Use prescaler (160Hz / 8 = 20Hz) - TC_CTRLA_WAVEGEN_MFRQ | // Use match frequency (MFRQ) mode - TC_CTRLA_MODE_COUNT8 | // Set the timer to 8-bit mode - TC_CTRLA_ENABLE; // Enable TC4 - REG_TC4_INTENSET = TC_INTENSET_OVF; // Enable TC4 overflow (OVF) interrupts - REG_TC4_COUNT8_CC0 = 1; // Set the CC0 as the TOP value for MFRQ (1 => 50ms per pulse) - while (TC4->COUNT8.STATUS.bit.SYNCBUSY) - ; // Wait for synchronization - - // Enable interrupts for TC4 in the Nested Vector InterruptController (NVIC), - NVIC_SetPriority(TC4_IRQn, 0); // Set NVIC priority for TC4 to 0 (highest) - NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts -} - -// ---------------------------------------------------------------------- -// Loop -// ---------------------------------------------------------------------- - -/** - * This callback is called when the TC4 timer hits an overflow interrupt. - */ -void TC4_Handler() -{ - DoughUI::Instance()->updatedLEDs(); - REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag. -} - -/** - * Disables the TC4 interrupts, suspending timed async updates to - * the user interface. - */ -void DoughUI::suspend() -{ - NVIC_DisableIRQ(TC4_IRQn); -} - -/** - * Enables the TC4 interrupts, resuming timed async updates to the - * user interface. - */ -void DoughUI::resume() -{ - NVIC_EnableIRQ(TC4_IRQn); -} - -/** - * Fire pending button events. - */ -void DoughUI::processButtonEvents() -{ - onoffButton.loop(); - setupButton.loop(); -} - -/** - * Clear pending button events. - */ -void DoughUI::clearButtonEvents() -{ - onoffButton.clearEvents(); - setupButton.clearEvents(); -} - -/** - * Update the state of all the LEDs in the system. - * This method is called both sync by methods in this class and async by - * 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 DoughUI::updatedLEDs() -{ - ledBuiltin.loop(); - led1.loop(); - led2.loop(); - led3.loop(); -} - -/** - * Flash all LEDs, one at a time. - */ -void DoughUI::_flash_all_leds() -{ - ledBuiltin.on(); - delay(100); - ledBuiltin.off(); - led1.on(); - delay(100); - led1.off(); - led2.on(); - delay(100); - led2.off(); - led3.on(); - delay(100); - led3.off(); -} diff --git a/src/UI/LED.cpp b/src/UI/LED.cpp new file mode 100644 index 0000000..b1ec9da --- /dev/null +++ b/src/UI/LED.cpp @@ -0,0 +1,176 @@ +#include "LED.h" + +namespace Dough +{ + LED::LED(int pin) + { + _pin = pin; + } + + void LED::setup() + { + pinMode(_pin, OUTPUT); + _state = OFF; + _setPin(LOW); + } + + void LED::loop() + { + unsigned long now = millis(); + bool tick = (now - _timer) > _time; + + if (_state == FLASH) + { + if (tick) + { + _setPin(LOW); + _state = OFF; + } + } + else if (_state == DIP) + { + if (tick) + { + _setPin(HIGH); + _state = ON; + } + } + else if (_state == BLINK_ON) + { + if (_blinkStep == _blinkOnStep) + { + _setPin(HIGH); + } + if (tick) + { + _setPin(LOW); + _state = BLINK_OFF; + _timer = now; + } + } + else if (_state == BLINK_OFF) + { + if (tick) + { + _state = BLINK_ON; + _timer = now; + _blinkStep++; + if (_blinkStep > _blinkOfSteps) + { + _blinkStep = 1; + } + } + } + else if (_state == PULSE) + { + if (tick) + { + _timer = now; + _time = 1; + _brightness += _pulseStep; + if (_brightness <= 0) + { + _time = 200; + _brightness = 0; + _pulseStep = -_pulseStep; + } + else if (_brightness >= 100) + { + _brightness = 100; + _pulseStep = -_pulseStep; + } + } + analogWrite(_pin, _brightness); + } + else if (_state == OFF) + { + _setPin(LOW); + } + else if (_state == ON) + { + _setPin(HIGH); + } + } + + void LED::_setPin(int high_or_low) + { + _pinState = high_or_low; + analogWrite(_pin, _pinState == LOW ? 0 : 255); + } + + void LED::on() + { + _state = ON; + loop(); + } + + void LED::off() + { + _state = OFF; + loop(); + } + + LED *LED::flash() + { + _setPin(HIGH); + _state = FLASH; + _timer = millis(); + _time = LED_TRANSITION_TIME_DEFAULT; + loop(); + return this; + } + + LED *LED::blink() + { + return blink(1, 1); + } + + LED *LED::dip() + { + _setPin(LOW); + _state = DIP; + _timer = millis(); + _time = LED_TRANSITION_TIME_DEFAULT; + loop(); + return this; + } + + LED *LED::blink(int onStep, int ofSteps) + { + _blinkOnStep = onStep; + _blinkOfSteps = ofSteps; + _blinkStep = 1; + _state = BLINK_ON; + _time = LED_TRANSITION_TIME_DEFAULT; + loop(); + return this; + } + + void LED::pulse() + { + _state = PULSE; + _brightness = 0; + _pulseStep = +8; + _time = 1; + } + + void LED::slow() + { + _time = LED_TRANSITION_TIME_SLOW; + } + + void LED::fast() + { + _time = LED_TRANSITION_TIME_FAST; + } + + bool LED::isOn() + { + return _pinState == HIGH; + } + + bool LED::isOff() + { + return _pinState == LOW; + } +} // namespace Dough \ No newline at end of file diff --git a/src/UI/LED.h b/src/UI/LED.h new file mode 100644 index 0000000..68c80ef --- /dev/null +++ b/src/UI/LED.h @@ -0,0 +1,59 @@ +#ifndef DOUGH_LED_H +#define DOUGH_LED_H + +// Delay times for blinking, flashing and dipping. +#define LED_TRANSITION_TIME_SLOW 400 +#define LED_TRANSITION_TIME_DEFAULT 250 +#define LED_TRANSITION_TIME_FAST 100 + +#include + +namespace Dough +{ + typedef enum + { + ON, + OFF, + BLINK_ON, + BLINK_OFF, + FLASH, + DIP, + PULSE + } LEDState; + + // This class provides a set of basic LED lighting patterns, which can + // be used in an async way. + class LED + { + public: + LED(int pin); + void setup(); + void loop(); + void on(); + void off(); + LED *blink(); + LED *blink(int onStep, int ofSteps); + LED *flash(); + LED *dip(); + void pulse(); + void slow(); + void fast(); + bool isOn(); + bool isOff(); + + private: + int _pin; + int _pinState = LOW; + LEDState _state = OFF; + void _setPin(int high_or_low); + unsigned long _timer; + unsigned int _time; + int _blinkOnStep; + int _blinkOfSteps; + int _blinkStep; + int _brightness; + int _pulseStep; + }; +} // namespace Dough + +#endif \ No newline at end of file diff --git a/src/UI/Logger.cpp b/src/UI/Logger.cpp new file mode 100644 index 0000000..02d573d --- /dev/null +++ b/src/UI/Logger.cpp @@ -0,0 +1,73 @@ +#include "Logger.h" + +namespace Dough +{ + Logger::Logger(const char *section) + { + _section = section; + } + + void Logger::suspend() + { + _suspended = true; + } + + void Logger::resume() + { + _suspended = false; + } + + void Logger::log(const char *fmt, ...) + { + if (_suspended) + { + return; + } + + char buf[LOGGER_PREFIX_BUFLEN]; + snprintf(buf, sizeof(buf) / sizeof(buf[0]), LOGGER_PREFIX_FORMAT, _section); + Serial.print(buf); + + va_list args; + va_start(args, fmt); + + while (*fmt != '\0') + { + if (*fmt == 'i') + { + int i = va_arg(args, int); + Serial.print(i); + } + else if (*fmt == 'f') + { + float f = va_arg(args, double); + Serial.print(f); + } + else if (*fmt == 'a') + { + IPAddress a = va_arg(args, IPAddress); + Serial.print(a); + } + else if (*fmt == 's') + { + const char *s = va_arg(args, const char *); + Serial.print(s); + } + else if (*fmt == 'S') + { + String S = va_arg(args, String); + Serial.print(S); + } + else + { + Serial.print(""); + } + fmt++; + } + va_end(args); + + Serial.println(""); + } +} // namespace Dough \ No newline at end of file diff --git a/src/UI/Logger.h b/src/UI/Logger.h new file mode 100644 index 0000000..271d8a4 --- /dev/null +++ b/src/UI/Logger.h @@ -0,0 +1,29 @@ +#ifndef DOUGH_LOGGER_H +#define DOUGH_LOGGER_H + +#define LOGGER_PREFIX_BUFLEN 16 +#define LOGGER_PREFIX_FORMAT "%11s | " + +#include +#include +#include + +namespace Dough +{ + // This class provides an interface for logging data in a structured + // manner to the serial console. + class Logger + { + public: + Logger(const char *section); + void log(const char *fmt, ...); + void suspend(); + void resume(); + + private: + const char *_section; + bool _suspended = false; + }; +} // namespace Dough + +#endif \ No newline at end of file diff --git a/src/UI/UI.cpp b/src/UI/UI.cpp new file mode 100644 index 0000000..b647087 --- /dev/null +++ b/src/UI/UI.cpp @@ -0,0 +1,182 @@ +#include "UI.h" + +namespace Dough +{ + // ---------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------- + + 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), + led1(LED1_PIN), + led2(LED2_PIN), + led3(LED3_PIN) {} + + // ---------------------------------------------------------------------- + // Setup + // ---------------------------------------------------------------------- + + void UI::setup() + { + // Setup the serial port, used for logging. + Serial.begin(LOG_BAUDRATE); +#ifdef LOG_WAIT_SERIAL + while (!Serial) + { + // wait for serial port to connect. Needed for native USB. + } +#endif + + // Setup the buttons. + onoffButton.setup(); + onoffButton.onInterrupt(UI::onoffButtonISR); + setupButton.setup(); + setupButton.onInterrupt(UI::setupButtonISR); + + // Setup the LEDs. + ledBuiltin.setup(); + led1.setup(); + led2.setup(); + led3.setup(); + + // Setup a timer interrupt that is used to update the + // user interface (a.k.a. "LEDs") in parallel to other activities. + // This allows for example to have a flashing LED, during the + // wifi connection setup. + _setupTimerInterrupt(); + + // Notify the user that we're on a roll! + _flash_all_leds(); + + // Enable the timer interrupt handling. + 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 + // helpful threads on the internet helped me piece the following code together. + void UI::_setupTimerInterrupt() + { + REG_GCLK_GENDIV = GCLK_GENDIV_DIV(200) | // Use divider (32kHz/200 = 160Hz) + GCLK_GENDIV_ID(4); // for Generic Clock GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY) + ; // Wait for synchronization + + REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW + GCLK_GENCTRL_GENEN | // and enable the clock + GCLK_GENCTRL_SRC_OSC32K | // using the 32kHz clock source as input + GCLK_GENCTRL_ID(4); // for Generic Clock GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY) + ; // Wait for synchronization + + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable timer + GCLK_CLKCTRL_GEN_GCLK4 | // using Generic Clock GCLK4 as input + GCLK_CLKCTRL_ID_TC4_TC5; // and feed its output to TC4 and TC5 + while (GCLK->STATUS.bit.SYNCBUSY) + ; // Wait for synchronization + + REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV8 | // Use prescaler (160Hz / 8 = 20Hz) + TC_CTRLA_WAVEGEN_MFRQ | // Use match frequency (MFRQ) mode + TC_CTRLA_MODE_COUNT8 | // Set the timer to 8-bit mode + TC_CTRLA_ENABLE; // Enable TC4 + REG_TC4_INTENSET = TC_INTENSET_OVF; // Enable TC4 overflow (OVF) interrupts + REG_TC4_COUNT8_CC0 = 1; // Set the CC0 as the TOP value for MFRQ (1 => 50ms per pulse) + while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization + } + + // ---------------------------------------------------------------------- + // Loop + // ---------------------------------------------------------------------- + + // 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() + { + NVIC_SetPriority(TC4_IRQn, 0); // Set NVIC priority for TC4 to 0 (highest) + NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts + } + + // Fire pending button events. + void UI::processButtonEvents() + { + onoffButton.loop(); + setupButton.loop(); + } + + // Clear pending button events. + void UI::clearButtonEvents() + { + onoffButton.clearEvents(); + setupButton.clearEvents(); + } + + // Update the state of all the LEDs in the system. + // This method is called both sync by methods in this class and async by + // 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() + { + ledBuiltin.loop(); + led1.loop(); + led2.loop(); + led3.loop(); + } + + // Flash all LEDs, one at a time. + void UI::_flash_all_leds() + { + ledBuiltin.on(); + delay(100); + ledBuiltin.off(); + led1.on(); + delay(100); + led1.off(); + led2.on(); + delay(100); + led2.off(); + led3.on(); + 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. +} + diff --git a/src/UI/DoughUI.h b/src/UI/UI.h similarity index 64% rename from src/UI/DoughUI.h rename to src/UI/UI.h index 9d34de0..1f0c6e8 100644 --- a/src/UI/DoughUI.h +++ b/src/UI/UI.h @@ -12,27 +12,27 @@ #include #include -#include "UI/DoughButton.h" -#include "UI/DoughLED.h" +#include "UI/Button.h" +#include "UI/LED.h" #include "config.h" -/** - * This class groups all user interface functionality: serial logging, - * LEDs and buttons. - */ -class DoughUI +namespace Dough +{ +// This class groups all user interface functionality: serial logging, +// LEDs and buttons. +class UI { public: - static DoughUI *Instance(); + static UI *Instance(); void setup(); static void onoffButtonISR(); static void setupButtonISR(); - DoughButton onoffButton; - DoughButton setupButton; - DoughLED ledBuiltin; - DoughLED led1; - DoughLED led2; - DoughLED led3; + Button onoffButton; + Button setupButton; + LED ledBuiltin; + LED led1; + LED led2; + LED led3; void processButtonEvents(); void clearButtonEvents(); void updatedLEDs(); @@ -40,10 +40,11 @@ public: void suspend(); private: - DoughUI(); + UI(); void _setupTimerInterrupt(); - static DoughUI *_instance; + static UI *_instance; void _flash_all_leds(); }; +} -#endif +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3f07a56..5678064 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,17 +8,18 @@ // TODO: make significantChange part of sensor class? DoughBoyState state = CONFIGURING; -auto logger = DoughLogger("MAIN"); +auto logger = Dough::Logger("MAIN"); void setup() { - TemperatureSensor::Instance()->setup(); - HumiditySensor::Instance()->setup(); - DistanceSensor::Instance()->setup(); - DoughWiFi::Instance()->setup(); + logger.log("s", "Initializing device"); + Dough::TemperatureSensor::Instance()->setup(); + Dough::HumiditySensor::Instance()->setup(); + Dough::DistanceSensor::Instance()->setup(); + Dough::WiFi::Instance()->setup(); Dough::MQTT::Instance()->setup(); - DataController::Instance()->setup(); - auto ui = DoughUI::Instance(); + Dough::DataController::Instance()->setup(); + auto ui = Dough::UI::Instance(); ui->setup(); ui->onoffButton.onPress(handleOnoffButtonPress); ui->setupButton.onPress(handleSetupButtonPress); @@ -27,8 +28,8 @@ void setup() void loop() { - auto ui = DoughUI::Instance(); - auto data = DataController::Instance(); + auto ui = Dough::UI::Instance(); + auto data = Dough::DataController::Instance(); auto mqtt = Dough::MQTT::Instance(); ui->processButtonEvents(); @@ -50,7 +51,7 @@ void loop() } else if (state == MEASURING) { - DataController::Instance()->loop(); + Dough::DataController::Instance()->loop(); } else if (state == CALIBRATING) { @@ -59,21 +60,19 @@ void loop() } else if (state == PAUSED) { - DataController::Instance()->clearHistory(); + Dough::DataController::Instance()->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. - */ +// 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 ui = DoughUI::Instance(); - auto network = DoughWiFi::Instance(); + auto ui = Dough::UI::Instance(); + auto network = Dough::WiFi::Instance(); auto mqtt = Dough::MQTT::Instance(); if (!network->isConnected()) @@ -144,7 +143,7 @@ void handleSetupButtonPress() void setStateToConfiguring() { - auto ui = DoughUI::Instance(); + auto ui = Dough::UI::Instance(); logger.log("s", "Waiting for configuration ..."); state = CONFIGURING; ui->led1.on(); @@ -155,7 +154,7 @@ void setStateToConfiguring() void setStateToMeasuring() { - auto ui = DoughUI::Instance(); + auto ui = Dough::UI::Instance(); logger.log("s", "Starting measurements"); state = MEASURING; ui->led1.on(); @@ -166,7 +165,7 @@ void setStateToMeasuring() void setStateToPaused() { - auto ui = DoughUI::Instance(); + auto ui = Dough::UI::Instance(); logger.log("s", "Pausing measurements"); state = PAUSED; ui->led1.on(); @@ -177,7 +176,7 @@ void setStateToPaused() void setStateToCalibrating() { - auto ui = DoughUI::Instance(); + auto ui = Dough::UI::Instance(); logger.log("s", "Requested device calibration"); state = CALIBRATING; ui->led1.on(); diff --git a/src/main.h b/src/main.h index 6aa5527..3070e11 100644 --- a/src/main.h +++ b/src/main.h @@ -5,11 +5,11 @@ #include "Sensors/TemperatureSensor.h" #include "Sensors/HumiditySensor.h" #include "Sensors/DistanceSensor.h" -#include "Network/DoughWiFi.h" +#include "Network/WiFi.h" #include "Network/MQTT.h" #include "Data/DataController.h" -#include "UI/DoughButton.h" -#include "UI/DoughUI.h" +#include "UI/Button.h" +#include "UI/UI.h" #include "config.h" typedef enum