Moved measuring and publish into the Measurements class. Further code cleanup steps will follow on this, but committing since we have a nicely working version now.

This commit is contained in:
Maurice Makaay 2020-07-11 22:12:59 +02:00
parent cebbe092e9
commit f1b941d964
16 changed files with 379 additions and 252 deletions

View File

@ -6,9 +6,6 @@
DataController *DataController::_instance = nullptr; DataController *DataController::_instance = nullptr;
/**
* Fetch the DoughData singleton.
*/
DataController *DataController::Instance() DataController *DataController::Instance()
{ {
if (DataController::_instance == nullptr) if (DataController::_instance == nullptr)
@ -18,9 +15,24 @@ DataController *DataController::Instance()
return DataController::_instance; return DataController::_instance;
} }
DataController::DataController() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK), DataController::DataController() : _temperatureMeasurements(
_humidityMeasurements(HUMIDITY_AVG_LOOKBACK), "temperature",
_distanceMeasurements(DISTANCE_AVG_LOOKBACK), readTemperature,
TEMPERATURE_AVG_LOOKBACK,
TEMPERATURE_SIGNIFICANT_CHANGE,
PUBLISH_INTERVAL),
_humidityMeasurements(
"humidity",
readHumidity,
HUMIDITY_AVG_LOOKBACK,
HUMIDITY_SIGNIFICANT_CHANGE,
PUBLISH_INTERVAL),
_distanceMeasurements(
"distance",
readDistance,
DISTANCE_AVG_LOOKBACK,
DISTANCE_SIGNIFICANT_CHANGE,
PUBLISH_INTERVAL),
_logger("DATA") _logger("DATA")
{ {
_ui = DoughUI::Instance(); _ui = DoughUI::Instance();
@ -100,7 +112,6 @@ void DataController::loop()
if (isConfigured()) if (isConfigured())
{ {
_sample(); _sample();
_publish();
} }
} }
@ -135,52 +146,18 @@ void DataController::_sample()
_ui->suspend(); _ui->suspend();
// Take a sample. // Take a sample.
Measurement m;
switch (_sampleType) switch (_sampleType)
{ {
case SAMPLE_TEMPERATURE: case SAMPLE_TEMPERATURE:
m = _sensors->readTemperature(); _temperatureMeasurements.process();
_temperatureMeasurements.add(m);
if (_temperatureLastPublished.ok && m.ok && _temperatureLastPublished.value != m.value)
{
if (m.value == _temperatureMeasurements.getAverage().value ||
abs(_temperatureLastPublished.value - m.value) > TEMPERATURE_SIGNIFICANT_CHANGE) {
_publishTemperature();
}
}
else if (_temperatureLastPublished.ok == false && m.ok) {
_publishTemperature();
}
_sampleType = SAMPLE_HUMIDITY; _sampleType = SAMPLE_HUMIDITY;
break; break;
case SAMPLE_HUMIDITY: case SAMPLE_HUMIDITY:
m = _sensors->readHumidity(); _humidityMeasurements.process();
_humidityMeasurements.add(m);
if (_humidityLastPublished.ok && m.ok && _humidityLastPublished.value != m.value)
{
if (m.value == _humidityMeasurements.getAverage().value ||
abs(_humidityLastPublished.value - m.value) > HUMIDITY_SIGNIFICANT_CHANGE) {
_publishHumidity();
}
}
else if (_humidityLastPublished.ok == false && m.ok) {
_publishHumidity();
}
_sampleType = SAMPLE_DISTANCE; _sampleType = SAMPLE_DISTANCE;
break; break;
case SAMPLE_DISTANCE: case SAMPLE_DISTANCE:
m = _sensors->readDistance(); _distanceMeasurements.process();
_distanceMeasurements.add(m);
if (_distanceLastPublished.ok && m.ok && _distanceLastPublished.value != m.value)
{
if (m.value == _distanceMeasurements.getAverage().value ||
abs(_distanceLastPublished.value - m.value) > DISTANCE_SIGNIFICANT_CHANGE) {
_publishDistance();
}
}
else if (_distanceLastPublished.ok == false && m.ok) {
_publishDistance();
}
break; break;
} }
@ -194,42 +171,3 @@ void DataController::_sample()
} }
} }
} }
void DataController::_publish()
{
if (_lastPublish == 0 || millis() - _lastPublish > PUBLISH_INTERVAL)
{
_lastPublish = millis();
_publishTemperature();
_publishHumidity();
_publishDistance();
_ui->led1.dip()->fast();
}
}
void DataController::_publishTemperature()
{
auto m = _temperatureMeasurements.getLast();
_temperatureLastPublished.ok = m.ok;
_temperatureLastPublished.value = m.value;
_mqtt->publish("temperature", m);
_mqtt->publish("temperature/average", _temperatureMeasurements.getAverage());
}
void DataController::_publishHumidity()
{
auto m = _humidityMeasurements.getLast();
_humidityLastPublished.ok = m.ok;
_humidityLastPublished.value = m.value;
_mqtt->publish("humidity", _humidityMeasurements.getLast());
_mqtt->publish("humidity/average", _humidityMeasurements.getAverage());
}
void DataController::_publishDistance()
{
auto m = _distanceMeasurements.getLast();
_distanceLastPublished.ok = m.ok;
_distanceLastPublished.value = m.value;
_mqtt->publish("distance", _distanceMeasurements.getLast());
_mqtt->publish("distance/average", _distanceMeasurements.getAverage());
}

