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:
Maurice Makaay 2020-07-11 02:13:36 +02:00
parent e66b8ccdd5
commit 10f8fc2290
20 changed files with 306 additions and 288 deletions

View File

@ -1,5 +1,4 @@
#include "Data/DataController.h"
#include "Data/Measurements.h"
// ----------------------------------------------------------------------
// Constructor
@ -21,7 +20,13 @@ DataController *DataController::Instance()
DataController::DataController() : _temperatureMeasurements(TEMPERATURE_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
@ -50,14 +55,10 @@ void DataController::handleMqttMessage(String &key, String &payload)
}
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()
{
return _containerHeightSet;
@ -73,21 +74,19 @@ void DataController::setContainerHeight(int height)
_containerHeightSet = false;
if (height <= HCSR04_MIN_MM)
{
DoughUI::Instance()->log("DATA", "sisis",
"ERROR - Container height ", height,
_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)
{
DoughUI::Instance()->log("DATA", "sisis",
"ERROR - Container height ", height,
_logger.log("sisis", "ERROR - Container height ", height,
"mm is more than the maximum measuring distance of ",
HCSR04_MAX_MM, "mm");
return;
}
DoughUI::Instance()->log("DATA", "sis", "Set container height to ", height, "mm");
_logger.log("sis", "Set container height to ", height, "mm");
_containerHeight = height;
_containerHeightSet = true;
}
@ -122,38 +121,36 @@ void DataController::_sample()
if (tick)
{
DoughUI *ui = DoughUI::Instance();
_lastSample = now;
DoughSensors *sensors = DoughSensors::Instance();
// 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();
_ui->led3.off();
delay(50);
ui->led3.on();
_ui->led3.on();
// Suspend the UI timer interrupts, to not let these interfere
// with the sensor measurements.
ui->suspend();
_ui->suspend();
// Take a sample.
switch (_sampleType)
{
case SAMPLE_TEMPERATURE:
_temperatureMeasurements.add(sensors->readTemperature());
_temperatureMeasurements.add(_sensors->readTemperature());
_sampleType = SAMPLE_HUMIDITY;
break;
case SAMPLE_HUMIDITY:
_humidityMeasurements.add(sensors->readHumidity());
_humidityMeasurements.add(_sensors->readHumidity());
_sampleType = SAMPLE_DISTANCE;
break;
case SAMPLE_DISTANCE:
_distanceMeasurements.add(sensors->readDistance());
_distanceMeasurements.add(_sensors->readDistance());
break;
}
ui->resume();
_ui->resume();
_sampleCounter++;
if (_sampleCounter == SAMPLE_CYCLE_LENGTH)
@ -166,77 +163,17 @@ void DataController::_sample()
void DataController::_publish()
{
static unsigned long lastSample = 0;
if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL)
if (_lastPublish == 0 || millis() - _lastPublish > PUBLISH_INTERVAL)
{
lastSample = millis();
_lastPublish = millis();
DoughUI *ui = DoughUI::Instance();
DoughMQTT *mqtt = DoughMQTT::Instance();
_mqtt->publish("temperature", _temperatureMeasurements.getLast());
_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");
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();
_ui->led1.dip()->fast();
}
}

View File

@ -27,6 +27,7 @@
#include "Network/DoughWiFi.h"
#include "Network/DoughMQTT.h"
#include "UI/DoughUI.h"
#include "UI/DoughLogger.h"
typedef enum
{
@ -36,36 +37,40 @@ typedef enum
} DoughSampleType;
/**
* The DoughData class is responsible for holding the device configuration,
* collecting measurements from sensors, gathering the statistics on these data,
* and publishing results to the MQTT broker.
* 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();
static DataController* Instance();
void setup();
void loop();
void clearHistory();
void setContainerHeight(int height);
bool isConfigured();
static void handleMqttConnect(DoughMQTT *mqtt);
static void handleMqttConnect(DoughMQTT* mqtt);
static void handleMqttMessage(String &key, String &value);
private:
DataController();
static DataController *_instance;
DoughSensors *_sensors;
static DataController* _instance;
Measurements _temperatureMeasurements;
Measurements _humidityMeasurements;
Measurements _distanceMeasurements;
DoughLogger _logger;
DoughUI* _ui;
DoughSensors* _sensors;
DoughMQTT* _mqtt;
unsigned long _lastSample = 0;
unsigned long _lastPublish = 0;
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
int _sampleCounter = 0;
int _containerHeight;
bool _containerHeightSet;
void _sample();
void _publish();
Measurements _temperatureMeasurements;
Measurements _humidityMeasurements;
Measurements _distanceMeasurements;
};
#endif

View File

@ -1,19 +1,17 @@
#include "Data/Measurement.h"
Measurement::Measurement() {}
Measurement::Measurement() { }
Measurement::Measurement(bool ok, int value)
Measurement Measurement::Failed()
{
this->ok = ok;
this->value = value;
Measurement m;
return m;
}
Measurement *Measurement::Failed()
Measurement Measurement::Value(int value)
{
return new Measurement(false, 0);
}
Measurement *Measurement::Ok(int value)
{
return new Measurement(true, value);
Measurement m;
m.ok = true;
m.value = value;
return m;
}

View File

@ -2,17 +2,17 @@
#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
{
public:
Measurement();
Measurement(bool ok, int value);
int value = 0;
bool ok = false;
static Measurement *Failed();
static Measurement *Ok(int value);
static Measurement Failed();
static Measurement Value(int value);
};
#endif

View File

@ -1,26 +1,27 @@
#include "Data/DataController.h"
#include "Data/Measurement.h"
typedef Measurement *MeasurementPtr;
#include "Data/Measurements.h"
#include "UI/DoughUI.h"
Measurements::Measurements(unsigned int avgLookback)
{
_storageSize = avgLookback;
_storage = new MeasurementPtr[avgLookback];
for (unsigned int i = 0; i < avgLookback; i++)
{
_storage[i] = nullptr;
_storage = new Measurement*[avgLookback];
for (unsigned int i = 0; i < avgLookback; i++) {
_storage[i] = new Measurement;
}
clearHistory();
}
void Measurements::add(Measurement *measurement)
void Measurements::add(Measurement measurement)
{
auto index = _next();
_storage[index] = measurement;
if (measurement->ok)
unsigned int index = _next();
_storage[index]->ok = measurement.ok;
_storage[index]->value = measurement.value;
if (measurement.ok)
{
_averageCount++;
_averageSum += measurement->value;
_averageSum += measurement.value;
}
}
@ -31,26 +32,28 @@ unsigned int Measurements::_next()
{
_index = 0;
}
if (_storage[_index] != nullptr && _storage[_index]->ok)
if (_storage[_index]->ok)
{
_averageSum -= _storage[_index]->value;
_averageCount--;
}
_storage[_index]->ok = false;
_storage[_index]->value = 0;
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)
{
result->ok = true;
result->value = round(_averageSum / _averageCount);
result.ok = true;
result.value = round(_averageSum / _averageCount);
}
return result;
}

View File

@ -1,23 +1,24 @@
#ifndef DOUGH_DATA_MEASUREMENTS_H
#define DOUGH_DATA_MEASUREMENTS_H
#include <Arduino.h>
#include "Data/Measurement.h"
/**
* The DoughDataMeasurements class is used to store measurements for a sensor
* and to keep track of running totals for handling average computations.
* This class is used to store measurements for a sensor and to keep
* track of running totals for handling average computations.
*/
class Measurements
{
public:
Measurements(unsigned int avgLookback);
void add(Measurement *measurement);
Measurement *getLast();
Measurement *getAverage();
void add(Measurement measurement);
Measurement getLast();
Measurement getAverage();
void clearHistory();
private:
Measurement **_storage;
Measurement** _storage;
unsigned int _storageSize;
int _averageSum = 0;
unsigned int _averageCount = 0;

View File

@ -4,12 +4,12 @@
// Constructor
// ----------------------------------------------------------------------
DoughMQTT *DoughMQTT::_instance = nullptr;
DoughMQTT* DoughMQTT::_instance = nullptr;
/**
* Fetch the DoughMQTT singleton.
*/
DoughMQTT *DoughMQTT::Instance()
DoughMQTT* DoughMQTT::Instance()
{
if (DoughMQTT::_instance == nullptr)
{
@ -18,10 +18,7 @@ DoughMQTT *DoughMQTT::Instance()
return DoughMQTT::_instance;
}
DoughMQTT::DoughMQTT()
{
_ui = DoughUI::Instance();
}
DoughMQTT::DoughMQTT() : _logger("MQTT") { }
// ----------------------------------------------------------------------
// Setup
@ -29,14 +26,14 @@ DoughMQTT::DoughMQTT()
void DoughMQTT::setup()
{
DoughWiFi *network = DoughWiFi::Instance();
DoughWiFi* network = DoughWiFi::Instance();
#ifdef MQTT_DEVICE_ID
_mqttDeviceId = MQTT_DEVICE_ID;
#else
_mqttDeviceId = network->getMacAddress();
#endif
_ui->log("MQTT", "ss", "Device ID = ", _mqttDeviceId);
_logger.log("ss", "Device ID = ", _mqttDeviceId);
_mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client);
}
@ -62,13 +59,13 @@ bool DoughMQTT::isConnected()
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);
// Check if the connection to the broker was successful.
if (!_mqttClient.connected())
{
_ui->log("MQTT", "s", "ERROR - Connection to broker failed");
_logger.log("s", "ERROR - Connection to broker failed");
return false;
}
@ -89,9 +86,9 @@ void DoughMQTT::procesIncomingsMessages()
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)
{
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];
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);
}
void DoughMQTT::publish(const char *key, const char *payload)
void DoughMQTT::publish(const char* key, const char* payload)
{
char topic[200];
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);
}
@ -125,3 +122,11 @@ void DoughMQTT::publish(const char *key, int payload)
snprintf(buf, 16, "%d", payload);
publish(key, buf);
}
void DoughMQTT::publish(const char *key, Measurement measurement) {
if (measurement.ok) {
publish(key, measurement.value);
} else {
publish(key, "null");
}
}

