Code formatting and structuring the project.
This commit is contained in:
parent
ea8723493c
commit
e66b8ccdd5
BIN
Schematics.fzz
BIN
Schematics.fzz
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 330 KiB |
|
@ -0,0 +1,242 @@
|
|||
#include "Data/DataController.h"
|
||||
#include "Data/Measurements.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DataController *DataController::_instance = nullptr;
|
||||
|
||||
/**
|
||||
* Fetch the DoughData singleton.
|
||||
*/
|
||||
DataController *DataController::Instance()
|
||||
{
|
||||
if (DataController::_instance == nullptr)
|
||||
{
|
||||
DataController::_instance = new DataController();
|
||||
}
|
||||
return DataController::_instance;
|
||||
}
|
||||
|
||||
DataController::DataController() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK),
|
||||
_humidityMeasurements(HUMIDITY_AVG_LOOKBACK),
|
||||
_distanceMeasurements(DISTANCE_AVG_LOOKBACK) {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DataController::setup()
|
||||
{
|
||||
_containerHeight = 0.00;
|
||||
_containerHeightSet = false;
|
||||
|
||||
DoughMQTT *mqtt = DoughMQTT::Instance();
|
||||
mqtt->onConnect(DataController::handleMqttConnect);
|
||||
mqtt->onMessage(DataController::handleMqttMessage);
|
||||
}
|
||||
|
||||
void DataController::handleMqttConnect(DoughMQTT *mqtt)
|
||||
{
|
||||
mqtt->subscribe("container_height");
|
||||
}
|
||||
|
||||
void DataController::handleMqttMessage(String &key, String &payload)
|
||||
{
|
||||
if (key.equals("container_height"))
|
||||
{
|
||||
DataController::Instance()->setContainerHeight(payload.toInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
DoughUI::Instance()->log("DATA", "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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
DoughUI::Instance()->log("DATA", "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,
|
||||
"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");
|
||||
_containerHeight = height;
|
||||
_containerHeightSet = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DataController::loop()
|
||||
{
|
||||
if (isConfigured())
|
||||
{
|
||||
_sample();
|
||||
_publish();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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();
|
||||
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.add(sensors->readTemperature());
|
||||
_sampleType = SAMPLE_HUMIDITY;
|
||||
break;
|
||||
case SAMPLE_HUMIDITY:
|
||||
_humidityMeasurements.add(sensors->readHumidity());
|
||||
_sampleType = SAMPLE_DISTANCE;
|
||||
break;
|
||||
case SAMPLE_DISTANCE:
|
||||
_distanceMeasurements.add(sensors->readDistance());
|
||||
break;
|
||||
}
|
||||
|
||||
ui->resume();
|
||||
|
||||
_sampleCounter++;
|
||||
if (_sampleCounter == SAMPLE_CYCLE_LENGTH)
|
||||
{
|
||||
_sampleCounter = 0;
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataController::_publish()
|
||||
{
|
||||
static unsigned long lastSample = 0;
|
||||
if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL)
|
||||
{
|
||||
lastSample = millis();
|
||||
|
||||
DoughUI *ui = DoughUI::Instance();
|
||||
DoughMQTT *mqtt = DoughMQTT::Instance();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef DOUGH_DATA_DATACONTROLLER_H
|
||||
#define DOUGH_DATA_DATACONTROLLER_H
|
||||
|
||||
// These definitions describes what measurements are performed in sequence.
|
||||
// One measurement is done every SAMPLE_INTERVAL microseconds.
|
||||
// We always start with a temperature measurement, then a humidity measurement,
|
||||
// and finally a number of distance measurements.
|
||||
// The SAMPLE_CYCLE_LENGTH defines the total number of samples in this sequence.
|
||||
#define SAMPLE_INTERVAL 1000
|
||||
#define SAMPLE_CYCLE_LENGTH 30 // 1 temperature + 1 humidity + 28 distance samples
|
||||
|
||||
// Two different values are published per sensor: a recent value and an average
|
||||
// value. These definition define the number of measurements to include in the
|
||||
// average computation.
|
||||
#define TEMPERATURE_AVG_LOOKBACK 10 // making this a 5 minute average
|
||||
#define HUMIDITY_AVG_LOOKBACK 10 // making this a 5 minute average
|
||||
#define DISTANCE_AVG_LOOKBACK 28 * 2 * 5 // making this a 5 minute average
|
||||
|
||||
// The minimal interval at which to publish measurements to the MQTT broker.
|
||||
// When significant changes occur in the measurements, then these will be published
|
||||
// to the MQTT broker at all times, independent from this interval.
|
||||
#define PUBLISH_INTERVAL 4000
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Data/Measurements.h"
|
||||
#include "Sensors/DoughSensors.h"
|
||||
#include "Network/DoughWiFi.h"
|
||||
#include "Network/DoughMQTT.h"
|
||||
#include "UI/DoughUI.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SAMPLE_TEMPERATURE,
|
||||
SAMPLE_HUMIDITY,
|
||||
SAMPLE_DISTANCE
|
||||
} 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.
|
||||
*/
|
||||
class DataController
|
||||
{
|
||||
public:
|
||||
static DataController *Instance();
|
||||
void setup();
|
||||
void loop();
|
||||
void clearHistory();
|
||||
void setContainerHeight(int height);
|
||||
bool isConfigured();
|
||||
static void handleMqttConnect(DoughMQTT *mqtt);
|
||||
static void handleMqttMessage(String &key, String &value);
|
||||
|
||||
private:
|
||||
DataController();
|
||||
static DataController *_instance;
|
||||
DoughSensors *_sensors;
|
||||
unsigned long _lastSample = 0;
|
||||
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
|
||||
int _sampleCounter = 0;
|
||||
int _containerHeight;
|
||||
bool _containerHeightSet;
|
||||
void _sample();
|
||||
void _publish();
|
||||
Measurements _temperatureMeasurements;
|
||||
Measurements _humidityMeasurements;
|
||||
Measurements _distanceMeasurements;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,19 @@
|
|||
#include "Data/Measurement.h"
|
||||
|
||||
Measurement::Measurement() {}
|
||||
|
||||
Measurement::Measurement(bool ok, int value)
|
||||
{
|
||||
this->ok = ok;
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
Measurement *Measurement::Failed()
|
||||
{
|
||||
return new Measurement(false, 0);
|
||||
}
|
||||
|
||||
Measurement *Measurement::Ok(int value)
|
||||
{
|
||||
return new Measurement(true, value);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef DOUGH_DATA_MEASUREMENT_H
|
||||
#define DOUGH_DATA_MEASUREMENT_H
|
||||
|
||||
/**
|
||||
* The DoughDataMeasurement class represents a single measurement.
|
||||
*/
|
||||
class Measurement
|
||||
{
|
||||
public:
|
||||
Measurement();
|
||||
Measurement(bool ok, int value);
|
||||
int value = 0;
|
||||
bool ok = false;
|
||||
static Measurement *Failed();
|
||||
static Measurement *Ok(int value);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,67 @@
|
|||
#include "Data/DataController.h"
|
||||
#include "Data/Measurement.h"
|
||||
|
||||
typedef Measurement *MeasurementPtr;
|
||||
|
||||
Measurements::Measurements(unsigned int avgLookback)
|
||||
{
|
||||
_storageSize = avgLookback;
|
||||
_storage = new MeasurementPtr[avgLookback];
|
||||
for (unsigned int i = 0; i < avgLookback; i++)
|
||||
{
|
||||
_storage[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Measurements::add(Measurement *measurement)
|
||||
{
|
||||
auto index = _next();
|
||||
_storage[index] = measurement;
|
||||
if (measurement->ok)
|
||||
{
|
||||
_averageCount++;
|
||||
_averageSum += measurement->value;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Measurements::_next()
|
||||
{
|
||||
_index++;
|
||||
if (_index == _storageSize)
|
||||
{
|
||||
_index = 0;
|
||||
}
|
||||
if (_storage[_index] != nullptr && _storage[_index]->ok)
|
||||
{
|
||||
_averageSum -= _storage[_index]->value;
|
||||
_averageCount--;
|
||||
}
|
||||
return _index;
|
||||
}
|
||||
|
||||
Measurement *Measurements::getLast()
|
||||
{
|
||||
return _storage[_index];
|
||||
}
|
||||
|
||||
Measurement *Measurements::getAverage()
|
||||
{
|
||||
auto result = new Measurement();
|
||||
if (_averageCount > 0)
|
||||
{
|
||||
result->ok = true;
|
||||
result->value = round(_averageSum / _averageCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Measurements::clearHistory()
|
||||
{
|
||||
_averageCount = 0;
|
||||
_averageSum = 0;
|
||||
for (unsigned int i = 0; i < _storageSize; i++)
|
||||
{
|
||||
_storage[i]->ok = false;
|
||||
_storage[i]->value = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef DOUGH_DATA_MEASUREMENTS_H
|
||||
#define DOUGH_DATA_MEASUREMENTS_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.
|
||||
*/
|
||||
class Measurements
|
||||
{
|
||||
public:
|
||||
Measurements(unsigned int avgLookback);
|
||||
void add(Measurement *measurement);
|
||||
Measurement *getLast();
|
||||
Measurement *getAverage();
|
||||
void clearHistory();
|
||||
|
||||
private:
|
||||
Measurement **_storage;
|
||||
unsigned int _storageSize;
|
||||
int _averageSum = 0;
|
||||
unsigned int _averageCount = 0;
|
||||
unsigned int _index = 0;
|
||||
unsigned int _next();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,42 +0,0 @@
|
|||
#ifndef DOUGH_BUTTON_H
|
||||
#define DOUGH_BUTTON_H
|
||||
|
||||
#define BUTTON_DEBOUNCE_DELAY 50
|
||||
#define BUTTON_LONGPRESS_DELAY 1000
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
typedef enum {
|
||||
UP,
|
||||
DOWN,
|
||||
DOWN_LONG,
|
||||
UP_AFTER_LONG,
|
||||
UP_AFTER_SHORT,
|
||||
READY_FOR_NEXT_PRESS
|
||||
} DoughButtonState;
|
||||
|
||||
typedef void (*DoughButtonHandler)();
|
||||
|
||||
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,274 +0,0 @@
|
|||
#include "DoughData.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DoughData* DoughData::_instance = nullptr;
|
||||
|
||||
/**
|
||||
* Fetch the DoughData singleton.
|
||||
*/
|
||||
DoughData* DoughData::Instance() {
|
||||
if (DoughData::_instance == nullptr) {
|
||||
DoughData::_instance = new DoughData();
|
||||
}
|
||||
return DoughData::_instance;
|
||||
}
|
||||
|
||||
DoughData::DoughData() : _temperatureMeasurements(TEMPERATURE_AVG_LOOKBACK),
|
||||
_humidityMeasurements(HUMIDITY_AVG_LOOKBACK),
|
||||
_distanceMeasurements(DISTANCE_AVG_LOOKBACK) {}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Measurements storage
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DoughDataMeasurements::DoughDataMeasurements(int avgLookback) {
|
||||
_storageSize = avgLookback;
|
||||
_storage = new DoughDataMeasurement[avgLookback];
|
||||
for (int i = 0; i < avgLookback; i++) {
|
||||
_storage[i] = DoughDataMeasurement();
|
||||
}
|
||||
}
|
||||
|
||||
void DoughDataMeasurements::registerValue(int value) {
|
||||
auto measurement = _next();
|
||||
_averageCount++;
|
||||
_averageSum += value;
|
||||
measurement->ok = true;
|
||||
measurement->value = value;
|
||||
}
|
||||
|
||||
void DoughDataMeasurements::registerFailed() {
|
||||
auto measurement = _next();
|
||||
measurement->ok = false;
|
||||
measurement->value = 0;
|
||||
}
|
||||
|
||||
DoughDataMeasurement* DoughDataMeasurements::_next() {
|
||||
_index++;
|
||||
if (_index == _storageSize) {
|
||||
_index = 0;
|
||||
}
|
||||
if (_storage[_index].ok) {
|
||||
_averageSum -= _storage[_index].value;
|
||||
_averageCount--;
|
||||
}
|
||||
return &(_storage[_index]);
|
||||
}
|
||||
|
||||
DoughDataMeasurement DoughDataMeasurements::getLast() {
|
||||
return _storage[_index];
|
||||
}
|
||||
|
||||
DoughDataMeasurement DoughDataMeasurements::getAverage() {
|
||||
DoughDataMeasurement result;
|
||||
if (_averageCount > 0) {
|
||||
result.ok = true;
|
||||
result.value = round(_averageSum / _averageCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DoughDataMeasurements::clearHistory() {
|
||||
_averageCount = 0;
|
||||
_averageSum = 0;
|
||||
for (unsigned int i = 0; i < _storageSize; i++) {
|
||||
_storage[i].ok = false;
|
||||
_storage[i].value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughData::setup() {
|
||||
_containerHeight = 0.00;
|
||||
_containerHeightSet = false;
|
||||
|
||||
DoughMQTT *mqtt = DoughMQTT::Instance();
|
||||
mqtt->onConnect(DoughData::handleMqttConnect);
|
||||
mqtt->onMessage(DoughData::handleMqttMessage);
|
||||
}
|
||||
|
||||
void DoughData::handleMqttConnect(DoughMQTT* mqtt) {
|
||||
mqtt->subscribe("container_height");
|
||||
}
|
||||
|
||||
void DoughData::handleMqttMessage(String &key, String &payload) {
|
||||
if (key.equals("container_height")) {
|
||||
DoughData::Instance()->setContainerHeight(payload.toInt());
|
||||
} else {
|
||||
DoughUI::Instance()->log("DATA", "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 DoughData::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 DoughData::setContainerHeight(int height) {
|
||||
_containerHeightSet = false;
|
||||
if (height <= HCSR04_MIN_MM) {
|
||||
DoughUI::Instance()->log("DATA", "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,
|
||||
"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");
|
||||
_containerHeight = height;
|
||||
_containerHeightSet = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughData::loop() {
|
||||
if (isConfigured()) {
|
||||
_sample();
|
||||
_publish();
|
||||
}
|
||||
}
|
||||
|
||||
void DoughData::clearHistory() {
|
||||
_temperatureMeasurements.clearHistory();
|
||||
_humidityMeasurements.clearHistory();
|
||||
_distanceMeasurements.clearHistory();
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
_sampleCounter = 0;
|
||||
}
|
||||
|
||||
void DoughData::_sample() {
|
||||
auto now = millis();
|
||||
auto delta = now - _lastSample;
|
||||
auto tick = _lastSample == 0 || delta >= SAMPLE_INTERVAL;
|
||||
|
||||
if (tick) {
|
||||
_lastSample = now;
|
||||
DoughUI* ui = DoughUI::Instance();
|
||||
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();
|
||||
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:
|
||||
sensors->readTemperature();
|
||||
if (sensors->temperatureOk) {
|
||||
_temperatureMeasurements.registerValue(sensors->temperature);
|
||||
} else {
|
||||
_temperatureMeasurements.registerFailed();
|
||||
}
|
||||
_sampleType = SAMPLE_HUMIDITY;
|
||||
break;
|
||||
case SAMPLE_HUMIDITY:
|
||||
sensors->readHumidity();
|
||||
if (sensors->humidityOk) {
|
||||
_humidityMeasurements.registerValue(sensors->humidity);
|
||||
} else {
|
||||
_humidityMeasurements.registerFailed();
|
||||
}
|
||||
_sampleType = SAMPLE_DISTANCE;
|
||||
break;
|
||||
case SAMPLE_DISTANCE:
|
||||
sensors->readDistance();
|
||||
if (sensors->distanceOk) {
|
||||
_distanceMeasurements.registerValue(sensors->distance);
|
||||
} else {
|
||||
_distanceMeasurements.registerFailed();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ui->resume();
|
||||
|
||||
_sampleCounter++;
|
||||
if (_sampleCounter == SAMPLE_CYCLE_LENGTH) {
|
||||
_sampleCounter = 0;
|
||||
_sampleType = SAMPLE_TEMPERATURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoughData::_publish() {
|
||||
static unsigned long lastSample = 0;
|
||||
if (lastSample == 0 || millis() - lastSample > PUBLISH_INTERVAL) {
|
||||
lastSample = millis();
|
||||
|
||||
DoughUI* ui = DoughUI::Instance();
|
||||
DoughMQTT* mqtt = DoughMQTT::Instance();
|
||||
|
||||
auto m = _temperatureMeasurements.getLast();
|
||||
if (m.ok) {
|
||||
mqtt->publish("temperature", m.value);
|
||||
} else {
|
||||
mqtt->publish("temperature", "null");
|
||||
}
|
||||
|
||||
m = _temperatureMeasurements.getAverage();
|
||||
if (m.ok) {
|
||||
mqtt->publish("temperature/average", m.value);
|
||||
} else {
|
||||
mqtt->publish("temperature/average", "null");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
#ifndef DOUGH_DATA_H
|
||||
#define DOUGH_DATA_H
|
||||
|
||||
// These definitions describes what measurements are performed in sequence.
|
||||
// One measurement is done every SAMPLE_INTERVAL microseconds.
|
||||
// We always start with a temperature measurement, then a humidity measurement,
|
||||
// and finally a number of distance measurements.
|
||||
// The SAMPLE_CYCLE_LENGTH defines the total number of samples in this sequence.
|
||||
#define SAMPLE_INTERVAL 1000
|
||||
#define SAMPLE_CYCLE_LENGTH 30 // 1 temperature + 1 humidity + 28 distance samples
|
||||
|
||||
// Two different values are published per sensor: a recent value and an average
|
||||
// value. These definition define the number of measurements to include in the
|
||||
// average computation.
|
||||
#define TEMPERATURE_AVG_LOOKBACK 10 // making this a 5 minute average
|
||||
#define HUMIDITY_AVG_LOOKBACK 10 // making this a 5 minute average
|
||||
#define DISTANCE_AVG_LOOKBACK 28 * 2 * 5 // making this a 5 minute average
|
||||
|
||||
// The minimal interval at which to publish measurements to the MQTT broker.
|
||||
// When significant changes occur in the measurements, then these will be published
|
||||
// to the MQTT broker at all times, independent from this interval.
|
||||
#define PUBLISH_INTERVAL 4000
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DoughSensors.h"
|
||||
#include "DoughNetwork.h"
|
||||
#include "DoughMQTT.h"
|
||||
#include "DoughUI.h"
|
||||
|
||||
typedef enum {
|
||||
SAMPLE_TEMPERATURE,
|
||||
SAMPLE_HUMIDITY,
|
||||
SAMPLE_DISTANCE
|
||||
} DoughSampleType;
|
||||
|
||||
/**
|
||||
* The DoughDataMeasurement struct represents a single measurement.
|
||||
*/
|
||||
struct DoughDataMeasurement {
|
||||
public:
|
||||
int value = 0;
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* The DoughDataMeasurements class is used to store measurements for a sensor
|
||||
* and to keep track of running totals for handling average computations.
|
||||
*/
|
||||
class DoughDataMeasurements {
|
||||
public:
|
||||
DoughDataMeasurements(int avgLookback);
|
||||
void registerValue(int value);
|
||||
void registerFailed();
|
||||
DoughDataMeasurement getLast();
|
||||
DoughDataMeasurement getAverage();
|
||||
void clearHistory();
|
||||
private:
|
||||
DoughDataMeasurement* _storage;
|
||||
unsigned int _storageSize;
|
||||
int _averageSum = 0;
|
||||
unsigned int _averageCount = 0;
|
||||
unsigned int _index = 0;
|
||||
DoughDataMeasurement* _next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class DoughData {
|
||||
public:
|
||||
static DoughData* Instance();
|
||||
void setup();
|
||||
void loop();
|
||||
void clearHistory();
|
||||
void setContainerHeight(int height);
|
||||
bool isConfigured();
|
||||
static void handleMqttConnect(DoughMQTT *mqtt);
|
||||
static void handleMqttMessage(String &key, String &value);
|
||||
|
||||
private:
|
||||
DoughData();
|
||||
static DoughData* _instance;
|
||||
DoughSensors * _sensors;
|
||||
unsigned long _lastSample = 0;
|
||||
DoughSampleType _sampleType = SAMPLE_TEMPERATURE;
|
||||
int _sampleCounter = 0;
|
||||
int _containerHeight;
|
||||
bool _containerHeightSet;
|
||||
void _sample();
|
||||
void _publish();
|
||||
DoughDataMeasurements _temperatureMeasurements;
|
||||
DoughDataMeasurements _humidityMeasurements;
|
||||
DoughDataMeasurements _distanceMeasurements;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,53 +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>
|
||||
#include "config.h"
|
||||
|
||||
typedef enum {
|
||||
ON,
|
||||
OFF,
|
||||
BLINK_ON,
|
||||
BLINK_OFF,
|
||||
FLASH,
|
||||
DIP,
|
||||
PULSE
|
||||
} DoughLEDState;
|
||||
|
||||
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,39 +0,0 @@
|
|||
#ifndef DOUGH_MQTT_H
|
||||
#define DOUGH_MQTT_H
|
||||
|
||||
#include <MQTT.h>
|
||||
#include <MQTTClient.h>
|
||||
#include "DoughNetwork.h"
|
||||
#include "DoughUI.h"
|
||||
#include "config.h"
|
||||
|
||||
class DoughMQTT;
|
||||
|
||||
typedef void (*DoughMQTTConnectHandler)(DoughMQTT* mqtt);
|
||||
typedef void (*DoughMQTTMessageHandler)(String &key, String &value);
|
||||
|
||||
class DoughMQTT {
|
||||
public:
|
||||
static DoughMQTT* Instance();
|
||||
void setup();
|
||||
void onConnect(DoughMQTTConnectHandler callback);
|
||||
void onMessage(DoughMQTTMessageHandler callback);
|
||||
bool isConnected();
|
||||
bool connect();
|
||||
void subscribe(const char* key);
|
||||
void procesIncomingsMessages();
|
||||
void publish(const char* key, const char* payload);
|
||||
void publish(const char* key, int payload);
|
||||
|
||||
private:
|
||||
DoughMQTT();
|
||||
static DoughMQTT* _instance;
|
||||
MQTTClient _mqttClient;
|
||||
DoughUI* _ui;
|
||||
DoughMQTTConnectHandler _onConnect = nullptr;
|
||||
MQTTClientCallbackSimple _onMessage = nullptr;
|
||||
static void handleMessage(String &topic, String &payload);
|
||||
char *_mqttDeviceId;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,26 +0,0 @@
|
|||
#ifndef DOUGH_NETWORK_H
|
||||
#define DOUGH_NETWORK_H
|
||||
|
||||
#include <WiFiNINA.h>
|
||||
#include "DoughUI.h"
|
||||
#include "config.h"
|
||||
|
||||
class DoughNetwork {
|
||||
public:
|
||||
static DoughNetwork* Instance();
|
||||
char *getMacAddress();
|
||||
void setup();
|
||||
void loop();
|
||||
bool isConnected();
|
||||
bool connect();
|
||||
WiFiClient client;
|
||||
|
||||
private:
|
||||
DoughNetwork();
|
||||
static DoughNetwork* _instance;
|
||||
void _setMacAddress();
|
||||
char _macAddress[18]; // max MAC address length + 1
|
||||
DoughUI* _ui;
|
||||
};
|
||||
|
||||
#endif
|
198
src/DoughUI.cpp
198
src/DoughUI.cpp
|
@ -1,198 +0,0 @@
|
|||
#include "DoughUI.h"
|
||||
|
||||
DoughUI* DoughUI::_instance = nullptr;
|
||||
|
||||
/**
|
||||
* Fetch the DoughUI singleton.
|
||||
*/
|
||||
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) {}
|
||||
|
||||
/**
|
||||
* Called from the main setup() function of the sketch.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
}
|
||||
|
||||
void DoughUI::resume() {
|
||||
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.
|
||||
*/
|
||||
void TC4_Handler() {
|
||||
DoughUI::Instance()->updatedLEDs();
|
||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 invocatino
|
||||
* 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();
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#ifndef DOUGH_UI_H
|
||||
#define DOUGH_UI_H
|
||||
|
||||
#define LOG_BAUDRATE 9600
|
||||
|
||||
// Define this one to wait for USB serial to come up.
|
||||
// This can be useful during development, when you want all
|
||||
// serial messages to appear in the serial monitor.
|
||||
// Without this, some of the initial serial messages might
|
||||
// be missing from the output.
|
||||
#undef LOG_WAIT_SERIAL
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiNINA.h>
|
||||
#include <stdarg.h>
|
||||
#include "DoughButton.h"
|
||||
#include "DoughLED.h"
|
||||
#include "config.h"
|
||||
|
||||
class DoughUI {
|
||||
public:
|
||||
static DoughUI* Instance();
|
||||
void setup();
|
||||
static void onoffButtonISR();
|
||||
static void setupButtonISR();
|
||||
DoughButton onoffButton;
|
||||
DoughButton setupButton;
|
||||
DoughLED ledBuiltin;
|
||||
DoughLED led1;
|
||||
DoughLED led2;
|
||||
DoughLED led3;
|
||||
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;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -4,19 +4,22 @@
|
|||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DoughMQTT* DoughMQTT::_instance = nullptr;
|
||||
DoughMQTT *DoughMQTT::_instance = nullptr;
|
||||
|
||||
/**
|
||||
* Fetch the DoughMQTT singleton.
|
||||
*/
|
||||
DoughMQTT* DoughMQTT::Instance() {
|
||||
if (DoughMQTT::_instance == nullptr) {
|
||||
DoughMQTT *DoughMQTT::Instance()
|
||||
{
|
||||
if (DoughMQTT::_instance == nullptr)
|
||||
{
|
||||
DoughMQTT::_instance = new DoughMQTT();
|
||||
}
|
||||
return DoughMQTT::_instance;
|
||||
}
|
||||
|
||||
DoughMQTT::DoughMQTT() {
|
||||
DoughMQTT::DoughMQTT()
|
||||
{
|
||||
_ui = DoughUI::Instance();
|
||||
}
|
||||
|
||||
|
@ -24,24 +27,27 @@ DoughMQTT::DoughMQTT() {
|
|||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughMQTT::setup() {
|
||||
DoughNetwork* network = DoughNetwork::Instance();
|
||||
void DoughMQTT::setup()
|
||||
{
|
||||
DoughWiFi *network = DoughWiFi::Instance();
|
||||
|
||||
#ifdef MQTT_DEVICE_ID
|
||||
#ifdef MQTT_DEVICE_ID
|
||||
_mqttDeviceId = MQTT_DEVICE_ID;
|
||||
#else
|
||||
#else
|
||||
_mqttDeviceId = network->getMacAddress();
|
||||
#endif
|
||||
#endif
|
||||
_ui->log("MQTT", "ss", "Device ID = ", _mqttDeviceId);
|
||||
|
||||
_mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client);
|
||||
_mqttClient.begin(MQTT_BROKER, MQTT_PORT, network->client);
|
||||
}
|
||||
|
||||
void DoughMQTT::onConnect(DoughMQTTConnectHandler callback) {
|
||||
void DoughMQTT::onConnect(DoughMQTTConnectHandler callback)
|
||||
{
|
||||
_onConnect = callback;
|
||||
}
|
||||
|
||||
void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) {
|
||||
void DoughMQTT::onMessage(MQTTClientCallbackSimple callback)
|
||||
{
|
||||
_onMessage = callback;
|
||||
}
|
||||
|
||||
|
@ -49,62 +55,73 @@ void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) {
|
|||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
bool DoughMQTT::isConnected() {
|
||||
bool DoughMQTT::isConnected()
|
||||
{
|
||||
return _mqttClient.connected();
|
||||
}
|
||||
|
||||
bool DoughMQTT::connect() {
|
||||
_ui->log("MQTT" , "sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT);
|
||||
bool DoughMQTT::connect()
|
||||
{
|
||||
_ui->log("MQTT", "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()) {
|
||||
if (!_mqttClient.connected())
|
||||
{
|
||||
_ui->log("MQTT", "s", "ERROR - Connection to broker failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
_mqttClient.onMessage(DoughMQTT::handleMessage);
|
||||
|
||||
if (_onConnect != nullptr) {
|
||||
if (_onConnect != nullptr)
|
||||
{
|
||||
_onConnect(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DoughMQTT::procesIncomingsMessages() {
|
||||
void DoughMQTT::procesIncomingsMessages()
|
||||
{
|
||||
_mqttClient.loop();
|
||||
}
|
||||
|
||||
void DoughMQTT::handleMessage(String &topic, String &payload) {
|
||||
DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload);
|
||||
void DoughMQTT::handleMessage(String &topic, String &payload)
|
||||
{
|
||||
DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload);
|
||||
|
||||
DoughMQTT *mqtt = DoughMQTT::Instance();
|
||||
if (mqtt->_onMessage != nullptr) {
|
||||
if (mqtt->_onMessage != nullptr)
|
||||
{
|
||||
int pos = topic.lastIndexOf('/');
|
||||
if (pos != -1) {
|
||||
topic.remove(0, pos+1);
|
||||
if (pos != -1)
|
||||
{
|
||||
topic.remove(0, pos + 1);
|
||||
mqtt->_onMessage(topic, 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);
|
||||
snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
|
||||
DoughUI::Instance()->log("MQTT", "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);
|
||||
snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
|
||||
DoughUI::Instance()->log("MQTT", "ssss", ">>> ", topic, " = ", payload);
|
||||
_mqttClient.publish(topic, payload);
|
||||
}
|
||||
|
||||
void DoughMQTT::publish(const char* key, int payload) {
|
||||
void DoughMQTT::publish(const char *key, int payload)
|
||||
{
|
||||
char buf[16];
|
||||
snprintf(buf, 16, "%d", payload);
|
||||
publish(key, buf);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef DOUGH_MQTT_H
|
||||
#define DOUGH_MQTT_H
|
||||
|
||||
#include <MQTT.h>
|
||||
#include <MQTTClient.h>
|
||||
#include "Network/DoughWiFi.h"
|
||||
#include "UI/DoughUI.h"
|
||||
#include "config.h"
|
||||
|
||||
class DoughMQTT;
|
||||
|
||||
typedef void (*DoughMQTTConnectHandler)(DoughMQTT *mqtt);
|
||||
typedef void (*DoughMQTTMessageHandler)(String &key, String &value);
|
||||
|
||||
class DoughMQTT
|
||||
{
|
||||
public:
|
||||
static DoughMQTT *Instance();
|
||||
void setup();
|
||||
void onConnect(DoughMQTTConnectHandler callback);
|
||||
void onMessage(DoughMQTTMessageHandler callback);
|
||||
bool isConnected();
|
||||
bool connect();
|
||||
void subscribe(const char *key);
|
||||
void procesIncomingsMessages();
|
||||
void publish(const char *key, const char *payload);
|
||||
void publish(const char *key, int payload);
|
||||
|
||||
private:
|
||||
DoughMQTT();
|
||||
static DoughMQTT *_instance;
|
||||
MQTTClient _mqttClient;
|
||||
DoughUI *_ui;
|
||||
DoughMQTTConnectHandler _onConnect = nullptr;
|
||||
MQTTClientCallbackSimple _onMessage = nullptr;
|
||||
static void handleMessage(String &topic, String &payload);
|
||||
char *_mqttDeviceId;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,22 +1,25 @@
|
|||
#include "DoughNetwork.h"
|
||||
#include "Network/DoughWiFi.h"
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
DoughNetwork* DoughNetwork::_instance = nullptr;
|
||||
DoughWiFi *DoughWiFi::_instance = nullptr;
|
||||
|
||||
/**
|
||||
* Fetch the DoughNetwork singleton.
|
||||
* Fetch the DoughWiFi singleton.
|
||||
*/
|
||||
DoughNetwork* DoughNetwork::Instance() {
|
||||
if (DoughNetwork::_instance == nullptr) {
|
||||
DoughNetwork::_instance = new DoughNetwork();
|
||||
DoughWiFi *DoughWiFi::Instance()
|
||||
{
|
||||
if (DoughWiFi::_instance == nullptr)
|
||||
{
|
||||
DoughWiFi::_instance = new DoughWiFi();
|
||||
}
|
||||
return DoughNetwork::_instance;
|
||||
return DoughWiFi::_instance;
|
||||
}
|
||||
|
||||
DoughNetwork::DoughNetwork() {
|
||||
DoughWiFi::DoughWiFi()
|
||||
{
|
||||
_ui = DoughUI::Instance();
|
||||
}
|
||||
|
||||
|
@ -24,57 +27,67 @@ DoughNetwork::DoughNetwork() {
|
|||
// Setup
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughNetwork::_setMacAddress() {
|
||||
void DoughWiFi::_setMacAddress()
|
||||
{
|
||||
byte mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
snprintf(
|
||||
_macAddress, sizeof(_macAddress)/sizeof(_macAddress[0]),
|
||||
_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 DoughNetwork::setup() {
|
||||
void DoughWiFi::setup()
|
||||
{
|
||||
_setMacAddress();
|
||||
DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress());
|
||||
DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
bool DoughNetwork::isConnected() {
|
||||
bool DoughWiFi::isConnected()
|
||||
{
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
}
|
||||
|
||||
bool DoughNetwork::connect() {
|
||||
bool DoughWiFi::connect()
|
||||
{
|
||||
int status = WiFi.status();
|
||||
|
||||
// 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");
|
||||
delay(5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check if the WiFi network is already up.
|
||||
if (status == WL_CONNECTED) {
|
||||
if (status == WL_CONNECTED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup the connection to the WiFi network.
|
||||
_ui->log("NETWORK", "ss", "WiFi network = ", WIFI_SSID);
|
||||
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
|
||||
|
||||
// Check if the connection attempt was successful.
|
||||
if (status == WL_CONNECTED) {
|
||||
if (status == WL_CONNECTED)
|
||||
{
|
||||
_ui->log("NETWORK", "sa", "IP-Address = ", WiFi.localIP());
|
||||
_ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm");
|
||||
_ui->log("NETWORK", "sis", "Signal strength = ", WiFi.RSSI(), " dBm");
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
_ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char* DoughNetwork::getMacAddress() {
|
||||
char *DoughWiFi::getMacAddress()
|
||||
{
|
||||
return _macAddress;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef DOUGH_NETWORK_H
|
||||
#define DOUGH_NETWORK_H
|
||||
|
||||
#include <WiFiNINA.h>
|
||||
#include "UI/DoughUI.h"
|
||||
#include "config.h"
|
||||
|
||||
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
|
||||
DoughUI *_ui;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -20,9 +20,6 @@ DoughSensors::DoughSensors() {
|
|||
_ui = DoughUI::Instance();
|
||||
_dht = new DHT(DHT11_DATA_PIN, DHT11);
|
||||
_hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
|
||||
temperature = 0;
|
||||
humidity = 0;
|
||||
distance = 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
@ -38,40 +35,38 @@ void DoughSensors::setup() {
|
|||
// loop
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
void DoughSensors::readTemperature() {
|
||||
Measurement* DoughSensors::readTemperature() {
|
||||
float t = _dht->readTemperature();
|
||||
if (isnan(t)) {
|
||||
_ui->log("SENSORS", "s", "ERROR - Temperature measurement failed");
|
||||
temperatureOk = false;
|
||||
return Measurement::Failed();
|
||||
} else {
|
||||
temperature = int(t);
|
||||
temperatureOk = true;
|
||||
_hcsr04->setTemperature(t);
|
||||
_hcsr04->setTemperature(int(t));
|
||||
auto m = new Measurement(true, int(t));
|
||||
_ui->log("SENSORS", "sisi", "Temperature = ", int(t), "°C ", m->value);
|
||||
return m;
|
||||
}
|
||||
_ui->log("SENSORS", "siss", "Temperature = ", temperature, "°C ", (temperatureOk ? "[OK]" : "[ERR]"));
|
||||
}
|
||||
|
||||
void DoughSensors::readHumidity() {
|
||||
Measurement* DoughSensors::readHumidity() {
|
||||
int h = _dht->readHumidity();
|
||||
if (h == 0) {
|
||||
_ui->log("SENSORS", "s", "ERROR - Humidity measurement failed");
|
||||
humidityOk = false;
|
||||
return Measurement::Failed();
|
||||
} else {
|
||||
humidity = h;
|
||||
humidityOk = true;
|
||||
_hcsr04->setHumidity(h);
|
||||
_ui->log("SENSORS", "sis", "Humidity = ", h, "%");
|
||||
return Measurement::Ok(h);
|
||||
}
|
||||
_ui->log("SENSORS", "siss", "Humidity = ", humidity, "% ", (humidityOk ? "[OK]" : "[ERR]"));
|
||||
}
|
||||
|
||||
void DoughSensors::readDistance() {
|
||||
Measurement* DoughSensors::readDistance() {
|
||||
int d = _hcsr04->readDistance();
|
||||
if (d == -1) {
|
||||
_ui->log("SENSORS", "s", "ERROR - Distance measurement failed");
|
||||
distanceOk = false;
|
||||
return Measurement::Failed();
|
||||
} else {
|
||||
distanceOk = true;
|
||||
distance = d;
|
||||
}
|
||||
_ui->log("SENSORS", "siss", "Distance = ", distance, "mm ", (distanceOk? "[OK]" : "[ERR]"));
|
||||
_ui->log("SENSORS", "sis", "Distance = ", d, "mm");
|
||||
return Measurement::Ok(d);
|
||||
}
|
||||
}
|
|
@ -2,24 +2,18 @@
|
|||
#define DOUGH_SENSORS_H
|
||||
|
||||
#include <DHT.h>
|
||||
#include "HCSR04.h"
|
||||
#include "DoughUI.h"
|
||||
#include "Sensors/HCSR04.h"
|
||||
#include "UI/DoughUI.h"
|
||||
#include "Data/Measurement.h"
|
||||
#include "config.h"
|
||||
|
||||
class DoughSensors {
|
||||
public:
|
||||
static DoughSensors* Instance();
|
||||
void setup();
|
||||
void readAll();
|
||||
void readTemperature();
|
||||
int temperature = 0;
|
||||
bool temperatureOk = false;
|
||||
void readHumidity();
|
||||
int humidity = 0;
|
||||
bool humidityOk = false;
|
||||
void readDistance();
|
||||
int distance = 0;
|
||||
bool distanceOk = false;
|
||||
Measurement* readTemperature();
|
||||
Measurement* readHumidity();
|
||||
Measurement* readDistance();
|
||||
|
||||
private:
|
||||
DoughSensors();
|
|
@ -1,4 +1,4 @@
|
|||
#include "HCSR04.h"
|
||||
#include "Sensors/HCSR04.h"
|
||||
|
||||
HCSR04::HCSR04(int triggerPin, int echoPin) {
|
||||
_triggerPin = triggerPin;
|
||||
|
@ -33,7 +33,6 @@ int HCSR04::readDistance() {
|
|||
_sortSamples();
|
||||
return _computeAverage();
|
||||
}
|
||||
DoughUI::Instance()->log("HCSR04", "s", "ERROR - Not enough samples for reading distance, returning NAN");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
#define HCSR04_INIT_HUMIDITY 50.000
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DoughUI.h"
|
||||
#include "config.h"
|
||||
|
||||
class HCSR04 {
|
|
@ -18,11 +18,13 @@
|
|||
* // Linking the function ot button interrupts.
|
||||
* myButton.onInterrupt(myButtonISR);
|
||||
*/
|
||||
DoughButton::DoughButton(int pin) {
|
||||
DoughButton::DoughButton(int pin)
|
||||
{
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
void DoughButton::setup() {
|
||||
void DoughButton::setup()
|
||||
{
|
||||
pinMode(_pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
|
@ -31,7 +33,8 @@ void DoughButton::setup() {
|
|||
* interrupts. The provided isr should relay interrupts to the
|
||||
* handleButtonState() method of this class (see constructor docs).
|
||||
*/
|
||||
void DoughButton::onInterrupt(DoughButtonHandler isr) {
|
||||
void DoughButton::onInterrupt(DoughButtonHandler isr)
|
||||
{
|
||||
attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE);
|
||||
}
|
||||
|
||||
|
@ -40,86 +43,111 @@ void DoughButton::onInterrupt(DoughButtonHandler isr) {
|
|||
* When specific handlers for long and/or short presses are
|
||||
* configured as well, those have precedence over this one.
|
||||
*/
|
||||
void DoughButton::onPress(DoughButtonHandler handler) {
|
||||
void DoughButton::onPress(DoughButtonHandler handler)
|
||||
{
|
||||
_pressHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an event handler for long button presses.
|
||||
*/
|
||||
void DoughButton::onLongPress(DoughButtonHandler handler) {
|
||||
void DoughButton::onLongPress(DoughButtonHandler handler)
|
||||
{
|
||||
_longPressHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an event handler for short button presses.
|
||||
*/
|
||||
void DoughButton::onShortPress(DoughButtonHandler handler) {
|
||||
void DoughButton::onShortPress(DoughButtonHandler handler)
|
||||
{
|
||||
_shortPressHandler = handler;
|
||||
}
|
||||
|
||||
void DoughButton::loop() {
|
||||
void DoughButton::loop()
|
||||
{
|
||||
handleButtonState();
|
||||
if (_state == UP_AFTER_SHORT) {
|
||||
if (_shortPressHandler != nullptr) {
|
||||
if (_state == UP_AFTER_SHORT)
|
||||
{
|
||||
if (_shortPressHandler != nullptr)
|
||||
{
|
||||
_shortPressHandler();
|
||||
}
|
||||
else if (_pressHandler != nullptr) {
|
||||
else if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
else if (_state == DOWN_LONG || _state == UP_AFTER_LONG) {
|
||||
if (_longPressHandler != nullptr) {
|
||||
else if (_state == DOWN_LONG || _state == UP_AFTER_LONG)
|
||||
{
|
||||
if (_longPressHandler != nullptr)
|
||||
{
|
||||
_longPressHandler();
|
||||
}
|
||||
else if (_pressHandler != nullptr) {
|
||||
else if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr) {
|
||||
if (_pressHandler != nullptr) {
|
||||
else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr)
|
||||
{
|
||||
if (_pressHandler != nullptr)
|
||||
{
|
||||
_pressHandler();
|
||||
}
|
||||
}
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
}
|
||||
|
||||
void DoughButton::clearEvents() {
|
||||
void DoughButton::clearEvents()
|
||||
{
|
||||
_state = READY_FOR_NEXT_PRESS;
|
||||
}
|
||||
|
||||
void DoughButton::handleButtonState() {
|
||||
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) {
|
||||
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) {
|
||||
if (interval < BUTTON_DEBOUNCE_DELAY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle button state changes.
|
||||
if (_state == READY_FOR_NEXT_PRESS && buttonIsUp) {
|
||||
if (_state == READY_FOR_NEXT_PRESS && buttonIsUp)
|
||||
{
|
||||
_state = UP;
|
||||
} else if (_state == UP && buttonIsDown) {
|
||||
}
|
||||
else if (_state == UP && buttonIsDown)
|
||||
{
|
||||
_state = DOWN;
|
||||
} else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY) {
|
||||
}
|
||||
else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY)
|
||||
{
|
||||
_state = DOWN_LONG;
|
||||
} else if (_state == DOWN && buttonIsUp) {
|
||||
}
|
||||
else if (_state == DOWN && buttonIsUp)
|
||||
{
|
||||
_state = UP_AFTER_SHORT;
|
||||
} else if (_state == DOWN_LONG && buttonIsUp) {
|
||||
}
|
||||
else if (_state == DOWN_LONG && buttonIsUp)
|
||||
{
|
||||
_state = UP_AFTER_LONG;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#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)();
|
||||
|
||||
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,92 +1,115 @@
|
|||
#include "DoughLED.h"
|
||||
|
||||
DoughLED::DoughLED(int pin) {
|
||||
DoughLED::DoughLED(int pin)
|
||||
{
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
void DoughLED::setup() {
|
||||
void DoughLED::setup()
|
||||
{
|
||||
pinMode(_pin, OUTPUT);
|
||||
_state = OFF;
|
||||
_setPin(LOW);
|
||||
}
|
||||
|
||||
void DoughLED::loop() {
|
||||
void DoughLED::loop()
|
||||
{
|
||||
unsigned long now = millis();
|
||||
bool tick = (now - _timer) > _time;
|
||||
|
||||
if (_state == FLASH) {
|
||||
if (tick) {
|
||||
|
||||
if (_state == FLASH)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = OFF;
|
||||
}
|
||||
}
|
||||
else if (_state == DIP) {
|
||||
if (tick) {
|
||||
else if (_state == DIP)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
_state = ON;
|
||||
}
|
||||
}
|
||||
else if (_state == BLINK_ON) {
|
||||
if (_blinkStep == _blinkOnStep) {
|
||||
else if (_state == BLINK_ON)
|
||||
{
|
||||
if (_blinkStep == _blinkOnStep)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
}
|
||||
if (tick) {
|
||||
if (tick)
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = BLINK_OFF;
|
||||
_timer = now;
|
||||
}
|
||||
}
|
||||
else if (_state == BLINK_OFF) {
|
||||
if (tick) {
|
||||
else if (_state == BLINK_OFF)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_state = BLINK_ON;
|
||||
_timer = now;
|
||||
_blinkStep++;
|
||||
if (_blinkStep > _blinkOfSteps) {
|
||||
if (_blinkStep > _blinkOfSteps)
|
||||
{
|
||||
_blinkStep = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_state == PULSE) {
|
||||
if (tick) {
|
||||
}
|
||||
else if (_state == PULSE)
|
||||
{
|
||||
if (tick)
|
||||
{
|
||||
_timer = now;
|
||||
_time = 1;
|
||||
_brightness += _pulseStep;
|
||||
if (_brightness <= 0) {
|
||||
if (_brightness <= 0)
|
||||
{
|
||||
_time = 200;
|
||||
_brightness = 0;
|
||||
_pulseStep = -_pulseStep;
|
||||
}
|
||||
else if (_brightness >= 100) {
|
||||
else if (_brightness >= 100)
|
||||
{
|
||||
_brightness = 100;
|
||||
_pulseStep = -_pulseStep;
|
||||
}
|
||||
}
|
||||
analogWrite(_pin, _brightness);
|
||||
}
|
||||
else if (_state == OFF) {
|
||||
else if (_state == OFF)
|
||||
{
|
||||
_setPin(LOW);
|
||||
}
|
||||
else if (_state == ON) {
|
||||
else if (_state == ON)
|
||||
{
|
||||
_setPin(HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void DoughLED::_setPin(int high_or_low) {
|
||||
void DoughLED::_setPin(int high_or_low)
|
||||
{
|
||||
_pinState = high_or_low;
|
||||
analogWrite(_pin, _pinState == LOW ? 0 : 255);
|
||||
analogWrite(_pin, _pinState == LOW ? 0 : 255);
|
||||
}
|
||||
|
||||
void DoughLED::on() {
|
||||
void DoughLED::on()
|
||||
{
|
||||
_state = ON;
|
||||
loop();
|
||||
}
|
||||
|
||||
void DoughLED::off() {
|
||||
void DoughLED::off()
|
||||
{
|
||||
_state = OFF;
|
||||
loop();
|
||||
}
|
||||
|
||||
DoughLED* DoughLED::flash() {
|
||||
DoughLED *DoughLED::flash()
|
||||
{
|
||||
_setPin(HIGH);
|
||||
_state = FLASH;
|
||||
_timer = millis();
|
||||
|
@ -95,11 +118,13 @@ DoughLED* DoughLED::flash() {
|
|||
return this;
|
||||
}
|
||||
|
||||
DoughLED* DoughLED::blink() {
|
||||
DoughLED *DoughLED::blink()
|
||||
{
|
||||
return blink(1, 1);
|
||||
}
|
||||
|
||||
DoughLED* DoughLED::dip() {
|
||||
DoughLED *DoughLED::dip()
|
||||
{
|
||||
_setPin(LOW);
|
||||
_state = DIP;
|
||||
_timer = millis();
|
||||
|
@ -108,7 +133,8 @@ DoughLED* DoughLED::dip() {
|
|||
return this;
|
||||
}
|
||||
|
||||
DoughLED* DoughLED::blink(int onStep, int ofSteps) {
|
||||
DoughLED *DoughLED::blink(int onStep, int ofSteps)
|
||||
{
|
||||
_blinkOnStep = onStep;
|
||||
_blinkOfSteps = ofSteps;
|
||||
_blinkStep = 1;
|
||||
|
@ -118,25 +144,30 @@ DoughLED* DoughLED::blink(int onStep, int ofSteps) {
|
|||
return this;
|
||||
}
|
||||
|
||||
void DoughLED::pulse() {
|
||||
void DoughLED::pulse()
|
||||
{
|
||||
_state = PULSE;
|
||||
_brightness = 0;
|
||||
_pulseStep = +8;
|
||||
_time = 1;
|
||||
}
|
||||
|
||||
void DoughLED::slow() {
|
||||
void DoughLED::slow()
|
||||
{
|
||||
_time = LED_TRANSITION_TIME_SLOW;
|
||||
}
|
||||
|
||||
void DoughLED::fast() {
|
||||
void DoughLED::fast()
|
||||
{
|
||||
_time = LED_TRANSITION_TIME_FAST;
|
||||
}
|
||||
|
||||
bool DoughLED::isOn() {
|
||||
bool DoughLED::isOn()
|
||||
{
|
||||
return _pinState == HIGH;
|
||||
}
|
||||
|
||||
bool DoughLED::isOff() {
|
||||
bool DoughLED::isOff()
|
||||
{
|
||||
return _pinState == LOW;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#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;
|
||||
|
||||
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
|
|
@ -0,0 +1,231 @@
|
|||
#include "DoughUI.h"
|
||||
|
||||
DoughUI *DoughUI::_instance = nullptr;
|
||||
|
||||
/**
|
||||
* Fetch the DoughUI singleton.
|
||||
*/
|
||||
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) {}
|
||||
|
||||
/**
|
||||
* Called from the main setup() function of the sketch.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
}
|
||||
|
||||
void DoughUI::resume()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
void TC4_Handler()
|
||||
{
|
||||
DoughUI::Instance()->updatedLEDs();
|
||||
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 invocatino
|
||||
* 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,47 @@
|
|||
#ifndef DOUGH_UI_H
|
||||
#define DOUGH_UI_H
|
||||
|
||||
#define LOG_BAUDRATE 9600
|
||||
|
||||
// Define this one to wait for USB serial to come up.
|
||||
// This can be useful during development, when you want all
|
||||
// serial messages to appear in the serial monitor.
|
||||
// Without this, some of the initial serial messages might
|
||||
// be missing from the output.
|
||||
#undef LOG_WAIT_SERIAL
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiNINA.h>
|
||||
#include <stdarg.h>
|
||||
#include "UI/DoughButton.h"
|
||||
#include "UI/DoughLED.h"
|
||||
#include "config.h"
|
||||
|
||||
class DoughUI
|
||||
{
|
||||
public:
|
||||
static DoughUI *Instance();
|
||||
void setup();
|
||||
static void onoffButtonISR();
|
||||
static void setupButtonISR();
|
||||
DoughButton onoffButton;
|
||||
DoughButton setupButton;
|
||||
DoughLED ledBuiltin;
|
||||
DoughLED led1;
|
||||
DoughLED led2;
|
||||
DoughLED led3;
|
||||
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;
|
||||
};
|
||||
|
||||
#endif
|
18
src/config.h
18
src/config.h
|
@ -1,20 +1,20 @@
|
|||
// The digital pin to which the DATA pin of the DHT11
|
||||
// temperature/humidity sensor is connected.
|
||||
#define DHT11_DATA_PIN 10
|
||||
#define DHT11_DATA_PIN 10
|
||||
|
||||
// The digital pins to which the TRIG and ECHO pins of
|
||||
// the HCSR04 distance sensor are connected.
|
||||
#define HCSR04_TRIG_PIN 4
|
||||
#define HCSR04_ECHO_PIN 5
|
||||
#define HCSR04_TRIG_PIN 4
|
||||
#define HCSR04_ECHO_PIN 5
|
||||
|
||||
// The digital pins to which the three LEDs are connected.
|
||||
#define LED1_PIN 8
|
||||
#define LED2_PIN 7
|
||||
#define LED3_PIN 6
|
||||
#define LED1_PIN 8
|
||||
#define LED2_PIN 7
|
||||
#define LED3_PIN 6
|
||||
|
||||
// The digital pins to which the push buttons are connected.
|
||||
#define ONOFF_BUTTON_PIN 2
|
||||
#define SETUP_BUTTON_PIN 3
|
||||
// The digital pins to which the push buttons are connected.
|
||||
#define ONOFF_BUTTON_PIN 2
|
||||
#define SETUP_BUTTON_PIN 3
|
||||
|
||||
// The network configuration and possibly overrides for the above
|
||||
// definitions are stored in a separate header file, which is
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// WPA2 WiFi connection configuration.
|
||||
#define WIFI_SSID "<SSID network name>"
|
||||
#define WIFI_PASSWORD "<network password>"
|
||||
#define WIFI_SSID "<SSID network name>"
|
||||
#define WIFI_PASSWORD "<network password>"
|
||||
|
||||
// MQTT broker configuration.
|
||||
#define MQTT_BROKER "<IP or hostname>"
|
||||
#define MQTT_PORT 1883
|
||||
#define MQTT_USERNAME "<mqtt username>"
|
||||
#define MQTT_PASSWORD "<mqtt password>"
|
||||
#define MQTT_BROKER "<IP or hostname>"
|
||||
#define MQTT_PORT 1883
|
||||
#define MQTT_USERNAME "<mqtt username>"
|
||||
#define MQTT_PASSWORD "<mqtt password>"
|
||||
|
||||
// The prefix to use for the MQTT publishing topic.
|
||||
#define MQTT_TOPIC_PREFIX "sensors/doughboy"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "DoughBoy.h"
|
||||
#include "main.h"
|
||||
|
||||
// TOOD: implement the calibration logic
|
||||
// TODO: use different timings for temperature, humidity and distance measurements. Temp/Humidity together takes about 500ms, which slows down stuff.
|
||||
|
@ -8,11 +8,12 @@
|
|||
|
||||
DoughBoyState state = CONFIGURING;
|
||||
|
||||
void setup() {
|
||||
void setup()
|
||||
{
|
||||
DoughSensors::Instance()->setup();
|
||||
DoughNetwork::Instance()->setup();
|
||||
DoughWiFi::Instance()->setup();
|
||||
DoughMQTT::Instance()->setup();
|
||||
DoughData::Instance()->setup();
|
||||
DataController::Instance()->setup();
|
||||
auto ui = DoughUI::Instance();
|
||||
ui->setup();
|
||||
ui->onoffButton.onPress(handleOnoffButtonPress);
|
||||
|
@ -20,34 +21,41 @@ void setup() {
|
|||
ui->log("MAIN", "s", "Initialization completed, starting device");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
void loop()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
auto data = DoughData::Instance();
|
||||
auto data = DataController::Instance();
|
||||
auto mqtt = DoughMQTT::Instance();
|
||||
|
||||
|
||||
ui->processButtonEvents();
|
||||
|
||||
if (!setupNetworkConnection()) {
|
||||
if (!setupNetworkConnection())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mqtt->procesIncomingsMessages();
|
||||
|
||||
if (state == CONFIGURING && data->isConfigured()) {
|
||||
|
||||
if (state == CONFIGURING && data->isConfigured())
|
||||
{
|
||||
setStateToMeasuring();
|
||||
}
|
||||
else if (state == MEASURING && !data->isConfigured()) {
|
||||
else if (state == MEASURING && !data->isConfigured())
|
||||
{
|
||||
setStateToConfiguring();
|
||||
}
|
||||
else if (state == MEASURING) {
|
||||
DoughData::Instance()->loop();
|
||||
else if (state == MEASURING)
|
||||
{
|
||||
DataController::Instance()->loop();
|
||||
}
|
||||
else if (state == CALIBRATING) {
|
||||
else if (state == CALIBRATING)
|
||||
{
|
||||
delay(3000);
|
||||
setStateToPaused();
|
||||
}
|
||||
else if (state == PAUSED) {
|
||||
DoughData::Instance()->clearHistory();
|
||||
else if (state == PAUSED)
|
||||
{
|
||||
DataController::Instance()->clearHistory();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,18 +64,23 @@ void loop() {
|
|||
* If not, then try to setup the connection.
|
||||
* Returns true if the connection was established, false otherwise.
|
||||
*/
|
||||
bool setupNetworkConnection() {
|
||||
bool setupNetworkConnection()
|
||||
{
|
||||
static auto connectionState = CONNECTING_WIFI;
|
||||
|
||||
|
||||
auto ui = DoughUI::Instance();
|
||||
auto network = DoughNetwork::Instance();
|
||||
auto network = DoughWiFi::Instance();
|
||||
auto mqtt = DoughMQTT::Instance();
|
||||
|
||||
if (!network->isConnected()) {
|
||||
if (connectionState == CONNECTED) {
|
||||
|
||||
if (!network->isConnected())
|
||||
{
|
||||
if (connectionState == CONNECTED)
|
||||
{
|
||||
ui->log("MAIN", "s", "ERROR - Connection to WiFi network lost! Reconnecting ...");
|
||||
} else {
|
||||
ui->log("MAIN", "s", "Connecting to the WiFi network ...");
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->log("MAIN", "s", "Connecting to the WiFi network ...");
|
||||
}
|
||||
connectionState = CONNECTING_WIFI;
|
||||
ui->led1.blink()->slow();
|
||||
|
@ -75,20 +88,26 @@ bool setupNetworkConnection() {
|
|||
ui->led3.off();
|
||||
network->connect();
|
||||
}
|
||||
if (network->isConnected() && !mqtt->isConnected()) {
|
||||
if (connectionState == CONNECTED) {
|
||||
if (network->isConnected() && !mqtt->isConnected())
|
||||
{
|
||||
if (connectionState == CONNECTED)
|
||||
{
|
||||
ui->log("MAIN", "s", "ERROR - Connection to the MQTT broker lost! Reconnecting ...");
|
||||
} else {
|
||||
ui->log("MAIN", "s", "Connecting to the MQTT broker ...");
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->log("MAIN", "s", "Connecting to the MQTT broker ...");
|
||||
}
|
||||
connectionState = CONNECTING_MQTT;
|
||||
ui->led1.blink()->fast();
|
||||
ui->led2.off();
|
||||
ui->led3.off();
|
||||
mqtt->connect();
|
||||
}
|
||||
if (network->isConnected() && mqtt->isConnected()) {
|
||||
if (connectionState != CONNECTED) {
|
||||
}
|
||||
if (network->isConnected() && mqtt->isConnected())
|
||||
{
|
||||
if (connectionState != CONNECTED)
|
||||
{
|
||||
ui->log("MAIN", "s", "Connection to MQTT broker established");
|
||||
ui->led1.on();
|
||||
ui->led2.off();
|
||||
|
@ -102,20 +121,25 @@ bool setupNetworkConnection() {
|
|||
return connectionState == CONNECTED;
|
||||
}
|
||||
|
||||
void handleOnoffButtonPress() {
|
||||
if (state == MEASURING) {
|
||||
void handleOnoffButtonPress()
|
||||
{
|
||||
if (state == MEASURING)
|
||||
{
|
||||
setStateToPaused();
|
||||
}
|
||||
else if (state == PAUSED) {
|
||||
else if (state == PAUSED)
|
||||
{
|
||||
setStateToMeasuring();
|
||||
}
|
||||
}
|
||||
|
||||
void handleSetupButtonPress() {
|
||||
void handleSetupButtonPress()
|
||||
{
|
||||
setStateToCalibrating();
|
||||
}
|
||||
|
||||
void setStateToConfiguring() {
|
||||
void setStateToConfiguring()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
ui->log("MAIN", "s", "Waiting for configuration ...");
|
||||
state = CONFIGURING;
|
||||
|
@ -125,7 +149,8 @@ void setStateToConfiguring() {
|
|||
DoughMQTT::Instance()->publish("state", "configuring");
|
||||
}
|
||||
|
||||
void setStateToMeasuring() {
|
||||
void setStateToMeasuring()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
ui->log("MAIN", "s", "Starting measurements");
|
||||
state = MEASURING;
|
||||
|
@ -135,22 +160,24 @@ void setStateToMeasuring() {
|
|||
DoughMQTT::Instance()->publish("state", "measuring");
|
||||
}
|
||||
|
||||
void setStateToPaused() {
|
||||
auto ui = DoughUI::Instance();
|
||||
void setStateToPaused()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
ui->log("MAIN", "s", "Pausing measurements");
|
||||
state = PAUSED;
|
||||
ui->led1.on();
|
||||
ui->led2.on();
|
||||
ui->led3.pulse();
|
||||
DoughMQTT::Instance()->publish("state", "paused");
|
||||
DoughMQTT::Instance()->publish("state", "paused");
|
||||
}
|
||||
|
||||
void setStateToCalibrating() {
|
||||
void setStateToCalibrating()
|
||||
{
|
||||
auto ui = DoughUI::Instance();
|
||||
ui->log("MAIN", "s", "Requested device calibration");
|
||||
state = CALIBRATING;
|
||||
ui->led1.on();
|
||||
ui->led2.blink()->slow();
|
||||
ui->led3.off();
|
||||
DoughMQTT::Instance()->publish("state", "calibrating");
|
||||
DoughMQTT::Instance()->publish("state", "calibrating");
|
||||
}
|
|
@ -2,21 +2,23 @@
|
|||
#define DOUGHBOY_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DoughNetwork.h"
|
||||
#include "DoughMQTT.h"
|
||||
#include "DoughSensors.h"
|
||||
#include "DoughData.h"
|
||||
#include "DoughButton.h"
|
||||
#include "DoughUI.h"
|
||||
#include "Network/DoughWiFi.h"
|
||||
#include "Network/DoughMQTT.h"
|
||||
#include "Sensors/DoughSensors.h"
|
||||
#include "Data/DataController.h"
|
||||
#include "UI/DoughButton.h"
|
||||
#include "UI/DoughUI.h"
|
||||
#include "config.h"
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
CONNECTING_WIFI,
|
||||
CONNECTING_MQTT,
|
||||
CONNECTED
|
||||
} DoughBoyConnectionState;
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
CONFIGURING,
|
||||
MEASURING,
|
||||
PAUSED,
|
Loading…
Reference in New Issue