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.
This commit is contained in:
parent
e66b8ccdd5
commit
10f8fc2290
|
@ -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,
|
||||
_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,
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "Network/DoughWiFi.h"
|
||||
#include "Network/DoughMQTT.h"
|
||||
#include "UI/DoughUI.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
|
@ -36,9 +37,9 @@ 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
|
||||
{
|
||||
|
@ -55,17 +56,21 @@ public:
|
|||
private:
|
||||
DataController();
|
||||
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
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
|
||||
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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
#ifndef DOUGH_DATA_MEASUREMENTS_H
|
||||
#define DOUGH_DATA_MEASUREMENTS_H
|
||||
|
||||
#include <Arduino.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.
|
||||
* 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:
|
||||
|
|
|
@ -18,10 +18,7 @@ DoughMQTT *DoughMQTT::Instance()
|
|||
return DoughMQTT::_instance;
|
||||
}
|
||||
|
||||
DoughMQTT::DoughMQTT()
|
||||
{
|
||||
_ui = DoughUI::Instance();
|
||||
}
|
||||
DoughMQTT::DoughMQTT() : _logger("MQTT") { }
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
|
@ -36,7 +33,7 @@ void DoughMQTT::setup()
|
|||
#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,7 +86,7 @@ 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();
|
||||
if (mqtt->_onMessage != nullptr)
|
||||
|
@ -107,7 +104,7 @@ 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);
|
||||
}
|
||||
|
||||
|
@ -115,7 +112,7 @@ 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);
|
||||
}
|
||||
|
||||
|
@ -125,3 +122,11 @@ void DoughMQTT::publish(const char *key, int payload)
|
|||
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");
|
||||
}
|
||||
}
|
|
@ -4,9 +4,14 @@
|
|||
#include <MQTT.h>
|
||||
#include <MQTTClient.h>
|
||||
#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);
|
||||
|
@ -25,12 +30,13 @@ public:
|
|||
void procesIncomingsMessages();
|
||||
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;
|
||||
MQTTClient _mqttClient;
|
||||
DoughUI *_ui;
|
||||
DoughLogger _logger;
|
||||
DoughMQTTConnectHandler _onConnect = nullptr;
|
||||
MQTTClientCallbackSimple _onMessage = nullptr;
|
||||
static void handleMessage(String &topic, String &payload);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
#define DOUGH_NETWORK_H
|
||||
|
||||
#include <WiFiNINA.h>
|
||||
#include "UI/DoughUI.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class encapsulates the connection to the WiFi network.
|
||||
*/
|
||||
class DoughWiFi
|
||||
{
|
||||
public:
|
||||
|
@ -21,7 +24,7 @@ private:
|
|||
static DoughWiFi* _instance;
|
||||
void _setMacAddress();
|
||||
char _macAddress[18]; // max MAC address length + 1
|
||||
DoughUI *_ui;
|
||||
DoughLogger _logger;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,25 @@
|
|||
|
||||
#include <DHT.h>
|
||||
#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;
|
||||
};
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class is used to get a distance reading from an HCSR04 sensor.
|
||||
*/
|
||||
class HCSR04 {
|
||||
public:
|
||||
HCSR04(int triggerPin, int echoPin);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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("<log(): invalid format char '");
|
||||
Serial.print(*fmt);
|
||||
Serial.print("'>");
|
||||
}
|
||||
fmt++;
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
Serial.println("");
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef DOUGH_LOGGER_H
|
||||
#define DOUGH_LOGGER_H
|
||||
|
||||
#define LOGGER_PREFIX_BUFLEN 16
|
||||
#define LOGGER_PREFIX_FORMAT "%10s | "
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <stdarg.h>
|
||||
#include <WiFiNINA.h>
|
||||
|
||||
/**
|
||||
* 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
|
|
@ -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("<log(): invalid format char '");
|
||||
Serial.print(*fmt);
|
||||
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);
|
||||
|
|
|
@ -11,12 +11,15 @@
|
|||
#undef LOG_WAIT_SERIAL
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiNINA.h>
|
||||
#include <stdarg.h>
|
||||
#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
|
||||
|
|
21
src/main.cpp
21
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();
|
||||
|
|
Loading…
Reference in New Issue