View File

@ -4,37 +4,43 @@
#include <MQTT.h>
#include <MQTTClient.h>
#include "Network/DoughWiFi.h"
#include "UI/DoughUI.h"
#include "Data/Measurement.h"
#include "UI/DoughLogger.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;
typedef void (*DoughMQTTConnectHandler)(DoughMQTT *mqtt);
typedef void (*DoughMQTTConnectHandler)(DoughMQTT* mqtt);
typedef void (*DoughMQTTMessageHandler)(String &key, String &value);
class DoughMQTT
{
public:
static DoughMQTT *Instance();
static DoughMQTT* Instance();
void setup();
void onConnect(DoughMQTTConnectHandler callback);
void onMessage(DoughMQTTMessageHandler callback);
bool isConnected();
bool connect();
void subscribe(const char *key);
void subscribe(const char* key);
void procesIncomingsMessages();
void publish(const char *key, const char *payload);
void publish(const char *key, int payload);
void publish(const char* key, const char* payload);
void publish(const char* key, int payload);
void publish(const char* key, Measurement measurement);
private:
DoughMQTT();
static DoughMQTT *_instance;
static DoughMQTT* _instance;
MQTTClient _mqttClient;
DoughUI *_ui;
DoughLogger _logger;
DoughMQTTConnectHandler _onConnect = nullptr;
MQTTClientCallbackSimple _onMessage = nullptr;
static void handleMessage(String &topic, String &payload);
char *_mqttDeviceId;
char* _mqttDeviceId;
};
#endif

