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
// ----------------------------------------------------------------------
DataController *DataController::_instance = nullptr;
DataController *DataController::Instance()
{
if (DataController::_instance == nullptr)
{
DataController::_instance = new DataController();
}
return DataController::_instance;
static DataController *_instance = new DataController();
return _instance;
}
DataController::DataController() : _temperatureMeasurements(
"temperature",
TemperatureSensor::Instance(),
TEMPERATURE_AVG_LOOKBACK,
TEMPERATURE_SIGNIFICANT_CHANGE,
30,
PUBLISH_INTERVAL),
_humidityMeasurements(
"humidity",
HumiditySensor::Instance(),
HUMIDITY_AVG_LOOKBACK,
HUMIDITY_SIGNIFICANT_CHANGE,
30,
PUBLISH_INTERVAL),
_distanceMeasurements(
"distance",
DistanceSensor::Instance(),
DISTANCE_AVG_LOOKBACK,
DISTANCE_SIGNIFICANT_CHANGE,
1,
PUBLISH_INTERVAL),
_logger("DATA")
{
@ -114,7 +109,11 @@ namespace Dough
{
if (isConfigured())
{
_sample();
_temperatureMeasurements.loop();
_humidityMeasurements.loop();
_distanceMeasurements.loop();
// Moved logic into sensor controller code.
//_sample();
}
}
@ -137,7 +136,7 @@ namespace Dough
{
_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
// in the upcoming code.
_ui->led3.off();
@ -152,15 +151,15 @@ namespace Dough
switch (_sampleType)
{
case SAMPLE_TEMPERATURE:
_temperatureMeasurements.process();
_temperatureMeasurements.loop();
_sampleType = SAMPLE_HUMIDITY;
break;
case SAMPLE_HUMIDITY:
_humidityMeasurements.process();
_humidityMeasurements.loop();
_sampleType = SAMPLE_DISTANCE;
break;
case SAMPLE_DISTANCE:
_distanceMeasurements.process();
_distanceMeasurements.loop();
break;
}

View File

@ -16,12 +16,6 @@
#define HUMIDITY_AVG_LOOKBACK 6 // 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
// 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.
@ -63,7 +57,6 @@ namespace Dough
private:
DataController();
static DataController *_instance;
UI *_ui;
MQTT *_mqtt;
SensorController _temperatureMeasurements;

View File

@ -7,15 +7,16 @@ namespace Dough
const char *mqttKey,
SensorBase *sensor,
unsigned int storageSize,
unsigned int significantChange,
unsigned int minimumMeasureTime,
unsigned int minimumPublishTime)
{
_mqttKey = mqttKey;
_sensor = sensor;
_storageSize = storageSize;
_significantChange = significantChange;
_minimumMeasureTime = minimumMeasureTime;
_minimumPublishTime = minimumPublishTime;
_mqtt = MQTT::Instance();
_ui = UI::Instance();
}
void SensorController::setup()
@ -34,16 +35,52 @@ namespace Dough
clearHistory();
}
void SensorController::process()
void SensorController::loop()
{
auto m = _sensor->read();
_store(m);
if (_mustMeasure())
{
_measure();
}
if (_mustPublish())
{
_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()
{
Measurement lastMeasurement = getLast();
@ -69,8 +106,10 @@ namespace Dough
return _lastPublishedAt == 0 || delta >= (_minimumPublishTime * 1000);
}
auto precision = _sensor->getPrecision();
// 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;
}
@ -78,7 +117,7 @@ namespace Dough
auto average = getAverage();
// 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;
}
@ -162,4 +201,4 @@ namespace Dough
_storage[i]->clear();
}
}
}
} // namespace Dough

View File

@ -5,26 +5,31 @@
#include "Sensors/SensorBase.h"
#include "Data/Measurement.h"
#include "Network/MQTT.h"
#include "UI/UI.h"
namespace Dough
{
// This class is used to store measurements for a sensor and to keep
// 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).
// It also provides functionality to decide when to read a measurement
// from a sensor and when to publish measurements to MQTT (after significant
// changes occur or when the last publish was too long ago).
class SensorController
{
public:
// 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
// The sensor to read, implements SensorBase.
// @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 minimumMeasureTime
// The number of seconds after which to read the next measurement
// from the sensor.
// @param minimumPublishTime
// The number of seconds after which to forcibly publish measurements
// to MQTT, even when no significant changes to measurements were seen.
@ -32,34 +37,38 @@ namespace Dough
const char *mqttKey,
SensorBase *sensor,
unsigned int storageSize,
unsigned int significantChange,
unsigned int minimumMeasureTime,
unsigned int minimumPublishTime);
void setup();
void process();
void loop();
Measurement getLast();
Measurement getAverage();
void clearHistory();
private:
MQTT *_mqtt;
UI *_ui;
const char *_mqttKey;
char *_mqttAverageKey;
SensorBase *_sensor;
Measurement **_storage;
unsigned int _storageSize;
unsigned int _significantChange;
unsigned int _minimumPublishTime;
int _averageSum = 0;
unsigned int _averageCount = 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;
Measurement _lastPublished;
Measurement _lastPublishedAverage;
bool _mustPublish();
void _publish();
void _store(Measurement measurement);
unsigned int _next();
};
}
} // namespace Dough
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,15 +6,10 @@ namespace Dough
// Constructor
// ----------------------------------------------------------------------
HumiditySensor *HumiditySensor::_instance = nullptr;
HumiditySensor *HumiditySensor::Instance()
{
if (HumiditySensor::_instance == nullptr)
{
HumiditySensor::_instance = new HumiditySensor();
}
return HumiditySensor::_instance;
static HumiditySensor *_instance = new HumiditySensor();
return _instance;
}
HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {}
@ -47,4 +42,9 @@ namespace Dough
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();
virtual void setup();
virtual Measurement read();
virtual unsigned int getPrecision();
private:
HumiditySensor();
static HumiditySensor *_instance;
Logger _logger;
};
}

View File

@ -6,15 +6,13 @@ namespace Dough
// 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()
{
if (SensorDHT11::_instance == nullptr)
{
SensorDHT11::_instance = new SensorDHT11();
}
return SensorDHT11::_instance;
static SensorDHT11 *_instance = new SensorDHT11();
return _instance;
}
SensorDHT11::SensorDHT11()

View File

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

View File

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

View File

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

View File

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

View File

@ -6,15 +6,10 @@ namespace Dough
// Constructor
// ----------------------------------------------------------------------
TemperatureSensor *TemperatureSensor::_instance = nullptr;
TemperatureSensor *TemperatureSensor::Instance()
{
if (TemperatureSensor::_instance == nullptr)
{
TemperatureSensor::_instance = new TemperatureSensor();
}
return TemperatureSensor::_instance;
static TemperatureSensor *_instance = new TemperatureSensor();
return _instance;
}
TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {}
@ -47,4 +42,9 @@ namespace Dough
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();
virtual void setup();
virtual Measurement read();
virtual unsigned int getPrecision();
private:
TemperatureSensor();
static TemperatureSensor *_instance;
Logger _logger;
};
}

View File

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