View File

@ -14,7 +14,7 @@
// in the average computation. // in the average computation.
#define TEMPERATURE_AVG_LOOKBACK 6 // making this a 3 minute average #define TEMPERATURE_AVG_LOOKBACK 6 // making this a 3 minute average
#define HUMIDITY_AVG_LOOKBACK 6 // making this a 3 minute average #define HUMIDITY_AVG_LOOKBACK 6 // making this a 3 minute average
#define DISTANCE_AVG_LOOKBACK 28 * 2 * 5 // making this a 5 minute average #define DISTANCE_AVG_LOOKBACK 28 * 2 * 3 // making this a 3 minute average
// When significant changes occur in the sensor measurements, they are // When significant changes occur in the sensor measurements, they are
// published to MQTT. These definitions specify what is considered significant. // published to MQTT. These definitions specify what is considered significant.
@ -22,10 +22,10 @@
#define HUMIDITY_SIGNIFICANT_CHANGE 2 // also to dampen flapping behavior. #define HUMIDITY_SIGNIFICANT_CHANGE 2 // also to dampen flapping behavior.
#define DISTANCE_SIGNIFICANT_CHANGE 3 // based on the sensor specification of 3mm resolution #define DISTANCE_SIGNIFICANT_CHANGE 3 // based on the sensor specification of 3mm resolution
// The minimal interval at which to forcibly publish measurements to the MQTT broker. // The minimal interval in seconds at which to forcibly publish measurements to the
// When significant changes occur in the measurements, then these will be published // MQTT broker. When significant changes occur in the measurements, then these will
// to the MQTT broker at all times, independent from this interval. // be published to the MQTT broker at all times, independent from this interval.
#define PUBLISH_INTERVAL 4000 #define PUBLISH_INTERVAL 300
#include <Arduino.h> #include <Arduino.h>
#include "Data/Measurements.h" #include "Data/Measurements.h"
@ -50,39 +50,31 @@ typedef enum
class DataController class DataController
{ {
public: public:
static DataController* Instance(); static DataController *Instance();
void setup(); void setup();
void loop(); void loop();
void clearHistory(); void clearHistory();
void setContainerHeight(int height); void setContainerHeight(int height);
bool isConfigured(); bool isConfigured();
static void handleMqttConnect(DoughMQTT* mqtt); static void handleMqttConnect(DoughMQTT *mqtt);
static void handleMqttMessage(String &key, String &value); static void handleMqttMessage(String &key, String &value);
private: private:
DataController(); DataController();
static DataController* _instance; static DataController *_instance;
DoughUI *_ui;
DoughMQTT *_mqtt;
DoughSensors *_sensors;
Measurements _temperatureMeasurements; Measurements _temperatureMeasurements;
Measurement _temperatureLastPublished;
void _publishTemperature();
Measurements _humidityMeasurements; Measurements _humidityMeasurements;
Measurement _humidityLastPublished;
void _publishHumidity();
Measurements _distanceMeasurements; Measurements _distanceMeasurements;
Measurement _distanceLastPublished;
void _publishDistance();
DoughLogger _logger; DoughLogger _logger;
DoughUI* _ui;
DoughSensors* _sensors;
DoughMQTT* _mqtt;
unsigned long _lastSample = 0; unsigned long _lastSample = 0;
unsigned long _lastPublish = 0;
DoughSampleType _sampleType = SAMPLE_TEMPERATURE; DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
int _sampleCounter = 0; int _sampleCounter = 0;
int _containerHeight; int _containerHeight;
bool _containerHeightSet; bool _containerHeightSet;
void _sample(); void _sample();
void _publish();
}; };
#endif #endif