View File

@ -18,10 +18,7 @@ DoughWiFi *DoughWiFi::Instance()
return DoughWiFi::_instance;
}
DoughWiFi::DoughWiFi()
{
_ui = DoughUI::Instance();
}
DoughWiFi::DoughWiFi() : _logger("WIFI") {}
// ----------------------------------------------------------------------
// Setup
@ -39,7 +36,7 @@ void DoughWiFi::_setMacAddress()
void DoughWiFi::setup()
{
_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.
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);
return false;
}
@ -70,19 +67,19 @@ bool DoughWiFi::connect()
}
// 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);
// Check if the connection attempt was successful.
if (status == WL_CONNECTED)
{
_ui->log("NETWORK", "sa", "IP-Address = ", WiFi.localIP());
_ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm");
_logger.log("sa", "IP-Address = ", WiFi.localIP());
_logger.log("sis", "Signal strength = ", WiFi.RSSI(), " dBm");
return true;
}
else
{
_ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
_logger.log("sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
return false;
}
}

View File

@ -2,14 +2,17 @@
#define DOUGH_NETWORK_H
#include <WiFiNINA.h>
#include "UI/DoughUI.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();
static DoughWiFi* Instance();
char* getMacAddress();
void setup();
void loop();
bool isConnected();
@ -18,10 +21,10 @@ public:
private:
DoughWiFi();
static DoughWiFi *_instance;
static DoughWiFi* _instance;
void _setMacAddress();
char _macAddress[18]; // max MAC address length + 1
DoughUI *_ui;
DoughLogger _logger;
};
#endif

