From 10f8fc22900059b2b412d44b034c7fc59f021f83 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 11 Jul 2020 02:13:36 +0200 Subject: [PATCH] Extracted the logging functionality into a separate logger, making it easier to log info per module. This also drags less of the UI functionality into low level modules, which seems a lot cleaner to me. --- src/Data/DataController.cpp | 125 +++++++++-------------------------- src/Data/DataController.h | 27 +++++--- src/Data/Measurement.cpp | 20 +++--- src/Data/Measurement.h | 8 +-- src/Data/Measurements.cpp | 43 ++++++------ src/Data/Measurements.h | 13 ++-- src/Network/DoughMQTT.cpp | 37 ++++++----- src/Network/DoughMQTT.h | 24 ++++--- src/Network/DoughWiFi.cpp | 17 ++--- src/Network/DoughWiFi.h | 13 ++-- src/Sensors/DoughSensors.cpp | 27 ++++---- src/Sensors/DoughSensors.h | 13 ++-- src/Sensors/HCSR04.h | 3 + src/UI/DoughButton.h | 9 +++ src/UI/DoughLED.h | 4 ++ src/UI/DoughLogger.cpp | 55 +++++++++++++++ src/UI/DoughLogger.h | 25 +++++++ src/UI/DoughUI.cpp | 102 +++++++++------------------- src/UI/DoughUI.h | 8 ++- src/main.cpp | 21 +++--- 20 files changed, 306 insertions(+), 288 deletions(-) create mode 100644 src/UI/DoughLogger.cpp create mode 100644 src/UI/DoughLogger.h diff --git a/src/Data/DataController.cpp b/src/Data/DataController.cpp index a24e1df..2073bda 100644 --- a/src/Data/DataController.cpp +++ b/src/Data/DataController.cpp @@ -1,5 +1,4 @@ #include "Data/DataController.h" -#include "Data/Measurements.h" // ---------------------------------------------------------------------- // Constructor @@ -21,7 +20,13 @@ DataController *DataController::Instance() DataController::DataController() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK), _humidityMeasurements(HUMIDITY_AVG_LOOKBACK), - _distanceMeasurements(DISTANCE_AVG_LOOKBACK) {} + _distanceMeasurements(DISTANCE_AVG_LOOKBACK), + _logger("DATA") +{ + _ui = DoughUI::Instance(); + _sensors = DoughSensors::Instance(); + _mqtt = DoughMQTT::Instance(); +} // ---------------------------------------------------------------------- // Setup @@ -50,14 +55,10 @@ void DataController::handleMqttMessage(String &key, String &payload) } else { - DoughUI::Instance()->log("DATA", "sS", "ERROR - Unhandled MQTT message, key = ", key); + DataController::Instance()->_logger.log("sS", "ERROR - Unhandled MQTT message, key = ", key); } } -/** - * Check if configuration has been taken care of. Some configuration is - * required before measurements can be processed. - */ bool DataController::isConfigured() { return _containerHeightSet; @@ -73,21 +74,19 @@ void DataController::setContainerHeight(int height) _containerHeightSet = false; if (height <= HCSR04_MIN_MM) { - DoughUI::Instance()->log("DATA", "sisis", - "ERROR - Container height ", height, - "mm is less than the minimum measuring distance of ", - HCSR04_MIN_MM, "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) { - DoughUI::Instance()->log("DATA", "sisis", - "ERROR - Container height ", height, - "mm is more than the maximum measuring distance of ", - HCSR04_MAX_MM, "mm"); + _logger.log("sisis", "ERROR - Container height ", height, + "mm is more than the maximum measuring distance of ", + HCSR04_MAX_MM, "mm"); return; } - DoughUI::Instance()->log("DATA", "sis", "Set container height to ", height, "mm"); + _logger.log("sis", "Set container height to ", height, "mm"); _containerHeight = height; _containerHeightSet = true; } @@ -122,38 +121,36 @@ void DataController::_sample() if (tick) { - DoughUI *ui = DoughUI::Instance(); _lastSample = now; - DoughSensors *sensors = DoughSensors::Instance(); // 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(); + _ui->led3.off(); delay(50); - ui->led3.on(); + _ui->led3.on(); // Suspend the UI timer interrupts, to not let these interfere // with the sensor measurements. - ui->suspend(); + _ui->suspend(); // Take a sample. switch (_sampleType) { case SAMPLE_TEMPERATURE: - _temperatureMeasurements.add(sensors->readTemperature()); + _temperatureMeasurements.add(_sensors->readTemperature()); _sampleType = SAMPLE_HUMIDITY; break; case SAMPLE_HUMIDITY: - _humidityMeasurements.add(sensors->readHumidity()); + _humidityMeasurements.add(_sensors->readHumidity()); _sampleType = SAMPLE_DISTANCE; break; case SAMPLE_DISTANCE: - _distanceMeasurements.add(sensors->readDistance()); + _distanceMeasurements.add(_sensors->readDistance()); break; } - ui->resume(); + _ui->resume(); _sampleCounter++; if (_sampleCounter == SAMPLE_CYCLE_LENGTH) @@ -166,77 +163,17 @@ void DataController::_sample() void DataController::_publish() { - static unsigned long lastSample = 0; - if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL) + if (_lastPublish == 0 || millis() - _lastPublish > PUBLISH_INTERVAL) { - lastSample = millis(); + _lastPublish = millis(); - DoughUI *ui = DoughUI::Instance(); - DoughMQTT *mqtt = DoughMQTT::Instance(); + _mqtt->publish("temperature", _temperatureMeasurements.getLast()); + _mqtt->publish("temperature/average", _temperatureMeasurements.getAverage()); + _mqtt->publish("humidity", _humidityMeasurements.getLast()); + _mqtt->publish("humidity/average", _humidityMeasurements.getAverage()); + _mqtt->publish("distance", _distanceMeasurements.getLast()); + _mqtt->publish("distance/average", _distanceMeasurements.getAverage()); - ui->log("DATA", "s", "Publish temperature"); - auto m = _temperatureMeasurements.getLast(); - if (m->ok) - { - mqtt->publish("temperature", m->value); - } - else - { - mqtt->publish("temperature", "null"); - } - - ui->log("DATA", "s", "Publish temperature average"); - m = _temperatureMeasurements.getAverage(); - if (m->ok) - { - mqtt->publish("temperature/average", m->value); - } - else - { - mqtt->publish("temperature/average", "null"); - } - - ui->log("DATA", "s", "Publish humidity"); - m = _humidityMeasurements.getLast(); - if (m->ok) - { - mqtt->publish("humidity", m->value); - } - else - { - mqtt->publish("humidity", "null"); - } - - m = _humidityMeasurements.getAverage(); - if (m->ok) - { - mqtt->publish("humidity/average", m->value); - } - else - { - mqtt->publish("humidity/average", "null"); - } - - m = _distanceMeasurements.getLast(); - if (m->ok) - { - mqtt->publish("distance", m->value); - } - else - { - mqtt->publish("distance", "null"); - } - - m = _distanceMeasurements.getAverage(); - if (m->ok) - { - mqtt->publish("distance/average", m->value); - } - else - { - mqtt->publish("distance/average", "null"); - } - - ui->led1.dip()->fast(); + _ui->led1.dip()->fast(); } } diff --git a/src/Data/DataController.h b/src/Data/DataController.h index 05eb4ff..af6afde 100644 --- a/src/Data/DataController.h +++ b/src/Data/DataController.h @@ -27,6 +27,7 @@ #include "Network/DoughWiFi.h" #include "Network/DoughMQTT.h" #include "UI/DoughUI.h" +#include "UI/DoughLogger.h" typedef enum { @@ -36,36 +37,40 @@ typedef enum } DoughSampleType; /** - * The DoughData class is responsible for holding the device configuration, - * collecting measurements from sensors, gathering the statistics on these data, - * and publishing results to the MQTT broker. + * 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(); + static DataController* Instance(); void setup(); void loop(); void clearHistory(); void setContainerHeight(int height); - bool isConfigured(); - static void handleMqttConnect(DoughMQTT *mqtt); + bool isConfigured(); + static void handleMqttConnect(DoughMQTT* mqtt); static void handleMqttMessage(String &key, String &value); private: DataController(); - static DataController *_instance; - DoughSensors *_sensors; + static DataController* _instance; + Measurements _temperatureMeasurements; + Measurements _humidityMeasurements; + Measurements _distanceMeasurements; + DoughLogger _logger; + DoughUI* _ui; + DoughSensors* _sensors; + DoughMQTT* _mqtt; unsigned long _lastSample = 0; + unsigned long _lastPublish = 0; DoughSampleType _sampleType = SAMPLE_TEMPERATURE; int _sampleCounter = 0; int _containerHeight; bool _containerHeightSet; void _sample(); void _publish(); - Measurements _temperatureMeasurements; - Measurements _humidityMeasurements; - Measurements _distanceMeasurements; }; #endif diff --git a/src/Data/Measurement.cpp b/src/Data/Measurement.cpp index 701abba..3421b39 100644 --- a/src/Data/Measurement.cpp +++ b/src/Data/Measurement.cpp @@ -1,19 +1,17 @@ #include "Data/Measurement.h" -Measurement::Measurement() {} +Measurement::Measurement() { } -Measurement::Measurement(bool ok, int value) +Measurement Measurement::Failed() { - this->ok = ok; - this->value = value; + Measurement m; + return m; } -Measurement *Measurement::Failed() +Measurement Measurement::Value(int value) { - return new Measurement(false, 0); -} - -Measurement *Measurement::Ok(int value) -{ - return new Measurement(true, value); + Measurement m; + m.ok = true; + m.value = value; + return m; } \ No newline at end of file diff --git a/src/Data/Measurement.h b/src/Data/Measurement.h index 44738ed..0b163cf 100644 --- a/src/Data/Measurement.h +++ b/src/Data/Measurement.h @@ -2,17 +2,17 @@ #define DOUGH_DATA_MEASUREMENT_H /** - * The DoughDataMeasurement class represents a single measurement. + * This class represents a single measurement, which can be either a + * successful (bearing a measurement value) or a failed one. */ class Measurement { public: Measurement(); - Measurement(bool ok, int value); int value = 0; bool ok = false; - static Measurement *Failed(); - static Measurement *Ok(int value); + static Measurement Failed(); + static Measurement Value(int value); }; #endif diff --git a/src/Data/Measurements.cpp b/src/Data/Measurements.cpp index c2021a9..4fe96f6 100644 --- a/src/Data/Measurements.cpp +++ b/src/Data/Measurements.cpp @@ -1,26 +1,27 @@ -#include "Data/DataController.h" -#include "Data/Measurement.h" - -typedef Measurement *MeasurementPtr; +#include "Data/Measurements.h" +#include "UI/DoughUI.h" Measurements::Measurements(unsigned int avgLookback) { _storageSize = avgLookback; - _storage = new MeasurementPtr[avgLookback]; - for (unsigned int i = 0; i < avgLookback; i++) - { - _storage[i] = nullptr; + + _storage = new Measurement*[avgLookback]; + for (unsigned int i = 0; i < avgLookback; i++) { + _storage[i] = new Measurement; } + clearHistory(); } -void Measurements::add(Measurement *measurement) +void Measurements::add(Measurement measurement) { - auto index = _next(); - _storage[index] = measurement; - if (measurement->ok) + unsigned int index = _next(); + _storage[index]->ok = measurement.ok; + _storage[index]->value = measurement.value; + + if (measurement.ok) { _averageCount++; - _averageSum += measurement->value; + _averageSum += measurement.value; } } @@ -31,26 +32,28 @@ unsigned int Measurements::_next() { _index = 0; } - if (_storage[_index] != nullptr && _storage[_index]->ok) + if (_storage[_index]->ok) { _averageSum -= _storage[_index]->value; _averageCount--; } + _storage[_index]->ok = false; + _storage[_index]->value = 0; return _index; } -Measurement *Measurements::getLast() +Measurement Measurements::getLast() { - return _storage[_index]; + return *_storage[_index]; } -Measurement *Measurements::getAverage() +Measurement Measurements::getAverage() { - auto result = new Measurement(); + Measurement result; if (_averageCount > 0) { - result->ok = true; - result->value = round(_averageSum / _averageCount); + result.ok = true; + result.value = round(_averageSum / _averageCount); } return result; } diff --git a/src/Data/Measurements.h b/src/Data/Measurements.h index a32d9ff..95e40b6 100644 --- a/src/Data/Measurements.h +++ b/src/Data/Measurements.h @@ -1,23 +1,24 @@ #ifndef DOUGH_DATA_MEASUREMENTS_H #define DOUGH_DATA_MEASUREMENTS_H +#include #include "Data/Measurement.h" /** - * The DoughDataMeasurements class is used to store measurements for a sensor - * and to keep track of running totals for handling average computations. + * This class is used to store measurements for a sensor and to keep + * track of running totals for handling average computations. */ class Measurements { public: Measurements(unsigned int avgLookback); - void add(Measurement *measurement); - Measurement *getLast(); - Measurement *getAverage(); + void add(Measurement measurement); + Measurement getLast(); + Measurement getAverage(); void clearHistory(); private: - Measurement **_storage; + Measurement** _storage; unsigned int _storageSize; int _averageSum = 0; unsigned int _averageCount = 0; diff --git a/src/Network/DoughMQTT.cpp b/src/Network/DoughMQTT.cpp index 61d8801..0038ff9 100644 --- a/src/Network/DoughMQTT.cpp +++ b/src/Network/DoughMQTT.cpp @@ -4,12 +4,12 @@ // Constructor // ---------------------------------------------------------------------- -DoughMQTT *DoughMQTT::_instance = nullptr; +DoughMQTT* DoughMQTT::_instance = nullptr; /** * Fetch the DoughMQTT singleton. */ -DoughMQTT *DoughMQTT::Instance() +DoughMQTT* DoughMQTT::Instance() { if (DoughMQTT::_instance == nullptr) { @@ -18,10 +18,7 @@ DoughMQTT *DoughMQTT::Instance() return DoughMQTT::_instance; } -DoughMQTT::DoughMQTT() -{ - _ui = DoughUI::Instance(); -} +DoughMQTT::DoughMQTT() : _logger("MQTT") { } // ---------------------------------------------------------------------- // Setup @@ -29,14 +26,14 @@ DoughMQTT::DoughMQTT() void DoughMQTT::setup() { - DoughWiFi *network = DoughWiFi::Instance(); + DoughWiFi* network = DoughWiFi::Instance(); #ifdef MQTT_DEVICE_ID _mqttDeviceId = MQTT_DEVICE_ID; #else _mqttDeviceId = network->getMacAddress(); #endif - _ui->log("MQTT", "ss", "Device ID = ", _mqttDeviceId); + _logger.log("ss", "Device ID = ", _mqttDeviceId); _mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client); } @@ -62,13 +59,13 @@ bool DoughMQTT::isConnected() bool DoughMQTT::connect() { - _ui->log("MQTT", "sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT); + _logger.log("sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT); _mqttClient.connect(_mqttDeviceId, MQTT_USERNAME, MQTT_PASSWORD); // Check if the connection to the broker was successful. if (!_mqttClient.connected()) { - _ui->log("MQTT", "s", "ERROR - Connection to broker failed"); + _logger.log("s", "ERROR - Connection to broker failed"); return false; } @@ -89,9 +86,9 @@ void DoughMQTT::procesIncomingsMessages() void DoughMQTT::handleMessage(String &topic, String &payload) { - DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload); + DoughMQTT::Instance()->_logger.log("sSsS", "<<< ", topic, " = ", payload); - DoughMQTT *mqtt = DoughMQTT::Instance(); + DoughMQTT* mqtt = DoughMQTT::Instance(); if (mqtt->_onMessage != nullptr) { int pos = topic.lastIndexOf('/'); @@ -103,19 +100,19 @@ void DoughMQTT::handleMessage(String &topic, String &payload) } } -void DoughMQTT::subscribe(const char *key) +void DoughMQTT::subscribe(const char* key) { char topic[200]; snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key); - DoughUI::Instance()->log("MQTT", "ss", "Subscribe to ", topic); + _logger.log("ss", "Subscribe to ", topic); _mqttClient.subscribe(topic); } -void DoughMQTT::publish(const char *key, const char *payload) +void DoughMQTT::publish(const char* key, const char* payload) { char topic[200]; snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key); - DoughUI::Instance()->log("MQTT", "ssss", ">>> ", topic, " = ", payload); + _logger.log("ssss", ">>> ", topic, " = ", payload); _mqttClient.publish(topic, payload); } @@ -124,4 +121,12 @@ void DoughMQTT::publish(const char *key, int payload) char buf[16]; snprintf(buf, 16, "%d", payload); publish(key, buf); +} + +void DoughMQTT::publish(const char *key, Measurement measurement) { + if (measurement.ok) { + publish(key, measurement.value); + } else { + publish(key, "null"); + } } \ No newline at end of file diff --git a/src/Network/DoughMQTT.h b/src/Network/DoughMQTT.h index d3afa45..880de25 100644 --- a/src/Network/DoughMQTT.h +++ b/src/Network/DoughMQTT.h @@ -4,37 +4,43 @@ #include #include #include "Network/DoughWiFi.h" -#include "UI/DoughUI.h" +#include "Data/Measurement.h" +#include "UI/DoughLogger.h" #include "config.h" +/** + * This class encapsulates the connection to the MQTT broker. + * MQTT is used to publish measurements and to store configuration data. + */ class DoughMQTT; -typedef void (*DoughMQTTConnectHandler)(DoughMQTT *mqtt); +typedef void (*DoughMQTTConnectHandler)(DoughMQTT* mqtt); typedef void (*DoughMQTTMessageHandler)(String &key, String &value); class DoughMQTT { public: - static DoughMQTT *Instance(); + static DoughMQTT* Instance(); void setup(); void onConnect(DoughMQTTConnectHandler callback); void onMessage(DoughMQTTMessageHandler callback); bool isConnected(); bool connect(); - void subscribe(const char *key); + void subscribe(const char* key); void procesIncomingsMessages(); - void publish(const char *key, const char *payload); - void publish(const char *key, int payload); + void publish(const char* key, const char* payload); + void publish(const char* key, int payload); + void publish(const char* key, Measurement measurement); private: DoughMQTT(); - static DoughMQTT *_instance; + static DoughMQTT* _instance; MQTTClient _mqttClient; - DoughUI *_ui; + DoughLogger _logger; DoughMQTTConnectHandler _onConnect = nullptr; MQTTClientCallbackSimple _onMessage = nullptr; static void handleMessage(String &topic, String &payload); - char *_mqttDeviceId; + char* _mqttDeviceId; }; #endif diff --git a/src/Network/DoughWiFi.cpp b/src/Network/DoughWiFi.cpp index 1f75379..c87364c 100644 --- a/src/Network/DoughWiFi.cpp +++ b/src/Network/DoughWiFi.cpp @@ -18,10 +18,7 @@ DoughWiFi *DoughWiFi::Instance() return DoughWiFi::_instance; } -DoughWiFi::DoughWiFi() -{ - _ui = DoughUI::Instance(); -} +DoughWiFi::DoughWiFi() : _logger("WIFI") {} // ---------------------------------------------------------------------- // Setup @@ -39,7 +36,7 @@ void DoughWiFi::_setMacAddress() void DoughWiFi::setup() { _setMacAddress(); - DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress()); + _logger.log("ss", "MAC address = ", getMacAddress()); } // ---------------------------------------------------------------------- @@ -58,7 +55,7 @@ bool DoughWiFi::connect() // Check if a device with a WiFi shield is used. if (status == WL_NO_SHIELD) { - _ui->log("NETWORK", "s", "ERROR - Device has no WiFi shield"); + _logger.log("s", "ERROR - Device has no WiFi shield"); delay(5000); return false; } @@ -70,19 +67,19 @@ bool DoughWiFi::connect() } // Setup the connection to the WiFi network. - _ui->log("NETWORK", "ss", "WiFi network = ", WIFI_SSID); + _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) { - _ui->log("NETWORK", "sa", "IP-Address = ", WiFi.localIP()); - _ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm"); + _logger.log("sa", "IP-Address = ", WiFi.localIP()); + _logger.log("sis", "Signal strength = ", WiFi.RSSI(), " dBm"); return true; } else { - _ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")"); + _logger.log("sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")"); return false; } } diff --git a/src/Network/DoughWiFi.h b/src/Network/DoughWiFi.h index 38de99e..e7502e6 100644 --- a/src/Network/DoughWiFi.h +++ b/src/Network/DoughWiFi.h @@ -2,14 +2,17 @@ #define DOUGH_NETWORK_H #include -#include "UI/DoughUI.h" +#include "UI/DoughLogger.h" #include "config.h" +/** + * This class encapsulates the connection to the WiFi network. + */ class DoughWiFi { public: - static DoughWiFi *Instance(); - char *getMacAddress(); + static DoughWiFi* Instance(); + char* getMacAddress(); void setup(); void loop(); bool isConnected(); @@ -18,10 +21,10 @@ public: private: DoughWiFi(); - static DoughWiFi *_instance; + static DoughWiFi* _instance; void _setMacAddress(); char _macAddress[18]; // max MAC address length + 1 - DoughUI *_ui; + DoughLogger _logger; }; #endif diff --git a/src/Sensors/DoughSensors.cpp b/src/Sensors/DoughSensors.cpp index 362ea5b..aed2f8b 100644 --- a/src/Sensors/DoughSensors.cpp +++ b/src/Sensors/DoughSensors.cpp @@ -16,8 +16,7 @@ DoughSensors* DoughSensors::Instance() { return DoughSensors::_instance; } -DoughSensors::DoughSensors() { - _ui = DoughUI::Instance(); +DoughSensors::DoughSensors() : _logger("SENSORS") { _dht = new DHT(DHT11_DATA_PIN, DHT11); _hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN); } @@ -35,38 +34,38 @@ void DoughSensors::setup() { // loop // ---------------------------------------------------------------------- -Measurement* DoughSensors::readTemperature() { +Measurement DoughSensors::readTemperature() { float t = _dht->readTemperature(); if (isnan(t)) { - _ui->log("SENSORS", "s", "ERROR - Temperature measurement failed"); + _logger.log("s", "ERROR - Temperature measurement failed"); return Measurement::Failed(); } else { + _logger.log("sis", "Temperature = ", int(t), "°C "); _hcsr04->setTemperature(int(t)); - auto m = new Measurement(true, int(t)); - _ui->log("SENSORS", "sisi", "Temperature = ", int(t), "°C ", m->value); + auto m = Measurement::Value(int(t)); return m; } } -Measurement* DoughSensors::readHumidity() { +Measurement DoughSensors::readHumidity() { int h = _dht->readHumidity(); if (h == 0) { - _ui->log("SENSORS", "s", "ERROR - Humidity measurement failed"); + _logger.log("s", "ERROR - Humidity measurement failed"); return Measurement::Failed(); } else { + _logger.log("sis", "Humidity = ", h, "%"); _hcsr04->setHumidity(h); - _ui->log("SENSORS", "sis", "Humidity = ", h, "%"); - return Measurement::Ok(h); + return Measurement::Value(h); } } -Measurement* DoughSensors::readDistance() { +Measurement DoughSensors::readDistance() { int d = _hcsr04->readDistance(); if (d == -1) { - _ui->log("SENSORS", "s", "ERROR - Distance measurement failed"); + _logger.log("s", "ERROR - Distance measurement failed"); return Measurement::Failed(); } else { - _ui->log("SENSORS", "sis", "Distance = ", d, "mm"); - return Measurement::Ok(d); + _logger.log("sis", "Distance = ", d, "mm"); + return Measurement::Value(d); } } diff --git a/src/Sensors/DoughSensors.h b/src/Sensors/DoughSensors.h index 09d1775..6fd8eb7 100644 --- a/src/Sensors/DoughSensors.h +++ b/src/Sensors/DoughSensors.h @@ -3,22 +3,25 @@ #include #include "Sensors/HCSR04.h" -#include "UI/DoughUI.h" +#include "UI/DoughLogger.h" #include "Data/Measurement.h" #include "config.h" +/** + * This class provides access to the sensors in the device. + */ class DoughSensors { public: static DoughSensors* Instance(); void setup(); - Measurement* readTemperature(); - Measurement* readHumidity(); - Measurement* readDistance(); + Measurement readTemperature(); + Measurement readHumidity(); + Measurement readDistance(); private: DoughSensors(); static DoughSensors* _instance; - DoughUI *_ui; + DoughLogger _logger; DHT* _dht; HCSR04* _hcsr04; }; diff --git a/src/Sensors/HCSR04.h b/src/Sensors/HCSR04.h index fff8cc5..3405540 100644 --- a/src/Sensors/HCSR04.h +++ b/src/Sensors/HCSR04.h @@ -28,6 +28,9 @@ #include #include "config.h" +/** + * This class is used to get a distance reading from an HCSR04 sensor. + */ class HCSR04 { public: HCSR04(int triggerPin, int echoPin); diff --git a/src/UI/DoughButton.h b/src/UI/DoughButton.h index 9189abd..c699aa1 100644 --- a/src/UI/DoughButton.h +++ b/src/UI/DoughButton.h @@ -18,6 +18,15 @@ typedef enum 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: diff --git a/src/UI/DoughLED.h b/src/UI/DoughLED.h index 01bd5cc..70eea63 100644 --- a/src/UI/DoughLED.h +++ b/src/UI/DoughLED.h @@ -19,6 +19,10 @@ typedef enum PULSE } DoughLEDState; +/** + * This class provides a set of basic LED lighting patterns, which can + * be used in an async way. + */ class DoughLED { public: diff --git a/src/UI/DoughLogger.cpp b/src/UI/DoughLogger.cpp new file mode 100644 index 0000000..b047270 --- /dev/null +++ b/src/UI/DoughLogger.cpp @@ -0,0 +1,55 @@ +#include "DoughLogger.h" + +DoughLogger::DoughLogger(const char *section) +{ + _section = section; +} + +void DoughLogger::log(const char* fmt, ...) +{ + 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 new file mode 100644 index 0000000..6e624c9 --- /dev/null +++ b/src/UI/DoughLogger.h @@ -0,0 +1,25 @@ +#ifndef DOUGH_LOGGER_H +#define DOUGH_LOGGER_H + +#define LOGGER_PREFIX_BUFLEN 16 +#define LOGGER_PREFIX_FORMAT "%10s | " + +#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, ...); + +private: + const char* _section; +}; + +#endif diff --git a/src/UI/DoughUI.cpp b/src/UI/DoughUI.cpp index 8fa4ac5..58cd72f 100644 --- a/src/UI/DoughUI.cpp +++ b/src/UI/DoughUI.cpp @@ -1,10 +1,11 @@ #include "DoughUI.h" +// ---------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------- + DoughUI *DoughUI::_instance = nullptr; -/** - * Fetch the DoughUI singleton. - */ DoughUI *DoughUI::Instance() { if (DoughUI::_instance == nullptr) @@ -21,9 +22,10 @@ DoughUI::DoughUI() : onoffButton(ONOFF_BUTTON_PIN), led2(LED2_PIN), led3(LED3_PIN) {} -/** - * Called from the main setup() function of the sketch. - */ +// ---------------------------------------------------------------------- +// Setup +// ---------------------------------------------------------------------- + void DoughUI::setup() { // Setup the serial port, used for logging. @@ -54,7 +56,7 @@ void DoughUI::setup() _setupTimerInterrupt(); // Notify the user that we're on a roll! - flash_all_leds(); + _flash_all_leds(); } void DoughUI::onoffButtonISR() @@ -67,58 +69,6 @@ void DoughUI::setupButtonISR() DoughUI::Instance()->setupButton.handleButtonState(); } -/** - * Log a message to the serial interface. - */ -void DoughUI::log(const char *category, const char *fmt, ...) -{ - char buf[12]; - snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%8s | ", category); - 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(""); -} - /** * 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 @@ -159,15 +109,9 @@ void DoughUI::_setupTimerInterrupt() NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts } -void DoughUI::resume() -{ - NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts -} - -void DoughUI::suspend() -{ - NVIC_DisableIRQ(TC4_IRQn); // Disable TC4 interrupts -} +// ---------------------------------------------------------------------- +// Loop +// ---------------------------------------------------------------------- /** * This callback is called when the TC4 timer hits an overflow interrupt. @@ -178,6 +122,24 @@ void TC4_Handler() 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. */ @@ -199,7 +161,7 @@ void DoughUI::clearButtonEvents() /** * 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 invocatino + * 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. */ @@ -214,7 +176,7 @@ void DoughUI::updatedLEDs() /** * Flash all LEDs, one at a time. */ -void DoughUI::flash_all_leds() +void DoughUI::_flash_all_leds() { ledBuiltin.on(); delay(100); diff --git a/src/UI/DoughUI.h b/src/UI/DoughUI.h index dc8ef9a..9d34de0 100644 --- a/src/UI/DoughUI.h +++ b/src/UI/DoughUI.h @@ -11,12 +11,15 @@ #undef LOG_WAIT_SERIAL #include -#include #include #include "UI/DoughButton.h" #include "UI/DoughLED.h" #include "config.h" +/** + * This class groups all user interface functionality: serial logging, + * LEDs and buttons. + */ class DoughUI { public: @@ -33,15 +36,14 @@ public: void processButtonEvents(); void clearButtonEvents(); void updatedLEDs(); - void flash_all_leds(); void resume(); void suspend(); - void log(const char *category, const char *fmt, ...); private: DoughUI(); void _setupTimerInterrupt(); static DoughUI *_instance; + void _flash_all_leds(); }; #endif diff --git a/src/main.cpp b/src/main.cpp index 860a275..fb79287 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ // TODO: use longer term averages for data DoughBoyState state = CONFIGURING; +auto logger = DoughLogger("MAIN"); void setup() { @@ -18,7 +19,7 @@ void setup() ui->setup(); ui->onoffButton.onPress(handleOnoffButtonPress); ui->setupButton.onPress(handleSetupButtonPress); - ui->log("MAIN", "s", "Initialization completed, starting device"); + logger.log("s", "Initialization completed, starting device"); } void loop() @@ -76,11 +77,11 @@ bool setupNetworkConnection() { if (connectionState == CONNECTED) { - ui->log("MAIN", "s", "ERROR - Connection to WiFi network lost! Reconnecting ..."); + logger.log("s", "ERROR - Connection to WiFi network lost! Reconnecting ..."); } else { - ui->log("MAIN", "s", "Connecting to the WiFi network ..."); + logger.log("s", "Connecting to the WiFi network ..."); } connectionState = CONNECTING_WIFI; ui->led1.blink()->slow(); @@ -92,11 +93,11 @@ bool setupNetworkConnection() { if (connectionState == CONNECTED) { - ui->log("MAIN", "s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); + logger.log("s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); } else { - ui->log("MAIN", "s", "Connecting to the MQTT broker ..."); + logger.log("s", "Connecting to the MQTT broker ..."); } connectionState = CONNECTING_MQTT; ui->led1.blink()->fast(); @@ -108,7 +109,7 @@ bool setupNetworkConnection() { if (connectionState != CONNECTED) { - ui->log("MAIN", "s", "Connection to MQTT broker established"); + logger.log("s", "Connection to MQTT broker established"); ui->led1.on(); ui->led2.off(); ui->led3.off(); @@ -141,7 +142,7 @@ void handleSetupButtonPress() void setStateToConfiguring() { auto ui = DoughUI::Instance(); - ui->log("MAIN", "s", "Waiting for configuration ..."); + logger.log("s", "Waiting for configuration ..."); state = CONFIGURING; ui->led1.on(); ui->led2.blink()->fast(); @@ -152,7 +153,7 @@ void setStateToConfiguring() void setStateToMeasuring() { auto ui = DoughUI::Instance(); - ui->log("MAIN", "s", "Starting measurements"); + logger.log("s", "Starting measurements"); state = MEASURING; ui->led1.on(); ui->led2.on(); @@ -163,7 +164,7 @@ void setStateToMeasuring() void setStateToPaused() { auto ui = DoughUI::Instance(); - ui->log("MAIN", "s", "Pausing measurements"); + logger.log("s", "Pausing measurements"); state = PAUSED; ui->led1.on(); ui->led2.on(); @@ -174,7 +175,7 @@ void setStateToPaused() void setStateToCalibrating() { auto ui = DoughUI::Instance(); - ui->log("MAIN", "s", "Requested device calibration"); + logger.log("s", "Requested device calibration"); state = CALIBRATING; ui->led1.on(); ui->led2.blink()->slow();