View File

@ -15,3 +15,15 @@ Measurement Measurement::Value(int value)
m.value = value; m.value = value;
return m; return m;
} }
void Measurement::clear()
{
ok = false;
value = 0;
}
void Measurement::copyTo(Measurement* target)
{
target->ok = ok;
target->value = value;
}

View File

@ -13,6 +13,8 @@ public:
bool ok = false; bool ok = false;
static Measurement Failed(); static Measurement Failed();
static Measurement Value(int value); static Measurement Value(int value);
void clear();
void copyTo(Measurement *target);
}; };
#endif #endif

View File

@ -1,22 +1,111 @@
#include "Data/Measurements.h" #include "Data/Measurements.h"
#include "UI/DoughUI.h" #include "UI/DoughUI.h"
Measurements::Measurements(unsigned int avgLookback) Measurements::Measurements(
const char *mqttKey,
Measurement (*measureFunc)(),
unsigned int storageSize,
unsigned int significantChange,
unsigned int minimumPublishTime)
{ {
_storageSize = avgLookback; _mqttKey = mqttKey;
_measureFunc = measureFunc;
_storageSize = storageSize;
_significantChange = significantChange;
_minimumPublishTime = minimumPublishTime;
_mqtt = DoughMQTT::Instance();
_storage = new Measurement*[avgLookback]; // Format the key to use for publishing the average (i.e. "<mqttKey>/average").
for (unsigned int i = 0; i < avgLookback; i++) { auto lenAverageKey = strlen(mqttKey) + 8; // +8 for the "/average" suffix
_mqttAverageKey = new char[lenAverageKey + 1]; // +1 for the ending \0
snprintf(_mqttAverageKey, lenAverageKey, "%s/average", _mqttKey);
// Initialize the storage for holding the measurements.
_storage = new Measurement *[storageSize];
for (unsigned int i = 0; i < storageSize; i++)
{
_storage[i] = new Measurement; _storage[i] = new Measurement;
} }
clearHistory(); clearHistory();
} }
void Measurements::add(Measurement measurement) void Measurements::process()
{ {
unsigned int index = _next(); auto m = _measureFunc();
_storage[index]->ok = measurement.ok; _add(m);
_storage[index]->value = measurement.value; if (_mustPublish())
{
_publish();
}
}
bool Measurements::_mustPublish()
{
Measurement lastMeasurement = getLast();
// When the measurement failed, then there's no need to publish it.
if (lastMeasurement.ok == false)
{
return false;
}
// When no data was published before, then this is a great time to do so.
if (_lastPublished.ok == false)
{
return true;
}
// If the value did not change, only publish when the minimum publishing
// time has passed.
if (_lastPublished.value == lastMeasurement.value)
{
auto now = millis();
auto delta = now - _lastPublishedAt;
return _lastPublishedAt == 0 || delta >= (_minimumPublishTime * 1000);
}
// When there is a significant change in the sensor value, then publish.
if (abs(_lastPublished.value - lastMeasurement.value) >= _significantChange)
{
return true;
}
auto average = getAverage();
// When there is a significant change in the average value, then publish.
if (average.ok && abs(_lastPublishedAverage.value - average.value) >= _significantChange)
{
return true;
}
// When the value changed less than the significant change, but it reached
// the current average value, then publish it, since we might have reached
// a stable value.
if (average.ok && average.value == lastMeasurement.value)
{
return true;
}
// Well, we're out of options. No reason to publish the data right now.
return false;
}
void Measurements::_publish()
{
auto average = getAverage();
auto last = getLast();
_mqtt->publish(_mqttKey, last);
_mqtt->publish(_mqttAverageKey, average);
_lastPublishedAt = millis();
average.copyTo(&_lastPublishedAverage);
last.copyTo(&_lastPublished);
}
void Measurements::_add(Measurement measurement)
{
measurement.copyTo(_storage[_next()]);
if (measurement.ok) if (measurement.ok)
{ {
@ -28,17 +117,22 @@ void Measurements::add(Measurement measurement)
unsigned int Measurements::_next() unsigned int Measurements::_next()
{ {
_index++; _index++;
// Wrap around at the end of the circular buffer.
if (_index == _storageSize) if (_index == _storageSize)
{ {
_index = 0; _index = 0;
} }
// If the new position contains an ok value, update the running totals.
if (_storage[_index]->ok) if (_storage[_index]->ok)
{ {
_averageSum -= _storage[_index]->value; _averageSum -= _storage[_index]->value;
_averageCount--; _averageCount--;
} }
_storage[_index]->ok = false;
_storage[_index]->value = 0; _storage[_index]->clear();
return _index; return _index;
} }
@ -49,13 +143,9 @@ Measurement Measurements::getLast()
Measurement Measurements::getAverage() Measurement Measurements::getAverage()
{ {
Measurement result; return _averageCount > 0
if (_averageCount > 0) ? Measurement::Value(round(_averageSum / _averageCount))
{ : Measurement::Failed();
result.ok = true;
result.value = round(_averageSum / _averageCount);
}
return result;
} }
void Measurements::clearHistory() void Measurements::clearHistory()
@ -64,7 +154,6 @@ void Measurements::clearHistory()
_averageSum = 0; _averageSum = 0;
for (unsigned int i = 0; i < _storageSize; i++) for (unsigned int i = 0; i < _storageSize; i++)
{ {
_storage[i]->ok = false; _storage[i]->clear();
_storage[i]->value = 0;
} }
} }

View File

@ -3,26 +3,61 @@
#include <Arduino.h> #include <Arduino.h>
#include "Data/Measurement.h" #include "Data/Measurement.h"
#include "Network/DoughMQTT.h"
/** /**
* This class is used to store measurements for a sensor and to keep * This class is used to store measurements for a sensor and to keep
* track of running totals for handling average computations. * track of running totals for handling average computations.
* It also provides functionality to decide when to publish measurements
* to MQTT (after significant changes occur or when the last publish
* was too long ago).
*/ */
class Measurements class Measurements
{ {
public: public:
Measurements(unsigned int avgLookback); /**
void add(Measurement measurement); * Create a new Measurements object.
*
* @param measureFunc
* Function that reads a sensor and returns a Measurement object.
* @param storageSize
* Number of measurements to keep track of for computing an average.
* @param significantChange
* Number that describes how much a measurement value needs to change,
* before it is considered significant and must be published to MQTT.
* @param minimumPublishTime
* The number of seconds after which to forcibly publish measurements
* to MQTT, even when no significant changes to measurements were seen.
*/
Measurements(
const char *mqttKey,
Measurement (*measureFunc)(),
unsigned int storageSize,
unsigned int significantChange,
unsigned int minimumPublishTime);
void process();
Measurement getLast(); Measurement getLast();
Measurement getAverage(); Measurement getAverage();
void clearHistory(); void clearHistory();
private: private:
Measurement** _storage; DoughMQTT *_mqtt;
const char *_mqttKey;
char *_mqttAverageKey;
Measurement (*_measureFunc)();
Measurement **_storage;
unsigned int _storageSize; unsigned int _storageSize;
unsigned int _significantChange;
unsigned int _minimumPublishTime;
int _averageSum = 0; int _averageSum = 0;
unsigned int _averageCount = 0; unsigned int _averageCount = 0;
unsigned int _index = 0; unsigned int _index = 0;
unsigned long _lastPublishedAt = 0;
Measurement _lastPublished;
Measurement _lastPublishedAverage;
bool _mustPublish();
void _publish();
void _add(Measurement measurement);
unsigned int _next(); unsigned int _next();
}; };

View File

@ -4,12 +4,9 @@
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
DoughMQTT* DoughMQTT::_instance = nullptr; DoughMQTT *DoughMQTT::_instance = nullptr;
/** DoughMQTT *DoughMQTT::Instance()
* Fetch the DoughMQTT singleton.
*/
DoughMQTT* DoughMQTT::Instance()
{ {
if (DoughMQTT::_instance == nullptr) if (DoughMQTT::_instance == nullptr)
{ {
@ -18,7 +15,7 @@ DoughMQTT* DoughMQTT::Instance()
return DoughMQTT::_instance; return DoughMQTT::_instance;
} }
DoughMQTT::DoughMQTT() : _logger("MQTT") { } DoughMQTT::DoughMQTT() : _logger("MQTT") {}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Setup // Setup
@ -26,7 +23,7 @@ DoughMQTT::DoughMQTT() : _logger("MQTT") { }
void DoughMQTT::setup() void DoughMQTT::setup()
{ {
DoughWiFi* network = DoughWiFi::Instance(); DoughWiFi *network = DoughWiFi::Instance();
#ifdef MQTT_DEVICE_ID #ifdef MQTT_DEVICE_ID
_mqttDeviceId = MQTT_DEVICE_ID; _mqttDeviceId = MQTT_DEVICE_ID;
@ -88,7 +85,7 @@ void DoughMQTT::handleMessage(String &topic, String &payload)
{ {
DoughMQTT::Instance()->_logger.log("sSsS", "<<< ", topic, " = ", payload); DoughMQTT::Instance()->_logger.log("sSsS", "<<< ", topic, " = ", payload);
DoughMQTT* mqtt = DoughMQTT::Instance(); DoughMQTT *mqtt = DoughMQTT::Instance();
if (mqtt->_onMessage != nullptr) if (mqtt->_onMessage != nullptr)
{ {
int pos = topic.lastIndexOf('/'); int pos = topic.lastIndexOf('/');
@ -100,7 +97,7 @@ void DoughMQTT::handleMessage(String &topic, String &payload)
} }
} }
void DoughMQTT::subscribe(const char* key) void DoughMQTT::subscribe(const char *key)
{ {
char topic[200]; 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);
@ -108,7 +105,7 @@ void DoughMQTT::subscribe(const char* key)
_mqttClient.subscribe(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]; 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);
@ -123,10 +120,14 @@ void DoughMQTT::publish(const char *key, int payload)
publish(key, buf); publish(key, buf);
} }
void DoughMQTT::publish(const char *key, Measurement measurement) { void DoughMQTT::publish(const char *key, Measurement measurement)
if (measurement.ok) { {
if (measurement.ok)
{
publish(key, measurement.value); publish(key, measurement.value);
} else { }
else
{
publish(key, "null"); publish(key, "null");
} }
} }

View File

@ -14,33 +14,33 @@
*/ */
class DoughMQTT; class DoughMQTT;
typedef void (*DoughMQTTConnectHandler)(DoughMQTT* mqtt); typedef void (*DoughMQTTConnectHandler)(DoughMQTT *mqtt);
typedef void (*DoughMQTTMessageHandler)(String &key, String &value); typedef void (*DoughMQTTMessageHandler)(String &key, String &value);
class DoughMQTT class DoughMQTT
{ {
public: public:
static DoughMQTT* Instance(); static DoughMQTT *Instance();
void setup(); void setup();
void onConnect(DoughMQTTConnectHandler callback); void onConnect(DoughMQTTConnectHandler callback);
void onMessage(DoughMQTTMessageHandler callback); void onMessage(DoughMQTTMessageHandler callback);
bool isConnected(); bool isConnected();
bool connect(); bool connect();
void subscribe(const char* key); void subscribe(const char *key);
void procesIncomingsMessages(); void procesIncomingsMessages();
void publish(const char* key, const char* payload); void publish(const char *key, const char *payload);
void publish(const char* key, int payload); void publish(const char *key, int payload);
void publish(const char* key, Measurement measurement); void publish(const char *key, Measurement measurement);
private: private:
DoughMQTT(); DoughMQTT();
static DoughMQTT* _instance; static DoughMQTT *_instance;
MQTTClient _mqttClient; MQTTClient _mqttClient;
DoughLogger _logger; DoughLogger _logger;
DoughMQTTConnectHandler _onConnect = nullptr; DoughMQTTConnectHandler _onConnect = nullptr;
MQTTClientCallbackSimple _onMessage = nullptr; MQTTClientCallbackSimple _onMessage = nullptr;
static void handleMessage(String &topic, String &payload); static void handleMessage(String &topic, String &payload);
char* _mqttDeviceId; char *_mqttDeviceId;
}; };
#endif #endif

View File

@ -6,9 +6,6 @@
DoughWiFi *DoughWiFi::_instance = nullptr; DoughWiFi *DoughWiFi::_instance = nullptr;
/**
* Fetch the DoughWiFi singleton.
*/
DoughWiFi *DoughWiFi::Instance() DoughWiFi *DoughWiFi::Instance()
{ {
if (DoughWiFi::_instance == nullptr) if (DoughWiFi::_instance == nullptr)

View File

@ -11,8 +11,8 @@
class DoughWiFi class DoughWiFi
{ {
public: public:
static DoughWiFi* Instance(); static DoughWiFi *Instance();
char* getMacAddress(); char *getMacAddress();
void setup(); void setup();
void loop(); void loop();
bool isConnected(); bool isConnected();
@ -21,7 +21,7 @@ public:
private: private:
DoughWiFi(); DoughWiFi();
static DoughWiFi* _instance; static DoughWiFi *_instance;
void _setMacAddress(); void _setMacAddress();
char _macAddress[18]; // max MAC address length + 1 char _macAddress[18]; // max MAC address length + 1
DoughLogger _logger; DoughLogger _logger;

View File

@ -4,19 +4,19 @@
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
DoughSensors* DoughSensors::_instance = nullptr; DoughSensors *DoughSensors::_instance = nullptr;
/** DoughSensors *DoughSensors::Instance()
* Fetch the DoughSensors singleton. {
*/ if (DoughSensors::_instance == nullptr)
DoughSensors* DoughSensors::Instance() { {
if (DoughSensors::_instance == nullptr) {
DoughSensors::_instance = new DoughSensors(); DoughSensors::_instance = new DoughSensors();
} }
return DoughSensors::_instance; return DoughSensors::_instance;
} }
DoughSensors::DoughSensors() : _logger("SENSORS") { DoughSensors::DoughSensors() : _logger("SENSORS")
{
_dht = new DHT(DHT11_DATA_PIN, DHT11); _dht = new DHT(DHT11_DATA_PIN, DHT11);
_hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN); _hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
} }
@ -25,7 +25,8 @@ DoughSensors::DoughSensors() : _logger("SENSORS") {
// setup // setup
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void DoughSensors::setup() { void DoughSensors::setup()
{
_dht->begin(); _dht->begin();
_hcsr04->begin(); _hcsr04->begin();
} }
@ -34,12 +35,16 @@ void DoughSensors::setup() {
// loop // loop
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
Measurement DoughSensors::readTemperature() { Measurement DoughSensors::readTemperature()
{
float t = _dht->readTemperature(); float t = _dht->readTemperature();
if (isnan(t)) { if (isnan(t))
{
_logger.log("s", "ERROR - Temperature measurement failed"); _logger.log("s", "ERROR - Temperature measurement failed");
return Measurement::Failed(); return Measurement::Failed();
} else { }
else
{
_logger.log("sis", "Temperature = ", int(t), "°C "); _logger.log("sis", "Temperature = ", int(t), "°C ");
_hcsr04->setTemperature(int(t)); _hcsr04->setTemperature(int(t));
auto m = Measurement::Value(int(t)); auto m = Measurement::Value(int(t));
@ -47,25 +52,52 @@ Measurement DoughSensors::readTemperature() {
} }
} }
Measurement DoughSensors::readHumidity() { Measurement DoughSensors::readHumidity()
{
int h = _dht->readHumidity(); int h = _dht->readHumidity();
if (h == 0) { if (h == 0)
{
_logger.log("s", "ERROR - Humidity measurement failed"); _logger.log("s", "ERROR - Humidity measurement failed");
return Measurement::Failed(); return Measurement::Failed();
} else { }
else
{
_logger.log("sis", "Humidity = ", h, "%"); _logger.log("sis", "Humidity = ", h, "%");
_hcsr04->setHumidity(h); _hcsr04->setHumidity(h);
return Measurement::Value(h); return Measurement::Value(h);
} }
} }
Measurement DoughSensors::readDistance() { Measurement DoughSensors::readDistance()
{
int d = _hcsr04->readDistance(); int d = _hcsr04->readDistance();
if (d == -1) { if (d == -1)
{
_logger.log("s", "ERROR - Distance measurement failed"); _logger.log("s", "ERROR - Distance measurement failed");
return Measurement::Failed(); return Measurement::Failed();
} else { }
else
{
_logger.log("sis", "Distance = ", d, "mm"); _logger.log("sis", "Distance = ", d, "mm");
return Measurement::Value(d); return Measurement::Value(d);
} }
} }
// ----------------------------------------------------------------------
// Function access to the sensor reading.
// ----------------------------------------------------------------------
Measurement readTemperature()
{
return DoughSensors::Instance()->readTemperature();
}
Measurement readHumidity()
{
return DoughSensors::Instance()->readHumidity();
}
Measurement readDistance()
{
return DoughSensors::Instance()->readDistance();
}

View File

@ -10,20 +10,25 @@
/** /**
* This class provides access to the sensors in the device. * This class provides access to the sensors in the device.
*/ */
class DoughSensors { class DoughSensors
public: {
static DoughSensors* Instance(); public:
static DoughSensors *Instance();
void setup(); void setup();
Measurement readTemperature(); Measurement readTemperature();
Measurement readHumidity(); Measurement readHumidity();
Measurement readDistance(); Measurement readDistance();
private: private:
DoughSensors(); DoughSensors();
static DoughSensors* _instance; static DoughSensors *_instance;
DoughLogger _logger; DoughLogger _logger;
DHT* _dht; DHT *_dht;
HCSR04* _hcsr04; HCSR04 *_hcsr04;
}; };
Measurement readTemperature();
Measurement readHumidity();
Measurement readDistance();
#endif #endif

View File

@ -1,22 +1,26 @@
#include "Sensors/HCSR04.h" #include "Sensors/HCSR04.h"
HCSR04::HCSR04(int triggerPin, int echoPin) { HCSR04::HCSR04(int triggerPin, int echoPin)
{
_triggerPin = triggerPin; _triggerPin = triggerPin;
_echoPin = echoPin; _echoPin = echoPin;
_temperature = HCSR04_INIT_TEMPERATURE; _temperature = HCSR04_INIT_TEMPERATURE;
_humidity = HCSR04_INIT_HUMIDITY; _humidity = HCSR04_INIT_HUMIDITY;
} }
void HCSR04::begin() { void HCSR04::begin()
{
pinMode(_triggerPin, OUTPUT); pinMode(_triggerPin, OUTPUT);
pinMode(_echoPin, INPUT); pinMode(_echoPin, INPUT);
} }
void HCSR04::setTemperature(int temperature) { void HCSR04::setTemperature(int temperature)
{
_temperature = temperature; _temperature = temperature;
} }
void HCSR04::setHumidity(int humidity) { void HCSR04::setHumidity(int humidity)
{
_humidity = humidity; _humidity = humidity;
} }
@ -25,11 +29,13 @@ void HCSR04::setHumidity(int humidity) {
* When reading the distance fails, -1 is returned. * When reading the distance fails, -1 is returned.
* Otherwise the distance in mm. * Otherwise the distance in mm.
*/ */
int HCSR04::readDistance() { int HCSR04::readDistance()
{
_setSpeedOfSound(); _setSpeedOfSound();
_setEchoTimeout(); _setEchoTimeout();
_takeSamples(); _takeSamples();
if (_haveEnoughSamples()) { if (_haveEnoughSamples())
{
_sortSamples(); _sortSamples();
return _computeAverage(); return _computeAverage();
} }
@ -41,39 +47,47 @@ int HCSR04::readDistance() {
* and relative humidity. I derived this formula from a YouTube * and relative humidity. I derived this formula from a YouTube
* video about the HC-SR04: https://youtu.be/6F1B_N6LuKw?t=1548 * video about the HC-SR04: https://youtu.be/6F1B_N6LuKw?t=1548
*/ */
void HCSR04::_setSpeedOfSound() { void HCSR04::_setSpeedOfSound()
{
_speedOfSound = _speedOfSound =
0.3314 + 0.3314 +
(0.000606 * _temperature) + (0.000606 * _temperature) +
(0.0000124 * _humidity); (0.0000124 * _humidity);
} }
void HCSR04::_setEchoTimeout() { void HCSR04::_setEchoTimeout()
{
_echoTimeout = HCSR04_MAX_MM * 2 / _speedOfSound; _echoTimeout = HCSR04_MAX_MM * 2 / _speedOfSound;
} }
void HCSR04::_takeSamples() { void HCSR04::_takeSamples()
{
_successfulSamples = 0; _successfulSamples = 0;
for (int i = 0; i<HCSR04_SAMPLES_TAKE; i++) { for (int i = 0; i < HCSR04_SAMPLES_TAKE; i++)
{
// Because I notice some repeating patterns in timings when doing // Because I notice some repeating patterns in timings when doing
// a tight loop here, I add some random waits to get a better spread // a tight loop here, I add some random waits to get a better spread
// of sample values. // of sample values.
if (i > 0) { if (i > 0)
{
delay(HCSR04_SAMPLE_WAIT + random(HCSR04_SAMPLE_WAIT_SPREAD)); delay(HCSR04_SAMPLE_WAIT + random(HCSR04_SAMPLE_WAIT_SPREAD));
} }
int distance = _takeSample(); int distance = _takeSample();
if (distance != -1) { if (distance != -1)
{
_samples[i] = distance; _samples[i] = distance;
_successfulSamples++; _successfulSamples++;
} }
} }
} }
bool HCSR04::_haveEnoughSamples() { bool HCSR04::_haveEnoughSamples()
{
return _successfulSamples >= HCSR04_SAMPLES_USE; return _successfulSamples >= HCSR04_SAMPLES_USE;
} }
int HCSR04::_takeSample() { int HCSR04::_takeSample()
{
// Send 10μs trigger to ask sensor for a measurement. // Send 10μs trigger to ask sensor for a measurement.
digitalWrite(HCSR04_TRIG_PIN, LOW); digitalWrite(HCSR04_TRIG_PIN, LOW);
delayMicroseconds(2); delayMicroseconds(2);
@ -86,20 +100,27 @@ int HCSR04::_takeSample() {
// Compute the distance, based on the echo signal length. // Compute the distance, based on the echo signal length.
double distance = durationMicroSec / 2.0 * _speedOfSound; double distance = durationMicroSec / 2.0 * _speedOfSound;
if (distance < HCSR04_MIN_MM || distance >= HCSR04_MAX_MM) { if (distance < HCSR04_MIN_MM || distance >= HCSR04_MAX_MM)
{
return -1; return -1;
} else { }
else
{
return distance; return distance;
} }
} }
void HCSR04::_sortSamples() { void HCSR04::_sortSamples()
{
int holder, x, y; int holder, x, y;
for(x = 0; x < _successfulSamples; x++) { for (x = 0; x < _successfulSamples; x++)
for(y = 0; y < _successfulSamples-1; y++) { {
if(_samples[y] > _samples[y+1]) { for (y = 0; y < _successfulSamples - 1; y++)
holder = _samples[y+1]; {
_samples[y+1] = _samples[y]; if (_samples[y] > _samples[y + 1])
{
holder = _samples[y + 1];
_samples[y + 1] = _samples[y];
_samples[y] = holder; _samples[y] = holder;
} }
} }
@ -112,11 +133,13 @@ void HCSR04::_sortSamples() {
* When not enough samples were collected in the previous steps, then * When not enough samples were collected in the previous steps, then
* NAN is returned. * NAN is returned.
*/ */
int HCSR04::_computeAverage() { int HCSR04::_computeAverage()
{
float sum = 0; float sum = 0;
int offset = (_successfulSamples - HCSR04_SAMPLES_USE) / 2; int offset = (_successfulSamples - HCSR04_SAMPLES_USE) / 2;
for (int i = 0; i<HCSR04_SAMPLES_USE; i++) { for (int i = 0; i < HCSR04_SAMPLES_USE; i++)
sum += _samples[i+offset]; {
sum += _samples[i + offset];
} }
return round(sum / HCSR04_SAMPLES_USE); return round(sum / HCSR04_SAMPLES_USE);

View File

@ -31,15 +31,16 @@
/** /**
* This class is used to get a distance reading from an HCSR04 sensor. * This class is used to get a distance reading from an HCSR04 sensor.
*/ */
class HCSR04 { class HCSR04
public: {
public:
HCSR04(int triggerPin, int echoPin); HCSR04(int triggerPin, int echoPin);
void begin(); void begin();
void setTemperature(int temperature); void setTemperature(int temperature);
void setHumidity(int humidity); void setHumidity(int humidity);
int readDistance(); int readDistance();
private: private:
int _triggerPin; int _triggerPin;
int _echoPin; int _echoPin;
int _humidity; int _humidity;

View File

@ -5,7 +5,7 @@ DoughLogger::DoughLogger(const char *section)
_section = section; _section = section;
} }
void DoughLogger::log(const char* fmt, ...) void DoughLogger::log(const char *fmt, ...)
{ {
char buf[LOGGER_PREFIX_BUFLEN]; char buf[LOGGER_PREFIX_BUFLEN];
snprintf(buf, sizeof(buf) / sizeof(buf[0]), LOGGER_PREFIX_FORMAT, _section); snprintf(buf, sizeof(buf) / sizeof(buf[0]), LOGGER_PREFIX_FORMAT, _section);

View File

@ -19,7 +19,7 @@ public:
void log(const char *fmt, ...); void log(const char *fmt, ...);
private: private:
const char* _section; const char *_section;
}; };
#endif #endif