View File

@ -16,8 +16,7 @@ DoughSensors* DoughSensors::Instance() {
return DoughSensors::_instance;
}
DoughSensors::DoughSensors() {
_ui = DoughUI::Instance();
DoughSensors::DoughSensors() : _logger("SENSORS") {
_dht = new DHT(DHT11_DATA_PIN, DHT11);
_hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
}
@ -35,38 +34,38 @@ void DoughSensors::setup() {
// loop
// ----------------------------------------------------------------------
Measurement* DoughSensors::readTemperature() {
Measurement DoughSensors::readTemperature() {
float t = _dht->readTemperature();
if (isnan(t)) {
_ui->log("SENSORS", "s", "ERROR - Temperature measurement failed");
_logger.log("s", "ERROR - Temperature measurement failed");
return Measurement::Failed();
} else {
_logger.log("sis", "Temperature = ", int(t), "°C ");
_hcsr04->setTemperature(int(t));
auto m = new Measurement(true, int(t));
_ui->log("SENSORS", "sisi", "Temperature = ", int(t), "°C ", m->value);
auto m = Measurement::Value(int(t));
return m;
}
}
Measurement* DoughSensors::readHumidity() {
Measurement DoughSensors::readHumidity() {
int h = _dht->readHumidity();
if (h == 0) {
_ui->log("SENSORS", "s", "ERROR - Humidity measurement failed");
_logger.log("s", "ERROR - Humidity measurement failed");
return Measurement::Failed();
} else {
_logger.log("sis", "Humidity = ", h, "%");
_hcsr04->setHumidity(h);
_ui->log("SENSORS", "sis", "Humidity = ", h, "%");
return Measurement::Ok(h);
return Measurement::Value(h);
}
}
Measurement* DoughSensors::readDistance() {
Measurement DoughSensors::readDistance() {
int d = _hcsr04->readDistance();
if (d == -1) {
_ui->log("SENSORS", "s", "ERROR - Distance measurement failed");
_logger.log("s", "ERROR - Distance measurement failed");
return Measurement::Failed();
} else {
_ui->log("SENSORS", "sis", "Distance = ", d, "mm");
return Measurement::Ok(d);
_logger.log("sis", "Distance = ", d, "mm");
return Measurement::Value(d);
}
}

View File

@ -3,22 +3,25 @@
#include <DHT.h>
#include "Sensors/HCSR04.h"
#include "UI/DoughUI.h"
#include "UI/DoughLogger.h"
#include "Data/Measurement.h"
#include "config.h"
/**
* This class provides access to the sensors in the device.
*/
class DoughSensors {
public:
static DoughSensors* Instance();
void setup();
Measurement* readTemperature();
Measurement* readHumidity();
Measurement* readDistance();
Measurement readTemperature();
Measurement readHumidity();
Measurement readDistance();
private:
DoughSensors();
static DoughSensors* _instance;
DoughUI *_ui;
DoughLogger _logger;
DHT* _dht;
HCSR04* _hcsr04;
};

View File

@ -28,6 +28,9 @@
#include <Arduino.h>
#include "config.h"
/**
* This class is used to get a distance reading from an HCSR04 sensor.
*/
class HCSR04 {
public:
HCSR04(int triggerPin, int echoPin);

View File

@ -18,6 +18,15 @@ typedef enum
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:

View File

@ -19,6 +19,10 @@ typedef enum
PULSE
} DoughLEDState;
/**
* This class provides a set of basic LED lighting patterns, which can
* be used in an async way.
*/
class DoughLED
{
public:

55
src/UI/DoughLogger.cpp Normal file
View File

@ -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("");
}

25
src/UI/DoughLogger.h Normal file
View File

@ -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

View File

@ -1,10 +1,11 @@
#include "DoughUI.h"
// ----------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------
DoughUI *DoughUI::_instance = nullptr;
/**
* Fetch the DoughUI singleton.
*/
DoughUI *DoughUI::Instance()
{
if (DoughUI::_instance == nullptr)
@ -21,9 +22,10 @@ DoughUI::DoughUI() : onoffButton(ONOFF_BUTTON_PIN),
led2(LED2_PIN),
led3(LED3_PIN) {}
/**
* Called from the main setup() function of the sketch.
*/
// ----------------------------------------------------------------------
// Setup
// ----------------------------------------------------------------------
void DoughUI::setup()
{
// Setup the serial port, used for logging.
@ -54,7 +56,7 @@ void DoughUI::setup()
_setupTimerInterrupt();
// Notify the user that we're on a roll!
flash_all_leds();
_flash_all_leds();
}
void DoughUI::onoffButtonISR()
@ -67,58 +69,6 @@ void DoughUI::setupButtonISR()
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
* 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
}
void DoughUI::resume()
{
NVIC_EnableIRQ(TC4_IRQn); // Enable TC4 interrupts
}
void DoughUI::suspend()
{
NVIC_DisableIRQ(TC4_IRQn); // Disable TC4 interrupts
}
// ----------------------------------------------------------------------
// Loop
// ----------------------------------------------------------------------
/**
* 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.
}
/**
* 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.
*/
@ -199,7 +161,7 @@ void DoughUI::clearButtonEvents()
/**
* 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 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
* something else.
*/
@ -214,7 +176,7 @@ void DoughUI::updatedLEDs()
/**
* Flash all LEDs, one at a time.
*/
void DoughUI::flash_all_leds()
void DoughUI::_flash_all_leds()
{
ledBuiltin.on();
delay(100);

View File

@ -11,12 +11,15 @@
#undef LOG_WAIT_SERIAL
#include <Arduino.h>
#include <WiFiNINA.h>
#include <stdarg.h>
#include "UI/DoughButton.h"
#include "UI/DoughLED.h"
#include "config.h"
/**
* This class groups all user interface functionality: serial logging,
* LEDs and buttons.
*/
class DoughUI
{
public:
@ -33,15 +36,14 @@ public:
void processButtonEvents();
void clearButtonEvents();
void updatedLEDs();
void flash_all_leds();
void resume();
void suspend();
void log(const char *category, const char *fmt, ...);
private:
DoughUI();
void _setupTimerInterrupt();
static DoughUI *_instance;
void _flash_all_leds();
};
#endif

View File

@ -7,6 +7,7 @@
// TODO: use longer term averages for data
DoughBoyState state = CONFIGURING;
auto logger = DoughLogger("MAIN");
void setup()
{
@ -18,7 +19,7 @@ void setup()
ui->setup();
ui->onoffButton.onPress(handleOnoffButtonPress);
ui->setupButton.onPress(handleSetupButtonPress);
ui->log("MAIN", "s", "Initialization completed, starting device");
logger.log("s", "Initialization completed, starting device");
}
void loop()
@ -76,11 +77,11 @@ bool setupNetworkConnection()
{
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
{
ui->log("MAIN", "s", "Connecting to the WiFi network ...");
logger.log("s", "Connecting to the WiFi network ...");
}
connectionState = CONNECTING_WIFI;
ui->led1.blink()->slow();
@ -92,11 +93,11 @@ bool setupNetworkConnection()
{
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
{
ui->log("MAIN", "s", "Connecting to the MQTT broker ...");
logger.log("s", "Connecting to the MQTT broker ...");
}
connectionState = CONNECTING_MQTT;
ui->led1.blink()->fast();
@ -108,7 +109,7 @@ bool setupNetworkConnection()
{
if (connectionState != CONNECTED)
{
ui->log("MAIN", "s", "Connection to MQTT broker established");
logger.log("s", "Connection to MQTT broker established");
ui->led1.on();
ui->led2.off();
ui->led3.off();
@ -141,7 +142,7 @@ void handleSetupButtonPress()
void setStateToConfiguring()
{
auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Waiting for configuration ...");
logger.log("s", "Waiting for configuration ...");
state = CONFIGURING;
ui->led1.on();
ui->led2.blink()->fast();
@ -152,7 +153,7 @@ void setStateToConfiguring()
void setStateToMeasuring()
{
auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Starting measurements");
logger.log("s", "Starting measurements");
state = MEASURING;
ui->led1.on();
ui->led2.on();
@ -163,7 +164,7 @@ void setStateToMeasuring()
void setStateToPaused()
{
auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Pausing measurements");
logger.log("s", "Pausing measurements");
state = PAUSED;
ui->led1.on();
ui->led2.on();
@ -174,7 +175,7 @@ void setStateToPaused()
void setStateToCalibrating()
{
auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Requested device calibration");
logger.log("s", "Requested device calibration");
state = CALIBRATING;
ui->led1.on();
ui->led2.blink()->slow();