Another step towards the new SensorController. It is now also responsible for triggering the measurements based on a simple parameter (after how many seconds to trigger a measurement).

This commit is contained in:
Maurice Makaay 2020-07-13 15:48:13 +02:00
parent c5547b37ee
commit a6e7609d66
20 changed files with 125 additions and 94 deletions

View File

@ -6,34 +6,29 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
DataController *DataController::_instance = nullptr;
DataController *DataController::Instance() DataController *DataController::Instance()
{ {
if (DataController::_instance == nullptr) static DataController *_instance = new DataController();
{ return _instance;
DataController::_instance = new DataController();
}
return DataController::_instance;
} }
DataController::DataController() : _temperatureMeasurements( DataController::DataController() : _temperatureMeasurements(
"temperature", "temperature",
TemperatureSensor::Instance(), TemperatureSensor::Instance(),
TEMPERATURE_AVG_LOOKBACK, TEMPERATURE_AVG_LOOKBACK,
TEMPERATURE_SIGNIFICANT_CHANGE, 30,
PUBLISH_INTERVAL), PUBLISH_INTERVAL),
_humidityMeasurements( _humidityMeasurements(
"humidity", "humidity",
HumiditySensor::Instance(), HumiditySensor::Instance(),
HUMIDITY_AVG_LOOKBACK, HUMIDITY_AVG_LOOKBACK,
HUMIDITY_SIGNIFICANT_CHANGE, 30,
PUBLISH_INTERVAL), PUBLISH_INTERVAL),
_distanceMeasurements( _distanceMeasurements(
"distance", "distance",
DistanceSensor::Instance(), DistanceSensor::Instance(),
DISTANCE_AVG_LOOKBACK, DISTANCE_AVG_LOOKBACK,
DISTANCE_SIGNIFICANT_CHANGE, 1,
PUBLISH_INTERVAL), PUBLISH_INTERVAL),
_logger("DATA") _logger("DATA")
{ {
@ -114,7 +109,11 @@ namespace Dough
{ {
if (isConfigured()) if (isConfigured())
{ {
_sample(); _temperatureMeasurements.loop();
_humidityMeasurements.loop();
_distanceMeasurements.loop();
// Moved logic into sensor controller code.
//_sample();
} }
} }
@ -137,7 +136,7 @@ namespace Dough
{ {
_lastSample = now; _lastSample = now;
// Quickly dip the LED to indicate that a measurement is started. // Quickly dip the LED to indicate that a measurement has started.
// This is done synchroneously, because we suspend the timer interrupts // This is done synchroneously, because we suspend the timer interrupts
// in the upcoming code. // in the upcoming code.
_ui->led3.off(); _ui->led3.off();
@ -152,15 +151,15 @@ namespace Dough
switch (_sampleType) switch (_sampleType)
{ {
case SAMPLE_TEMPERATURE: case SAMPLE_TEMPERATURE:
_temperatureMeasurements.process(); _temperatureMeasurements.loop();
_sampleType = SAMPLE_HUMIDITY; _sampleType = SAMPLE_HUMIDITY;
break; break;
case SAMPLE_HUMIDITY: case SAMPLE_HUMIDITY:
_humidityMeasurements.process(); _humidityMeasurements.loop();
_sampleType = SAMPLE_DISTANCE; _sampleType = SAMPLE_DISTANCE;
break; break;
case SAMPLE_DISTANCE: case SAMPLE_DISTANCE:
_distanceMeasurements.process(); _distanceMeasurements.loop();
break; break;
} }

View File

@ -16,12 +16,6 @@
#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 * 3 // making this a 3 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
// published to MQTT. These definitions specify what is considered significant.
#define TEMPERATURE_SIGNIFICANT_CHANGE 2 // to dampen flapping between two values on transition
#define HUMIDITY_SIGNIFICANT_CHANGE 2 // also to dampen flapping behavior.
#define DISTANCE_SIGNIFICANT_CHANGE 3 // based on the sensor specification of 3mm resolution
// The minimal interval in seconds at which to forcibly publish measurements to the // The minimal interval in seconds at which to forcibly publish measurements to the
// MQTT broker. When significant changes occur in the measurements, then these will // 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. // be published to the MQTT broker at all times, independent from this interval.
@ -63,7 +57,6 @@ namespace Dough
private: private:
DataController(); DataController();
static DataController *_instance;
UI *_ui; UI *_ui;
MQTT *_mqtt; MQTT *_mqtt;
SensorController _temperatureMeasurements; SensorController _temperatureMeasurements;

View File

@ -7,15 +7,16 @@ namespace Dough
const char *mqttKey, const char *mqttKey,
SensorBase *sensor, SensorBase *sensor,
unsigned int storageSize, unsigned int storageSize,
unsigned int significantChange, unsigned int minimumMeasureTime,
unsigned int minimumPublishTime) unsigned int minimumPublishTime)
{ {
_mqttKey = mqttKey; _mqttKey = mqttKey;
_sensor = sensor; _sensor = sensor;
_storageSize = storageSize; _storageSize = storageSize;
_significantChange = significantChange; _minimumMeasureTime = minimumMeasureTime;
_minimumPublishTime = minimumPublishTime; _minimumPublishTime = minimumPublishTime;
_mqtt = MQTT::Instance(); _mqtt = MQTT::Instance();
_ui = UI::Instance();
} }
void SensorController::setup() void SensorController::setup()
@ -34,16 +35,52 @@ namespace Dough
clearHistory(); clearHistory();
} }
void SensorController::process() void SensorController::loop()
{ {
auto m = _sensor->read(); if (_mustMeasure())
_store(m); {
_measure();
}
if (_mustPublish()) if (_mustPublish())
{ {
_publish(); _publish();
} }
} }
bool SensorController::_mustMeasure()
{
// When no measurement was done yet, then do one now.
if (_lastMeasuredAt == 0)
{
return true;
}
// When enough time has passed since the last measurement,
// then start another measurement.
auto now = millis();
auto delta = now - _lastMeasuredAt;
return delta >= (_minimumMeasureTime * 1000);
}
void SensorController::_measure()
{
_lastMeasuredAt = millis();
// Quickly dip the LED to indicate that a measurement has started.
// This is done synchroneously, because we suspend the timer interrupts
// in the upcoming code.
_ui->led3.off();
delay(50);
_ui->led3.on();
// Read a measurement from the sensor. Suspend the user interface
// interrupts in the meanwhile, to not disturb the timing-sensitive
// sensor readings.
_ui->suspend();
_store(_sensor->read());
_ui->resume();
}
bool SensorController::_mustPublish() bool SensorController::_mustPublish()
{ {
Measurement lastMeasurement = getLast(); Measurement lastMeasurement = getLast();
@ -69,8 +106,10 @@ namespace Dough
return _lastPublishedAt == 0 || delta >= (_minimumPublishTime * 1000); return _lastPublishedAt == 0 || delta >= (_minimumPublishTime * 1000);
} }
auto precision = _sensor->getPrecision();
// When there is a significant change in the sensor value, then publish. // When there is a significant change in the sensor value, then publish.
if (abs(_lastPublished.value - lastMeasurement.value) >= _significantChange) if (abs(_lastPublished.value - lastMeasurement.value) >= precision)
{ {
return true; return true;
} }
@ -78,7 +117,7 @@ namespace Dough
auto average = getAverage(); auto average = getAverage();
// When there is a significant change in the average value, then publish. // When there is a significant change in the average value, then publish.
if (average.ok && abs(_lastPublishedAverage.value - average.value) >= _significantChange) if (average.ok && abs(_lastPublishedAverage.value - average.value) >= precision)
{ {
return true; return true;
} }
@ -162,4 +201,4 @@ namespace Dough
_storage[i]->clear(); _storage[i]->clear();
} }
} }
} } // namespace Dough

