Completed moving all code into its own Dough namespace, to prevent naming clashes with stuff like WiFi and DHT11.
This commit is contained in:
parent
3f8eb8b2b8
commit
a31840c479
|
@ -1,176 +1,177 @@
|
|||
#include "Data/DataController.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DataController *DataController::_instance = nullptr;
|
||||
|
||||
DataController *DataController::Instance()
|
||||
namespace Dough
|
||||
{
|
||||
if (DataController::_instance == nullptr)
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DataController *DataController::_instance = nullptr;
|
||||
|
||||
DataController *DataController::Instance()
|
||||
{
|
||||
DataController::_instance = new DataController();
|
||||
}
|
||||
return DataController::_instance;
|
||||
}
|
||||
|
||||
DataController::DataController() : _temperatureMeasurements(
|
||||
"temperature",
|
||||
TemperatureSensor::Instance(),
|
||||
TEMPERATURE_AVG_LOOKBACK,
|
||||
TEMPERATURE_SIGNIFICANT_CHANGE,
|
||||
PUBLISH_INTERVAL),
|
||||
_humidityMeasurements(
|
||||
"humidity",
|
||||
HumiditySensor::Instance(),
|
||||
HUMIDITY_AVG_LOOKBACK,
|
||||
HUMIDITY_SIGNIFICANT_CHANGE,
|
||||
PUBLISH_INTERVAL),
|
||||
_distanceMeasurements(
|
||||
"distance",
|
||||
DistanceSensor::Instance(),
|
||||
DISTANCE_AVG_LOOKBACK,
|
||||
DISTANCE_SIGNIFICANT_CHANGE,
|
||||
PUBLISH_INTERVAL),
|
||||
_logger("DATA")
|
||||
{
|
||||
_ui = DoughUI::Instance();
|
||||
_mqtt = Dough::MQTT::Instance();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DataController::setup()
|
||||
{
|
||||
_containerHeight = 0.00;
|
||||
_containerHeightSet = false;
|
||||
|
||||
Dough::MQTT *mqtt = Dough::MQTT::Instance();
|
||||
mqtt->onConnect(DataController::handleMqttConnect);
|
||||
mqtt->onMessage(DataController::handleMqttMessage);
|
||||
|
||||
_temperatureMeasurements.setup();
|
||||
_humidityMeasurements.setup();
|
||||
_distanceMeasurements.setup();
|
||||
}
|
||||
|
||||
void DataController::handleMqttConnect(Dough::MQTT *mqtt)
|
||||
{
|
||||
mqtt->subscribe("container_height");
|
||||
}
|
||||
|
||||
void DataController::handleMqttMessage(String &key, String &payload)
|
||||
{
|
||||
if (key.equals("container_height"))
|
||||
{
|
||||
DataController::Instance()->setContainerHeight(payload.toInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
DataController::Instance()->_logger.log("sS", "ERROR - Unhandled MQTT message, key = ", key);
|
||||
}
|
||||
}
|
||||
|
||||
bool DataController::isConfigured()
|
||||
{
|
||||
return _containerHeightSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the container height in mm. This is the distance between the sensor
|
||||
* and the bottom of the container. It is used to determine the height of
|
||||
* the starter or dough by subtracting the distance measurement from it.
|
||||
*/
|
||||
void DataController::setContainerHeight(int height)
|
||||
{
|
||||
_containerHeightSet = false;
|
||||
if (height <= HCSR04_MIN_MM)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_logger.log("sisis", "ERROR - Container height ", height,
|
||||
"mm is more than the maximum measuring distance of ",
|
||||
HCSR04_MAX_MM, "mm");
|
||||
return;
|
||||
}
|
||||
_logger.log("sis", "Set container height to ", height, "mm");
|
||||
_containerHeight = height;
|
||||
_containerHeightSet = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DataController::loop()
|
||||
{
|
||||
if (isConfigured())
|
||||
{
|
||||
_sample();
|
||||
}
|
||||
}
|
||||
|
||||
void DataController::clearHistory()
|
||||
{
|
||||
_temperatureMeasurements.clearHistory();
|
||||
_humidityMeasurements.clearHistory();
|
||||
_distanceMeasurements.clearHistory();
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
_sampleCounter = 0;
|
||||
}
|
||||
|
||||
void DataController::_sample()
|
||||
{
|
||||
auto now = millis();
|
||||
auto delta = now - _lastSample;
|
||||
auto tick = _lastSample == 0 || delta >= SAMPLE_INTERVAL;
|
||||
|
||||
if (tick)
|
||||
{
|
||||
_lastSample = now;
|
||||
|
||||
// Quickly dip the LED to indicate that a measurement is started.
|
||||
// This is done synchroneously, because we suspend the timer interrupts
|
||||
// in the upcoming code.
|
||||
_ui->led3.off();
|
||||
delay(50);
|
||||
_ui->led3.on();
|
||||
|
||||
// Suspend the UI timer interrupts, to not let these interfere
|
||||
// with the sensor measurements.
|
||||
_ui->suspend();
|
||||
|
||||
// Take a sample.
|
||||
switch (_sampleType)
|
||||
if (DataController::_instance == nullptr)
|
||||
{
|
||||
case SAMPLE_TEMPERATURE:
|
||||
_temperatureMeasurements.process();
|
||||
_sampleType = SAMPLE_HUMIDITY;
|
||||
break;
|
||||
case SAMPLE_HUMIDITY:
|
||||
_humidityMeasurements.process();
|
||||
_sampleType = SAMPLE_DISTANCE;
|
||||
break;
|
||||
case SAMPLE_DISTANCE:
|
||||
_distanceMeasurements.process();
|
||||
break;
|
||||
DataController::_instance = new DataController();
|
||||
}
|
||||
return DataController::_instance;
|
||||
}
|
||||
|
||||
_ui->resume();
|
||||
DataController::DataController() : _temperatureMeasurements(
|
||||
"temperature",
|
||||
TemperatureSensor::Instance(),
|
||||
TEMPERATURE_AVG_LOOKBACK,
|
||||
TEMPERATURE_SIGNIFICANT_CHANGE,
|
||||
PUBLISH_INTERVAL),
|
||||
_humidityMeasurements(
|
||||
"humidity",
|
||||
HumiditySensor::Instance(),
|
||||
HUMIDITY_AVG_LOOKBACK,
|
||||
HUMIDITY_SIGNIFICANT_CHANGE,
|
||||
PUBLISH_INTERVAL),
|
||||
_distanceMeasurements(
|
||||
"distance",
|
||||
DistanceSensor::Instance(),
|
||||
DISTANCE_AVG_LOOKBACK,
|
||||
DISTANCE_SIGNIFICANT_CHANGE,
|
||||
PUBLISH_INTERVAL),
|
||||
_logger("DATA")
|
||||
{
|
||||
_ui = UI::Instance();
|
||||
_mqtt = MQTT::Instance();
|
||||
}
|
||||
|
||||
_sampleCounter++;
|
||||
if (_sampleCounter == SAMPLE_CYCLE_LENGTH)
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DataController::setup()
|
||||
{
|
||||
_containerHeight = 0.00;
|
||||
_containerHeightSet = false;
|
||||
|
||||
MQTT *mqtt = MQTT::Instance();
|
||||
mqtt->onConnect(DataController::handleMqttConnect);
|
||||
mqtt->onMessage(DataController::handleMqttMessage);
|
||||
|
||||
_temperatureMeasurements.setup();
|
||||
_humidityMeasurements.setup();
|
||||
_distanceMeasurements.setup();
|
||||
}
|
||||
|
||||
void DataController::handleMqttConnect(MQTT *mqtt)
|
||||
{
|
||||
mqtt->subscribe("container_height");
|
||||
}
|
||||
|
||||
void DataController::handleMqttMessage(String &key, String &payload)
|
||||
{
|
||||
if (key.equals("container_height"))
|
||||
{
|
||||
_sampleCounter = 0;
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
DataController::Instance()->setContainerHeight(payload.toInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
DataController::Instance()->_logger.log("sS", "ERROR - Unhandled MQTT message, key = ", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DataController::isConfigured()
|
||||
{
|
||||
return _containerHeightSet;
|
||||
}
|
||||
|
||||
// Set the container height in mm. This is the distance between the sensor
|
||||
// and the bottom of the container. It is used to determine the height of
|
||||
// the starter or dough by subtracting the distance measurement from it.
|
||||
void DataController::setContainerHeight(int height)
|
||||
{
|
||||
_containerHeightSet = false;
|
||||
if (height <= HCSR04_MIN_MM)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_logger.log("sisis", "ERROR - Container height ", height,
|
||||
"mm is more than the maximum measuring distance of ",
|
||||
HCSR04_MAX_MM, "mm");
|
||||
return;
|
||||
}
|
||||
_logger.log("sis", "Set container height to ", height, "mm");
|
||||
_containerHeight = height;
|
||||
_containerHeightSet = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DataController::loop()
|
||||
{
|
||||
if (isConfigured())
|
||||
{
|
||||
_sample();
|
||||
}
|
||||
}
|
||||
|
||||
void DataController::clearHistory()
|
||||
{
|
||||
_temperatureMeasurements.clearHistory();
|
||||
_humidityMeasurements.clearHistory();
|
||||
_distanceMeasurements.clearHistory();
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
_sampleCounter = 0;
|
||||
}
|
||||
|
||||
void DataController::_sample()
|
||||
{
|
||||
auto now = millis();
|
||||
auto delta = now - _lastSample;
|
||||
auto tick = _lastSample == 0 || delta >= SAMPLE_INTERVAL;
|
||||
|
||||
if (tick)
|
||||
{
|
||||
_lastSample = now;
|
||||
|
||||
// Quickly dip the LED to indicate that a measurement is started.
|
||||
// This is done synchroneously, because we suspend the timer interrupts
|
||||
// in the upcoming code.
|
||||
_ui->led3.off();
|
||||
delay(50);
|
||||
_ui->led3.on();
|
||||
|
||||
// Suspend the UI timer interrupts, to not let these interfere
|
||||
// with the sensor measurements.
|
||||
_ui->suspend();
|
||||
|
||||
// Take a sample.
|
||||
switch (_sampleType)
|
||||
{
|
||||
case SAMPLE_TEMPERATURE:
|
||||
_temperatureMeasurements.process();
|
||||
_sampleType = SAMPLE_HUMIDITY;
|
||||
break;
|
||||
case SAMPLE_HUMIDITY:
|
||||
_humidityMeasurements.process();
|
||||
_sampleType = SAMPLE_DISTANCE;
|
||||
break;
|
||||
case SAMPLE_DISTANCE:
|
||||
_distanceMeasurements.process();
|
||||
break;
|
||||
}
|
||||
|
||||
_ui->resume();
|
||||
|
||||
_sampleCounter++;
|
||||
if (_sampleCounter == SAMPLE_CYCLE_LENGTH)
|
||||
{
|
||||
_sampleCounter = 0;
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Dough
|
|
@ -28,54 +28,55 @@
|
|||
#define PUBLISH_INTERVAL 300
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Data/Measurements.h"
|
||||
#include "Data/SensorController.h"
|
||||
#include "Sensors/TemperatureSensor.h"
|
||||
#include "Sensors/HumiditySensor.h"
|
||||
#include "Sensors/DistanceSensor.h"
|
||||
#include "Network/DoughWiFi.h"
|
||||
#include "Network/WiFi.h"
|
||||
#include "Network/MQTT.h"
|
||||
#include "UI/DoughUI.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "UI/UI.h"
|
||||
#include "UI/Logger.h"
|
||||
|
||||
typedef enum
|
||||
namespace Dough
|
||||
{
|
||||
SAMPLE_TEMPERATURE,
|
||||
SAMPLE_HUMIDITY,
|
||||
SAMPLE_DISTANCE
|
||||
} DoughSampleType;
|
||||
typedef enum
|
||||
{
|
||||
SAMPLE_TEMPERATURE,
|
||||
SAMPLE_HUMIDITY,
|
||||
SAMPLE_DISTANCE
|
||||
} DoughSampleType;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
public:
|
||||
static DataController *Instance();
|
||||
void setup();
|
||||
void loop();
|
||||
void clearHistory();
|
||||
void setContainerHeight(int height);
|
||||
bool isConfigured();
|
||||
static void handleMqttConnect(Dough::MQTT *mqtt);
|
||||
static void handleMqttMessage(String &key, String &value);
|
||||
// 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
|
||||
{
|
||||
public:
|
||||
static DataController *Instance();
|
||||
void setup();
|
||||
void loop();
|
||||
void clearHistory();
|
||||
void setContainerHeight(int height);
|
||||
bool isConfigured();
|
||||
static void handleMqttConnect(MQTT *mqtt);
|
||||
static void handleMqttMessage(String &key, String &value);
|
||||
|
||||
private:
|
||||
DataController();
|
||||
static DataController *_instance;
|
||||
DoughUI *_ui;
|
||||
Dough::MQTT *_mqtt;
|
||||
Measurements _temperatureMeasurements;
|
||||
Measurements _humidityMeasurements;
|
||||
Measurements _distanceMeasurements;
|
||||
DoughLogger _logger;
|
||||
unsigned long _lastSample = 0;
|
||||
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
|
||||
int _sampleCounter = 0;
|
||||
int _containerHeight;
|
||||
bool _containerHeightSet;
|
||||
void _sample();
|
||||
};
|
||||
private:
|
||||
DataController();
|
||||
static DataController *_instance;
|
||||
UI *_ui;
|
||||
MQTT *_mqtt;
|
||||
SensorController _temperatureMeasurements;
|
||||
SensorController _humidityMeasurements;
|
||||
SensorController _distanceMeasurements;
|
||||
Logger _logger;
|
||||
unsigned long _lastSample = 0;
|
||||
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
|
||||
int _sampleCounter = 0;
|
||||
int _containerHeight;
|
||||
bool _containerHeightSet;
|
||||
void _sample();
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -1,29 +1,32 @@
|
|||
#include "Data/Measurement.h"
|
||||
|
||||
Measurement::Measurement() { }
|
||||
|
||||
Measurement Measurement::Failed()
|
||||
namespace Dough
|
||||
{
|
||||
Measurement m;
|
||||
return m;
|
||||
}
|
||||
Measurement::Measurement() {}
|
||||
|
||||
Measurement Measurement::Value(int value)
|
||||
{
|
||||
Measurement m;
|
||||
m.ok = true;
|
||||
m.value = value;
|
||||
return m;
|
||||
}
|
||||
Measurement Measurement::Failed()
|
||||
{
|
||||
Measurement m;
|
||||
return m;
|
||||
}
|
||||
|
||||
void Measurement::clear()
|
||||
{
|
||||
ok = false;
|
||||
value = 0;
|
||||
}
|
||||
Measurement Measurement::Value(int value)
|
||||
{
|
||||
Measurement m;
|
||||
m.ok = true;
|
||||
m.value = value;
|
||||
return m;
|
||||
}
|
||||
|
||||
void Measurement::copyTo(Measurement* target)
|
||||
{
|
||||
target->ok = ok;
|
||||
target->value = value;
|
||||
}
|
||||
void Measurement::clear()
|
||||
{
|
||||
ok = false;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
void Measurement::copyTo(Measurement *target)
|
||||
{
|
||||
target->ok = ok;
|
||||
target->value = value;
|
||||
}
|
||||
} // namespace Dough
|
|
@ -1,20 +1,21 @@
|
|||
#ifndef DOUGH_DATA_MEASUREMENT_H
|
||||
#define DOUGH_DATA_MEASUREMENT_H
|
||||
|
||||
/**
|
||||
* This class represents a single measurement, which can be either a
|
||||
* successful (bearing a measurement value) or a failed one.
|
||||
*/
|
||||
class Measurement
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
Measurement();
|
||||
int value = 0;
|
||||
bool ok = false;
|
||||
static Measurement Failed();
|
||||
static Measurement Value(int value);
|
||||
void clear();
|
||||
void copyTo(Measurement *target);
|
||||
};
|
||||
// This class represents a single measurement, which can be either a
|
||||
// successful (bearing a measurement value) or a failed one.
|
||||
class Measurement
|
||||
{
|
||||
public:
|
||||
Measurement();
|
||||
int value = 0;
|
||||
bool ok = false;
|
||||
static Measurement Failed();
|
||||
static Measurement Value(int value);
|
||||
void clear();
|
||||
void copyTo(Measurement *target);
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
#include "Data/Measurements.h"
|
||||
#include "UI/DoughUI.h"
|
||||
|
||||
Measurements::Measurements(
|
||||
const char *mqttKey,
|
||||
SensorBase *sensor,
|
||||
unsigned int storageSize,
|
||||
unsigned int significantChange,
|
||||
unsigned int minimumPublishTime)
|
||||
{
|
||||
_mqttKey = mqttKey;
|
||||
_sensor = sensor;
|
||||
_storageSize = storageSize;
|
||||
_significantChange = significantChange;
|
||||
_minimumPublishTime = minimumPublishTime;
|
||||
_mqtt = Dough::MQTT::Instance();
|
||||
}
|
||||
|
||||
void Measurements::setup()
|
||||
{
|
||||
// Format the key to use for publishing the average (i.e. "<mqttKey>/average").
|
||||
auto lenAverageKey = strlen(_mqttKey) + 9; // +9 for the "/average\0" suffix
|
||||
_mqttAverageKey = new char[lenAverageKey];
|
||||
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;
|
||||
}
|
||||
clearHistory();
|
||||
}
|
||||
|
||||
void Measurements::process()
|
||||
{
|
||||
auto m = _sensor->read();
|
||||
_store(m);
|
||||
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::_store(Measurement measurement)
|
||||
{
|
||||
measurement.copyTo(_storage[_next()]);
|
||||
|
||||
if (measurement.ok)
|
||||
{
|
||||
_averageCount++;
|
||||
_averageSum += measurement.value;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Measurements::_next()
|
||||
{
|
||||
_index++;
|
||||
|
||||
// Wrap around at the end of the circular buffer.
|
||||
if (_index == _storageSize)
|
||||
{
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
// If the new position contains an ok value, update the running totals.
|
||||
if (_storage[_index]->ok)
|
||||
{
|
||||
_averageSum -= _storage[_index]->value;
|
||||
_averageCount--;
|
||||
}
|
||||
|
||||
_storage[_index]->clear();
|
||||
|
||||
return _index;
|
||||
}
|
||||
|
||||
Measurement Measurements::getLast()
|
||||
{
|
||||
return *_storage[_index];
|
||||
}
|
||||
|
||||
Measurement Measurements::getAverage()
|
||||
{
|
||||
return _averageCount > 0
|
||||
? Measurement::Value(round(_averageSum / _averageCount))
|
||||
: Measurement::Failed();
|
||||
}
|
||||
|
||||
void Measurements::clearHistory()
|
||||
{
|
||||
_averageCount = 0;
|
||||
_averageSum = 0;
|
||||
for (unsigned int i = 0; i < _storageSize; i++)
|
||||
{
|
||||
_storage[i]->clear();
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#ifndef DOUGH_DATA_MEASUREMENTS_H
|
||||
#define DOUGH_DATA_MEASUREMENTS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Sensors/SensorBase.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "Network/MQTT.h"
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
class Measurements
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a new Measurements object.
|
||||
*
|
||||
* @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 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,
|
||||
SensorBase *sensor,
|
||||
unsigned int storageSize,
|
||||
unsigned int significantChange,
|
||||
unsigned int minimumPublishTime);
|
||||
void setup();
|
||||
void process();
|
||||
Measurement getLast();
|
||||
Measurement getAverage();
|
||||
void clearHistory();
|
||||
|
||||
private:
|
||||
Dough::MQTT *_mqtt;
|
||||
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;
|
||||
unsigned long _lastPublishedAt = 0;
|
||||
Measurement _lastPublished;
|
||||
Measurement _lastPublishedAverage;
|
||||
bool _mustPublish();
|
||||
void _publish();
|
||||
void _store(Measurement measurement);
|
||||
unsigned int _next();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,165 @@
|
|||
#include "Data/SensorController.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
SensorController::SensorController(
|
||||
const char *mqttKey,
|
||||
SensorBase *sensor,
|
||||
unsigned int storageSize,
|
||||
unsigned int significantChange,
|
||||
unsigned int minimumPublishTime)
|
||||
{
|
||||
_mqttKey = mqttKey;
|
||||
_sensor = sensor;
|
||||
_storageSize = storageSize;
|
||||
_significantChange = significantChange;
|
||||
_minimumPublishTime = minimumPublishTime;
|
||||
_mqtt = MQTT::Instance();
|
||||
}
|
||||
|
||||
void SensorController::setup()
|
||||
{
|
||||
// Format the key to use for publishing the average (i.e. "<mqttKey>/average").
|
||||
auto lenAverageKey = strlen(_mqttKey) + 9; // +9 for the "/average\0" suffix
|
||||
_mqttAverageKey = new char[lenAverageKey];
|
||||
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;
|
||||
}
|
||||
clearHistory();
|
||||
}
|
||||
|
||||
void SensorController::process()
|
||||
{
|
||||
auto m = _sensor->read();
|
||||
_store(m);
|
||||
if (_mustPublish())
|
||||
{
|
||||
_publish();
|
||||
}
|
||||
}
|
||||
|
||||
bool SensorController::_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 SensorController::_publish()
|
||||
{
|
||||
auto average = getAverage();
|
||||
auto last = getLast();
|
||||
|
||||
_mqtt->publish(_mqttKey, last);
|
||||
_mqtt->publish(_mqttAverageKey, average);
|
||||
|
||||
_lastPublishedAt = millis();
|
||||
average.copyTo(&_lastPublishedAverage);
|
||||
last.copyTo(&_lastPublished);
|
||||
}
|
||||
|
||||
void SensorController::_store(Measurement measurement)
|
||||
{
|
||||
measurement.copyTo(_storage[_next()]);
|
||||
|
||||
if (measurement.ok)
|
||||
{
|
||||
_averageCount++;
|
||||
_averageSum += measurement.value;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int SensorController::_next()
|
||||
{
|
||||
_index++;
|
||||
|
||||
// Wrap around at the end of the circular buffer.
|
||||
if (_index == _storageSize)
|
||||
{
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
// If the new position contains an ok value, update the running totals.
|
||||
if (_storage[_index]->ok)
|
||||
{
|
||||
_averageSum -= _storage[_index]->value;
|
||||
_averageCount--;
|
||||
}
|
||||
|
||||
_storage[_index]->clear();
|
||||
|
||||
return _index;
|
||||
}
|
||||
|
||||
Measurement SensorController::getLast()
|
||||
{
|
||||
return *_storage[_index];
|
||||
}
|
||||
|
||||
Measurement SensorController::getAverage()
|
||||
{
|
||||
return _averageCount > 0
|
||||
? Measurement::Value(round(_averageSum / _averageCount))
|
||||
: Measurement::Failed();
|
||||
}
|
||||
|
||||
void SensorController::clearHistory()
|
||||
{
|
||||
_averageCount = 0;
|
||||
_averageSum = 0;
|
||||
for (unsigned int i = 0; i < _storageSize; i++)
|
||||
{
|
||||
_storage[i]->clear();
|
||||
}
|
||||
}
|
||||
} // namespace Dough
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef DOUGH_DATA_MEASUREMENTS_H
|
||||
#define DOUGH_DATA_MEASUREMENTS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Sensors/SensorBase.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "Network/MQTT.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).
|
||||
class SensorController
|
||||
{
|
||||
public:
|
||||
// Create a new Measurements object.
|
||||
//
|
||||
// @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 minimumPublishTime
|
||||
// The number of seconds after which to forcibly publish measurements
|
||||
// to MQTT, even when no significant changes to measurements were seen.
|
||||
SensorController(
|
||||
const char *mqttKey,
|
||||
SensorBase *sensor,
|
||||
unsigned int storageSize,
|
||||
unsigned int significantChange,
|
||||
unsigned int minimumPublishTime);
|
||||
void setup();
|
||||
void process();
|
||||
Measurement getLast();
|
||||
Measurement getAverage();
|
||||
void clearHistory();
|
||||
|
||||
private:
|
||||
MQTT *_mqtt;
|
||||
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;
|
||||
unsigned long _lastPublishedAt = 0;
|
||||
Measurement _lastPublished;
|
||||
Measurement _lastPublishedAverage;
|
||||
bool _mustPublish();
|
||||
void _publish();
|
||||
void _store(Measurement measurement);
|
||||
unsigned int _next();
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -1,87 +0,0 @@
|
|||
#include "Network/DoughWiFi.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DoughWiFi *DoughWiFi::_instance = nullptr;
|
||||
|
||||
DoughWiFi *DoughWiFi::Instance()
|
||||
{
|
||||
if (DoughWiFi::_instance == nullptr)
|
||||
{
|
||||
DoughWiFi::_instance = new DoughWiFi();
|
||||
}
|
||||
return DoughWiFi::_instance;
|
||||
}
|
||||
|
||||
DoughWiFi::DoughWiFi() : _logger("WIFI") {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughWiFi::_setMacAddress()
|
||||
{
|
||||
byte mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
snprintf(
|
||||
_macAddress, sizeof(_macAddress) / sizeof(_macAddress[0]),
|
||||
"%x:%x:%x:%x:%x:%x", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
|
||||
}
|
||||
|
||||
void DoughWiFi::setup()
|
||||
{
|
||||
_setMacAddress();
|
||||
_logger.log("ss", "MAC address = ", getMacAddress());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
bool DoughWiFi::isConnected()
|
||||
{
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
}
|
||||
|
||||
bool DoughWiFi::connect()
|
||||
{
|
||||
int status = WiFi.status();
|
||||
|
||||
// Check if a device with a WiFi shield is used.
|
||||
if (status == WL_NO_SHIELD)
|
||||
{
|
||||
_logger.log("s", "ERROR - Device has no WiFi shield");
|
||||
delay(5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the WiFi network is already up.
|
||||
if (status == WL_CONNECTED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup the connection to the WiFi network.
|
||||
_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)
|
||||
{
|
||||
_logger.log("sa", "IP-Address = ", WiFi.localIP());
|
||||
_logger.log("sis", "Signal strength = ", WiFi.RSSI(), " dBm");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.log("sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char *DoughWiFi::getMacAddress()
|
||||
{
|
||||
return _macAddress;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#ifndef DOUGH_NETWORK_H
|
||||
#define DOUGH_NETWORK_H
|
||||
|
||||
#include <WiFiNINA.h>
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class encapsulates the connection to the WiFi network.
|
||||
*/
|
||||
class DoughWiFi
|
||||
{
|
||||
public:
|
||||
static DoughWiFi *Instance();
|
||||
char *getMacAddress();
|
||||
void setup();
|
||||
void loop();
|
||||
bool isConnected();
|
||||
bool connect();
|
||||
WiFiClient client;
|
||||
|
||||
private:
|
||||
DoughWiFi();
|
||||
static DoughWiFi *_instance;
|
||||
void _setMacAddress();
|
||||
char _macAddress[18]; // max MAC address length + 1
|
||||
DoughLogger _logger;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,4 +1,4 @@
|
|||
#include "MQTT.h"
|
||||
#include "Network/MQTT.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ namespace Dough
|
|||
|
||||
void MQTT::setup()
|
||||
{
|
||||
DoughWiFi *network = DoughWiFi::Instance();
|
||||
WiFi *network = WiFi::Instance();
|
||||
|
||||
#ifdef MQTT_DEVICE_ID
|
||||
_mqttDeviceId = MQTT_DEVICE_ID;
|
||||
|
|
|
@ -3,17 +3,15 @@
|
|||
|
||||
#include <MQTT.h>
|
||||
#include <MQTTClient.h>
|
||||
#include "Network/DoughWiFi.h"
|
||||
#include "Network/WiFi.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "UI/Logger.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
/**
|
||||
* This class encapsulates the connection to the MQTT broker.
|
||||
* MQTT is used to publish measurements and to store configuration data.
|
||||
*/
|
||||
// This class encapsulates the connection to the MQTT broker.
|
||||
// MQTT is used to publish measurements and to store configuration data.
|
||||
class MQTT;
|
||||
|
||||
typedef void (*MQTTConnectHandler)(MQTT *mqtt);
|
||||
|
@ -38,7 +36,7 @@ namespace Dough
|
|||
MQTT();
|
||||
static MQTT *_instance;
|
||||
MQTTClient _mqttClient;
|
||||
DoughLogger _logger;
|
||||
Logger _logger;
|
||||
MQTTConnectHandler _onConnect = nullptr;
|
||||
MQTTClientCallbackSimple _onMessage = nullptr;
|
||||
static void handleMessage(String &topic, String &payload);
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
#include "Network/WiFi.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
WiFi *WiFi::_instance = nullptr;
|
||||
|
||||
WiFi *WiFi::Instance()
|
||||
{
|
||||
if (WiFi::_instance == nullptr)
|
||||
{
|
||||
WiFi::_instance = new WiFi();
|
||||
}
|
||||
return WiFi::_instance;
|
||||
}
|
||||
|
||||
WiFi::WiFi() : _logger("WIFI") {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void WiFi::_setMacAddress()
|
||||
{
|
||||
byte mac[6];
|
||||
::WiFi.macAddress(mac);
|
||||
snprintf(
|
||||
_macAddress, sizeof(_macAddress) / sizeof(_macAddress[0]),
|
||||
"%x:%x:%x:%x:%x:%x", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
|
||||
}
|
||||
|
||||
void WiFi::setup()
|
||||
{
|
||||
_setMacAddress();
|
||||
_logger.log("ss", "MAC address = ", getMacAddress());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
bool WiFi::isConnected()
|
||||
{
|
||||
return ::WiFi.status() == WL_CONNECTED;
|
||||
}
|
||||
|
||||
bool WiFi::connect()
|
||||
{
|
||||
int status = ::WiFi.status();
|
||||
|
||||
// Check if a device with a WiFi shield is used.
|
||||
if (status == WL_NO_SHIELD)
|
||||
{
|
||||
_logger.log("s", "ERROR - Device has no WiFi shield");
|
||||
delay(5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the WiFi network is already up.
|
||||
if (status == WL_CONNECTED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup the connection to the WiFi network.
|
||||
_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)
|
||||
{
|
||||
_logger.log("sa", "IP-Address = ", ::WiFi.localIP());
|
||||
_logger.log("sis", "Signal strength = ", ::WiFi.RSSI(), " dBm");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.log("sis", "ERROR - WiFi connection failed (reason: ", ::WiFi.reasonCode(), ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char *WiFi::getMacAddress()
|
||||
{
|
||||
return _macAddress;
|
||||
}
|
||||
} // namespace Dough
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef DOUGH_NETWORK_H
|
||||
#define DOUGH_NETWORK_H
|
||||
|
||||
#include <WiFiNINA.h>
|
||||
#include "UI/Logger.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
// This class encapsulates the connection to the WiFi network.
|
||||
class WiFi
|
||||
{
|
||||
public:
|
||||
static WiFi *Instance();
|
||||
char *getMacAddress();
|
||||
void setup();
|
||||
void loop();
|
||||
bool isConnected();
|
||||
bool connect();
|
||||
WiFiClient client;
|
||||
|
||||
private:
|
||||
WiFi();
|
||||
static WiFi *_instance;
|
||||
void _setMacAddress();
|
||||
char _macAddress[18]; // max MAC address length + 1
|
||||
Logger _logger;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -1,59 +1,62 @@
|
|||
#include "DistanceSensor.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DistanceSensor *DistanceSensor::_instance = nullptr;
|
||||
|
||||
DistanceSensor *DistanceSensor::Instance()
|
||||
namespace Dough
|
||||
{
|
||||
if (DistanceSensor::_instance == nullptr)
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DistanceSensor *DistanceSensor::_instance = nullptr;
|
||||
|
||||
DistanceSensor *DistanceSensor::Instance()
|
||||
{
|
||||
DistanceSensor::_instance = new DistanceSensor();
|
||||
if (DistanceSensor::_instance == nullptr)
|
||||
{
|
||||
DistanceSensor::_instance = new DistanceSensor();
|
||||
}
|
||||
return DistanceSensor::_instance;
|
||||
}
|
||||
return DistanceSensor::_instance;
|
||||
}
|
||||
|
||||
DistanceSensor::DistanceSensor() : _logger("DISTANCE")
|
||||
{
|
||||
_hcsr04 = new SensorHCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DistanceSensor::setup()
|
||||
{
|
||||
_hcsr04->setup();
|
||||
}
|
||||
|
||||
void DistanceSensor::setTemperature(int temperature)
|
||||
{
|
||||
_hcsr04->setTemperature(temperature);
|
||||
}
|
||||
|
||||
void DistanceSensor::setHumidity(int humidity)
|
||||
{
|
||||
_hcsr04->setHumidity(humidity);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Measurement DistanceSensor::read()
|
||||
{
|
||||
int d = _hcsr04->readDistance();
|
||||
if (d == -1)
|
||||
DistanceSensor::DistanceSensor() : _logger("DISTANCE")
|
||||
{
|
||||
_logger.log("s", "ERROR - Distance measurement failed");
|
||||
return Measurement::Failed();
|
||||
_hcsr04 = new SensorHCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
|
||||
}
|
||||
else
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DistanceSensor::setup()
|
||||
{
|
||||
_logger.log("sis", "Distance = ", d, "mm");
|
||||
return Measurement::Value(d);
|
||||
_hcsr04->setup();
|
||||
}
|
||||
}
|
||||
|
||||
void DistanceSensor::setTemperature(int temperature)
|
||||
{
|
||||
_hcsr04->setTemperature(temperature);
|
||||
}
|
||||
|
||||
void DistanceSensor::setHumidity(int humidity)
|
||||
{
|
||||
_hcsr04->setHumidity(humidity);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Measurement DistanceSensor::read()
|
||||
{
|
||||
int d = _hcsr04->readDistance();
|
||||
if (d == -1)
|
||||
{
|
||||
_logger.log("s", "ERROR - Distance measurement failed");
|
||||
return Measurement::Failed();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.log("sis", "Distance = ", d, "mm");
|
||||
return Measurement::Value(d);
|
||||
}
|
||||
}
|
||||
} // namespace Dough
|
|
@ -3,27 +3,28 @@
|
|||
|
||||
#include "Sensors/SensorBase.h"
|
||||
#include "Sensors/LowLevel/SensorHCSR04.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "UI/Logger.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class provides access to the distance sensor in the device.
|
||||
*/
|
||||
class DistanceSensor : public SensorBase
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
static DistanceSensor *Instance();
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
void setTemperature(int temperature);
|
||||
void setHumidity(int humidity);
|
||||
// This class provides access to the distance sensor in the device.
|
||||
class DistanceSensor : public SensorBase
|
||||
{
|
||||
public:
|
||||
static DistanceSensor *Instance();
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
void setTemperature(int temperature);
|
||||
void setHumidity(int humidity);
|
||||
|
||||
private:
|
||||
DistanceSensor();
|
||||
static DistanceSensor *_instance;
|
||||
DoughLogger _logger;
|
||||
SensorHCSR04 *_hcsr04;
|
||||
};
|
||||
private:
|
||||
DistanceSensor();
|
||||
static DistanceSensor *_instance;
|
||||
Logger _logger;
|
||||
SensorHCSR04 *_hcsr04;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -1,47 +1,50 @@
|
|||
#include "HumiditySensor.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
HumiditySensor *HumiditySensor::_instance = nullptr;
|
||||
|
||||
HumiditySensor *HumiditySensor::Instance()
|
||||
namespace Dough
|
||||
{
|
||||
if (HumiditySensor::_instance == nullptr)
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
HumiditySensor *HumiditySensor::_instance = nullptr;
|
||||
|
||||
HumiditySensor *HumiditySensor::Instance()
|
||||
{
|
||||
HumiditySensor::_instance = new HumiditySensor();
|
||||
if (HumiditySensor::_instance == nullptr)
|
||||
{
|
||||
HumiditySensor::_instance = new HumiditySensor();
|
||||
}
|
||||
return HumiditySensor::_instance;
|
||||
}
|
||||
return HumiditySensor::_instance;
|
||||
}
|
||||
|
||||
HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {}
|
||||
HumiditySensor::HumiditySensor() : _logger("HUMIDITY") {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void HumiditySensor::setup()
|
||||
{
|
||||
SensorDHT11::Instance()->begin();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Measurement HumiditySensor::read()
|
||||
{
|
||||
float t = SensorDHT11::Instance()->readHumidity();
|
||||
if (t == -1)
|
||||
void HumiditySensor::setup()
|
||||
{
|
||||
_logger.log("s", "ERROR - Humidity measurement failed");
|
||||
return Measurement::Failed();
|
||||
SensorDHT11::Instance()->begin();
|
||||
}
|
||||
else
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Measurement HumiditySensor::read()
|
||||
{
|
||||
_logger.log("sis", "Humidity = ", int(t), "%");
|
||||
DistanceSensor::Instance()->setHumidity(int(t));
|
||||
return Measurement::Value(int(t));
|
||||
float t = SensorDHT11::Instance()->readHumidity();
|
||||
if (t == -1)
|
||||
{
|
||||
_logger.log("s", "ERROR - Humidity measurement failed");
|
||||
return Measurement::Failed();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.log("sis", "Humidity = ", int(t), "%");
|
||||
DistanceSensor::Instance()->setHumidity(int(t));
|
||||
return Measurement::Value(int(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Dough
|
|
@ -3,25 +3,26 @@
|
|||
|
||||
#include "Sensors/SensorBase.h"
|
||||
#include "Sensors/LowLevel/SensorDHT11.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "UI/Logger.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "Sensors/DistanceSensor.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class provides access to the humidity sensor in the device.
|
||||
*/
|
||||
class HumiditySensor : public SensorBase
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
static HumiditySensor *Instance();
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
// This class provides access to the humidity sensor in the device.
|
||||
class HumiditySensor : public SensorBase
|
||||
{
|
||||
public:
|
||||
static HumiditySensor *Instance();
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
|
||||
private:
|
||||
HumiditySensor();
|
||||
static HumiditySensor *_instance;
|
||||
DoughLogger _logger;
|
||||
};
|
||||
private:
|
||||
HumiditySensor();
|
||||
static HumiditySensor *_instance;
|
||||
Logger _logger;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -1,44 +1,47 @@
|
|||
#include "Sensors/LowLevel/SensorDHT11.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
SensorDHT11 *SensorDHT11::_instance = nullptr;
|
||||
|
||||
SensorDHT11 *SensorDHT11::Instance()
|
||||
namespace Dough
|
||||
{
|
||||
if (SensorDHT11::_instance == nullptr)
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
SensorDHT11 *SensorDHT11::_instance = nullptr;
|
||||
|
||||
SensorDHT11 *SensorDHT11::Instance()
|
||||
{
|
||||
SensorDHT11::_instance = new SensorDHT11();
|
||||
if (SensorDHT11::_instance == nullptr)
|
||||
{
|
||||
SensorDHT11::_instance = new SensorDHT11();
|
||||
}
|
||||
return SensorDHT11::_instance;
|
||||
}
|
||||
return SensorDHT11::_instance;
|
||||
}
|
||||
|
||||
SensorDHT11::SensorDHT11()
|
||||
{
|
||||
_dht = new DHT(DHT11_DATA_PIN, DHT11);
|
||||
}
|
||||
SensorDHT11::SensorDHT11()
|
||||
{
|
||||
_dht = new DHT(DHT11_DATA_PIN, DHT11);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void SensorDHT11::begin()
|
||||
{
|
||||
_dht->begin();
|
||||
}
|
||||
void SensorDHT11::begin()
|
||||
{
|
||||
_dht->begin();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
float SensorDHT11::readHumidity()
|
||||
{
|
||||
return _dht->readHumidity();
|
||||
}
|
||||
float SensorDHT11::readHumidity()
|
||||
{
|
||||
return _dht->readHumidity();
|
||||
}
|
||||
|
||||
float SensorDHT11::readTemperature()
|
||||
{
|
||||
return _dht->readTemperature();
|
||||
}
|
||||
float SensorDHT11::readTemperature()
|
||||
{
|
||||
return _dht->readTemperature();
|
||||
}
|
||||
} // namespace Dough
|
|
@ -4,21 +4,22 @@
|
|||
#include <DHT.h>
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class provides access to the DHT11 sensor in the device.
|
||||
*/
|
||||
class SensorDHT11
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
static SensorDHT11 *Instance();
|
||||
void begin();
|
||||
float readTemperature();
|
||||
float readHumidity();
|
||||
// This class provides access to the DHT11 sensor in the device.
|
||||
class SensorDHT11
|
||||
{
|
||||
public:
|
||||
static SensorDHT11 *Instance();
|
||||
void begin();
|
||||
float readTemperature();
|
||||
float readHumidity();
|
||||
|
||||
private:
|
||||
SensorDHT11();
|
||||
static SensorDHT11 *_instance;
|
||||
DHT *_dht;
|
||||
};
|
||||
private:
|
||||
SensorDHT11();
|
||||
static SensorDHT11 *_instance;
|
||||
DHT *_dht;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -1,155 +1,152 @@
|
|||
#include "Sensors/LowLevel/SensorHCSR04.h"
|
||||
|
||||
SensorHCSR04::SensorHCSR04(int triggerPin, int echoPin) : _logger("HCSR04")
|
||||
namespace Dough
|
||||
{
|
||||
_triggerPin = triggerPin;
|
||||
_echoPin = echoPin;
|
||||
_temperature = HCSR04_INIT_TEMPERATURE;
|
||||
_humidity = HCSR04_INIT_HUMIDITY;
|
||||
#ifndef HCSR04_DEBUG
|
||||
_logger.suspend();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SensorHCSR04::setup()
|
||||
{
|
||||
_logger.log("sisi", "Setup output pin ", _triggerPin, " and input pin ", _echoPin);
|
||||
pinMode(_triggerPin, OUTPUT);
|
||||
pinMode(_echoPin, INPUT);
|
||||
}
|
||||
|
||||
void SensorHCSR04::setTemperature(int temperature)
|
||||
{
|
||||
_logger.log("sis", "Set temperature to ", temperature, "°C");
|
||||
_temperature = temperature;
|
||||
}
|
||||
|
||||
void SensorHCSR04::setHumidity(int humidity)
|
||||
{
|
||||
_logger.log("sis", "Set humidity to ", humidity, "%");
|
||||
_humidity = humidity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a distance reading.
|
||||
* When reading the distance fails, -1 is returned.
|
||||
* Otherwise the distance in mm.
|
||||
*/
|
||||
int SensorHCSR04::readDistance()
|
||||
{
|
||||
_setSpeedOfSound();
|
||||
_setEchoTimeout();
|
||||
_takeSamples();
|
||||
if (_haveEnoughSamples())
|
||||
SensorHCSR04::SensorHCSR04(int triggerPin, int echoPin) : _logger("HCSR04")
|
||||
{
|
||||
_sortSamples();
|
||||
return _computeAverage();
|
||||
_triggerPin = triggerPin;
|
||||
_echoPin = echoPin;
|
||||
_temperature = HCSR04_INIT_TEMPERATURE;
|
||||
_humidity = HCSR04_INIT_HUMIDITY;
|
||||
#ifndef HCSR04_DEBUG
|
||||
_logger.suspend();
|
||||
#endif
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the speed of sound in mm/Ms, depending on the temperature
|
||||
* and relative humidity. I derived this formula from a YouTube
|
||||
* video about the HC-SR04: https://youtu.be/6F1B_N6LuKw?t=1548
|
||||
*/
|
||||
void SensorHCSR04::_setSpeedOfSound()
|
||||
{
|
||||
_speedOfSound =
|
||||
0.3314 +
|
||||
(0.000606 * _temperature) +
|
||||
(0.0000124 * _humidity);
|
||||
_logger.log("sfs", "Speed of sound = ", _speedOfSound, "mm/Ms");
|
||||
}
|
||||
|
||||
void SensorHCSR04::_setEchoTimeout()
|
||||
{
|
||||
_echoTimeout = HCSR04_MAX_MM * 2 / _speedOfSound;
|
||||
_logger.log("sfs", "Echo timeout = ", _echoTimeout, "Ms");
|
||||
}
|
||||
|
||||
void SensorHCSR04::_takeSamples()
|
||||
{
|
||||
_successfulSamples = 0;
|
||||
for (int i = 0; i < HCSR04_SAMPLES_TAKE; i++)
|
||||
void SensorHCSR04::setup()
|
||||
{
|
||||
// Because I notice some repeating patterns in timings when doing
|
||||
// a tight loop here, I add some random waits to get a better spread
|
||||
// of sample values.
|
||||
if (i > 0)
|
||||
{
|
||||
delay(HCSR04_SAMPLE_WAIT + random(HCSR04_SAMPLE_WAIT_SPREAD));
|
||||
}
|
||||
int distance = _takeSample();
|
||||
if (distance != -1)
|
||||
{
|
||||
_samples[i] = distance;
|
||||
_successfulSamples++;
|
||||
}
|
||||
_logger.log("sisi", "Setup output pin ", _triggerPin, " and input pin ", _echoPin);
|
||||
pinMode(_triggerPin, OUTPUT);
|
||||
pinMode(_echoPin, INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
bool SensorHCSR04::_haveEnoughSamples()
|
||||
{
|
||||
return _successfulSamples >= HCSR04_SAMPLES_USE;
|
||||
}
|
||||
|
||||
int SensorHCSR04::_takeSample()
|
||||
{
|
||||
// Send 10μs trigger to ask sensor for a measurement.
|
||||
digitalWrite(HCSR04_TRIG_PIN, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(HCSR04_TRIG_PIN, HIGH);
|
||||
delayMicroseconds(10);
|
||||
digitalWrite(HCSR04_TRIG_PIN, LOW);
|
||||
|
||||
// Measure the length of echo signal.
|
||||
unsigned long durationMicroSec = pulseIn(HCSR04_ECHO_PIN, HIGH, _echoTimeout);
|
||||
|
||||
// Compute the distance, based on the echo signal length.
|
||||
double distance = durationMicroSec / 2.0 * _speedOfSound;
|
||||
_logger.log("sfs", "Sample result = ", distance, "mm");
|
||||
if (distance < HCSR04_MIN_MM || distance >= HCSR04_MAX_MM)
|
||||
void SensorHCSR04::setTemperature(int temperature)
|
||||
{
|
||||
_logger.log("sis", "Set temperature to ", temperature, "°C");
|
||||
_temperature = temperature;
|
||||
}
|
||||
|
||||
void SensorHCSR04::setHumidity(int humidity)
|
||||
{
|
||||
_logger.log("sis", "Set humidity to ", humidity, "%");
|
||||
_humidity = humidity;
|
||||
}
|
||||
|
||||
// Get a distance reading.
|
||||
// When reading the distance fails, -1 is returned.
|
||||
// Otherwise the distance in mm.
|
||||
int SensorHCSR04::readDistance()
|
||||
{
|
||||
_setSpeedOfSound();
|
||||
_setEchoTimeout();
|
||||
_takeSamples();
|
||||
if (_haveEnoughSamples())
|
||||
{
|
||||
_sortSamples();
|
||||
return _computeAverage();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return distance;
|
||||
}
|
||||
}
|
||||
|
||||
void SensorHCSR04::_sortSamples()
|
||||
{
|
||||
int holder, x, y;
|
||||
for (x = 0; x < _successfulSamples; x++)
|
||||
// Sets the speed of sound in mm/Ms, depending on the temperature
|
||||
// and relative humidity. I derived this formula from a YouTube
|
||||
// video about the HC-SR04: https://youtu.be/6F1B_N6LuKw?t=1548
|
||||
void SensorHCSR04::_setSpeedOfSound()
|
||||
{
|
||||
for (y = 0; y < _successfulSamples - 1; y++)
|
||||
_speedOfSound =
|
||||
0.3314 +
|
||||
(0.000606 * _temperature) +
|
||||
(0.0000124 * _humidity);
|
||||
_logger.log("sfs", "Speed of sound = ", _speedOfSound, "mm/Ms");
|
||||
}
|
||||
|
||||
void SensorHCSR04::_setEchoTimeout()
|
||||
{
|
||||
_echoTimeout = HCSR04_MAX_MM * 2 / _speedOfSound;
|
||||
_logger.log("sfs", "Echo timeout = ", _echoTimeout, "Ms");
|
||||
}
|
||||
|
||||
void SensorHCSR04::_takeSamples()
|
||||
{
|
||||
_successfulSamples = 0;
|
||||
for (int i = 0; i < HCSR04_SAMPLES_TAKE; i++)
|
||||
{
|
||||
if (_samples[y] > _samples[y + 1])
|
||||
// Because I notice some repeating patterns in timings when doing
|
||||
// a tight loop here, I add some random waits to get a better spread
|
||||
// of sample values.
|
||||
if (i > 0)
|
||||
{
|
||||
holder = _samples[y + 1];
|
||||
_samples[y + 1] = _samples[y];
|
||||
_samples[y] = holder;
|
||||
delay(HCSR04_SAMPLE_WAIT + random(HCSR04_SAMPLE_WAIT_SPREAD));
|
||||
}
|
||||
int distance = _takeSample();
|
||||
if (distance != -1)
|
||||
{
|
||||
_samples[i] = distance;
|
||||
_successfulSamples++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the average of the samples. To get rid of measuring extremes,
|
||||
* only a subset of measurements from the middle are used.
|
||||
* When not enough samples were collected in the previous steps, then
|
||||
* NAN is returned.
|
||||
*/
|
||||
int SensorHCSR04::_computeAverage()
|
||||
{
|
||||
float sum = 0;
|
||||
int offset = (_successfulSamples - HCSR04_SAMPLES_USE) / 2;
|
||||
for (int i = 0; i < HCSR04_SAMPLES_USE; i++)
|
||||
bool SensorHCSR04::_haveEnoughSamples()
|
||||
{
|
||||
sum += _samples[i + offset];
|
||||
return _successfulSamples >= HCSR04_SAMPLES_USE;
|
||||
}
|
||||
|
||||
return round(sum / HCSR04_SAMPLES_USE);
|
||||
}
|
||||
int SensorHCSR04::_takeSample()
|
||||
{
|
||||
// Send 10μs trigger to ask sensor for a measurement.
|
||||
digitalWrite(HCSR04_TRIG_PIN, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(HCSR04_TRIG_PIN, HIGH);
|
||||
delayMicroseconds(10);
|
||||
digitalWrite(HCSR04_TRIG_PIN, LOW);
|
||||
|
||||
// Measure the length of echo signal.
|
||||
unsigned long durationMicroSec = pulseIn(HCSR04_ECHO_PIN, HIGH, _echoTimeout);
|
||||
|
||||
// Compute the distance, based on the echo signal length.
|
||||
double distance = durationMicroSec / 2.0 * _speedOfSound;
|
||||
_logger.log("sfs", "Sample result = ", distance, "mm");
|
||||
if (distance < HCSR04_MIN_MM || distance >= HCSR04_MAX_MM)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return distance;
|
||||
}
|
||||
}
|
||||
|
||||
void SensorHCSR04::_sortSamples()
|
||||
{
|
||||
int holder, x, y;
|
||||
for (x = 0; x < _successfulSamples; x++)
|
||||
{
|
||||
for (y = 0; y < _successfulSamples - 1; y++)
|
||||
{
|
||||
if (_samples[y] > _samples[y + 1])
|
||||
{
|
||||
holder = _samples[y + 1];
|
||||
_samples[y + 1] = _samples[y];
|
||||
_samples[y] = holder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the average of the samples. To get rid of measuring extremes,
|
||||
// only a subset of measurements from the middle are used.
|
||||
// When not enough samples were collected in the previous steps, then
|
||||
// NAN is returned.
|
||||
int SensorHCSR04::_computeAverage()
|
||||
{
|
||||
float sum = 0;
|
||||
int offset = (_successfulSamples - HCSR04_SAMPLES_USE) / 2;
|
||||
for (int i = 0; i < HCSR04_SAMPLES_USE; i++)
|
||||
{
|
||||
sum += _samples[i + offset];
|
||||
}
|
||||
|
||||
return round(sum / HCSR04_SAMPLES_USE);
|
||||
}
|
||||
} // namespace Dough
|
|
@ -29,38 +29,39 @@
|
|||
#undef HCSR04_DEBUG
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "UI/Logger.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class is used to get a distance reading from an HCSR04 sensor.
|
||||
*/
|
||||
class SensorHCSR04
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
SensorHCSR04(int triggerPin, int echoPin);
|
||||
void setup();
|
||||
void setTemperature(int temperature);
|
||||
void setHumidity(int humidity);
|
||||
int readDistance();
|
||||
// This class is used to get a distance reading from an HCSR04 sensor.
|
||||
class SensorHCSR04
|
||||
{
|
||||
public:
|
||||
SensorHCSR04(int triggerPin, int echoPin);
|
||||
void setup();
|
||||
void setTemperature(int temperature);
|
||||
void setHumidity(int humidity);
|
||||
int readDistance();
|
||||
|
||||
private:
|
||||
DoughLogger _logger;
|
||||
int _triggerPin;
|
||||
int _echoPin;
|
||||
int _humidity;
|
||||
int _temperature;
|
||||
void _setSpeedOfSound();
|
||||
float _speedOfSound;
|
||||
void _setEchoTimeout();
|
||||
int _echoTimeout;
|
||||
float _samples[HCSR04_SAMPLES_TAKE];
|
||||
void _takeSamples();
|
||||
bool _haveEnoughSamples();
|
||||
int _takeSample();
|
||||
int _successfulSamples;
|
||||
void _sortSamples();
|
||||
int _computeAverage();
|
||||
};
|
||||
private:
|
||||
Logger _logger;
|
||||
int _triggerPin;
|
||||
int _echoPin;
|
||||
int _humidity;
|
||||
int _temperature;
|
||||
void _setSpeedOfSound();
|
||||
float _speedOfSound;
|
||||
void _setEchoTimeout();
|
||||
int _echoTimeout;
|
||||
float _samples[HCSR04_SAMPLES_TAKE];
|
||||
void _takeSamples();
|
||||
bool _haveEnoughSamples();
|
||||
int _takeSample();
|
||||
int _successfulSamples;
|
||||
void _sortSamples();
|
||||
int _computeAverage();
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -3,14 +3,15 @@
|
|||
|
||||
#include "Data/Measurement.h"
|
||||
|
||||
/**
|
||||
* This interface is implemented by all sensors.
|
||||
*/
|
||||
class SensorBase
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
};
|
||||
// This interface is implemented by all sensors.
|
||||
class SensorBase
|
||||
{
|
||||
public:
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,47 +1,50 @@
|
|||
#include "TemperatureSensor.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
TemperatureSensor *TemperatureSensor::_instance = nullptr;
|
||||
|
||||
TemperatureSensor *TemperatureSensor::Instance()
|
||||
namespace Dough
|
||||
{
|
||||
if (TemperatureSensor::_instance == nullptr)
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
TemperatureSensor *TemperatureSensor::_instance = nullptr;
|
||||
|
||||
TemperatureSensor *TemperatureSensor::Instance()
|
||||
{
|
||||
TemperatureSensor::_instance = new TemperatureSensor();
|
||||
if (TemperatureSensor::_instance == nullptr)
|
||||
{
|
||||
TemperatureSensor::_instance = new TemperatureSensor();
|
||||
}
|
||||
return TemperatureSensor::_instance;
|
||||
}
|
||||
return TemperatureSensor::_instance;
|
||||
}
|
||||
|
||||
TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {}
|
||||
TemperatureSensor::TemperatureSensor() : _logger("TEMPERATURE") {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------------------------
|
||||
// setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void TemperatureSensor::setup()
|
||||
{
|
||||
SensorDHT11::Instance()->begin();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Measurement TemperatureSensor::read()
|
||||
{
|
||||
float t = SensorDHT11::Instance()->readTemperature();
|
||||
if (isnan(t))
|
||||
void TemperatureSensor::setup()
|
||||
{
|
||||
_logger.log("s", "ERROR - Temperature measurement failed");
|
||||
return Measurement::Failed();
|
||||
SensorDHT11::Instance()->begin();
|
||||
}
|
||||
else
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Measurement TemperatureSensor::read()
|
||||
{
|
||||
_logger.log("sis", "Temperature = ", int(t), "°C");
|
||||
DistanceSensor::Instance()->setTemperature(int(t));
|
||||
return Measurement::Value(int(t));
|
||||
float t = SensorDHT11::Instance()->readTemperature();
|
||||
if (isnan(t))
|
||||
{
|
||||
_logger.log("s", "ERROR - Temperature measurement failed");
|
||||
return Measurement::Failed();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.log("sis", "Temperature = ", int(t), "°C");
|
||||
DistanceSensor::Instance()->setTemperature(int(t));
|
||||
return Measurement::Value(int(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Dough
|
|
@ -3,25 +3,26 @@
|
|||
|
||||
#include "Sensors/SensorBase.h"
|
||||
#include "Sensors/LowLevel/SensorDHT11.h"
|
||||
#include "UI/DoughLogger.h"
|
||||
#include "UI/Logger.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "Sensors/DistanceSensor.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class provides access to the temperature sensor in the device.
|
||||
*/
|
||||
class TemperatureSensor : public SensorBase
|
||||
namespace Dough
|
||||
{
|
||||
public:
|
||||
static TemperatureSensor *Instance();
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
// This class provides access to the temperature sensor in the device.
|
||||
class TemperatureSensor : public SensorBase
|
||||
{
|
||||
public:
|
||||
static TemperatureSensor *Instance();
|
||||
virtual void setup();
|
||||
virtual Measurement read();
|
||||
|
||||
private:
|
||||
TemperatureSensor();
|
||||
static TemperatureSensor *_instance;
|
||||
DoughLogger _logger;
|
||||
};
|
||||
private:
|
||||
TemperatureSensor();
|
||||
static TemperatureSensor *_instance;
|
||||
Logger _logger;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -0,0 +1,146 @@
|
|||
#include "Button.h"
|
||||
|
||||
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
|
||||
// and linked to the button to get the interrupts working. Pattern:
|
||||
//
|
||||
// // Construct the button instance.
|
||||
// Button myButton(MYBUTTON_PIN);
|
||||
//
|
||||
// // A function for handling interrupts.
|
||||
// void myButtonISR() {
|
||||
// myButton.handleButtonState();
|
||||
// }
|
||||
//
|
||||
// // Linking the function ot button interrupts.
|
||||
// myButton.onInterrupt(myButtonISR);
|
||||
Button::Button(int pin)
|
||||
{
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
void Button::setup()
|
||||
{
|
||||
pinMode(_pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
// Assign an interrupt service routine (ISR) for handling button
|
||||
// interrupts. The provided isr should relay interrupts to the
|
||||
// handleButtonState() method of this class (see constructor docs).
|
||||
void Button::onInterrupt(ButtonHandler isr)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE);
|
||||
}
|
||||
|
||||
// Assign an event handler for short and long button presses.
|
||||
// When specific handlers for long and/or short presses are
|
||||
// configured as well, those have precedence over this one.
|
||||
void Button::onPress(ButtonHandler handler)
|
||||
{
|
||||
_pressHandler = handler;
|
||||
}
|
||||
|
||||
// Assign an event handler for long button presses.
|
||||
void Button::onLongPress(ButtonHandler handler)
|
||||
{
|
||||
_longPressHandler = handler;
|
||||
}
|
||||
|
||||
// Assign an event handler for short button presses.
|
||||
void Button::onShortPress(ButtonHandler handler)
|
||||
{
|
||||
_shortPressHandler = handler;
|
||||
}
|
||||
|
||||
void Button::loop()
|
||||
{
|
||||
handleButtonState();
|
||||
if (_state == UP_AFTER_SHORT)
|
||||
{
|
||||
if (_shortPressHandler != nullptr)
|
||||
{
|
||||
_shortPressHandler();
|
||||
}
|
||||
else if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
else if (_state == DOWN_LONG || _state == UP_AFTER_LONG)
|
||||
{
|
||||
if (_longPressHandler != nullptr)
|
||||
{
|
||||
_longPressHandler();
|
||||
}
|
||||
else if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr)
|
||||
{
|
||||
if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
}
|
||||
|
||||
void Button::clearEvents()
|
||||
{
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
|
||||
void Button::handleButtonState()
|
||||
{
|
||||
bool buttonIsDown = digitalRead(_pin) == 0;
|
||||
bool buttonIsUp = !buttonIsDown;
|
||||
|
||||
// When the button state has changed since the last time, then
|
||||
// start the debounce timer.
|
||||
if (buttonIsDown != _debounceState)
|
||||
{
|
||||
_debounceTimer = millis();
|
||||
_debounceState = buttonIsDown;
|
||||
}
|
||||
|
||||
unsigned long interval = (millis() - _debounceTimer);
|
||||
|
||||
// Only when the last state change has been stable for longer than the
|
||||
// configured debounce delay, then we accept the current state as
|
||||
// a stabilized button state.
|
||||
if (interval < BUTTON_DEBOUNCE_DELAY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle button state changes.
|
||||
if (_state == READY_FOR_NEXT_PRESS && buttonIsUp)
|
||||
{
|
||||
_state = UP;
|
||||
}
|
||||
else if (_state == UP && buttonIsDown)
|
||||
{
|
||||
_state = DOWN;
|
||||
}
|
||||
else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY)
|
||||
{
|
||||
_state = DOWN_LONG;
|
||||
}
|
||||
else if (_state == DOWN && buttonIsUp)
|
||||
{
|
||||
_state = UP_AFTER_SHORT;
|
||||
}
|
||||
else if (_state == DOWN_LONG && buttonIsUp)
|
||||
{
|
||||
_state = UP_AFTER_LONG;
|
||||
}
|
||||
}
|
||||
} // namespace Dough
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef DOUGH_BUTTON_H
|
||||
#define DOUGH_BUTTON_H
|
||||
|
||||
#define BUTTON_DEBOUNCE_DELAY 50
|
||||
#define BUTTON_LONGPRESS_DELAY 1000
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
typedef enum
|
||||
{
|
||||
UP,
|
||||
DOWN,
|
||||
DOWN_LONG,
|
||||
UP_AFTER_LONG,
|
||||
UP_AFTER_SHORT,
|
||||
READY_FOR_NEXT_PRESS
|
||||
} ButtonState;
|
||||
|
||||
typedef void (*ButtonHandler)();
|
||||
|
||||
// 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 Button
|
||||
{
|
||||
public:
|
||||
Button(int pin);
|
||||
void setup();
|
||||
void loop();
|
||||
void onInterrupt(ButtonHandler isr);
|
||||
void onPress(ButtonHandler handler);
|
||||
void onShortPress(ButtonHandler handler);
|
||||
void onLongPress(ButtonHandler handler);
|
||||
void clearEvents();
|
||||
void handleButtonState();
|
||||
|
||||
private:
|
||||
int _pin;
|
||||
ButtonHandler _pressHandler = nullptr;
|
||||
ButtonHandler _shortPressHandler = nullptr;
|
||||
ButtonHandler _longPressHandler = nullptr;
|
||||
bool _debounceState = false;
|
||||
unsigned long _debounceTimer = 0;
|
||||
ButtonState _state = UP;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -1,153 +0,0 @@
|
|||
#include "DoughButton.h"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* and linked to the button to get the interrupts working. Pattern:
|
||||
*
|
||||
* // Construct the button instance.
|
||||
* DoughButton myButton(MYBUTTON_PIN);
|
||||
*
|
||||
* // A function for handling interrupts.
|
||||
* void myButtonISR() {
|
||||
* myButton.handleButtonState();
|
||||
* }
|
||||
*
|
||||
* // Linking the function ot button interrupts.
|
||||
* myButton.onInterrupt(myButtonISR);
|
||||
*/
|
||||
DoughButton::DoughButton(int pin)
|
||||
{
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
void DoughButton::setup()
|
||||
{
|
||||
pinMode(_pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an interrupt service routine (ISR) for handling button
|
||||
* interrupts. The provided isr should relay interrupts to the
|
||||
* handleButtonState() method of this class (see constructor docs).
|
||||
*/
|
||||
void DoughButton::onInterrupt(DoughButtonHandler isr)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an event handler for short and long button presses.
|
||||
* When specific handlers for long and/or short presses are
|
||||
* configured as well, those have precedence over this one.
|
||||
*/
|
||||
void DoughButton::onPress(DoughButtonHandler handler)
|
||||
{
|
||||
_pressHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an event handler for long button presses.
|
||||
*/
|
||||
void DoughButton::onLongPress(DoughButtonHandler handler)
|
||||
{
|
||||
_longPressHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an event handler for short button presses.
|
||||
*/
|
||||
void DoughButton::onShortPress(DoughButtonHandler handler)
|
||||
{
|
||||
_shortPressHandler = handler;
|
||||
}
|
||||
|
||||
void DoughButton::loop()
|
||||
{
|
||||
handleButtonState();
|
||||
if (_state == UP_AFTER_SHORT)
|
||||
{
|
||||
if (_shortPressHandler != nullptr)
|
||||
{
|
||||
_shortPressHandler();
|
||||
}
|
||||
else if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
else if (_state == DOWN_LONG || _state == UP_AFTER_LONG)
|
||||
{
|
||||
if (_longPressHandler != nullptr)
|
||||
{
|
||||
_longPressHandler();
|
||||
}
|
||||
else if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr)
|
||||
{
|
||||
if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
}
|
||||
|
||||
void DoughButton::clearEvents()
|
||||
{
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
|
||||
void DoughButton::handleButtonState()
|
||||
{
|
||||
bool buttonIsDown = digitalRead(_pin) == 0;
|
||||
bool buttonIsUp = !buttonIsDown;
|
||||
|
||||
// When the button state has changed since the last time, then
|
||||
// start the debounce timer.
|
||||
if (buttonIsDown != _debounceState)
|
||||
{
|
||||
_debounceTimer = millis();
|
||||
_debounceState = buttonIsDown;
|
||||
}
|
||||
|
||||
unsigned long interval = (millis() - _debounceTimer);
|
||||
|
||||
// Only when the last state change has been stable for longer than the
|
||||
// configured debounce delay, then we accept the current state as
|
||||
// a stabilized button state.
|
||||
if (interval < BUTTON_DEBOUNCE_DELAY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle button state changes.
|
||||
if (_state == READY_FOR_NEXT_PRESS && buttonIsUp)
|
||||
{
|
||||
_state = UP;
|
||||
}
|
||||
else if (_state == UP && buttonIsDown)
|
||||
{
|
||||
_state = DOWN;
|
||||
}
|
||||
else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY)
|
||||
{
|
||||
_state = DOWN_LONG;
|
||||
}
|
||||
else if (_state == DOWN && buttonIsUp)
|
||||
{
|
||||
_state = UP_AFTER_SHORT;
|
||||
}
|
||||
else if (_state == DOWN_LONG && buttonIsUp)
|
||||
{
|
||||
_state = UP_AFTER_LONG;
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
#ifndef DOUGH_BUTTON_H
|
||||
#define DOUGH_BUTTON_H
|
||||
|
||||
#define BUTTON_DEBOUNCE_DELAY 50
|
||||
#define BUTTON_LONGPRESS_DELAY 1000
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UP,
|
||||
DOWN,
|
||||
DOWN_LONG,
|
||||
UP_AFTER_LONG,
|
||||
UP_AFTER_SHORT,
|
||||
READY_FOR_NEXT_PRESS
|
||||
} DoughButtonState;
|
||||
|
||||
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:
|
||||
DoughButton(int pin);
|
||||
void setup();
|
||||
void loop();
|
||||
void onInterrupt(DoughButtonHandler isr);
|
||||
void onPress(DoughButtonHandler handler);
|
||||
void onShortPress(DoughButtonHandler handler);
|
||||
void onLongPress(DoughButtonHandler handler);
|
||||
void clearEvents();
|
||||
void handleButtonState();
|
||||
|
||||
private:
|
||||
int _pin;
|
||||
DoughButtonHandler _pressHandler = nullptr;
|
||||
DoughButtonHandler _shortPressHandler = nullptr;
|
||||
DoughButtonHandler _longPressHandler = nullptr;
|
||||
bool _debounceState = false;
|
||||
unsigned long _debounceTimer = 0;
|
||||
DoughButtonState _state = UP;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,173 +0,0 @@
|
|||
#include "DoughLED.h"
|
||||
|
||||
DoughLED::DoughLED(int pin)
|
||||
{
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
void DoughLED::setup()
|
||||
{
|
||||
pinMode(_pin, OUTPUT);
|
||||
_state = OFF;
|
||||
_setPin(LOW);
|
||||
}
|
||||
|
||||
void DoughLED::loop()
|
||||
{
|
||||
unsigned long now = millis();
|
||||
bool tick = (now - _timer) > _time;
|
||||
|
||||
if (_state == FLASH)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = OFF;
|
||||
}
|
||||
}
|
||||
else if (_state == DIP)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
_state = ON;
|
||||
}
|
||||
}
|
||||
else if (_state == BLINK_ON)
|
||||
{
|
||||
if (_blinkStep == _blinkOnStep)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
}
|
||||
if (tick)
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = BLINK_OFF;
|
||||
_timer = now;
|
||||
}
|
||||
}
|
||||
else if (_state == BLINK_OFF)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_state = BLINK_ON;
|
||||
_timer = now;
|
||||
_blinkStep++;
|
||||
if (_blinkStep > _blinkOfSteps)
|
||||
{
|
||||
_blinkStep = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_state == PULSE)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_timer = now;
|
||||
_time = 1;
|
||||
_brightness += _pulseStep;
|
||||
if (_brightness <= 0)
|
||||
{
|
||||
_time = 200;
|
||||
_brightness = 0;
|
||||
_pulseStep = -_pulseStep;
|
||||
}
|
||||
else if (_brightness >= 100)
|
||||
{
|
||||
_brightness = 100;
|
||||
_pulseStep = -_pulseStep;
|
||||
}
|
||||
}
|
||||
analogWrite(_pin, _brightness);
|
||||
}
|
||||
else if (_state == OFF)
|
||||
{
|
||||
_setPin(LOW);
|
||||
}
|
||||
else if (_state == ON)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void DoughLED::_setPin(int high_or_low)
|
||||
{
|
||||
_pinState = high_or_low;
|
||||
analogWrite(_pin, _pinState == LOW ? 0 : 255);
|
||||
}
|
||||
|
||||
void DoughLED::on()
|
||||
{
|
||||
_state = ON;
|
||||
loop();
|
||||
}
|
||||
|
||||
void DoughLED::off()
|
||||
{
|
||||
_state = OFF;
|
||||
loop();
|
||||
}
|
||||
|
||||
DoughLED *DoughLED::flash()
|
||||
{
|
||||
_setPin(HIGH);
|
||||
_state = FLASH;
|
||||
_timer = millis();
|
||||
_time = LED_TRANSITION_TIME_DEFAULT;
|
||||
loop();
|
||||
return this;
|
||||
}
|
||||
|
||||
DoughLED *DoughLED::blink()
|
||||
{
|
||||
return blink(1, 1);
|
||||
}
|
||||
|
||||
DoughLED *DoughLED::dip()
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = DIP;
|
||||
_timer = millis();
|
||||
_time = LED_TRANSITION_TIME_DEFAULT;
|
||||
loop();
|
||||
return this;
|
||||
}
|
||||
|
||||
DoughLED *DoughLED::blink(int onStep, int ofSteps)
|
||||
{
|
||||
_blinkOnStep = onStep;
|
||||
_blinkOfSteps = ofSteps;
|
||||
_blinkStep = 1;
|
||||
_state = BLINK_ON;
|
||||
_time = LED_TRANSITION_TIME_DEFAULT;
|
||||
loop();
|
||||
return this;
|
||||
}
|
||||
|
||||
void DoughLED::pulse()
|
||||
{
|
||||
_state = PULSE;
|
||||
_brightness = 0;
|
||||
_pulseStep = +8;
|
||||
_time = 1;
|
||||
}
|
||||
|
||||
void DoughLED::slow()
|
||||
{
|
||||
_time = LED_TRANSITION_TIME_SLOW;
|
||||
}
|
||||
|
||||
void DoughLED::fast()
|
||||
{
|
||||
_time = LED_TRANSITION_TIME_FAST;
|
||||
}
|
||||
|
||||
bool DoughLED::isOn()
|
||||
{
|
||||
return _pinState == HIGH;
|
||||
}
|
||||
|
||||
bool DoughLED::isOff()
|
||||
{
|
||||
return _pinState == LOW;
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
#ifndef DOUGH_LED_H
|
||||
#define DOUGH_LED_H
|
||||
|
||||
// Delay times for blinking, flashing and dipping.
|
||||
#define LED_TRANSITION_TIME_SLOW 400
|
||||
#define LED_TRANSITION_TIME_DEFAULT 250
|
||||
#define LED_TRANSITION_TIME_FAST 100
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ON,
|
||||
OFF,
|
||||
BLINK_ON,
|
||||
BLINK_OFF,
|
||||
FLASH,
|
||||
DIP,
|
||||
PULSE
|
||||
} DoughLEDState;
|
||||
|
||||
/**
|
||||
* This class provides a set of basic LED lighting patterns, which can
|
||||
* be used in an async way.
|
||||
*/
|
||||
class DoughLED
|
||||
{
|
||||
public:
|
||||
DoughLED(int pin);
|
||||
void setup();
|
||||
void loop();
|
||||
void on();
|
||||
void off();
|
||||
DoughLED *blink();
|
||||
DoughLED *blink(int onStep, int ofSteps);
|
||||
DoughLED *flash();
|
||||
DoughLED *dip();
|
||||
void pulse();
|
||||
void slow();
|
||||
void fast();
|
||||
bool isOn();
|
||||
bool isOff();
|
||||
|
||||
private:
|
||||
int _pin;
|
||||
int _pinState = LOW;
|
||||
DoughLEDState _state = OFF;
|
||||
void _setPin(int high_or_low);
|
||||
unsigned long _timer;
|
||||
unsigned int _time;
|
||||
int _blinkOnStep;
|
||||
int _blinkOfSteps;
|
||||
int _blinkStep;
|
||||
int _brightness;
|
||||
int _pulseStep;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,70 +0,0 @@
|
|||
#include "DoughLogger.h"
|
||||
|
||||
DoughLogger::DoughLogger(const char *section)
|
||||
{
|
||||
_section = section;
|
||||
}
|
||||
|
||||
void DoughLogger::suspend()
|
||||
{
|
||||
_suspended = true;
|
||||
}
|
||||
|
||||
void DoughLogger::resume()
|
||||
{
|
||||
_suspended = false;
|
||||
}
|
||||
|
||||
void DoughLogger::log(const char *fmt, ...)
|
||||
{
|
||||
if (_suspended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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("");
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
#ifndef DOUGH_LOGGER_H
|
||||
#define DOUGH_LOGGER_H
|
||||
|
||||
#define LOGGER_PREFIX_BUFLEN 16
|
||||
#define LOGGER_PREFIX_FORMAT "%11s | "
|
||||
|
||||
#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, ...);
|
||||
void suspend();
|
||||
void resume();
|
||||
|
||||
private:
|
||||
const char *_section;
|
||||
bool _suspended = false;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,193 +0,0 @@
|
|||
#include "DoughUI.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DoughUI *DoughUI::_instance = nullptr;
|
||||
|
||||
DoughUI *DoughUI::Instance()
|
||||
{
|
||||
if (DoughUI::_instance == nullptr)
|
||||
{
|
||||
DoughUI::_instance = new DoughUI();
|
||||
}
|
||||
return DoughUI::_instance;
|
||||
}
|
||||
|
||||
DoughUI::DoughUI() : onoffButton(ONOFF_BUTTON_PIN),
|
||||
setupButton(SETUP_BUTTON_PIN),
|
||||
ledBuiltin(LED_BUILTIN),
|
||||
led1(LED1_PIN),
|
||||
led2(LED2_PIN),
|
||||
led3(LED3_PIN) {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughUI::setup()
|
||||
{
|
||||
// Setup the serial port, used for logging.
|
||||
Serial.begin(LOG_BAUDRATE);
|
||||
#ifdef LOG_WAIT_SERIAL
|
||||
while (!Serial)
|
||||
{
|
||||
// wait for serial port to connect. Needed for native USB.
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setup the buttons.
|
||||
onoffButton.setup();
|
||||
onoffButton.onInterrupt(DoughUI::onoffButtonISR);
|
||||
setupButton.setup();
|
||||
setupButton.onInterrupt(DoughUI::setupButtonISR);
|
||||
|
||||
// Setup the LEDs.
|
||||
ledBuiltin.setup();
|
||||
led1.setup();
|
||||
led2.setup();
|
||||
led3.setup();
|
||||
|
||||
// Setup a timer interrupt that is used to update the
|
||||
// user interface (a.k.a. "LEDs") in parallel to other activities.
|
||||
// This allows for example to have a flashing LED, during the
|
||||
// wifi connection setup.
|
||||
_setupTimerInterrupt();
|
||||
|
||||
// Notify the user that we're on a roll!
|
||||
_flash_all_leds();
|
||||
}
|
||||
|
||||
void DoughUI::onoffButtonISR()
|
||||
{
|
||||
DoughUI::Instance()->onoffButton.handleButtonState();
|
||||
}
|
||||
|
||||
void DoughUI::setupButtonISR()
|
||||
{
|
||||
DoughUI::Instance()->setupButton.handleButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a timer interrupt for updating the GUI. Unfortunately, the standard
|
||||
* libraries that I can find for this, are not equipped to work for the
|
||||
* Arduino Nano 33 IOT architecture. Luckily, documentation and various
|
||||
* helpful threads on the internet helped me piece the following code together.
|
||||
*/
|
||||
void DoughUI::_setupTimerInterrupt()
|
||||
{
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(200) | // Use divider (32kHz/200 = 160Hz)
|
||||
GCLK_GENDIV_ID(4); // for Generic Clock GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
|
||||
GCLK_GENCTRL_GENEN | // and enable the clock
|
||||
GCLK_GENCTRL_SRC_OSC32K | // using the 32kHz clock source as input
|
||||
GCLK_GENCTRL_ID(4); // for Generic Clock GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable timer
|
||||
GCLK_CLKCTRL_GEN_GCLK4 | // using Generic Clock GCLK4 as input
|
||||
GCLK_CLKCTRL_ID_TC4_TC5; // and feed its output to TC4 and TC5
|
||||
while (GCLK->STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV8 | // Use prescaler (160Hz / 8 = 20Hz)
|
||||
TC_CTRLA_WAVEGEN_MFRQ | // Use match frequency (MFRQ) mode
|
||||
TC_CTRLA_MODE_COUNT8 | // Set the timer to 8-bit mode
|
||||
TC_CTRLA_ENABLE; // Enable TC4
|
||||
REG_TC4_INTENSET = TC_INTENSET_OVF; // Enable TC4 overflow (OVF) interrupts
|
||||
REG_TC4_COUNT8_CC0 = 1; // Set the CC0 as the TOP value for MFRQ (1 => 50ms per pulse)
|
||||
while (TC4->COUNT8.STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
// Enable interrupts for TC4 in the Nested Vector InterruptController (NVIC),
|
||||
NVIC_SetPriority(TC4_IRQn, 0); // Set NVIC priority for TC4 to 0 (highest)
|
||||
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This callback is called when the TC4 timer hits an overflow interrupt.
|
||||
*/
|
||||
void TC4_Handler()
|
||||
{
|
||||
DoughUI::Instance()->updatedLEDs();
|
||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void DoughUI::processButtonEvents()
|
||||
{
|
||||
onoffButton.loop();
|
||||
setupButton.loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear pending button events.
|
||||
*/
|
||||
void DoughUI::clearButtonEvents()
|
||||
{
|
||||
onoffButton.clearEvents();
|
||||
setupButton.clearEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of all the LEDs in the system.
|
||||
* This method is called both sync by methods in this class and async by
|
||||
* the timer interrupt code from above. The timer interrupt based invocation
|
||||
* makes it possible to do LED updates, while the device is busy doing
|
||||
* something else.
|
||||
*/
|
||||
void DoughUI::updatedLEDs()
|
||||
{
|
||||
ledBuiltin.loop();
|
||||
led1.loop();
|
||||
led2.loop();
|
||||
led3.loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flash all LEDs, one at a time.
|
||||
*/
|
||||
void DoughUI::_flash_all_leds()
|
||||
{
|
||||
ledBuiltin.on();
|
||||
delay(100);
|
||||
ledBuiltin.off();
|
||||
led1.on();
|
||||
delay(100);
|
||||
led1.off();
|
||||
led2.on();
|
||||
delay(100);
|
||||
led2.off();
|
||||
led3.on();
|
||||
delay(100);
|
||||
led3.off();
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
#include "LED.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
LED::LED(int pin)
|
||||
{
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
void LED::setup()
|
||||
{
|
||||
pinMode(_pin, OUTPUT);
|
||||
_state = OFF;
|
||||
_setPin(LOW);
|
||||
}
|
||||
|
||||
void LED::loop()
|
||||
{
|
||||
unsigned long now = millis();
|
||||
bool tick = (now - _timer) > _time;
|
||||
|
||||
if (_state == FLASH)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = OFF;
|
||||
}
|
||||
}
|
||||
else if (_state == DIP)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
_state = ON;
|
||||
}
|
||||
}
|
||||
else if (_state == BLINK_ON)
|
||||
{
|
||||
if (_blinkStep == _blinkOnStep)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
}
|
||||
if (tick)
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = BLINK_OFF;
|
||||
_timer = now;
|
||||
}
|
||||
}
|
||||
else if (_state == BLINK_OFF)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_state = BLINK_ON;
|
||||
_timer = now;
|
||||
_blinkStep++;
|
||||
if (_blinkStep > _blinkOfSteps)
|
||||
{
|
||||
_blinkStep = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_state == PULSE)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_timer = now;
|
||||
_time = 1;
|
||||
_brightness += _pulseStep;
|
||||
if (_brightness <= 0)
|
||||
{
|
||||
_time = 200;
|
||||
_brightness = 0;
|
||||
_pulseStep = -_pulseStep;
|
||||
}
|
||||
else if (_brightness >= 100)
|
||||
{
|
||||
_brightness = 100;
|
||||
_pulseStep = -_pulseStep;
|
||||
}
|
||||
}
|
||||
analogWrite(_pin, _brightness);
|
||||
}
|
||||
else if (_state == OFF)
|
||||
{
|
||||
_setPin(LOW);
|
||||
}
|
||||
else if (_state == ON)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void LED::_setPin(int high_or_low)
|
||||
{
|
||||
_pinState = high_or_low;
|
||||
analogWrite(_pin, _pinState == LOW ? 0 : 255);
|
||||
}
|
||||
|
||||
void LED::on()
|
||||
{
|
||||
_state = ON;
|
||||
loop();
|
||||
}
|
||||
|
||||
void LED::off()
|
||||
{
|
||||
_state = OFF;
|
||||
loop();
|
||||
}
|
||||
|
||||
LED *LED::flash()
|
||||
{
|
||||
_setPin(HIGH);
|
||||
_state = FLASH;
|
||||
_timer = millis();
|
||||
_time = LED_TRANSITION_TIME_DEFAULT;
|
||||
loop();
|
||||
return this;
|
||||
}
|
||||
|
||||
LED *LED::blink()
|
||||
{
|
||||
return blink(1, 1);
|
||||
}
|
||||
|
||||
LED *LED::dip()
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = DIP;
|
||||
_timer = millis();
|
||||
_time = LED_TRANSITION_TIME_DEFAULT;
|
||||
loop();
|
||||
return this;
|
||||
}
|
||||
|
||||
LED *LED::blink(int onStep, int ofSteps)
|
||||
{
|
||||
_blinkOnStep = onStep;
|
||||
_blinkOfSteps = ofSteps;
|
||||
_blinkStep = 1;
|
||||
_state = BLINK_ON;
|
||||
_time = LED_TRANSITION_TIME_DEFAULT;
|
||||
loop();
|
||||
return this;
|
||||
}
|
||||
|
||||
void LED::pulse()
|
||||
{
|
||||
_state = PULSE;
|
||||
_brightness = 0;
|
||||
_pulseStep = +8;
|
||||
_time = 1;
|
||||
}
|
||||
|
||||
void LED::slow()
|
||||
{
|
||||
_time = LED_TRANSITION_TIME_SLOW;
|
||||
}
|
||||
|
||||
void LED::fast()
|
||||
{
|
||||
_time = LED_TRANSITION_TIME_FAST;
|
||||
}
|
||||
|
||||
bool LED::isOn()
|
||||
{
|
||||
return _pinState == HIGH;
|
||||
}
|
||||
|
||||
bool LED::isOff()
|
||||
{
|
||||
return _pinState == LOW;
|
||||
}
|
||||
} // namespace Dough
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef DOUGH_LED_H
|
||||
#define DOUGH_LED_H
|
||||
|
||||
// Delay times for blinking, flashing and dipping.
|
||||
#define LED_TRANSITION_TIME_SLOW 400
|
||||
#define LED_TRANSITION_TIME_DEFAULT 250
|
||||
#define LED_TRANSITION_TIME_FAST 100
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
typedef enum
|
||||
{
|
||||
ON,
|
||||
OFF,
|
||||
BLINK_ON,
|
||||
BLINK_OFF,
|
||||
FLASH,
|
||||
DIP,
|
||||
PULSE
|
||||
} LEDState;
|
||||
|
||||
// This class provides a set of basic LED lighting patterns, which can
|
||||
// be used in an async way.
|
||||
class LED
|
||||
{
|
||||
public:
|
||||
LED(int pin);
|
||||
void setup();
|
||||
void loop();
|
||||
void on();
|
||||
void off();
|
||||
LED *blink();
|
||||
LED *blink(int onStep, int ofSteps);
|
||||
LED *flash();
|
||||
LED *dip();
|
||||
void pulse();
|
||||
void slow();
|
||||
void fast();
|
||||
bool isOn();
|
||||
bool isOff();
|
||||
|
||||
private:
|
||||
int _pin;
|
||||
int _pinState = LOW;
|
||||
LEDState _state = OFF;
|
||||
void _setPin(int high_or_low);
|
||||
unsigned long _timer;
|
||||
unsigned int _time;
|
||||
int _blinkOnStep;
|
||||
int _blinkOfSteps;
|
||||
int _blinkStep;
|
||||
int _brightness;
|
||||
int _pulseStep;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -0,0 +1,73 @@
|
|||
#include "Logger.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
Logger::Logger(const char *section)
|
||||
{
|
||||
_section = section;
|
||||
}
|
||||
|
||||
void Logger::suspend()
|
||||
{
|
||||
_suspended = true;
|
||||
}
|
||||
|
||||
void Logger::resume()
|
||||
{
|
||||
_suspended = false;
|
||||
}
|
||||
|
||||
void Logger::log(const char *fmt, ...)
|
||||
{
|
||||
if (_suspended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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("");
|
||||
}
|
||||
} // namespace Dough
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef DOUGH_LOGGER_H
|
||||
#define DOUGH_LOGGER_H
|
||||
|
||||
#define LOGGER_PREFIX_BUFLEN 16
|
||||
#define LOGGER_PREFIX_FORMAT "%11s | "
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <stdarg.h>
|
||||
#include <WiFiNINA.h>
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
// This class provides an interface for logging data in a structured
|
||||
// manner to the serial console.
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
Logger(const char *section);
|
||||
void log(const char *fmt, ...);
|
||||
void suspend();
|
||||
void resume();
|
||||
|
||||
private:
|
||||
const char *_section;
|
||||
bool _suspended = false;
|
||||
};
|
||||
} // namespace Dough
|
||||
|
||||
#endif
|
|
@ -0,0 +1,182 @@
|
|||
#include "UI.h"
|
||||
|
||||
namespace Dough
|
||||
{
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
UI *UI::_instance = nullptr;
|
||||
|
||||
UI *UI::Instance()
|
||||
{
|
||||
if (UI::_instance == nullptr)
|
||||
{
|
||||
UI::_instance = new UI();
|
||||
}
|
||||
return UI::_instance;
|
||||
}
|
||||
|
||||
UI::UI() : onoffButton(ONOFF_BUTTON_PIN),
|
||||
setupButton(SETUP_BUTTON_PIN),
|
||||
ledBuiltin(LED_BUILTIN),
|
||||
led1(LED1_PIN),
|
||||
led2(LED2_PIN),
|
||||
led3(LED3_PIN) {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void UI::setup()
|
||||
{
|
||||
// Setup the serial port, used for logging.
|
||||
Serial.begin(LOG_BAUDRATE);
|
||||
#ifdef LOG_WAIT_SERIAL
|
||||
while (!Serial)
|
||||
{
|
||||
// wait for serial port to connect. Needed for native USB.
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setup the buttons.
|
||||
onoffButton.setup();
|
||||
onoffButton.onInterrupt(UI::onoffButtonISR);
|
||||
setupButton.setup();
|
||||
setupButton.onInterrupt(UI::setupButtonISR);
|
||||
|
||||
// Setup the LEDs.
|
||||
ledBuiltin.setup();
|
||||
led1.setup();
|
||||
led2.setup();
|
||||
led3.setup();
|
||||
|
||||
// Setup a timer interrupt that is used to update the
|
||||
// user interface (a.k.a. "LEDs") in parallel to other activities.
|
||||
// This allows for example to have a flashing LED, during the
|
||||
// wifi connection setup.
|
||||
_setupTimerInterrupt();
|
||||
|
||||
// Notify the user that we're on a roll!
|
||||
_flash_all_leds();
|
||||
|
||||
// Enable the timer interrupt handling.
|
||||
resume();
|
||||
}
|
||||
|
||||
void UI::onoffButtonISR()
|
||||
{
|
||||
UI::Instance()->onoffButton.handleButtonState();
|
||||
}
|
||||
|
||||
void UI::setupButtonISR()
|
||||
{
|
||||
UI::Instance()->setupButton.handleButtonState();
|
||||
}
|
||||
|
||||
// Setup a timer interrupt for updating the GUI. Unfortunately, the standard
|
||||
// libraries that I can find for this, are not equipped to work for the
|
||||
// Arduino Nano 33 IOT architecture. Luckily, documentation and various
|
||||
// helpful threads on the internet helped me piece the following code together.
|
||||
void UI::_setupTimerInterrupt()
|
||||
{
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(200) | // Use divider (32kHz/200 = 160Hz)
|
||||
GCLK_GENDIV_ID(4); // for Generic Clock GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
|
||||
GCLK_GENCTRL_GENEN | // and enable the clock
|
||||
GCLK_GENCTRL_SRC_OSC32K | // using the 32kHz clock source as input
|
||||
GCLK_GENCTRL_ID(4); // for Generic Clock GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable timer
|
||||
GCLK_CLKCTRL_GEN_GCLK4 | // using Generic Clock GCLK4 as input
|
||||
GCLK_CLKCTRL_ID_TC4_TC5; // and feed its output to TC4 and TC5
|
||||
while (GCLK->STATUS.bit.SYNCBUSY)
|
||||
; // Wait for synchronization
|
||||
|
||||
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV8 | // Use prescaler (160Hz / 8 = 20Hz)
|
||||
TC_CTRLA_WAVEGEN_MFRQ | // Use match frequency (MFRQ) mode
|
||||
TC_CTRLA_MODE_COUNT8 | // Set the timer to 8-bit mode
|
||||
TC_CTRLA_ENABLE; // Enable TC4
|
||||
REG_TC4_INTENSET = TC_INTENSET_OVF; // Enable TC4 overflow (OVF) interrupts
|
||||
REG_TC4_COUNT8_CC0 = 1; // Set the CC0 as the TOP value for MFRQ (1 => 50ms per pulse)
|
||||
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// Disables the TC4 interrupts, suspending timed async updates to
|
||||
// the user interface.
|
||||
void UI::suspend()
|
||||
{
|
||||
NVIC_DisableIRQ(TC4_IRQn);
|
||||
}
|
||||
|
||||
// Enables the TC4 interrupts in the Nested Vector InterruptController (NVIC),
|
||||
// starting timed async updates for the user interface.
|
||||
void UI::resume()
|
||||
{
|
||||
NVIC_SetPriority(TC4_IRQn, 0); // Set NVIC priority for TC4 to 0 (highest)
|
||||
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
|
||||
}
|
||||
|
||||
// Fire pending button events.
|
||||
void UI::processButtonEvents()
|
||||
{
|
||||
onoffButton.loop();
|
||||
setupButton.loop();
|
||||
}
|
||||
|
||||
// Clear pending button events.
|
||||
void UI::clearButtonEvents()
|
||||
{
|
||||
onoffButton.clearEvents();
|
||||
setupButton.clearEvents();
|
||||
}
|
||||
|
||||
// Update the state of all the LEDs in the system.
|
||||
// This method is called both sync by methods in this class and async by
|
||||
// the timer interrupt code from above. The timer interrupt based invocation
|
||||
// makes it possible to do LED updates, while the device is busy doing
|
||||
// something else.
|
||||
void UI::updatedLEDs()
|
||||
{
|
||||
ledBuiltin.loop();
|
||||
led1.loop();
|
||||
led2.loop();
|
||||
led3.loop();
|
||||
}
|
||||
|
||||
// Flash all LEDs, one at a time.
|
||||
void UI::_flash_all_leds()
|
||||
{
|
||||
ledBuiltin.on();
|
||||
delay(100);
|
||||
ledBuiltin.off();
|
||||
led1.on();
|
||||
delay(100);
|
||||
led1.off();
|
||||
led2.on();
|
||||
delay(100);
|
||||
led2.off();
|
||||
led3.on();
|
||||
delay(100);
|
||||
led3.off();
|
||||
}
|
||||
} // namespace Dough
|
||||
|
||||
// This callback is called when the TC4 timer hits an overflow interrupt.
|
||||
// Defined outside the Dough namespace, because TC4_Handler is a hard-coded
|
||||
// root namespace function name.
|
||||
void TC4_Handler()
|
||||
{
|
||||
Dough::UI::Instance()->updatedLEDs();
|
||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
||||
}
|
||||
|
|
@ -12,27 +12,27 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <stdarg.h>
|
||||
#include "UI/DoughButton.h"
|
||||
#include "UI/DoughLED.h"
|
||||
#include "UI/Button.h"
|
||||
#include "UI/LED.h"
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* This class groups all user interface functionality: serial logging,
|
||||
* LEDs and buttons.
|
||||
*/
|
||||
class DoughUI
|
||||
namespace Dough
|
||||
{
|
||||
// This class groups all user interface functionality: serial logging,
|
||||
// LEDs and buttons.
|
||||
class UI
|
||||
{
|
||||
public:
|
||||
static DoughUI *Instance();
|
||||
static UI *Instance();
|
||||
void setup();
|
||||
static void onoffButtonISR();
|
||||
static void setupButtonISR();
|
||||
DoughButton onoffButton;
|
||||
DoughButton setupButton;
|
||||
DoughLED ledBuiltin;
|
||||
DoughLED led1;
|
||||
DoughLED led2;
|
||||
DoughLED led3;
|
||||
Button onoffButton;
|
||||
Button setupButton;
|
||||
LED ledBuiltin;
|
||||
LED led1;
|
||||
LED led2;
|
||||
LED led3;
|
||||
void processButtonEvents();
|
||||
void clearButtonEvents();
|
||||
void updatedLEDs();
|
||||
|
@ -40,10 +40,11 @@ public:
|
|||
void suspend();
|
||||
|
||||
private:
|
||||
DoughUI();
|
||||
UI();
|
||||
void _setupTimerInterrupt();
|
||||
static DoughUI *_instance;
|
||||
static UI *_instance;
|
||||
void _flash_all_leds();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
43
src/main.cpp
43
src/main.cpp
|
@ -8,17 +8,18 @@
|
|||
// TODO: make significantChange part of sensor class?
|
||||
|
||||
DoughBoyState state = CONFIGURING;
|
||||
auto logger = DoughLogger("MAIN");
|
||||
auto logger = Dough::Logger("MAIN");
|
||||
|
||||
void setup()
|
||||
{
|
||||
TemperatureSensor::Instance()->setup();
|
||||
HumiditySensor::Instance()->setup();
|
||||
DistanceSensor::Instance()->setup();
|
||||
DoughWiFi::Instance()->setup();
|
||||
logger.log("s", "Initializing device");
|
||||
Dough::TemperatureSensor::Instance()->setup();
|
||||
Dough::HumiditySensor::Instance()->setup();
|
||||
Dough::DistanceSensor::Instance()->setup();
|
||||
Dough::WiFi::Instance()->setup();
|
||||
Dough::MQTT::Instance()->setup();
|
||||
DataController::Instance()->setup();
|
||||
auto ui = DoughUI::Instance();
|
||||
Dough::DataController::Instance()->setup();
|
||||
auto ui = Dough::UI::Instance();
|
||||
ui->setup();
|
||||
ui->onoffButton.onPress(handleOnoffButtonPress);
|
||||
ui->setupButton.onPress(handleSetupButtonPress);
|
||||
|
@ -27,8 +28,8 @@ void setup()
|
|||
|
||||
void loop()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
auto data = DataController::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
auto data = Dough::DataController::Instance();
|
||||
auto mqtt = Dough::MQTT::Instance();
|
||||
|
||||
ui->processButtonEvents();
|
||||
|
@ -50,7 +51,7 @@ void loop()
|
|||
}
|
||||
else if (state == MEASURING)
|
||||
{
|
||||
DataController::Instance()->loop();
|
||||
Dough::DataController::Instance()->loop();
|
||||
}
|
||||
else if (state == CALIBRATING)
|
||||
{
|
||||
|
@ -59,21 +60,19 @@ void loop()
|
|||
}
|
||||
else if (state == PAUSED)
|
||||
{
|
||||
DataController::Instance()->clearHistory();
|
||||
Dough::DataController::Instance()->clearHistory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device is connected to the WiFi network and the MQTT broker.
|
||||
* If not, then try to setup the connection.
|
||||
* Returns true if the connection was established, false otherwise.
|
||||
*/
|
||||
// Check if the device is connected to the WiFi network and the MQTT broker.
|
||||
// If not, then try to setup the connection.
|
||||
// Returns true if the connection was established, false otherwise.
|
||||
bool setupNetworkConnection()
|
||||
{
|
||||
static auto connectionState = CONNECTING_WIFI;
|
||||
|
||||
auto ui = DoughUI::Instance();
|
||||
auto network = DoughWiFi::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
auto network = Dough::WiFi::Instance();
|
||||
auto mqtt = Dough::MQTT::Instance();
|
||||
|
||||
if (!network->isConnected())
|
||||
|
@ -144,7 +143,7 @@ void handleSetupButtonPress()
|
|||
|
||||
void setStateToConfiguring()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
logger.log("s", "Waiting for configuration ...");
|
||||
state = CONFIGURING;
|
||||
ui->led1.on();
|
||||
|
@ -155,7 +154,7 @@ void setStateToConfiguring()
|
|||
|
||||
void setStateToMeasuring()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
logger.log("s", "Starting measurements");
|
||||
state = MEASURING;
|
||||
ui->led1.on();
|
||||
|
@ -166,7 +165,7 @@ void setStateToMeasuring()
|
|||
|
||||
void setStateToPaused()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
logger.log("s", "Pausing measurements");
|
||||
state = PAUSED;
|
||||
ui->led1.on();
|
||||
|
@ -177,7 +176,7 @@ void setStateToPaused()
|
|||
|
||||
void setStateToCalibrating()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
auto ui = Dough::UI::Instance();
|
||||
logger.log("s", "Requested device calibration");
|
||||
state = CALIBRATING;
|
||||
ui->led1.on();
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
#include "Sensors/TemperatureSensor.h"
|
||||
#include "Sensors/HumiditySensor.h"
|
||||
#include "Sensors/DistanceSensor.h"
|
||||
#include "Network/DoughWiFi.h"
|
||||
#include "Network/WiFi.h"
|
||||
#include "Network/MQTT.h"
|
||||
#include "Data/DataController.h"
|
||||
#include "UI/DoughButton.h"
|
||||
#include "UI/DoughUI.h"
|
||||
#include "UI/Button.h"
|
||||
#include "UI/UI.h"
|
||||
#include "config.h"
|
||||
|
||||
typedef enum
|
||||
|
|
Loading…
Reference in New Issue