diff --git a/Schematics.fzz b/Schematics.fzz index 164e260..851a6b1 100644 Binary files a/Schematics.fzz and b/Schematics.fzz differ diff --git a/Schematics.png b/Schematics.png new file mode 100644 index 0000000..3f4ac57 Binary files /dev/null and b/Schematics.png differ diff --git a/src/Data/DataController.cpp b/src/Data/DataController.cpp new file mode 100644 index 0000000..a24e1df --- /dev/null +++ b/src/Data/DataController.cpp @@ -0,0 +1,242 @@ +#include "Data/DataController.h" +#include "Data/Measurements.h" + +// ---------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------- + +DataController *DataController::_instance = nullptr; + +/** + * Fetch the DoughData singleton. + */ +DataController *DataController::Instance() +{ + if (DataController::_instance == nullptr) + { + DataController::_instance = new DataController(); + } + return DataController::_instance; +} + +DataController::DataController() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK), + _humidityMeasurements(HUMIDITY_AVG_LOOKBACK), + _distanceMeasurements(DISTANCE_AVG_LOOKBACK) {} + +// ---------------------------------------------------------------------- +// Setup +// ---------------------------------------------------------------------- + +void DataController::setup() +{ + _containerHeight = 0.00; + _containerHeightSet = false; + + DoughMQTT *mqtt = DoughMQTT::Instance(); + mqtt->onConnect(DataController::handleMqttConnect); + mqtt->onMessage(DataController::handleMqttMessage); +} + +void DataController::handleMqttConnect(DoughMQTT *mqtt) +{ + mqtt->subscribe("container_height"); +} + +void DataController::handleMqttMessage(String &key, String &payload) +{ + if (key.equals("container_height")) + { + DataController::Instance()->setContainerHeight(payload.toInt()); + } + else + { + DoughUI::Instance()->log("DATA", "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; +} + +/** + * 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) + { + DoughUI::Instance()->log("DATA", "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"); + return; + } + DoughUI::Instance()->log("DATA", "sis", "Set container height to ", height, "mm"); + _containerHeight = height; + _containerHeightSet = true; +} + +// ---------------------------------------------------------------------- +// Loop +// ---------------------------------------------------------------------- + +void DataController::loop() +{ + if (isConfigured()) + { + _sample(); + _publish(); + } +} + +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) + { + 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(); + 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.add(sensors->readTemperature()); + _sampleType = SAMPLE_HUMIDITY; + break; + case SAMPLE_HUMIDITY: + _humidityMeasurements.add(sensors->readHumidity()); + _sampleType = SAMPLE_DISTANCE; + break; + case SAMPLE_DISTANCE: + _distanceMeasurements.add(sensors->readDistance()); + break; + } + + ui->resume(); + + _sampleCounter++; + if (_sampleCounter == SAMPLE_CYCLE_LENGTH) + { + _sampleCounter = 0; + _sampleType = SAMPLE_TEMPERATURE; + } + } +} + +void DataController::_publish() +{ + static unsigned long lastSample = 0; + if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL) + { + lastSample = millis(); + + DoughUI *ui = DoughUI::Instance(); + DoughMQTT *mqtt = DoughMQTT::Instance(); + + 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(); + } +} diff --git a/src/Data/DataController.h b/src/Data/DataController.h new file mode 100644 index 0000000..05eb4ff --- /dev/null +++ b/src/Data/DataController.h @@ -0,0 +1,71 @@ +#ifndef DOUGH_DATA_DATACONTROLLER_H +#define DOUGH_DATA_DATACONTROLLER_H + +// These definitions describes what measurements are performed in sequence. +// One measurement is done every SAMPLE_INTERVAL microseconds. +// We always start with a temperature measurement, then a humidity measurement, +// and finally a number of distance measurements. +// The SAMPLE_CYCLE_LENGTH defines the total number of samples in this sequence. +#define SAMPLE_INTERVAL 1000 +#define SAMPLE_CYCLE_LENGTH 30 // 1 temperature + 1 humidity + 28 distance samples + +// Two different values are published per sensor: a recent value and an average +// value. These definition define the number of measurements to include in the +// average computation. +#define TEMPERATURE_AVG_LOOKBACK 10 // making this a 5 minute average +#define HUMIDITY_AVG_LOOKBACK 10 // making this a 5 minute average +#define DISTANCE_AVG_LOOKBACK 28 * 2 * 5 // making this a 5 minute average + +// The minimal interval at which to publish measurements to the MQTT broker. +// When significant changes occur in the measurements, then these will be published +// to the MQTT broker at all times, independent from this interval. +#define PUBLISH_INTERVAL 4000 + +#include +#include "Data/Measurements.h" +#include "Sensors/DoughSensors.h" +#include "Network/DoughWiFi.h" +#include "Network/DoughMQTT.h" +#include "UI/DoughUI.h" + +typedef enum +{ + SAMPLE_TEMPERATURE, + SAMPLE_HUMIDITY, + SAMPLE_DISTANCE +} 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. + */ +class DataController +{ +public: + static DataController *Instance(); + void setup(); + void loop(); + void clearHistory(); + void setContainerHeight(int height); + bool isConfigured(); + static void handleMqttConnect(DoughMQTT *mqtt); + static void handleMqttMessage(String &key, String &value); + +private: + DataController(); + static DataController *_instance; + DoughSensors *_sensors; + unsigned long _lastSample = 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 new file mode 100644 index 0000000..701abba --- /dev/null +++ b/src/Data/Measurement.cpp @@ -0,0 +1,19 @@ +#include "Data/Measurement.h" + +Measurement::Measurement() {} + +Measurement::Measurement(bool ok, int value) +{ + this->ok = ok; + this->value = value; +} + +Measurement *Measurement::Failed() +{ + return new Measurement(false, 0); +} + +Measurement *Measurement::Ok(int value) +{ + return new Measurement(true, value); +} \ No newline at end of file diff --git a/src/Data/Measurement.h b/src/Data/Measurement.h new file mode 100644 index 0000000..44738ed --- /dev/null +++ b/src/Data/Measurement.h @@ -0,0 +1,18 @@ +#ifndef DOUGH_DATA_MEASUREMENT_H +#define DOUGH_DATA_MEASUREMENT_H + +/** + * The DoughDataMeasurement class represents a single measurement. + */ +class Measurement +{ +public: + Measurement(); + Measurement(bool ok, int value); + int value = 0; + bool ok = false; + static Measurement *Failed(); + static Measurement *Ok(int value); +}; + +#endif diff --git a/src/Data/Measurements.cpp b/src/Data/Measurements.cpp new file mode 100644 index 0000000..c2021a9 --- /dev/null +++ b/src/Data/Measurements.cpp @@ -0,0 +1,67 @@ +#include "Data/DataController.h" +#include "Data/Measurement.h" + +typedef Measurement *MeasurementPtr; + +Measurements::Measurements(unsigned int avgLookback) +{ + _storageSize = avgLookback; + _storage = new MeasurementPtr[avgLookback]; + for (unsigned int i = 0; i < avgLookback; i++) + { + _storage[i] = nullptr; + } +} + +void Measurements::add(Measurement *measurement) +{ + auto index = _next(); + _storage[index] = measurement; + if (measurement->ok) + { + _averageCount++; + _averageSum += measurement->value; + } +} + +unsigned int Measurements::_next() +{ + _index++; + if (_index == _storageSize) + { + _index = 0; + } + if (_storage[_index] != nullptr && _storage[_index]->ok) + { + _averageSum -= _storage[_index]->value; + _averageCount--; + } + return _index; +} + +Measurement *Measurements::getLast() +{ + return _storage[_index]; +} + +Measurement *Measurements::getAverage() +{ + auto result = new Measurement(); + if (_averageCount > 0) + { + result->ok = true; + result->value = round(_averageSum / _averageCount); + } + return result; +} + +void Measurements::clearHistory() +{ + _averageCount = 0; + _averageSum = 0; + for (unsigned int i = 0; i < _storageSize; i++) + { + _storage[i]->ok = false; + _storage[i]->value = 0; + } +} diff --git a/src/Data/Measurements.h b/src/Data/Measurements.h new file mode 100644 index 0000000..a32d9ff --- /dev/null +++ b/src/Data/Measurements.h @@ -0,0 +1,28 @@ +#ifndef DOUGH_DATA_MEASUREMENTS_H +#define DOUGH_DATA_MEASUREMENTS_H + +#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. + */ +class Measurements +{ +public: + Measurements(unsigned int avgLookback); + void add(Measurement *measurement); + Measurement *getLast(); + Measurement *getAverage(); + void clearHistory(); + +private: + Measurement **_storage; + unsigned int _storageSize; + int _averageSum = 0; + unsigned int _averageCount = 0; + unsigned int _index = 0; + unsigned int _next(); +}; + +#endif diff --git a/src/DoughButton.h b/src/DoughButton.h deleted file mode 100644 index fa28adc..0000000 --- a/src/DoughButton.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef DOUGH_BUTTON_H -#define DOUGH_BUTTON_H - -#define BUTTON_DEBOUNCE_DELAY 50 -#define BUTTON_LONGPRESS_DELAY 1000 - -#include -#include "config.h" - -typedef enum { - UP, - DOWN, - DOWN_LONG, - UP_AFTER_LONG, - UP_AFTER_SHORT, - READY_FOR_NEXT_PRESS -} DoughButtonState; - -typedef void (*DoughButtonHandler)(); - -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/DoughData.cpp b/src/DoughData.cpp deleted file mode 100644 index 0fb7c97..0000000 --- a/src/DoughData.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "DoughData.h" - -// ---------------------------------------------------------------------- -// Constructor -// ---------------------------------------------------------------------- - -DoughData* DoughData::_instance = nullptr; - -/** - * Fetch the DoughData singleton. - */ -DoughData* DoughData::Instance() { - if (DoughData::_instance == nullptr) { - DoughData::_instance = new DoughData(); - } - return DoughData::_instance; -} - -DoughData::DoughData() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK), - _humidityMeasurements(HUMIDITY_AVG_LOOKBACK), - _distanceMeasurements(DISTANCE_AVG_LOOKBACK) {} - -// ---------------------------------------------------------------------- -// Measurements storage -// ---------------------------------------------------------------------- - -DoughDataMeasurements::DoughDataMeasurements(int avgLookback) { - _storageSize = avgLookback; - _storage = new DoughDataMeasurement[avgLookback]; - for (int i = 0; i < avgLookback; i++) { - _storage[i] = DoughDataMeasurement(); - } -} - -void DoughDataMeasurements::registerValue(int value) { - auto measurement = _next(); - _averageCount++; - _averageSum += value; - measurement->ok = true; - measurement->value = value; -} - -void DoughDataMeasurements::registerFailed() { - auto measurement = _next(); - measurement->ok = false; - measurement->value = 0; -} - -DoughDataMeasurement* DoughDataMeasurements::_next() { - _index++; - if (_index == _storageSize) { - _index = 0; - } - if (_storage[_index].ok) { - _averageSum -= _storage[_index].value; - _averageCount--; - } - return &(_storage[_index]); -} - -DoughDataMeasurement DoughDataMeasurements::getLast() { - return _storage[_index]; -} - -DoughDataMeasurement DoughDataMeasurements::getAverage() { - DoughDataMeasurement result; - if (_averageCount > 0) { - result.ok = true; - result.value = round(_averageSum / _averageCount); - } - return result; -} - -void DoughDataMeasurements::clearHistory() { - _averageCount = 0; - _averageSum = 0; - for (unsigned int i = 0; i < _storageSize; i++) { - _storage[i].ok = false; - _storage[i].value = 0; - } -} - -// ---------------------------------------------------------------------- -// Setup -// ---------------------------------------------------------------------- - -void DoughData::setup() { - _containerHeight = 0.00; - _containerHeightSet = false; - - DoughMQTT *mqtt = DoughMQTT::Instance(); - mqtt->onConnect(DoughData::handleMqttConnect); - mqtt->onMessage(DoughData::handleMqttMessage); -} - -void DoughData::handleMqttConnect(DoughMQTT* mqtt) { - mqtt->subscribe("container_height"); -} - -void DoughData::handleMqttMessage(String &key, String &payload) { - if (key.equals("container_height")) { - DoughData::Instance()->setContainerHeight(payload.toInt()); - } else { - DoughUI::Instance()->log("DATA", "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 DoughData::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 DoughData::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"); - 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"); - return; - } - DoughUI::Instance()->log("DATA", "sis", "Set container height to ", height, "mm"); - _containerHeight = height; - _containerHeightSet = true; -} - -// ---------------------------------------------------------------------- -// Loop -// ---------------------------------------------------------------------- - -void DoughData::loop() { - if (isConfigured()) { - _sample(); - _publish(); - } -} - -void DoughData::clearHistory() { - _temperatureMeasurements.clearHistory(); - _humidityMeasurements.clearHistory(); - _distanceMeasurements.clearHistory(); - _sampleType = SAMPLE_TEMPERATURE; - _sampleCounter = 0; -} - -void DoughData::_sample() { - auto now = millis(); - auto delta = now - _lastSample; - auto tick = _lastSample == 0 || delta >= SAMPLE_INTERVAL; - - if (tick) { - _lastSample = now; - DoughUI* ui = DoughUI::Instance(); - 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(); - 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: - sensors->readTemperature(); - if (sensors->temperatureOk) { - _temperatureMeasurements.registerValue(sensors->temperature); - } else { - _temperatureMeasurements.registerFailed(); - } - _sampleType = SAMPLE_HUMIDITY; - break; - case SAMPLE_HUMIDITY: - sensors->readHumidity(); - if (sensors->humidityOk) { - _humidityMeasurements.registerValue(sensors->humidity); - } else { - _humidityMeasurements.registerFailed(); - } - _sampleType = SAMPLE_DISTANCE; - break; - case SAMPLE_DISTANCE: - sensors->readDistance(); - if (sensors->distanceOk) { - _distanceMeasurements.registerValue(sensors->distance); - } else { - _distanceMeasurements.registerFailed(); - } - break; - } - - ui->resume(); - - _sampleCounter++; - if (_sampleCounter == SAMPLE_CYCLE_LENGTH) { - _sampleCounter = 0; - _sampleType = SAMPLE_TEMPERATURE; - } - } -} - -void DoughData::_publish() { - static unsigned long lastSample = 0; - if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL) { - lastSample = millis(); - - DoughUI* ui = DoughUI::Instance(); - DoughMQTT* mqtt = DoughMQTT::Instance(); - - auto m = _temperatureMeasurements.getLast(); - if (m.ok) { - mqtt->publish("temperature", m.value); - } else { - mqtt->publish("temperature", "null"); - } - - m = _temperatureMeasurements.getAverage(); - if (m.ok) { - mqtt->publish("temperature/average", m.value); - } else { - mqtt->publish("temperature/average", "null"); - } - - 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(); - } -} diff --git a/src/DoughData.h b/src/DoughData.h deleted file mode 100644 index 039643e..0000000 --- a/src/DoughData.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef DOUGH_DATA_H -#define DOUGH_DATA_H - -// These definitions describes what measurements are performed in sequence. -// One measurement is done every SAMPLE_INTERVAL microseconds. -// We always start with a temperature measurement, then a humidity measurement, -// and finally a number of distance measurements. -// The SAMPLE_CYCLE_LENGTH defines the total number of samples in this sequence. -#define SAMPLE_INTERVAL 1000 -#define SAMPLE_CYCLE_LENGTH 30 // 1 temperature + 1 humidity + 28 distance samples - -// Two different values are published per sensor: a recent value and an average -// value. These definition define the number of measurements to include in the -// average computation. -#define TEMPERATURE_AVG_LOOKBACK 10 // making this a 5 minute average -#define HUMIDITY_AVG_LOOKBACK 10 // making this a 5 minute average -#define DISTANCE_AVG_LOOKBACK 28 * 2 * 5 // making this a 5 minute average - -// The minimal interval at which to publish measurements to the MQTT broker. -// When significant changes occur in the measurements, then these will be published -// to the MQTT broker at all times, independent from this interval. -#define PUBLISH_INTERVAL 4000 - -#include -#include "DoughSensors.h" -#include "DoughNetwork.h" -#include "DoughMQTT.h" -#include "DoughUI.h" - -typedef enum { - SAMPLE_TEMPERATURE, - SAMPLE_HUMIDITY, - SAMPLE_DISTANCE -} DoughSampleType; - -/** - * The DoughDataMeasurement struct represents a single measurement. - */ -struct DoughDataMeasurement { - public: - int value = 0; - bool ok = false; -}; - -/** - * The DoughDataMeasurements class is used to store measurements for a sensor - * and to keep track of running totals for handling average computations. - */ -class DoughDataMeasurements { - public: - DoughDataMeasurements(int avgLookback); - void registerValue(int value); - void registerFailed(); - DoughDataMeasurement getLast(); - DoughDataMeasurement getAverage(); - void clearHistory(); - private: - DoughDataMeasurement* _storage; - unsigned int _storageSize; - int _averageSum = 0; - unsigned int _averageCount = 0; - unsigned int _index = 0; - DoughDataMeasurement* _next(); -}; - -/** - * 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. - */ -class DoughData { - public: - static DoughData* Instance(); - void setup(); - void loop(); - void clearHistory(); - void setContainerHeight(int height); - bool isConfigured(); - static void handleMqttConnect(DoughMQTT *mqtt); - static void handleMqttMessage(String &key, String &value); - - private: - DoughData(); - static DoughData* _instance; - DoughSensors * _sensors; - unsigned long _lastSample = 0; - DoughSampleType _sampleType = SAMPLE_TEMPERATURE; - int _sampleCounter = 0; - int _containerHeight; - bool _containerHeightSet; - void _sample(); - void _publish(); - DoughDataMeasurements _temperatureMeasurements; - DoughDataMeasurements _humidityMeasurements; - DoughDataMeasurements _distanceMeasurements; -}; - -#endif diff --git a/src/DoughLED.h b/src/DoughLED.h deleted file mode 100644 index acb11d5..0000000 --- a/src/DoughLED.h +++ /dev/null @@ -1,53 +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 -#include "config.h" - -typedef enum { - ON, - OFF, - BLINK_ON, - BLINK_OFF, - FLASH, - DIP, - PULSE -} DoughLEDState; - -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/DoughMQTT.h b/src/DoughMQTT.h deleted file mode 100644 index 9b85d5e..0000000 --- a/src/DoughMQTT.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef DOUGH_MQTT_H -#define DOUGH_MQTT_H - -#include -#include -#include "DoughNetwork.h" -#include "DoughUI.h" -#include "config.h" - -class DoughMQTT; - -typedef void (*DoughMQTTConnectHandler)(DoughMQTT* mqtt); -typedef void (*DoughMQTTMessageHandler)(String &key, String &value); - -class DoughMQTT { - public: - static DoughMQTT* Instance(); - void setup(); - void onConnect(DoughMQTTConnectHandler callback); - void onMessage(DoughMQTTMessageHandler callback); - bool isConnected(); - bool connect(); - void subscribe(const char* key); - void procesIncomingsMessages(); - void publish(const char* key, const char* payload); - void publish(const char* key, int payload); - - private: - DoughMQTT(); - static DoughMQTT* _instance; - MQTTClient _mqttClient; - DoughUI* _ui; - DoughMQTTConnectHandler _onConnect = nullptr; - MQTTClientCallbackSimple _onMessage = nullptr; - static void handleMessage(String &topic, String &payload); - char *_mqttDeviceId; -}; - -#endif diff --git a/src/DoughNetwork.h b/src/DoughNetwork.h deleted file mode 100644 index 621167d..0000000 --- a/src/DoughNetwork.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef DOUGH_NETWORK_H -#define DOUGH_NETWORK_H - -#include -#include "DoughUI.h" -#include "config.h" - -class DoughNetwork { - public: - static DoughNetwork* Instance(); - char *getMacAddress(); - void setup(); - void loop(); - bool isConnected(); - bool connect(); - WiFiClient client; - - private: - DoughNetwork(); - static DoughNetwork* _instance; - void _setMacAddress(); - char _macAddress[18]; // max MAC address length + 1 - DoughUI* _ui; -}; - -#endif diff --git a/src/DoughUI.cpp b/src/DoughUI.cpp deleted file mode 100644 index dac7eba..0000000 --- a/src/DoughUI.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "DoughUI.h" - -DoughUI* DoughUI::_instance = nullptr; - -/** - * Fetch the DoughUI singleton. - */ -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) {} - -/** - * Called from the main setup() function of the sketch. - */ -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(); -} - -/** - * 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 - * 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 -} - -void DoughUI::resume() { - NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts -} - -void DoughUI::suspend() { - NVIC_DisableIRQ(TC4_IRQn); // Disable TC4 interrupts -} - -/** - * 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. -} - -/** - * 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 invocatino - * 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/DoughUI.h b/src/DoughUI.h deleted file mode 100644 index c08be2c..0000000 --- a/src/DoughUI.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef DOUGH_UI_H -#define DOUGH_UI_H - -#define LOG_BAUDRATE 9600 - -// Define this one to wait for USB serial to come up. -// This can be useful during development, when you want all -// serial messages to appear in the serial monitor. -// Without this, some of the initial serial messages might -// be missing from the output. -#undef LOG_WAIT_SERIAL - -#include -#include -#include -#include "DoughButton.h" -#include "DoughLED.h" -#include "config.h" - -class DoughUI { - public: - static DoughUI* Instance(); - void setup(); - static void onoffButtonISR(); - static void setupButtonISR(); - DoughButton onoffButton; - DoughButton setupButton; - DoughLED ledBuiltin; - DoughLED led1; - DoughLED led2; - DoughLED led3; - 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; -}; - -#endif diff --git a/src/DoughMQTT.cpp b/src/Network/DoughMQTT.cpp similarity index 57% rename from src/DoughMQTT.cpp rename to src/Network/DoughMQTT.cpp index 2603b11..61d8801 100644 --- a/src/DoughMQTT.cpp +++ b/src/Network/DoughMQTT.cpp @@ -4,19 +4,22 @@ // Constructor // ---------------------------------------------------------------------- -DoughMQTT* DoughMQTT::_instance = nullptr; +DoughMQTT *DoughMQTT::_instance = nullptr; /** * Fetch the DoughMQTT singleton. */ -DoughMQTT* DoughMQTT::Instance() { - if (DoughMQTT::_instance == nullptr) { +DoughMQTT *DoughMQTT::Instance() +{ + if (DoughMQTT::_instance == nullptr) + { DoughMQTT::_instance = new DoughMQTT(); } return DoughMQTT::_instance; } -DoughMQTT::DoughMQTT() { +DoughMQTT::DoughMQTT() +{ _ui = DoughUI::Instance(); } @@ -24,24 +27,27 @@ DoughMQTT::DoughMQTT() { // Setup // ---------------------------------------------------------------------- -void DoughMQTT::setup() { - DoughNetwork* network = DoughNetwork::Instance(); +void DoughMQTT::setup() +{ + DoughWiFi *network = DoughWiFi::Instance(); - #ifdef MQTT_DEVICE_ID +#ifdef MQTT_DEVICE_ID _mqttDeviceId = MQTT_DEVICE_ID; - #else +#else _mqttDeviceId = network->getMacAddress(); - #endif +#endif _ui->log("MQTT", "ss", "Device ID = ", _mqttDeviceId); - _mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client); + _mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client); } -void DoughMQTT::onConnect(DoughMQTTConnectHandler callback) { +void DoughMQTT::onConnect(DoughMQTTConnectHandler callback) +{ _onConnect = callback; } -void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) { +void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) +{ _onMessage = callback; } @@ -49,62 +55,73 @@ void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) { // Loop // ---------------------------------------------------------------------- -bool DoughMQTT::isConnected() { +bool DoughMQTT::isConnected() +{ return _mqttClient.connected(); } -bool DoughMQTT::connect() { - _ui->log("MQTT" , "sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT); +bool DoughMQTT::connect() +{ + _ui->log("MQTT", "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()) { + if (!_mqttClient.connected()) + { _ui->log("MQTT", "s", "ERROR - Connection to broker failed"); return false; } _mqttClient.onMessage(DoughMQTT::handleMessage); - if (_onConnect != nullptr) { + if (_onConnect != nullptr) + { _onConnect(this); } return true; } -void DoughMQTT::procesIncomingsMessages() { +void DoughMQTT::procesIncomingsMessages() +{ _mqttClient.loop(); } -void DoughMQTT::handleMessage(String &topic, String &payload) { - DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload); +void DoughMQTT::handleMessage(String &topic, String &payload) +{ + DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload); DoughMQTT *mqtt = DoughMQTT::Instance(); - if (mqtt->_onMessage != nullptr) { + if (mqtt->_onMessage != nullptr) + { int pos = topic.lastIndexOf('/'); - if (pos != -1) { - topic.remove(0, pos+1); + if (pos != -1) + { + topic.remove(0, pos + 1); mqtt->_onMessage(topic, 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); + snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key); DoughUI::Instance()->log("MQTT", "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); + snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key); DoughUI::Instance()->log("MQTT", "ssss", ">>> ", topic, " = ", payload); _mqttClient.publish(topic, payload); } -void DoughMQTT::publish(const char* key, int payload) { +void DoughMQTT::publish(const char *key, int payload) +{ char buf[16]; snprintf(buf, 16, "%d", payload); publish(key, buf); -} +} \ No newline at end of file diff --git a/src/Network/DoughMQTT.h b/src/Network/DoughMQTT.h new file mode 100644 index 0000000..d3afa45 --- /dev/null +++ b/src/Network/DoughMQTT.h @@ -0,0 +1,40 @@ +#ifndef DOUGH_MQTT_H +#define DOUGH_MQTT_H + +#include +#include +#include "Network/DoughWiFi.h" +#include "UI/DoughUI.h" +#include "config.h" + +class DoughMQTT; + +typedef void (*DoughMQTTConnectHandler)(DoughMQTT *mqtt); +typedef void (*DoughMQTTMessageHandler)(String &key, String &value); + +class DoughMQTT +{ +public: + static DoughMQTT *Instance(); + void setup(); + void onConnect(DoughMQTTConnectHandler callback); + void onMessage(DoughMQTTMessageHandler callback); + bool isConnected(); + bool connect(); + void subscribe(const char *key); + void procesIncomingsMessages(); + void publish(const char *key, const char *payload); + void publish(const char *key, int payload); + +private: + DoughMQTT(); + static DoughMQTT *_instance; + MQTTClient _mqttClient; + DoughUI *_ui; + DoughMQTTConnectHandler _onConnect = nullptr; + MQTTClientCallbackSimple _onMessage = nullptr; + static void handleMessage(String &topic, String &payload); + char *_mqttDeviceId; +}; + +#endif diff --git a/src/DoughNetwork.cpp b/src/Network/DoughWiFi.cpp similarity index 69% rename from src/DoughNetwork.cpp rename to src/Network/DoughWiFi.cpp index f702083..1f75379 100644 --- a/src/DoughNetwork.cpp +++ b/src/Network/DoughWiFi.cpp @@ -1,22 +1,25 @@ -#include "DoughNetwork.h" +#include "Network/DoughWiFi.h" // ---------------------------------------------------------------------- // Constructor // ---------------------------------------------------------------------- -DoughNetwork* DoughNetwork::_instance = nullptr; +DoughWiFi *DoughWiFi::_instance = nullptr; /** - * Fetch the DoughNetwork singleton. + * Fetch the DoughWiFi singleton. */ -DoughNetwork* DoughNetwork::Instance() { - if (DoughNetwork::_instance == nullptr) { - DoughNetwork::_instance = new DoughNetwork(); +DoughWiFi *DoughWiFi::Instance() +{ + if (DoughWiFi::_instance == nullptr) + { + DoughWiFi::_instance = new DoughWiFi(); } - return DoughNetwork::_instance; + return DoughWiFi::_instance; } -DoughNetwork::DoughNetwork() { +DoughWiFi::DoughWiFi() +{ _ui = DoughUI::Instance(); } @@ -24,57 +27,67 @@ DoughNetwork::DoughNetwork() { // Setup // ---------------------------------------------------------------------- -void DoughNetwork::_setMacAddress() { +void DoughWiFi::_setMacAddress() +{ byte mac[6]; WiFi.macAddress(mac); snprintf( - _macAddress, sizeof(_macAddress)/sizeof(_macAddress[0]), + _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 DoughNetwork::setup() { +void DoughWiFi::setup() +{ _setMacAddress(); - DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress()); + DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress()); } // ---------------------------------------------------------------------- // Loop // ---------------------------------------------------------------------- -bool DoughNetwork::isConnected() { +bool DoughWiFi::isConnected() +{ return WiFi.status() == WL_CONNECTED; } -bool DoughNetwork::connect() { +bool DoughWiFi::connect() +{ int status = WiFi.status(); // Check if a device with a WiFi shield is used. - if (status == WL_NO_SHIELD) { + if (status == WL_NO_SHIELD) + { _ui->log("NETWORK", "s", "ERROR - Device has no WiFi shield"); delay(5000); return false; } - + // Check if the WiFi network is already up. - if (status == WL_CONNECTED) { + if (status == WL_CONNECTED) + { return true; } // Setup the connection to the WiFi network. _ui->log("NETWORK", "ss", "WiFi network = ", WIFI_SSID); status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - + // Check if the connection attempt was successful. - if (status == WL_CONNECTED) { + if (status == WL_CONNECTED) + { _ui->log("NETWORK", "sa", "IP-Address = ", WiFi.localIP()); - _ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm"); + _ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm"); return true; - } else { + } + else + { _ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")"); return false; } } -char* DoughNetwork::getMacAddress() { +char *DoughWiFi::getMacAddress() +{ return _macAddress; } diff --git a/src/Network/DoughWiFi.h b/src/Network/DoughWiFi.h new file mode 100644 index 0000000..38de99e --- /dev/null +++ b/src/Network/DoughWiFi.h @@ -0,0 +1,27 @@ +#ifndef DOUGH_NETWORK_H +#define DOUGH_NETWORK_H + +#include +#include "UI/DoughUI.h" +#include "config.h" + +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 + DoughUI *_ui; +}; + +#endif diff --git a/src/DoughSensors.cpp b/src/Sensors/DoughSensors.cpp similarity index 66% rename from src/DoughSensors.cpp rename to src/Sensors/DoughSensors.cpp index db3a5fd..362ea5b 100644 --- a/src/DoughSensors.cpp +++ b/src/Sensors/DoughSensors.cpp @@ -20,9 +20,6 @@ DoughSensors::DoughSensors() { _ui = DoughUI::Instance(); _dht = new DHT(DHT11_DATA_PIN, DHT11); _hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN); - temperature = 0; - humidity = 0; - distance = 0; } // ---------------------------------------------------------------------- @@ -38,40 +35,38 @@ void DoughSensors::setup() { // loop // ---------------------------------------------------------------------- -void DoughSensors::readTemperature() { +Measurement* DoughSensors::readTemperature() { float t = _dht->readTemperature(); if (isnan(t)) { _ui->log("SENSORS", "s", "ERROR - Temperature measurement failed"); - temperatureOk = false; + return Measurement::Failed(); } else { - temperature = int(t); - temperatureOk = true; - _hcsr04->setTemperature(t); + _hcsr04->setTemperature(int(t)); + auto m = new Measurement(true, int(t)); + _ui->log("SENSORS", "sisi", "Temperature = ", int(t), "°C ", m->value); + return m; } - _ui->log("SENSORS", "siss", "Temperature = ", temperature, "°C ", (temperatureOk ? "[OK]" : "[ERR]")); } -void DoughSensors::readHumidity() { +Measurement* DoughSensors::readHumidity() { int h = _dht->readHumidity(); if (h == 0) { _ui->log("SENSORS", "s", "ERROR - Humidity measurement failed"); - humidityOk = false; + return Measurement::Failed(); } else { - humidity = h; - humidityOk = true; _hcsr04->setHumidity(h); + _ui->log("SENSORS", "sis", "Humidity = ", h, "%"); + return Measurement::Ok(h); } - _ui->log("SENSORS", "siss", "Humidity = ", humidity, "% ", (humidityOk ? "[OK]" : "[ERR]")); } -void DoughSensors::readDistance() { +Measurement* DoughSensors::readDistance() { int d = _hcsr04->readDistance(); if (d == -1) { _ui->log("SENSORS", "s", "ERROR - Distance measurement failed"); - distanceOk = false; + return Measurement::Failed(); } else { - distanceOk = true; - distance = d; - } - _ui->log("SENSORS", "siss", "Distance = ", distance, "mm ", (distanceOk? "[OK]" : "[ERR]")); + _ui->log("SENSORS", "sis", "Distance = ", d, "mm"); + return Measurement::Ok(d); + } } diff --git a/src/DoughSensors.h b/src/Sensors/DoughSensors.h similarity index 50% rename from src/DoughSensors.h rename to src/Sensors/DoughSensors.h index dd28676..09d1775 100644 --- a/src/DoughSensors.h +++ b/src/Sensors/DoughSensors.h @@ -2,24 +2,18 @@ #define DOUGH_SENSORS_H #include -#include "HCSR04.h" -#include "DoughUI.h" +#include "Sensors/HCSR04.h" +#include "UI/DoughUI.h" +#include "Data/Measurement.h" #include "config.h" class DoughSensors { public: static DoughSensors* Instance(); void setup(); - void readAll(); - void readTemperature(); - int temperature = 0; - bool temperatureOk = false; - void readHumidity(); - int humidity = 0; - bool humidityOk = false; - void readDistance(); - int distance = 0; - bool distanceOk = false; + Measurement* readTemperature(); + Measurement* readHumidity(); + Measurement* readDistance(); private: DoughSensors(); diff --git a/src/HCSR04.cpp b/src/Sensors/HCSR04.cpp similarity index 96% rename from src/HCSR04.cpp rename to src/Sensors/HCSR04.cpp index b31e3e3..fe32991 100644 --- a/src/HCSR04.cpp +++ b/src/Sensors/HCSR04.cpp @@ -1,4 +1,4 @@ -#include "HCSR04.h" +#include "Sensors/HCSR04.h" HCSR04::HCSR04(int triggerPin, int echoPin) { _triggerPin = triggerPin; @@ -33,7 +33,6 @@ int HCSR04::readDistance() { _sortSamples(); return _computeAverage(); } - DoughUI::Instance()->log("HCSR04", "s", "ERROR - Not enough samples for reading distance, returning NAN"); return -1; } diff --git a/src/HCSR04.h b/src/Sensors/HCSR04.h similarity index 98% rename from src/HCSR04.h rename to src/Sensors/HCSR04.h index 17e9c3e..fff8cc5 100644 --- a/src/HCSR04.h +++ b/src/Sensors/HCSR04.h @@ -26,7 +26,6 @@ #define HCSR04_INIT_HUMIDITY 50.000 #include -#include "DoughUI.h" #include "config.h" class HCSR04 { diff --git a/src/DoughButton.cpp b/src/UI/DoughButton.cpp similarity index 67% rename from src/DoughButton.cpp rename to src/UI/DoughButton.cpp index 70e33dc..e755564 100644 --- a/src/DoughButton.cpp +++ b/src/UI/DoughButton.cpp @@ -18,11 +18,13 @@ * // Linking the function ot button interrupts. * myButton.onInterrupt(myButtonISR); */ -DoughButton::DoughButton(int pin) { +DoughButton::DoughButton(int pin) +{ _pin = pin; } -void DoughButton::setup() { +void DoughButton::setup() +{ pinMode(_pin, INPUT_PULLUP); } @@ -31,7 +33,8 @@ void DoughButton::setup() { * interrupts. The provided isr should relay interrupts to the * handleButtonState() method of this class (see constructor docs). */ -void DoughButton::onInterrupt(DoughButtonHandler isr) { +void DoughButton::onInterrupt(DoughButtonHandler isr) +{ attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE); } @@ -40,86 +43,111 @@ void DoughButton::onInterrupt(DoughButtonHandler isr) { * When specific handlers for long and/or short presses are * configured as well, those have precedence over this one. */ -void DoughButton::onPress(DoughButtonHandler handler) { +void DoughButton::onPress(DoughButtonHandler handler) +{ _pressHandler = handler; } /** * Assign an event handler for long button presses. */ -void DoughButton::onLongPress(DoughButtonHandler handler) { +void DoughButton::onLongPress(DoughButtonHandler handler) +{ _longPressHandler = handler; } /** * Assign an event handler for short button presses. */ -void DoughButton::onShortPress(DoughButtonHandler handler) { +void DoughButton::onShortPress(DoughButtonHandler handler) +{ _shortPressHandler = handler; } -void DoughButton::loop() { +void DoughButton::loop() +{ handleButtonState(); - if (_state == UP_AFTER_SHORT) { - if (_shortPressHandler != nullptr) { + if (_state == UP_AFTER_SHORT) + { + if (_shortPressHandler != nullptr) + { _shortPressHandler(); } - else if (_pressHandler != nullptr) { + else if (_pressHandler != nullptr) + { _pressHandler(); } _state = READY_FOR_NEXT_PRESS; } - else if (_state == DOWN_LONG || _state == UP_AFTER_LONG) { - if (_longPressHandler != nullptr) { + else if (_state == DOWN_LONG || _state == UP_AFTER_LONG) + { + if (_longPressHandler != nullptr) + { _longPressHandler(); } - else if (_pressHandler != nullptr) { + else if (_pressHandler != nullptr) + { _pressHandler(); } _state = READY_FOR_NEXT_PRESS; } - else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr) { - if (_pressHandler != nullptr) { + else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr) + { + if (_pressHandler != nullptr) + { _pressHandler(); - } + } _state = READY_FOR_NEXT_PRESS; } } -void DoughButton::clearEvents() { +void DoughButton::clearEvents() +{ _state = READY_FOR_NEXT_PRESS; } -void DoughButton::handleButtonState() { +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) { + 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) { + if (interval < BUTTON_DEBOUNCE_DELAY) + { return; } // Handle button state changes. - if (_state == READY_FOR_NEXT_PRESS && buttonIsUp) { + if (_state == READY_FOR_NEXT_PRESS && buttonIsUp) + { _state = UP; - } else if (_state == UP && buttonIsDown) { + } + else if (_state == UP && buttonIsDown) + { _state = DOWN; - } else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY) { + } + else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY) + { _state = DOWN_LONG; - } else if (_state == DOWN && buttonIsUp) { + } + else if (_state == DOWN && buttonIsUp) + { _state = UP_AFTER_SHORT; - } else if (_state == DOWN_LONG && buttonIsUp) { + } + else if (_state == DOWN_LONG && buttonIsUp) + { _state = UP_AFTER_LONG; } } diff --git a/src/UI/DoughButton.h b/src/UI/DoughButton.h new file mode 100644 index 0000000..9189abd --- /dev/null +++ b/src/UI/DoughButton.h @@ -0,0 +1,44 @@ +#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)(); + +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/DoughLED.cpp b/src/UI/DoughLED.cpp similarity index 62% rename from src/DoughLED.cpp rename to src/UI/DoughLED.cpp index 3a848c9..ada3dc8 100644 --- a/src/DoughLED.cpp +++ b/src/UI/DoughLED.cpp @@ -1,92 +1,115 @@ #include "DoughLED.h" -DoughLED::DoughLED(int pin) { +DoughLED::DoughLED(int pin) +{ _pin = pin; } -void DoughLED::setup() { +void DoughLED::setup() +{ pinMode(_pin, OUTPUT); _state = OFF; _setPin(LOW); } -void DoughLED::loop() { +void DoughLED::loop() +{ unsigned long now = millis(); bool tick = (now - _timer) > _time; - - if (_state == FLASH) { - if (tick) { + + if (_state == FLASH) + { + if (tick) + { _setPin(LOW); _state = OFF; } } - else if (_state == DIP) { - if (tick) { + else if (_state == DIP) + { + if (tick) + { _setPin(HIGH); _state = ON; } } - else if (_state == BLINK_ON) { - if (_blinkStep == _blinkOnStep) { + else if (_state == BLINK_ON) + { + if (_blinkStep == _blinkOnStep) + { _setPin(HIGH); } - if (tick) { + if (tick) + { _setPin(LOW); _state = BLINK_OFF; _timer = now; } } - else if (_state == BLINK_OFF) { - if (tick) { + else if (_state == BLINK_OFF) + { + if (tick) + { _state = BLINK_ON; _timer = now; _blinkStep++; - if (_blinkStep > _blinkOfSteps) { + if (_blinkStep > _blinkOfSteps) + { _blinkStep = 1; } } - } - else if (_state == PULSE) { - if (tick) { + } + else if (_state == PULSE) + { + if (tick) + { _timer = now; _time = 1; _brightness += _pulseStep; - if (_brightness <= 0) { + if (_brightness <= 0) + { _time = 200; _brightness = 0; _pulseStep = -_pulseStep; } - else if (_brightness >= 100) { + else if (_brightness >= 100) + { _brightness = 100; _pulseStep = -_pulseStep; } } analogWrite(_pin, _brightness); } - else if (_state == OFF) { + else if (_state == OFF) + { _setPin(LOW); } - else if (_state == ON) { + else if (_state == ON) + { _setPin(HIGH); } } -void DoughLED::_setPin(int high_or_low) { +void DoughLED::_setPin(int high_or_low) +{ _pinState = high_or_low; - analogWrite(_pin, _pinState == LOW ? 0 : 255); + analogWrite(_pin, _pinState == LOW ? 0 : 255); } -void DoughLED::on() { +void DoughLED::on() +{ _state = ON; loop(); } -void DoughLED::off() { +void DoughLED::off() +{ _state = OFF; loop(); } -DoughLED* DoughLED::flash() { +DoughLED *DoughLED::flash() +{ _setPin(HIGH); _state = FLASH; _timer = millis(); @@ -95,11 +118,13 @@ DoughLED* DoughLED::flash() { return this; } -DoughLED* DoughLED::blink() { +DoughLED *DoughLED::blink() +{ return blink(1, 1); } -DoughLED* DoughLED::dip() { +DoughLED *DoughLED::dip() +{ _setPin(LOW); _state = DIP; _timer = millis(); @@ -108,7 +133,8 @@ DoughLED* DoughLED::dip() { return this; } -DoughLED* DoughLED::blink(int onStep, int ofSteps) { +DoughLED *DoughLED::blink(int onStep, int ofSteps) +{ _blinkOnStep = onStep; _blinkOfSteps = ofSteps; _blinkStep = 1; @@ -118,25 +144,30 @@ DoughLED* DoughLED::blink(int onStep, int ofSteps) { return this; } -void DoughLED::pulse() { +void DoughLED::pulse() +{ _state = PULSE; _brightness = 0; _pulseStep = +8; _time = 1; } -void DoughLED::slow() { +void DoughLED::slow() +{ _time = LED_TRANSITION_TIME_SLOW; } -void DoughLED::fast() { +void DoughLED::fast() +{ _time = LED_TRANSITION_TIME_FAST; } -bool DoughLED::isOn() { +bool DoughLED::isOn() +{ return _pinState == HIGH; } -bool DoughLED::isOff() { +bool DoughLED::isOff() +{ return _pinState == LOW; } diff --git a/src/UI/DoughLED.h b/src/UI/DoughLED.h new file mode 100644 index 0000000..01bd5cc --- /dev/null +++ b/src/UI/DoughLED.h @@ -0,0 +1,54 @@ +#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; + +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/DoughUI.cpp b/src/UI/DoughUI.cpp new file mode 100644 index 0000000..8fa4ac5 --- /dev/null +++ b/src/UI/DoughUI.cpp @@ -0,0 +1,231 @@ +#include "DoughUI.h" + +DoughUI *DoughUI::_instance = nullptr; + +/** + * Fetch the DoughUI singleton. + */ +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) {} + +/** + * Called from the main setup() function of the sketch. + */ +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(); +} + +/** + * 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 + * 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 +} + +void DoughUI::resume() +{ + NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts +} + +void DoughUI::suspend() +{ + NVIC_DisableIRQ(TC4_IRQn); // Disable TC4 interrupts +} + +/** + * 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. +} + +/** + * 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 invocatino + * 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/DoughUI.h b/src/UI/DoughUI.h new file mode 100644 index 0000000..dc8ef9a --- /dev/null +++ b/src/UI/DoughUI.h @@ -0,0 +1,47 @@ +#ifndef DOUGH_UI_H +#define DOUGH_UI_H + +#define LOG_BAUDRATE 9600 + +// Define this one to wait for USB serial to come up. +// This can be useful during development, when you want all +// serial messages to appear in the serial monitor. +// Without this, some of the initial serial messages might +// be missing from the output. +#undef LOG_WAIT_SERIAL + +#include +#include +#include +#include "UI/DoughButton.h" +#include "UI/DoughLED.h" +#include "config.h" + +class DoughUI +{ +public: + static DoughUI *Instance(); + void setup(); + static void onoffButtonISR(); + static void setupButtonISR(); + DoughButton onoffButton; + DoughButton setupButton; + DoughLED ledBuiltin; + DoughLED led1; + DoughLED led2; + DoughLED led3; + 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; +}; + +#endif diff --git a/src/config.h b/src/config.h index fc42e26..9f89aa0 100644 --- a/src/config.h +++ b/src/config.h @@ -1,20 +1,20 @@ // The digital pin to which the DATA pin of the DHT11 // temperature/humidity sensor is connected. -#define DHT11_DATA_PIN 10 +#define DHT11_DATA_PIN 10 // The digital pins to which the TRIG and ECHO pins of // the HCSR04 distance sensor are connected. -#define HCSR04_TRIG_PIN 4 -#define HCSR04_ECHO_PIN 5 +#define HCSR04_TRIG_PIN 4 +#define HCSR04_ECHO_PIN 5 // The digital pins to which the three LEDs are connected. -#define LED1_PIN 8 -#define LED2_PIN 7 -#define LED3_PIN 6 +#define LED1_PIN 8 +#define LED2_PIN 7 +#define LED3_PIN 6 -// The digital pins to which the push buttons are connected. -#define ONOFF_BUTTON_PIN 2 -#define SETUP_BUTTON_PIN 3 +// The digital pins to which the push buttons are connected. +#define ONOFF_BUTTON_PIN 2 +#define SETUP_BUTTON_PIN 3 // The network configuration and possibly overrides for the above // definitions are stored in a separate header file, which is diff --git a/src/config_local.example.h b/src/config_local.example.h index 3ef8998..6e78a45 100644 --- a/src/config_local.example.h +++ b/src/config_local.example.h @@ -1,12 +1,12 @@ // WPA2 WiFi connection configuration. -#define WIFI_SSID "" -#define WIFI_PASSWORD "" +#define WIFI_SSID "" +#define WIFI_PASSWORD "" // MQTT broker configuration. -#define MQTT_BROKER "" -#define MQTT_PORT 1883 -#define MQTT_USERNAME "" -#define MQTT_PASSWORD "" +#define MQTT_BROKER "" +#define MQTT_PORT 1883 +#define MQTT_USERNAME "" +#define MQTT_PASSWORD "" // The prefix to use for the MQTT publishing topic. #define MQTT_TOPIC_PREFIX "sensors/doughboy" diff --git a/src/DoughBoy.cpp b/src/main.cpp similarity index 70% rename from src/DoughBoy.cpp rename to src/main.cpp index b2f6add..860a275 100644 --- a/src/DoughBoy.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "DoughBoy.h" +#include "main.h" // TOOD: implement the calibration logic // TODO: use different timings for temperature, humidity and distance measurements. Temp/Humidity together takes about 500ms, which slows down stuff. @@ -8,11 +8,12 @@ DoughBoyState state = CONFIGURING; -void setup() { +void setup() +{ DoughSensors::Instance()->setup(); - DoughNetwork::Instance()->setup(); + DoughWiFi::Instance()->setup(); DoughMQTT::Instance()->setup(); - DoughData::Instance()->setup(); + DataController::Instance()->setup(); auto ui = DoughUI::Instance(); ui->setup(); ui->onoffButton.onPress(handleOnoffButtonPress); @@ -20,34 +21,41 @@ void setup() { ui->log("MAIN", "s", "Initialization completed, starting device"); } -void loop() { +void loop() +{ auto ui = DoughUI::Instance(); - auto data = DoughData::Instance(); + auto data = DataController::Instance(); auto mqtt = DoughMQTT::Instance(); - + ui->processButtonEvents(); - if (!setupNetworkConnection()) { + if (!setupNetworkConnection()) + { return; } mqtt->procesIncomingsMessages(); - - if (state == CONFIGURING && data->isConfigured()) { + + if (state == CONFIGURING && data->isConfigured()) + { setStateToMeasuring(); } - else if (state == MEASURING && !data->isConfigured()) { + else if (state == MEASURING && !data->isConfigured()) + { setStateToConfiguring(); } - else if (state == MEASURING) { - DoughData::Instance()->loop(); + else if (state == MEASURING) + { + DataController::Instance()->loop(); } - else if (state == CALIBRATING) { + else if (state == CALIBRATING) + { delay(3000); setStateToPaused(); } - else if (state == PAUSED) { - DoughData::Instance()->clearHistory(); + else if (state == PAUSED) + { + DataController::Instance()->clearHistory(); } } @@ -56,18 +64,23 @@ void loop() { * If not, then try to setup the connection. * Returns true if the connection was established, false otherwise. */ -bool setupNetworkConnection() { +bool setupNetworkConnection() +{ static auto connectionState = CONNECTING_WIFI; - + auto ui = DoughUI::Instance(); - auto network = DoughNetwork::Instance(); + auto network = DoughWiFi::Instance(); auto mqtt = DoughMQTT::Instance(); - - if (!network->isConnected()) { - if (connectionState == CONNECTED) { + + if (!network->isConnected()) + { + if (connectionState == CONNECTED) + { ui->log("MAIN", "s", "ERROR - Connection to WiFi network lost! Reconnecting ..."); - } else { - ui->log("MAIN", "s", "Connecting to the WiFi network ..."); + } + else + { + ui->log("MAIN", "s", "Connecting to the WiFi network ..."); } connectionState = CONNECTING_WIFI; ui->led1.blink()->slow(); @@ -75,20 +88,26 @@ bool setupNetworkConnection() { ui->led3.off(); network->connect(); } - if (network->isConnected() && !mqtt->isConnected()) { - if (connectionState == CONNECTED) { + if (network->isConnected() && !mqtt->isConnected()) + { + if (connectionState == CONNECTED) + { ui->log("MAIN", "s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); - } else { - ui->log("MAIN", "s", "Connecting to the MQTT broker ..."); + } + else + { + ui->log("MAIN", "s", "Connecting to the MQTT broker ..."); } connectionState = CONNECTING_MQTT; ui->led1.blink()->fast(); ui->led2.off(); ui->led3.off(); mqtt->connect(); - } - if (network->isConnected() && mqtt->isConnected()) { - if (connectionState != CONNECTED) { + } + if (network->isConnected() && mqtt->isConnected()) + { + if (connectionState != CONNECTED) + { ui->log("MAIN", "s", "Connection to MQTT broker established"); ui->led1.on(); ui->led2.off(); @@ -102,20 +121,25 @@ bool setupNetworkConnection() { return connectionState == CONNECTED; } -void handleOnoffButtonPress() { - if (state == MEASURING) { +void handleOnoffButtonPress() +{ + if (state == MEASURING) + { setStateToPaused(); } - else if (state == PAUSED) { + else if (state == PAUSED) + { setStateToMeasuring(); } } -void handleSetupButtonPress() { +void handleSetupButtonPress() +{ setStateToCalibrating(); } -void setStateToConfiguring() { +void setStateToConfiguring() +{ auto ui = DoughUI::Instance(); ui->log("MAIN", "s", "Waiting for configuration ..."); state = CONFIGURING; @@ -125,7 +149,8 @@ void setStateToConfiguring() { DoughMQTT::Instance()->publish("state", "configuring"); } -void setStateToMeasuring() { +void setStateToMeasuring() +{ auto ui = DoughUI::Instance(); ui->log("MAIN", "s", "Starting measurements"); state = MEASURING; @@ -135,22 +160,24 @@ void setStateToMeasuring() { DoughMQTT::Instance()->publish("state", "measuring"); } -void setStateToPaused() { - auto ui = DoughUI::Instance(); +void setStateToPaused() +{ + auto ui = DoughUI::Instance(); ui->log("MAIN", "s", "Pausing measurements"); state = PAUSED; ui->led1.on(); ui->led2.on(); ui->led3.pulse(); - DoughMQTT::Instance()->publish("state", "paused"); + DoughMQTT::Instance()->publish("state", "paused"); } -void setStateToCalibrating() { +void setStateToCalibrating() +{ auto ui = DoughUI::Instance(); ui->log("MAIN", "s", "Requested device calibration"); state = CALIBRATING; ui->led1.on(); ui->led2.blink()->slow(); ui->led3.off(); - DoughMQTT::Instance()->publish("state", "calibrating"); + DoughMQTT::Instance()->publish("state", "calibrating"); } diff --git a/src/DoughBoy.h b/src/main.h similarity index 70% rename from src/DoughBoy.h rename to src/main.h index 373acc6..95cbcc7 100644 --- a/src/DoughBoy.h +++ b/src/main.h @@ -2,21 +2,23 @@ #define DOUGHBOY_H #include -#include "DoughNetwork.h" -#include "DoughMQTT.h" -#include "DoughSensors.h" -#include "DoughData.h" -#include "DoughButton.h" -#include "DoughUI.h" +#include "Network/DoughWiFi.h" +#include "Network/DoughMQTT.h" +#include "Sensors/DoughSensors.h" +#include "Data/DataController.h" +#include "UI/DoughButton.h" +#include "UI/DoughUI.h" #include "config.h" -typedef enum { +typedef enum +{ CONNECTING_WIFI, CONNECTING_MQTT, CONNECTED } DoughBoyConnectionState; -typedef enum { +typedef enum +{ CONFIGURING, MEASURING, PAUSED,