View File

@ -5,26 +5,31 @@
#include "Sensors/SensorBase.h" #include "Sensors/SensorBase.h"
#include "Data/Measurement.h" #include "Data/Measurement.h"
#include "Network/MQTT.h" #include "Network/MQTT.h"
#include "UI/UI.h"
namespace Dough namespace Dough
{ {
// 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 // It also provides functionality to decide when to read a measurement
// to MQTT (after significant changes occur or when the last publish // from a sensor and when to publish measurements to MQTT (after significant
// was too long ago). // changes occur or when the last publish was too long ago).
class SensorController class SensorController
{ {
public: public:
// Create a new Measurements object. // Create a new Measurements object.
// //
// @param mqttKey
// The key to use when publishing sensor values to MQTT.
// The full key will be "<prefix>/<mqttKey>" for measurement values
// and "<prefix>/<mqttKey>/average" for average values.
// @param sensor // @param sensor
// The sensor to read, implements SensorBase. // The sensor to read, implements SensorBase.
// @param storageSize // @param storageSize
// Number of measurements to keep track of for computing an average. // Number of measurements to keep track of for computing an average.
// @param significantChange // @param minimumMeasureTime
// Number that describes how much a measurement value needs to change, // The number of seconds after which to read the next measurement
// before it is considered significant and must be published to MQTT. // from the sensor.
// @param minimumPublishTime // @param minimumPublishTime
// The number of seconds after which to forcibly publish measurements // The number of seconds after which to forcibly publish measurements
// to MQTT, even when no significant changes to measurements were seen. // to MQTT, even when no significant changes to measurements were seen.
@ -32,34 +37,38 @@ namespace Dough
const char *mqttKey, const char *mqttKey,
SensorBase *sensor, SensorBase *sensor,
unsigned int storageSize, unsigned int storageSize,
unsigned int significantChange, unsigned int minimumMeasureTime,
unsigned int minimumPublishTime); unsigned int minimumPublishTime);
void setup(); void setup();
void process(); void loop();
Measurement getLast(); Measurement getLast();
Measurement getAverage(); Measurement getAverage();
void clearHistory(); void clearHistory();
private: private:
MQTT *_mqtt; MQTT *_mqtt;
UI *_ui;
const char *_mqttKey; const char *_mqttKey;
char *_mqttAverageKey; char *_mqttAverageKey;
SensorBase *_sensor; SensorBase *_sensor;
Measurement **_storage; 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;
bool _mustMeasure();
void _measure();
unsigned int _minimumMeasureTime;
unsigned long _lastMeasuredAt = 0;
bool _mustPublish();
void _publish();
unsigned int _minimumPublishTime;
unsigned long _lastPublishedAt = 0; unsigned long _lastPublishedAt = 0;
Measurement _lastPublished; Measurement _lastPublished;
Measurement _lastPublishedAverage; Measurement _lastPublishedAverage;
bool _mustPublish();
void _publish();
void _store(Measurement measurement); void _store(Measurement measurement);
unsigned int _next(); unsigned int _next();
}; };
} } // namespace Dough
#endif #endif

