Extracted the logging functionality into a separate logger, making it easier to log info per module. This also drags less of the UI functionality into low level modules, which seems a lot cleaner to me.
This commit is contained in:
parent
e66b8ccdd5
commit
10f8fc2290
|
@ -1,5 +1,4 @@
|
||||||
#include "Data/DataController.h"
|
#include "Data/DataController.h"
|
||||||
#include "Data/Measurements.h"
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Constructor
|
// Constructor
|
||||||
|
@ -21,7 +20,13 @@ DataController *DataController::Instance()
|
||||||
|
|
||||||
DataController::DataController() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK),
|
DataController::DataController() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK),
|
||||||
_humidityMeasurements(HUMIDITY_AVG_LOOKBACK),
|
_humidityMeasurements(HUMIDITY_AVG_LOOKBACK),
|
||||||
_distanceMeasurements(DISTANCE_AVG_LOOKBACK) {}
|
_distanceMeasurements(DISTANCE_AVG_LOOKBACK),
|
||||||
|
_logger("DATA")
|
||||||
|
{
|
||||||
|
_ui = DoughUI::Instance();
|
||||||
|
_sensors = DoughSensors::Instance();
|
||||||
|
_mqtt = DoughMQTT::Instance();
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Setup
|
// Setup
|
||||||
|
@ -50,14 +55,10 @@ void DataController::handleMqttMessage(String &key, String &payload)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DoughUI::Instance()->log("DATA", "sS", "ERROR - Unhandled MQTT message, key = ", key);
|
DataController::Instance()->_logger.log("sS", "ERROR - Unhandled MQTT message, key = ", key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if configuration has been taken care of. Some configuration is
|
|
||||||
* required before measurements can be processed.
|
|
||||||
*/
|
|
||||||
bool DataController::isConfigured()
|
bool DataController::isConfigured()
|
||||||
{
|
{
|
||||||
return _containerHeightSet;
|
return _containerHeightSet;
|
||||||
|
@ -73,21 +74,19 @@ void DataController::setContainerHeight(int height)
|
||||||
_containerHeightSet = false;
|
_containerHeightSet = false;
|
||||||
if (height <= HCSR04_MIN_MM)
|
if (height <= HCSR04_MIN_MM)
|
||||||
{
|
{
|
||||||
DoughUI::Instance()->log("DATA", "sisis",
|
_logger.log("sisis", "ERROR - Container height ", height,
|
||||||
"ERROR - Container height ", height,
|
"mm is less than the minimum measuring distance of ",
|
||||||
"mm is less than the minimum measuring distance of ",
|
HCSR04_MIN_MM, "mm");
|
||||||
HCSR04_MIN_MM, "mm");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (height >= HCSR04_MAX_MM)
|
if (height >= HCSR04_MAX_MM)
|
||||||
{
|
{
|
||||||
DoughUI::Instance()->log("DATA", "sisis",
|
_logger.log("sisis", "ERROR - Container height ", height,
|
||||||
"ERROR - Container height ", height,
|
"mm is more than the maximum measuring distance of ",
|
||||||
"mm is more than the maximum measuring distance of ",
|
HCSR04_MAX_MM, "mm");
|
||||||
HCSR04_MAX_MM, "mm");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DoughUI::Instance()->log("DATA", "sis", "Set container height to ", height, "mm");
|
_logger.log("sis", "Set container height to ", height, "mm");
|
||||||
_containerHeight = height;
|
_containerHeight = height;
|
||||||
_containerHeightSet = true;
|
_containerHeightSet = true;
|
||||||
}
|
}
|
||||||
|
@ -122,38 +121,36 @@ void DataController::_sample()
|
||||||
|
|
||||||
if (tick)
|
if (tick)
|
||||||
{
|
{
|
||||||
DoughUI *ui = DoughUI::Instance();
|
|
||||||
_lastSample = now;
|
_lastSample = now;
|
||||||
DoughSensors *sensors = DoughSensors::Instance();
|
|
||||||
|
|
||||||
// Quickly dip the LED to indicate that a measurement is started.
|
// Quickly dip the LED to indicate that a measurement is started.
|
||||||
// This is done synchroneously, because we suspend the timer interrupts
|
// This is done synchroneously, because we suspend the timer interrupts
|
||||||
// in the upcoming code.
|
// in the upcoming code.
|
||||||
ui->led3.off();
|
_ui->led3.off();
|
||||||
delay(50);
|
delay(50);
|
||||||
ui->led3.on();
|
_ui->led3.on();
|
||||||
|
|
||||||
// Suspend the UI timer interrupts, to not let these interfere
|
// Suspend the UI timer interrupts, to not let these interfere
|
||||||
// with the sensor measurements.
|
// with the sensor measurements.
|
||||||
ui->suspend();
|
_ui->suspend();
|
||||||
|
|
||||||
// Take a sample.
|
// Take a sample.
|
||||||
switch (_sampleType)
|
switch (_sampleType)
|
||||||
{
|
{
|
||||||
case SAMPLE_TEMPERATURE:
|
case SAMPLE_TEMPERATURE:
|
||||||
_temperatureMeasurements.add(sensors->readTemperature());
|
_temperatureMeasurements.add(_sensors->readTemperature());
|
||||||
_sampleType = SAMPLE_HUMIDITY;
|
_sampleType = SAMPLE_HUMIDITY;
|
||||||
break;
|
break;
|
||||||
case SAMPLE_HUMIDITY:
|
case SAMPLE_HUMIDITY:
|
||||||
_humidityMeasurements.add(sensors->readHumidity());
|
_humidityMeasurements.add(_sensors->readHumidity());
|
||||||
_sampleType = SAMPLE_DISTANCE;
|
_sampleType = SAMPLE_DISTANCE;
|
||||||
break;
|
break;
|
||||||
case SAMPLE_DISTANCE:
|
case SAMPLE_DISTANCE:
|
||||||
_distanceMeasurements.add(sensors->readDistance());
|
_distanceMeasurements.add(_sensors->readDistance());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->resume();
|
_ui->resume();
|
||||||
|
|
||||||
_sampleCounter++;
|
_sampleCounter++;
|
||||||
if (_sampleCounter == SAMPLE_CYCLE_LENGTH)
|
if (_sampleCounter == SAMPLE_CYCLE_LENGTH)
|
||||||
|
@ -166,77 +163,17 @@ void DataController::_sample()
|
||||||
|
|
||||||
void DataController::_publish()
|
void DataController::_publish()
|
||||||
{
|
{
|
||||||
static unsigned long lastSample = 0;
|
if (_lastPublish == 0 || millis() - _lastPublish > PUBLISH_INTERVAL)
|
||||||
if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL)
|
|
||||||
{
|
{
|
||||||
lastSample = millis();
|
_lastPublish = millis();
|
||||||
|
|
||||||
DoughUI *ui = DoughUI::Instance();
|
_mqtt->publish("temperature", _temperatureMeasurements.getLast());
|
||||||
DoughMQTT *mqtt = DoughMQTT::Instance();
|
_mqtt->publish("temperature/average", _temperatureMeasurements.getAverage());
|
||||||
|
_mqtt->publish("humidity", _humidityMeasurements.getLast());
|
||||||
|
_mqtt->publish("humidity/average", _humidityMeasurements.getAverage());
|
||||||
|
_mqtt->publish("distance", _distanceMeasurements.getLast());
|
||||||
|
_mqtt->publish("distance/average", _distanceMeasurements.getAverage());
|
||||||
|
|
||||||
ui->log("DATA", "s", "Publish temperature");
|
_ui->led1.dip()->fast();
|
||||||
auto m = _temperatureMeasurements.getLast();
|
|
||||||
if (m->ok)
|
|
||||||
{
|
|
||||||
mqtt->publish("temperature", m->value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mqtt->publish("temperature", "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->log("DATA", "s", "Publish temperature average");
|
|
||||||
m = _temperatureMeasurements.getAverage();
|
|
||||||
if (m->ok)
|
|
||||||
{
|
|
||||||
mqtt->publish("temperature/average", m->value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mqtt->publish("temperature/average", "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->log("DATA", "s", "Publish humidity");
|
|
||||||
m = _humidityMeasurements.getLast();
|
|
||||||
if (m->ok)
|
|
||||||
{
|
|
||||||
mqtt->publish("humidity", m->value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mqtt->publish("humidity", "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
m = _humidityMeasurements.getAverage();
|
|
||||||
if (m->ok)
|
|
||||||
{
|
|
||||||
mqtt->publish("humidity/average", m->value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mqtt->publish("humidity/average", "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
m = _distanceMeasurements.getLast();
|
|
||||||
if (m->ok)
|
|
||||||
{
|
|
||||||
mqtt->publish("distance", m->value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mqtt->publish("distance", "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
m = _distanceMeasurements.getAverage();
|
|
||||||
if (m->ok)
|
|
||||||
{
|
|
||||||
mqtt->publish("distance/average", m->value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mqtt->publish("distance/average", "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->led1.dip()->fast();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "Network/DoughWiFi.h"
|
#include "Network/DoughWiFi.h"
|
||||||
#include "Network/DoughMQTT.h"
|
#include "Network/DoughMQTT.h"
|
||||||
#include "UI/DoughUI.h"
|
#include "UI/DoughUI.h"
|
||||||
|
#include "UI/DoughLogger.h"
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
|
@ -36,36 +37,40 @@ typedef enum
|
||||||
} DoughSampleType;
|
} DoughSampleType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DoughData class is responsible for holding the device configuration,
|
* This class is responsible for handling all things "data".
|
||||||
* collecting measurements from sensors, gathering the statistics on these data,
|
* It holds the device configuration, collects measurements from sensors,
|
||||||
* and publishing results to the MQTT broker.
|
* gathers statistics on these data, and publishing results to the MQTT broker.
|
||||||
*/
|
*/
|
||||||
class DataController
|
class DataController
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static DataController *Instance();
|
static DataController* Instance();
|
||||||
void setup();
|
void setup();
|
||||||
void loop();
|
void loop();
|
||||||
void clearHistory();
|
void clearHistory();
|
||||||
void setContainerHeight(int height);
|
void setContainerHeight(int height);
|
||||||
bool isConfigured();
|
bool isConfigured();
|
||||||
static void handleMqttConnect(DoughMQTT *mqtt);
|
static void handleMqttConnect(DoughMQTT* mqtt);
|
||||||
static void handleMqttMessage(String &key, String &value);
|
static void handleMqttMessage(String &key, String &value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DataController();
|
DataController();
|
||||||
static DataController *_instance;
|
static DataController* _instance;
|
||||||
DoughSensors *_sensors;
|
Measurements _temperatureMeasurements;
|
||||||
|
Measurements _humidityMeasurements;
|
||||||
|
Measurements _distanceMeasurements;
|
||||||
|
DoughLogger _logger;
|
||||||
|
DoughUI* _ui;
|
||||||
|
DoughSensors* _sensors;
|
||||||
|
DoughMQTT* _mqtt;
|
||||||
unsigned long _lastSample = 0;
|
unsigned long _lastSample = 0;
|
||||||
|
unsigned long _lastPublish = 0;
|
||||||
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
|
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
|
||||||
int _sampleCounter = 0;
|
int _sampleCounter = 0;
|
||||||
int _containerHeight;
|
int _containerHeight;
|
||||||
bool _containerHeightSet;
|
bool _containerHeightSet;
|
||||||
void _sample();
|
void _sample();
|
||||||
void _publish();
|
void _publish();
|
||||||
Measurements _temperatureMeasurements;
|
|
||||||
Measurements _humidityMeasurements;
|
|
||||||
Measurements _distanceMeasurements;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
#include "Data/Measurement.h"
|
#include "Data/Measurement.h"
|
||||||
|
|
||||||
Measurement::Measurement() {}
|
Measurement::Measurement() { }
|
||||||
|
|
||||||
Measurement::Measurement(bool ok, int value)
|
Measurement Measurement::Failed()
|
||||||
{
|
{
|
||||||
this->ok = ok;
|
Measurement m;
|
||||||
this->value = value;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
Measurement *Measurement::Failed()
|
Measurement Measurement::Value(int value)
|
||||||
{
|
{
|
||||||
return new Measurement(false, 0);
|
Measurement m;
|
||||||
}
|
m.ok = true;
|
||||||
|
m.value = value;
|
||||||
Measurement *Measurement::Ok(int value)
|
return m;
|
||||||
{
|
|
||||||
return new Measurement(true, value);
|
|
||||||
}
|
}
|
|
@ -2,17 +2,17 @@
|
||||||
#define DOUGH_DATA_MEASUREMENT_H
|
#define DOUGH_DATA_MEASUREMENT_H
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DoughDataMeasurement class represents a single measurement.
|
* This class represents a single measurement, which can be either a
|
||||||
|
* successful (bearing a measurement value) or a failed one.
|
||||||
*/
|
*/
|
||||||
class Measurement
|
class Measurement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Measurement();
|
Measurement();
|
||||||
Measurement(bool ok, int value);
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
static Measurement *Failed();
|
static Measurement Failed();
|
||||||
static Measurement *Ok(int value);
|
static Measurement Value(int value);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
#include "Data/DataController.h"
|
#include "Data/Measurements.h"
|
||||||
#include "Data/Measurement.h"
|
#include "UI/DoughUI.h"
|
||||||
|
|
||||||
typedef Measurement *MeasurementPtr;
|
|
||||||
|
|
||||||
Measurements::Measurements(unsigned int avgLookback)
|
Measurements::Measurements(unsigned int avgLookback)
|
||||||
{
|
{
|
||||||
_storageSize = avgLookback;
|
_storageSize = avgLookback;
|
||||||
_storage = new MeasurementPtr[avgLookback];
|
|
||||||
for (unsigned int i = 0; i < avgLookback; i++)
|
_storage = new Measurement*[avgLookback];
|
||||||
{
|
for (unsigned int i = 0; i < avgLookback; i++) {
|
||||||
_storage[i] = nullptr;
|
_storage[i] = new Measurement;
|
||||||
}
|
}
|
||||||
|
clearHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Measurements::add(Measurement *measurement)
|
void Measurements::add(Measurement measurement)
|
||||||
{
|
{
|
||||||
auto index = _next();
|
unsigned int index = _next();
|
||||||
_storage[index] = measurement;
|
_storage[index]->ok = measurement.ok;
|
||||||
if (measurement->ok)
|
_storage[index]->value = measurement.value;
|
||||||
|
|
||||||
|
if (measurement.ok)
|
||||||
{
|
{
|
||||||
_averageCount++;
|
_averageCount++;
|
||||||
_averageSum += measurement->value;
|
_averageSum += measurement.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,26 +32,28 @@ unsigned int Measurements::_next()
|
||||||
{
|
{
|
||||||
_index = 0;
|
_index = 0;
|
||||||
}
|
}
|
||||||
if (_storage[_index] != nullptr && _storage[_index]->ok)
|
if (_storage[_index]->ok)
|
||||||
{
|
{
|
||||||
_averageSum -= _storage[_index]->value;
|
_averageSum -= _storage[_index]->value;
|
||||||
_averageCount--;
|
_averageCount--;
|
||||||
}
|
}
|
||||||
|
_storage[_index]->ok = false;
|
||||||
|
_storage[_index]->value = 0;
|
||||||
return _index;
|
return _index;
|
||||||
}
|
}
|
||||||
|
|
||||||
Measurement *Measurements::getLast()
|
Measurement Measurements::getLast()
|
||||||
{
|
{
|
||||||
return _storage[_index];
|
return *_storage[_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
Measurement *Measurements::getAverage()
|
Measurement Measurements::getAverage()
|
||||||
{
|
{
|
||||||
auto result = new Measurement();
|
Measurement result;
|
||||||
if (_averageCount > 0)
|
if (_averageCount > 0)
|
||||||
{
|
{
|
||||||
result->ok = true;
|
result.ok = true;
|
||||||
result->value = round(_averageSum / _averageCount);
|
result.value = round(_averageSum / _averageCount);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
#ifndef DOUGH_DATA_MEASUREMENTS_H
|
#ifndef DOUGH_DATA_MEASUREMENTS_H
|
||||||
#define DOUGH_DATA_MEASUREMENTS_H
|
#define DOUGH_DATA_MEASUREMENTS_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
#include "Data/Measurement.h"
|
#include "Data/Measurement.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DoughDataMeasurements class is used to store measurements for a sensor
|
* This class is used to store measurements for a sensor and to keep
|
||||||
* and to keep track of running totals for handling average computations.
|
* track of running totals for handling average computations.
|
||||||
*/
|
*/
|
||||||
class Measurements
|
class Measurements
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Measurements(unsigned int avgLookback);
|
Measurements(unsigned int avgLookback);
|
||||||
void add(Measurement *measurement);
|
void add(Measurement measurement);
|
||||||
Measurement *getLast();
|
Measurement getLast();
|
||||||
Measurement *getAverage();
|
Measurement getAverage();
|
||||||
void clearHistory();
|
void clearHistory();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Measurement **_storage;
|
Measurement** _storage;
|
||||||
unsigned int _storageSize;
|
unsigned int _storageSize;
|
||||||
int _averageSum = 0;
|
int _averageSum = 0;
|
||||||
unsigned int _averageCount = 0;
|
unsigned int _averageCount = 0;
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
// Constructor
|
// Constructor
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
DoughMQTT *DoughMQTT::_instance = nullptr;
|
DoughMQTT* DoughMQTT::_instance = nullptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the DoughMQTT singleton.
|
* Fetch the DoughMQTT singleton.
|
||||||
*/
|
*/
|
||||||
DoughMQTT *DoughMQTT::Instance()
|
DoughMQTT* DoughMQTT::Instance()
|
||||||
{
|
{
|
||||||
if (DoughMQTT::_instance == nullptr)
|
if (DoughMQTT::_instance == nullptr)
|
||||||
{
|
{
|
||||||
|
@ -18,10 +18,7 @@ DoughMQTT *DoughMQTT::Instance()
|
||||||
return DoughMQTT::_instance;
|
return DoughMQTT::_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
DoughMQTT::DoughMQTT()
|
DoughMQTT::DoughMQTT() : _logger("MQTT") { }
|
||||||
{
|
|
||||||
_ui = DoughUI::Instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Setup
|
// Setup
|
||||||
|
@ -29,14 +26,14 @@ DoughMQTT::DoughMQTT()
|
||||||
|
|
||||||
void DoughMQTT::setup()
|
void DoughMQTT::setup()
|
||||||
{
|
{
|
||||||
DoughWiFi *network = DoughWiFi::Instance();
|
DoughWiFi* network = DoughWiFi::Instance();
|
||||||
|
|
||||||
#ifdef MQTT_DEVICE_ID
|
#ifdef MQTT_DEVICE_ID
|
||||||
_mqttDeviceId = MQTT_DEVICE_ID;
|
_mqttDeviceId = MQTT_DEVICE_ID;
|
||||||
#else
|
#else
|
||||||
_mqttDeviceId = network->getMacAddress();
|
_mqttDeviceId = network->getMacAddress();
|
||||||
#endif
|
#endif
|
||||||
_ui->log("MQTT", "ss", "Device ID = ", _mqttDeviceId);
|
_logger.log("ss", "Device ID = ", _mqttDeviceId);
|
||||||
|
|
||||||
_mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client);
|
_mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client);
|
||||||
}
|
}
|
||||||
|
@ -62,13 +59,13 @@ bool DoughMQTT::isConnected()
|
||||||
|
|
||||||
bool DoughMQTT::connect()
|
bool DoughMQTT::connect()
|
||||||
{
|
{
|
||||||
_ui->log("MQTT", "sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT);
|
_logger.log("sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT);
|
||||||
_mqttClient.connect(_mqttDeviceId, MQTT_USERNAME, MQTT_PASSWORD);
|
_mqttClient.connect(_mqttDeviceId, MQTT_USERNAME, MQTT_PASSWORD);
|
||||||
|
|
||||||
// Check if the connection to the broker was successful.
|
// Check if the connection to the broker was successful.
|
||||||
if (!_mqttClient.connected())
|
if (!_mqttClient.connected())
|
||||||
{
|
{
|
||||||
_ui->log("MQTT", "s", "ERROR - Connection to broker failed");
|
_logger.log("s", "ERROR - Connection to broker failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,9 +86,9 @@ void DoughMQTT::procesIncomingsMessages()
|
||||||
|
|
||||||
void DoughMQTT::handleMessage(String &topic, String &payload)
|
void DoughMQTT::handleMessage(String &topic, String &payload)
|
||||||
{
|
{
|
||||||
DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload);
|
DoughMQTT::Instance()->_logger.log("sSsS", "<<< ", topic, " = ", payload);
|
||||||
|
|
||||||
DoughMQTT *mqtt = DoughMQTT::Instance();
|
DoughMQTT* mqtt = DoughMQTT::Instance();
|
||||||
if (mqtt->_onMessage != nullptr)
|
if (mqtt->_onMessage != nullptr)
|
||||||
{
|
{
|
||||||
int pos = topic.lastIndexOf('/');
|
int pos = topic.lastIndexOf('/');
|
||||||
|
@ -103,19 +100,19 @@ void DoughMQTT::handleMessage(String &topic, String &payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoughMQTT::subscribe(const char *key)
|
void DoughMQTT::subscribe(const char* key)
|
||||||
{
|
{
|
||||||
char topic[200];
|
char topic[200];
|
||||||
snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
|
snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
|
||||||
DoughUI::Instance()->log("MQTT", "ss", "Subscribe to ", topic);
|
_logger.log("ss", "Subscribe to ", topic);
|
||||||
_mqttClient.subscribe(topic);
|
_mqttClient.subscribe(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoughMQTT::publish(const char *key, const char *payload)
|
void DoughMQTT::publish(const char* key, const char* payload)
|
||||||
{
|
{
|
||||||
char topic[200];
|
char topic[200];
|
||||||
snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
|
snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
|
||||||
DoughUI::Instance()->log("MQTT", "ssss", ">>> ", topic, " = ", payload);
|
_logger.log("ssss", ">>> ", topic, " = ", payload);
|
||||||
_mqttClient.publish(topic, payload);
|
_mqttClient.publish(topic, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,4 +121,12 @@ void DoughMQTT::publish(const char *key, int payload)
|
||||||
char buf[16];
|
char buf[16];
|
||||||
snprintf(buf, 16, "%d", payload);
|
snprintf(buf, 16, "%d", payload);
|
||||||
publish(key, buf);
|
publish(key, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoughMQTT::publish(const char *key, Measurement measurement) {
|
||||||
|
if (measurement.ok) {
|
||||||
|
publish(key, measurement.value);
|
||||||
|
} else {
|
||||||
|
publish(key, "null");
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,37 +4,43 @@
|
||||||
#include <MQTT.h>
|
#include <MQTT.h>
|
||||||
#include <MQTTClient.h>
|
#include <MQTTClient.h>
|
||||||
#include "Network/DoughWiFi.h"
|
#include "Network/DoughWiFi.h"
|
||||||
#include "UI/DoughUI.h"
|
#include "Data/Measurement.h"
|
||||||
|
#include "UI/DoughLogger.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class encapsulates the connection to the MQTT broker.
|
||||||
|
* MQTT is used to publish measurements and to store configuration data.
|
||||||
|
*/
|
||||||
class DoughMQTT;
|
class DoughMQTT;
|
||||||
|
|
||||||
typedef void (*DoughMQTTConnectHandler)(DoughMQTT *mqtt);
|
typedef void (*DoughMQTTConnectHandler)(DoughMQTT* mqtt);
|
||||||
typedef void (*DoughMQTTMessageHandler)(String &key, String &value);
|
typedef void (*DoughMQTTMessageHandler)(String &key, String &value);
|
||||||
|
|
||||||
class DoughMQTT
|
class DoughMQTT
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static DoughMQTT *Instance();
|
static DoughMQTT* Instance();
|
||||||
void setup();
|
void setup();
|
||||||
void onConnect(DoughMQTTConnectHandler callback);
|
void onConnect(DoughMQTTConnectHandler callback);
|
||||||
void onMessage(DoughMQTTMessageHandler callback);
|
void onMessage(DoughMQTTMessageHandler callback);
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
bool connect();
|
bool connect();
|
||||||
void subscribe(const char *key);
|
void subscribe(const char* key);
|
||||||
void procesIncomingsMessages();
|
void procesIncomingsMessages();
|
||||||
void publish(const char *key, const char *payload);
|
void publish(const char* key, const char* payload);
|
||||||
void publish(const char *key, int payload);
|
void publish(const char* key, int payload);
|
||||||
|
void publish(const char* key, Measurement measurement);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DoughMQTT();
|
DoughMQTT();
|
||||||
static DoughMQTT *_instance;
|
static DoughMQTT* _instance;
|
||||||
MQTTClient _mqttClient;
|
MQTTClient _mqttClient;
|
||||||
DoughUI *_ui;
|
DoughLogger _logger;
|
||||||
DoughMQTTConnectHandler _onConnect = nullptr;
|
DoughMQTTConnectHandler _onConnect = nullptr;
|
||||||
MQTTClientCallbackSimple _onMessage = nullptr;
|
MQTTClientCallbackSimple _onMessage = nullptr;
|
||||||
static void handleMessage(String &topic, String &payload);
|
static void handleMessage(String &topic, String &payload);
|
||||||
char *_mqttDeviceId;
|
char* _mqttDeviceId;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -18,10 +18,7 @@ DoughWiFi *DoughWiFi::Instance()
|
||||||
return DoughWiFi::_instance;
|
return DoughWiFi::_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
DoughWiFi::DoughWiFi()
|
DoughWiFi::DoughWiFi() : _logger("WIFI") {}
|
||||||
{
|
|
||||||
_ui = DoughUI::Instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Setup
|
// Setup
|
||||||
|
@ -39,7 +36,7 @@ void DoughWiFi::_setMacAddress()
|
||||||
void DoughWiFi::setup()
|
void DoughWiFi::setup()
|
||||||
{
|
{
|
||||||
_setMacAddress();
|
_setMacAddress();
|
||||||
DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress());
|
_logger.log("ss", "MAC address = ", getMacAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
@ -58,7 +55,7 @@ bool DoughWiFi::connect()
|
||||||
// Check if a device with a WiFi shield is used.
|
// Check if a device with a WiFi shield is used.
|
||||||
if (status == WL_NO_SHIELD)
|
if (status == WL_NO_SHIELD)
|
||||||
{
|
{
|
||||||
_ui->log("NETWORK", "s", "ERROR - Device has no WiFi shield");
|
_logger.log("s", "ERROR - Device has no WiFi shield");
|
||||||
delay(5000);
|
delay(5000);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -70,19 +67,19 @@ bool DoughWiFi::connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the connection to the WiFi network.
|
// Setup the connection to the WiFi network.
|
||||||
_ui->log("NETWORK", "ss", "WiFi network = ", WIFI_SSID);
|
_logger.log("ss", "WiFi network = ", WIFI_SSID);
|
||||||
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
|
||||||
// Check if the connection attempt was successful.
|
// Check if the connection attempt was successful.
|
||||||
if (status == WL_CONNECTED)
|
if (status == WL_CONNECTED)
|
||||||
{
|
{
|
||||||
_ui->log("NETWORK", "sa", "IP-Address = ", WiFi.localIP());
|
_logger.log("sa", "IP-Address = ", WiFi.localIP());
|
||||||
_ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm");
|
_logger.log("sis", "Signal strength = ", WiFi.RSSI(), " dBm");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
|
_logger.log("sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,17 @@
|
||||||
#define DOUGH_NETWORK_H
|
#define DOUGH_NETWORK_H
|
||||||
|
|
||||||
#include <WiFiNINA.h>
|
#include <WiFiNINA.h>
|
||||||
#include "UI/DoughUI.h"
|
#include "UI/DoughLogger.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class encapsulates the connection to the WiFi network.
|
||||||
|
*/
|
||||||
class DoughWiFi
|
class DoughWiFi
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static DoughWiFi *Instance();
|
static DoughWiFi* Instance();
|
||||||
char *getMacAddress();
|
char* getMacAddress();
|
||||||
void setup();
|
void setup();
|
||||||
void loop();
|
void loop();
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
|
@ -18,10 +21,10 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DoughWiFi();
|
DoughWiFi();
|
||||||
static DoughWiFi *_instance;
|
static DoughWiFi* _instance;
|
||||||
void _setMacAddress();
|
void _setMacAddress();
|
||||||
char _macAddress[18]; // max MAC address length + 1
|
char _macAddress[18]; // max MAC address length + 1
|
||||||
DoughUI *_ui;
|
DoughLogger _logger;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -16,8 +16,7 @@ DoughSensors* DoughSensors::Instance() {
|
||||||
return DoughSensors::_instance;
|
return DoughSensors::_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
DoughSensors::DoughSensors() {
|
DoughSensors::DoughSensors() : _logger("SENSORS") {
|
||||||
_ui = DoughUI::Instance();
|
|
||||||
_dht = new DHT(DHT11_DATA_PIN, DHT11);
|
_dht = new DHT(DHT11_DATA_PIN, DHT11);
|
||||||
_hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
|
_hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
|
||||||
}
|
}
|
||||||
|
@ -35,38 +34,38 @@ void DoughSensors::setup() {
|
||||||
// loop
|
// loop
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
Measurement* DoughSensors::readTemperature() {
|
Measurement DoughSensors::readTemperature() {
|
||||||
float t = _dht->readTemperature();
|
float t = _dht->readTemperature();
|
||||||
if (isnan(t)) {
|
if (isnan(t)) {
|
||||||
_ui->log("SENSORS", "s", "ERROR - Temperature measurement failed");
|
_logger.log("s", "ERROR - Temperature measurement failed");
|
||||||
return Measurement::Failed();
|
return Measurement::Failed();
|
||||||
} else {
|
} else {
|
||||||
|
_logger.log("sis", "Temperature = ", int(t), "°C ");
|
||||||
_hcsr04->setTemperature(int(t));
|
_hcsr04->setTemperature(int(t));
|
||||||
auto m = new Measurement(true, int(t));
|
auto m = Measurement::Value(int(t));
|
||||||
_ui->log("SENSORS", "sisi", "Temperature = ", int(t), "°C ", m->value);
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Measurement* DoughSensors::readHumidity() {
|
Measurement DoughSensors::readHumidity() {
|
||||||
int h = _dht->readHumidity();
|
int h = _dht->readHumidity();
|
||||||
if (h == 0) {
|
if (h == 0) {
|
||||||
_ui->log("SENSORS", "s", "ERROR - Humidity measurement failed");
|
_logger.log("s", "ERROR - Humidity measurement failed");
|
||||||
return Measurement::Failed();
|
return Measurement::Failed();
|
||||||
} else {
|
} else {
|
||||||
|
_logger.log("sis", "Humidity = ", h, "%");
|
||||||
_hcsr04->setHumidity(h);
|
_hcsr04->setHumidity(h);
|
||||||
_ui->log("SENSORS", "sis", "Humidity = ", h, "%");
|
return Measurement::Value(h);
|
||||||
return Measurement::Ok(h);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Measurement* DoughSensors::readDistance() {
|
Measurement DoughSensors::readDistance() {
|
||||||
int d = _hcsr04->readDistance();
|
int d = _hcsr04->readDistance();
|
||||||
if (d == -1) {
|
if (d == -1) {
|
||||||
_ui->log("SENSORS", "s", "ERROR - Distance measurement failed");
|
_logger.log("s", "ERROR - Distance measurement failed");
|
||||||
return Measurement::Failed();
|
return Measurement::Failed();
|
||||||
} else {
|
} else {
|
||||||
_ui->log("SENSORS", "sis", "Distance = ", d, "mm");
|
_logger.log("sis", "Distance = ", d, "mm");
|
||||||
return Measurement::Ok(d);
|
return Measurement::Value(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,25 @@
|
||||||
|
|
||||||
#include <DHT.h>
|
#include <DHT.h>
|
||||||
#include "Sensors/HCSR04.h"
|
#include "Sensors/HCSR04.h"
|
||||||
#include "UI/DoughUI.h"
|
#include "UI/DoughLogger.h"
|
||||||
#include "Data/Measurement.h"
|
#include "Data/Measurement.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides access to the sensors in the device.
|
||||||
|
*/
|
||||||
class DoughSensors {
|
class DoughSensors {
|
||||||
public:
|
public:
|
||||||
static DoughSensors* Instance();
|
static DoughSensors* Instance();
|
||||||
void setup();
|
void setup();
|
||||||
Measurement* readTemperature();
|
Measurement readTemperature();
|
||||||
Measurement* readHumidity();
|
Measurement readHumidity();
|
||||||
Measurement* readDistance();
|
Measurement readDistance();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DoughSensors();
|
DoughSensors();
|
||||||
static DoughSensors* _instance;
|
static DoughSensors* _instance;
|
||||||
DoughUI *_ui;
|
DoughLogger _logger;
|
||||||
DHT* _dht;
|
DHT* _dht;
|
||||||
HCSR04* _hcsr04;
|
HCSR04* _hcsr04;
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to get a distance reading from an HCSR04 sensor.
|
||||||
|
*/
|
||||||
class HCSR04 {
|
class HCSR04 {
|
||||||
public:
|
public:
|
||||||
HCSR04(int triggerPin, int echoPin);
|
HCSR04(int triggerPin, int echoPin);
|
||||||
|
|
|
@ -18,6 +18,15 @@ typedef enum
|
||||||
|
|
||||||
typedef void (*DoughButtonHandler)();
|
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
|
class DoughButton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -19,6 +19,10 @@ typedef enum
|
||||||
PULSE
|
PULSE
|
||||||
} DoughLEDState;
|
} DoughLEDState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a set of basic LED lighting patterns, which can
|
||||||
|
* be used in an async way.
|
||||||
|
*/
|
||||||
class DoughLED
|
class DoughLED
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include "DoughLogger.h"
|
||||||
|
|
||||||
|
DoughLogger::DoughLogger(const char *section)
|
||||||
|
{
|
||||||
|
_section = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoughLogger::log(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[LOGGER_PREFIX_BUFLEN];
|
||||||
|
snprintf(buf, sizeof(buf) / sizeof(buf[0]), LOGGER_PREFIX_FORMAT, _section);
|
||||||
|
Serial.print(buf);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
while (*fmt != '\0')
|
||||||
|
{
|
||||||
|
if (*fmt == 'i')
|
||||||
|
{
|
||||||
|
int i = va_arg(args, int);
|
||||||
|
Serial.print(i);
|
||||||
|
}
|
||||||
|
else if (*fmt == 'f')
|
||||||
|
{
|
||||||
|
float f = va_arg(args, double);
|
||||||
|
Serial.print(f);
|
||||||
|
}
|
||||||
|
else if (*fmt == 'a')
|
||||||
|
{
|
||||||
|
IPAddress a = va_arg(args, IPAddress);
|
||||||
|
Serial.print(a);
|
||||||
|
}
|
||||||
|
else if (*fmt == 's')
|
||||||
|
{
|
||||||
|
const char *s = va_arg(args, const char *);
|
||||||
|
Serial.print(s);
|
||||||
|
}
|
||||||
|
else if (*fmt == 'S')
|
||||||
|
{
|
||||||
|
String S = va_arg(args, String);
|
||||||
|
Serial.print(S);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("<log(): invalid format char '");
|
||||||
|
Serial.print(*fmt);
|
||||||
|
Serial.print("'>");
|
||||||
|
}
|
||||||
|
fmt++;
|
||||||
|
}
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
Serial.println("");
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef DOUGH_LOGGER_H
|
||||||
|
#define DOUGH_LOGGER_H
|
||||||
|
|
||||||
|
#define LOGGER_PREFIX_BUFLEN 16
|
||||||
|
#define LOGGER_PREFIX_FORMAT "%10s | "
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <WiFiNINA.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides an interface for logging data in a structured
|
||||||
|
* manner to the serial console.
|
||||||
|
*/
|
||||||
|
class DoughLogger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DoughLogger(const char *section);
|
||||||
|
void log(const char *fmt, ...);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* _section;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,10 +1,11 @@
|
||||||
#include "DoughUI.h"
|
#include "DoughUI.h"
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Constructor
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
DoughUI *DoughUI::_instance = nullptr;
|
DoughUI *DoughUI::_instance = nullptr;
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the DoughUI singleton.
|
|
||||||
*/
|
|
||||||
DoughUI *DoughUI::Instance()
|
DoughUI *DoughUI::Instance()
|
||||||
{
|
{
|
||||||
if (DoughUI::_instance == nullptr)
|
if (DoughUI::_instance == nullptr)
|
||||||
|
@ -21,9 +22,10 @@ DoughUI::DoughUI() : onoffButton(ONOFF_BUTTON_PIN),
|
||||||
led2(LED2_PIN),
|
led2(LED2_PIN),
|
||||||
led3(LED3_PIN) {}
|
led3(LED3_PIN) {}
|
||||||
|
|
||||||
/**
|
// ----------------------------------------------------------------------
|
||||||
* Called from the main setup() function of the sketch.
|
// Setup
|
||||||
*/
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
void DoughUI::setup()
|
void DoughUI::setup()
|
||||||
{
|
{
|
||||||
// Setup the serial port, used for logging.
|
// Setup the serial port, used for logging.
|
||||||
|
@ -54,7 +56,7 @@ void DoughUI::setup()
|
||||||
_setupTimerInterrupt();
|
_setupTimerInterrupt();
|
||||||
|
|
||||||
// Notify the user that we're on a roll!
|
// Notify the user that we're on a roll!
|
||||||
flash_all_leds();
|
_flash_all_leds();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoughUI::onoffButtonISR()
|
void DoughUI::onoffButtonISR()
|
||||||
|
@ -67,58 +69,6 @@ void DoughUI::setupButtonISR()
|
||||||
DoughUI::Instance()->setupButton.handleButtonState();
|
DoughUI::Instance()->setupButton.handleButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message to the serial interface.
|
|
||||||
*/
|
|
||||||
void DoughUI::log(const char *category, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
char buf[12];
|
|
||||||
snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%8s | ", category);
|
|
||||||
Serial.print(buf);
|
|
||||||
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
|
|
||||||
while (*fmt != '\0')
|
|
||||||
{
|
|
||||||
if (*fmt == 'i')
|
|
||||||
{
|
|
||||||
int i = va_arg(args, int);
|
|
||||||
Serial.print(i);
|
|
||||||
}
|
|
||||||
else if (*fmt == 'f')
|
|
||||||
{
|
|
||||||
float f = va_arg(args, double);
|
|
||||||
Serial.print(f);
|
|
||||||
}
|
|
||||||
else if (*fmt == 'a')
|
|
||||||
{
|
|
||||||
IPAddress a = va_arg(args, IPAddress);
|
|
||||||
Serial.print(a);
|
|
||||||
}
|
|
||||||
else if (*fmt == 's')
|
|
||||||
{
|
|
||||||
const char *s = va_arg(args, const char *);
|
|
||||||
Serial.print(s);
|
|
||||||
}
|
|
||||||
else if (*fmt == 'S')
|
|
||||||
{
|
|
||||||
String S = va_arg(args, String);
|
|
||||||
Serial.print(S);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.print("<log(): invalid format char '");
|
|
||||||
Serial.print(*fmt);
|
|
||||||
Serial.print("'>");
|
|
||||||
}
|
|
||||||
fmt++;
|
|
||||||
}
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
Serial.println("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup a timer interrupt for updating the GUI. Unfortunately, the standard
|
* 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
|
* libraries that I can find for this, are not equipped to work for the
|
||||||
|
@ -159,15 +109,9 @@ void DoughUI::_setupTimerInterrupt()
|
||||||
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
|
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoughUI::resume()
|
// ----------------------------------------------------------------------
|
||||||
{
|
// Loop
|
||||||
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
|
// ----------------------------------------------------------------------
|
||||||
}
|
|
||||||
|
|
||||||
void DoughUI::suspend()
|
|
||||||
{
|
|
||||||
NVIC_DisableIRQ(TC4_IRQn); // Disable TC4 interrupts
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This callback is called when the TC4 timer hits an overflow interrupt.
|
* This callback is called when the TC4 timer hits an overflow interrupt.
|
||||||
|
@ -178,6 +122,24 @@ void TC4_Handler()
|
||||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
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.
|
* Fire pending button events.
|
||||||
*/
|
*/
|
||||||
|
@ -199,7 +161,7 @@ void DoughUI::clearButtonEvents()
|
||||||
/**
|
/**
|
||||||
* Update the state of all the LEDs in the system.
|
* Update the state of all the LEDs in the system.
|
||||||
* This method is called both sync by methods in this class and async by
|
* This method is called both sync by methods in this class and async by
|
||||||
* the timer interrupt code from above. The timer interrupt based invocatino
|
* the timer interrupt code from above. The timer interrupt based invocation
|
||||||
* makes it possible to do LED updates, while the device is busy doing
|
* makes it possible to do LED updates, while the device is busy doing
|
||||||
* something else.
|
* something else.
|
||||||
*/
|
*/
|
||||||
|
@ -214,7 +176,7 @@ void DoughUI::updatedLEDs()
|
||||||
/**
|
/**
|
||||||
* Flash all LEDs, one at a time.
|
* Flash all LEDs, one at a time.
|
||||||
*/
|
*/
|
||||||
void DoughUI::flash_all_leds()
|
void DoughUI::_flash_all_leds()
|
||||||
{
|
{
|
||||||
ledBuiltin.on();
|
ledBuiltin.on();
|
||||||
delay(100);
|
delay(100);
|
||||||
|
|
|
@ -11,12 +11,15 @@
|
||||||
#undef LOG_WAIT_SERIAL
|
#undef LOG_WAIT_SERIAL
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFiNINA.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "UI/DoughButton.h"
|
#include "UI/DoughButton.h"
|
||||||
#include "UI/DoughLED.h"
|
#include "UI/DoughLED.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class groups all user interface functionality: serial logging,
|
||||||
|
* LEDs and buttons.
|
||||||
|
*/
|
||||||
class DoughUI
|
class DoughUI
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -33,15 +36,14 @@ public:
|
||||||
void processButtonEvents();
|
void processButtonEvents();
|
||||||
void clearButtonEvents();
|
void clearButtonEvents();
|
||||||
void updatedLEDs();
|
void updatedLEDs();
|
||||||
void flash_all_leds();
|
|
||||||
void resume();
|
void resume();
|
||||||
void suspend();
|
void suspend();
|
||||||
void log(const char *category, const char *fmt, ...);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DoughUI();
|
DoughUI();
|
||||||
void _setupTimerInterrupt();
|
void _setupTimerInterrupt();
|
||||||
static DoughUI *_instance;
|
static DoughUI *_instance;
|
||||||
|
void _flash_all_leds();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
21
src/main.cpp
21
src/main.cpp
|
@ -7,6 +7,7 @@
|
||||||
// TODO: use longer term averages for data
|
// TODO: use longer term averages for data
|
||||||
|
|
||||||
DoughBoyState state = CONFIGURING;
|
DoughBoyState state = CONFIGURING;
|
||||||
|
auto logger = DoughLogger("MAIN");
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
@ -18,7 +19,7 @@ void setup()
|
||||||
ui->setup();
|
ui->setup();
|
||||||
ui->onoffButton.onPress(handleOnoffButtonPress);
|
ui->onoffButton.onPress(handleOnoffButtonPress);
|
||||||
ui->setupButton.onPress(handleSetupButtonPress);
|
ui->setupButton.onPress(handleSetupButtonPress);
|
||||||
ui->log("MAIN", "s", "Initialization completed, starting device");
|
logger.log("s", "Initialization completed, starting device");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
|
@ -76,11 +77,11 @@ bool setupNetworkConnection()
|
||||||
{
|
{
|
||||||
if (connectionState == CONNECTED)
|
if (connectionState == CONNECTED)
|
||||||
{
|
{
|
||||||
ui->log("MAIN", "s", "ERROR - Connection to WiFi network lost! Reconnecting ...");
|
logger.log("s", "ERROR - Connection to WiFi network lost! Reconnecting ...");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->log("MAIN", "s", "Connecting to the WiFi network ...");
|
logger.log("s", "Connecting to the WiFi network ...");
|
||||||
}
|
}
|
||||||
connectionState = CONNECTING_WIFI;
|
connectionState = CONNECTING_WIFI;
|
||||||
ui->led1.blink()->slow();
|
ui->led1.blink()->slow();
|
||||||
|
@ -92,11 +93,11 @@ bool setupNetworkConnection()
|
||||||
{
|
{
|
||||||
if (connectionState == CONNECTED)
|
if (connectionState == CONNECTED)
|
||||||
{
|
{
|
||||||
ui->log("MAIN", "s", "ERROR - Connection to the MQTT broker lost! Reconnecting ...");
|
logger.log("s", "ERROR - Connection to the MQTT broker lost! Reconnecting ...");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->log("MAIN", "s", "Connecting to the MQTT broker ...");
|
logger.log("s", "Connecting to the MQTT broker ...");
|
||||||
}
|
}
|
||||||
connectionState = CONNECTING_MQTT;
|
connectionState = CONNECTING_MQTT;
|
||||||
ui->led1.blink()->fast();
|
ui->led1.blink()->fast();
|
||||||
|
@ -108,7 +109,7 @@ bool setupNetworkConnection()
|
||||||
{
|
{
|
||||||
if (connectionState != CONNECTED)
|
if (connectionState != CONNECTED)
|
||||||
{
|
{
|
||||||
ui->log("MAIN", "s", "Connection to MQTT broker established");
|
logger.log("s", "Connection to MQTT broker established");
|
||||||
ui->led1.on();
|
ui->led1.on();
|
||||||
ui->led2.off();
|
ui->led2.off();
|
||||||
ui->led3.off();
|
ui->led3.off();
|
||||||
|
@ -141,7 +142,7 @@ void handleSetupButtonPress()
|
||||||
void setStateToConfiguring()
|
void setStateToConfiguring()
|
||||||
{
|
{
|
||||||
auto ui = DoughUI::Instance();
|
auto ui = DoughUI::Instance();
|
||||||
ui->log("MAIN", "s", "Waiting for configuration ...");
|
logger.log("s", "Waiting for configuration ...");
|
||||||
state = CONFIGURING;
|
state = CONFIGURING;
|
||||||
ui->led1.on();
|
ui->led1.on();
|
||||||
ui->led2.blink()->fast();
|
ui->led2.blink()->fast();
|
||||||
|
@ -152,7 +153,7 @@ void setStateToConfiguring()
|
||||||
void setStateToMeasuring()
|
void setStateToMeasuring()
|
||||||
{
|
{
|
||||||
auto ui = DoughUI::Instance();
|
auto ui = DoughUI::Instance();
|
||||||
ui->log("MAIN", "s", "Starting measurements");
|
logger.log("s", "Starting measurements");
|
||||||
state = MEASURING;
|
state = MEASURING;
|
||||||
ui->led1.on();
|
ui->led1.on();
|
||||||
ui->led2.on();
|
ui->led2.on();
|
||||||
|
@ -163,7 +164,7 @@ void setStateToMeasuring()
|
||||||
void setStateToPaused()
|
void setStateToPaused()
|
||||||
{
|
{
|
||||||
auto ui = DoughUI::Instance();
|
auto ui = DoughUI::Instance();
|
||||||
ui->log("MAIN", "s", "Pausing measurements");
|
logger.log("s", "Pausing measurements");
|
||||||
state = PAUSED;
|
state = PAUSED;
|
||||||
ui->led1.on();
|
ui->led1.on();
|
||||||
ui->led2.on();
|
ui->led2.on();
|
||||||
|
@ -174,7 +175,7 @@ void setStateToPaused()
|
||||||
void setStateToCalibrating()
|
void setStateToCalibrating()
|
||||||
{
|
{
|
||||||
auto ui = DoughUI::Instance();
|
auto ui = DoughUI::Instance();
|
||||||
ui->log("MAIN", "s", "Requested device calibration");
|
logger.log("s", "Requested device calibration");
|
||||||
state = CALIBRATING;
|
state = CALIBRATING;
|
||||||
ui->led1.on();
|
ui->led1.on();
|
||||||
ui->led2.blink()->slow();
|
ui->led2.blink()->slow();
|
||||||
|
|
Loading…
Reference in New Issue