View File

@ -7,15 +7,10 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
MQTT *MQTT::_instance = nullptr;
MQTT *MQTT::Instance() MQTT *MQTT::Instance()
{ {
if (MQTT::_instance == nullptr) static MQTT *_instance = new MQTT();
{ return _instance;
MQTT::_instance = new MQTT();
}
return MQTT::_instance;
} }
MQTT::MQTT() : _logger("MQTT") {} MQTT::MQTT() : _logger("MQTT") {}
@ -81,6 +76,8 @@ namespace Dough
void MQTT::procesIncomingsMessages() void MQTT::procesIncomingsMessages()
{ {
// Calling loop() on the wrapped MQTT client, will fetch the
// incoming messages and distribute them to the onMessage callback.
_mqttClient.loop(); _mqttClient.loop();
} }

View File

@ -34,7 +34,6 @@ namespace Dough
private: private:
MQTT(); MQTT();
static MQTT *_instance;
MQTTClient _mqttClient; MQTTClient _mqttClient;
Logger _logger; Logger _logger;
MQTTConnectHandler _onConnect = nullptr; MQTTConnectHandler _onConnect = nullptr;

View File

@ -6,15 +6,10 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
WiFi *WiFi::_instance = nullptr;
WiFi *WiFi::Instance() WiFi *WiFi::Instance()
{ {
if (WiFi::_instance == nullptr) static WiFi *_instance = new WiFi();
{ return _instance;
WiFi::_instance = new WiFi();
}
return WiFi::_instance;
} }
WiFi::WiFi() : _logger("WIFI") {} WiFi::WiFi() : _logger("WIFI") {}

View File

@ -21,7 +21,6 @@ namespace Dough
private: private:
WiFi(); WiFi();
static WiFi *_instance;
void _setMacAddress(); void _setMacAddress();
char _macAddress[18]; // max MAC address length + 1 char _macAddress[18]; // max MAC address length + 1
Logger _logger; Logger _logger;

View File

@ -6,15 +6,10 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
DistanceSensor *DistanceSensor::_instance = nullptr;
DistanceSensor *DistanceSensor::Instance() DistanceSensor *DistanceSensor::Instance()
{ {
if (DistanceSensor::_instance == nullptr) static DistanceSensor *_instance = new DistanceSensor();
{ return _instance;
DistanceSensor::_instance = new DistanceSensor();
}
return DistanceSensor::_instance;
} }
DistanceSensor::DistanceSensor() : _logger("DISTANCE") DistanceSensor::DistanceSensor() : _logger("DISTANCE")
@ -59,4 +54,9 @@ namespace Dough
return Measurement::Value(d); return Measurement::Value(d);
} }
} }
unsigned int DistanceSensor::getPrecision()
{
return 3; // according to the sensor specifications
}
} }

View File

@ -14,14 +14,14 @@ namespace Dough
{ {
public: public:
static DistanceSensor *Instance(); static DistanceSensor *Instance();
virtual void setup();
virtual Measurement read();
void setTemperature(int temperature); void setTemperature(int temperature);
void setHumidity(int humidity); void setHumidity(int humidity);
virtual void setup();
virtual Measurement read();
virtual unsigned int getPrecision();
private: private:
DistanceSensor(); DistanceSensor();
static DistanceSensor *_instance;
Logger _logger; Logger _logger;
SensorHCSR04 *_hcsr04; SensorHCSR04 *_hcsr04;
}; };

View File

@ -6,15 +6,10 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
HumiditySensor *HumiditySensor::_instance = nullptr;
HumiditySensor *HumiditySensor::Instance() HumiditySensor *HumiditySensor::Instance()
{ {
if (HumiditySensor::_instance == nullptr) static HumiditySensor *_instance = new HumiditySensor();
{ return _instance;
HumiditySensor::_instance = new HumiditySensor();
}
return HumiditySensor::_instance;
} }
HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {} HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {}
@ -47,4 +42,9 @@ namespace Dough
return Measurement::Value(int(t)); return Measurement::Value(int(t));
} }
} }
unsigned int HumiditySensor::getPrecision()
{
return 2; // prevent flapping when transitioning from value A to value B
}
} }

View File

@ -17,10 +17,10 @@ namespace Dough
static HumiditySensor *Instance(); static HumiditySensor *Instance();
virtual void setup(); virtual void setup();
virtual Measurement read(); virtual Measurement read();
virtual unsigned int getPrecision();
private: private:
HumiditySensor(); HumiditySensor();
static HumiditySensor *_instance;
Logger _logger; Logger _logger;
}; };
} }

View File

@ -6,15 +6,13 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
SensorDHT11 *SensorDHT11::_instance = nullptr; // I am using a singleton here, to make it possible to use the physical
// DHT11 sensor from the two logical sensors TemperatureSensor and
// HumiditySensor.
SensorDHT11 *SensorDHT11::Instance() SensorDHT11 *SensorDHT11::Instance()
{ {
if (SensorDHT11::_instance == nullptr) static SensorDHT11 *_instance = new SensorDHT11();
{ return _instance;
SensorDHT11::_instance = new SensorDHT11();
}
return SensorDHT11::_instance;
} }
SensorDHT11::SensorDHT11() SensorDHT11::SensorDHT11()

View File

@ -17,7 +17,6 @@ namespace Dough
private: private:
SensorDHT11(); SensorDHT11();
static SensorDHT11 *_instance;
DHT *_dht; DHT *_dht;
}; };
} }

View File

@ -4,6 +4,7 @@ namespace Dough
{ {
SensorHCSR04::SensorHCSR04(int triggerPin, int echoPin) : _logger("HCSR04") SensorHCSR04::SensorHCSR04(int triggerPin, int echoPin) : _logger("HCSR04")
{ {
precision = 3;
_triggerPin = triggerPin; _triggerPin = triggerPin;
_echoPin = echoPin; _echoPin = echoPin;
_temperature = HCSR04_INIT_TEMPERATURE; _temperature = HCSR04_INIT_TEMPERATURE;

View File

@ -43,6 +43,7 @@ namespace Dough
void setTemperature(int temperature); void setTemperature(int temperature);
void setHumidity(int humidity); void setHumidity(int humidity);
int readDistance(); int readDistance();
int precision;
private: private:
Logger _logger; Logger _logger;

View File

@ -11,6 +11,7 @@ namespace Dough
public: public:
virtual void setup(); virtual void setup();
virtual Measurement read(); virtual Measurement read();
virtual unsigned int getPrecision();
}; };
} }

View File

@ -6,15 +6,10 @@ namespace Dough
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
TemperatureSensor *TemperatureSensor::_instance = nullptr;
TemperatureSensor *TemperatureSensor::Instance() TemperatureSensor *TemperatureSensor::Instance()
{ {
if (TemperatureSensor::_instance == nullptr) static TemperatureSensor *_instance = new TemperatureSensor();
{ return _instance;
TemperatureSensor::_instance = new TemperatureSensor();
}
return TemperatureSensor::_instance;
} }
TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {} TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {}
@ -47,4 +42,9 @@ namespace Dough
return Measurement::Value(int(t)); return Measurement::Value(int(t));
} }
} }
}
unsigned int TemperatureSensor::getPrecision()
{
return 2; // prevent flapping when transitioning from value A to value B
}
} // namespace Dough

View File

@ -17,10 +17,10 @@ namespace Dough
static TemperatureSensor *Instance(); static TemperatureSensor *Instance();
virtual void setup(); virtual void setup();
virtual Measurement read(); virtual Measurement read();
virtual unsigned int getPrecision();
private: private:
TemperatureSensor(); TemperatureSensor();
static TemperatureSensor *_instance;
Logger _logger; Logger _logger;
}; };
} }

View File

@ -3,6 +3,7 @@
namespace Dough namespace Dough
{ {
// Constructor for a button instance. // Constructor for a button instance.
//
// As a necessary evil, because of the way attachinterrupt() works in // As a necessary evil, because of the way attachinterrupt() works in
// Arduino, construction needs a bit of extra work to get the button // Arduino, construction needs a bit of extra work to get the button
// working. An interrupt service routine (ISR) function must be created // working. An interrupt service routine (ISR) function must be created