Code formatting and structuring the project.

This commit is contained in:
Maurice Makaay 2020-07-09 21:09:46 +02:00
parent ea8723493c
commit e66b8ccdd5
34 changed files with 1205 additions and 988 deletions

Binary file not shown.

BIN
Schematics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

242
src/Data/DataController.cpp Normal file
View File

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

71
src/Data/DataController.h Normal file
View File

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

19
src/Data/Measurement.cpp Normal file
View File

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

18
src/Data/Measurement.h Normal file
View File

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

67
src/Data/Measurements.cpp Normal file
View File

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

28
src/Data/Measurements.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,19 +4,22 @@
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
DoughMQTT* DoughMQTT::_instance = nullptr; DoughMQTT *DoughMQTT::_instance = nullptr;
/** /**
* Fetch the DoughMQTT singleton. * Fetch the DoughMQTT singleton.
*/ */
DoughMQTT* DoughMQTT::Instance() { DoughMQTT *DoughMQTT::Instance()
if (DoughMQTT::_instance == nullptr) { {
if (DoughMQTT::_instance == nullptr)
{
DoughMQTT::_instance = new DoughMQTT(); DoughMQTT::_instance = new DoughMQTT();
} }
return DoughMQTT::_instance; return DoughMQTT::_instance;
} }
DoughMQTT::DoughMQTT() { DoughMQTT::DoughMQTT()
{
_ui = DoughUI::Instance(); _ui = DoughUI::Instance();
} }
@ -24,24 +27,27 @@ DoughMQTT::DoughMQTT() {
// Setup // Setup
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void DoughMQTT::setup() { void DoughMQTT::setup()
DoughNetwork* network = DoughNetwork::Instance(); {
DoughWiFi *network = DoughWiFi::Instance();
#ifdef MQTT_DEVICE_ID #ifdef MQTT_DEVICE_ID
_mqttDeviceId = MQTT_DEVICE_ID; _mqttDeviceId = MQTT_DEVICE_ID;
#else #else
_mqttDeviceId = network->getMacAddress(); _mqttDeviceId = network->getMacAddress();
#endif #endif
_ui->log("MQTT", "ss", "Device ID = ", _mqttDeviceId); _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; _onConnect = callback;
} }
void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) { void DoughMQTT::onMessage(MQTTClientCallbackSimple callback)
{
_onMessage = callback; _onMessage = callback;
} }
@ -49,62 +55,73 @@ void DoughMQTT::onMessage(MQTTClientCallbackSimple callback) {
// Loop // Loop
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
bool DoughMQTT::isConnected() { bool DoughMQTT::isConnected()
{
return _mqttClient.connected(); return _mqttClient.connected();
} }
bool DoughMQTT::connect() { bool DoughMQTT::connect()
_ui->log("MQTT" , "sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT); {
_ui->log("MQTT", "sssi", "Broker = ", MQTT_BROKER, ":", MQTT_PORT);
_mqttClient.connect(_mqttDeviceId, MQTT_USERNAME, MQTT_PASSWORD); _mqttClient.connect(_mqttDeviceId, MQTT_USERNAME, MQTT_PASSWORD);
// Check if the connection to the broker was successful. // Check if the connection to the broker was successful.
if (!_mqttClient.connected()) { if (!_mqttClient.connected())
{
_ui->log("MQTT", "s", "ERROR - Connection to broker failed"); _ui->log("MQTT", "s", "ERROR - Connection to broker failed");
return false; return false;
} }
_mqttClient.onMessage(DoughMQTT::handleMessage); _mqttClient.onMessage(DoughMQTT::handleMessage);
if (_onConnect != nullptr) { if (_onConnect != nullptr)
{
_onConnect(this); _onConnect(this);
} }
return true; return true;
} }
void DoughMQTT::procesIncomingsMessages() { void DoughMQTT::procesIncomingsMessages()
{
_mqttClient.loop(); _mqttClient.loop();
} }
void DoughMQTT::handleMessage(String &topic, String &payload) { void DoughMQTT::handleMessage(String &topic, String &payload)
DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload); {
DoughUI::Instance()->log("MQTT", "sSsS", "<<< ", topic, " = ", payload);
DoughMQTT *mqtt = DoughMQTT::Instance(); DoughMQTT *mqtt = DoughMQTT::Instance();
if (mqtt->_onMessage != nullptr) { if (mqtt->_onMessage != nullptr)
{
int pos = topic.lastIndexOf('/'); int pos = topic.lastIndexOf('/');
if (pos != -1) { if (pos != -1)
topic.remove(0, pos+1); {
topic.remove(0, pos + 1);
mqtt->_onMessage(topic, payload); mqtt->_onMessage(topic, payload);
} }
} }
} }
void DoughMQTT::subscribe(const char* key) { void DoughMQTT::subscribe(const char *key)
{
char topic[200]; char topic[200];
snprintf(topic, sizeof(topic)/sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key); snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
DoughUI::Instance()->log("MQTT", "ss", "Subscribe to ", topic); DoughUI::Instance()->log("MQTT", "ss", "Subscribe to ", topic);
_mqttClient.subscribe(topic); _mqttClient.subscribe(topic);
} }
void DoughMQTT::publish(const char* key, const char* payload) { void DoughMQTT::publish(const char *key, const char *payload)
{
char topic[200]; char topic[200];
snprintf(topic, sizeof(topic)/sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key); snprintf(topic, sizeof(topic) / sizeof(topic[0]), "%s/%s/%s", MQTT_TOPIC_PREFIX, _mqttDeviceId, key);
DoughUI::Instance()->log("MQTT", "ssss", ">>> ", topic, " = ", payload); DoughUI::Instance()->log("MQTT", "ssss", ">>> ", topic, " = ", payload);
_mqttClient.publish(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]; char buf[16];
snprintf(buf, 16, "%d", payload); snprintf(buf, 16, "%d", payload);
publish(key, buf); publish(key, buf);
} }

40
src/Network/DoughMQTT.h Normal file
View File

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

View File

@ -1,22 +1,25 @@
#include "DoughNetwork.h" #include "Network/DoughWiFi.h"
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Constructor // Constructor
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
DoughNetwork* DoughNetwork::_instance = nullptr; DoughWiFi *DoughWiFi::_instance = nullptr;
/** /**
* Fetch the DoughNetwork singleton. * Fetch the DoughWiFi singleton.
*/ */
DoughNetwork* DoughNetwork::Instance() { DoughWiFi *DoughWiFi::Instance()
if (DoughNetwork::_instance == nullptr) { {
DoughNetwork::_instance = new DoughNetwork(); if (DoughWiFi::_instance == nullptr)
{
DoughWiFi::_instance = new DoughWiFi();
} }
return DoughNetwork::_instance; return DoughWiFi::_instance;
} }
DoughNetwork::DoughNetwork() { DoughWiFi::DoughWiFi()
{
_ui = DoughUI::Instance(); _ui = DoughUI::Instance();
} }
@ -24,57 +27,67 @@ DoughNetwork::DoughNetwork() {
// Setup // Setup
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void DoughNetwork::_setMacAddress() { void DoughWiFi::_setMacAddress()
{
byte mac[6]; byte mac[6];
WiFi.macAddress(mac); WiFi.macAddress(mac);
snprintf( 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]); "%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(); _setMacAddress();
DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress()); DoughUI::Instance()->log("NETWORK", "ss", "MAC address = ", getMacAddress());
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Loop // Loop
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
bool DoughNetwork::isConnected() { bool DoughWiFi::isConnected()
{
return WiFi.status() == WL_CONNECTED; return WiFi.status() == WL_CONNECTED;
} }
bool DoughNetwork::connect() { bool DoughWiFi::connect()
{
int status = WiFi.status(); int status = WiFi.status();
// Check if a device with a WiFi shield is used. // Check if a device with a WiFi shield is used.
if (status == WL_NO_SHIELD) { if (status == WL_NO_SHIELD)
{
_ui->log("NETWORK", "s", "ERROR - Device has no WiFi shield"); _ui->log("NETWORK", "s", "ERROR - Device has no WiFi shield");
delay(5000); delay(5000);
return false; return false;
} }
// Check if the WiFi network is already up. // Check if the WiFi network is already up.
if (status == WL_CONNECTED) { if (status == WL_CONNECTED)
{
return true; return true;
} }
// Setup the connection to the WiFi network. // Setup the connection to the WiFi network.
_ui->log("NETWORK", "ss", "WiFi network = ", WIFI_SSID); _ui->log("NETWORK", "ss", "WiFi network = ", WIFI_SSID);
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD); status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// Check if the connection attempt was successful. // Check if the connection attempt was successful.
if (status == WL_CONNECTED) { if (status == WL_CONNECTED)
{
_ui->log("NETWORK", "sa", "IP-Address = ", WiFi.localIP()); _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; return true;
} else { }
else
{
_ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")"); _ui->log("NETWORK", "sis", "ERROR - WiFi connection failed (reason: ", WiFi.reasonCode(), ")");
return false; return false;
} }
} }
char* DoughNetwork::getMacAddress() { char *DoughWiFi::getMacAddress()
{
return _macAddress; return _macAddress;
} }

27
src/Network/DoughWiFi.h Normal file
View File

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

View File

@ -20,9 +20,6 @@ DoughSensors::DoughSensors() {
_ui = DoughUI::Instance(); _ui = DoughUI::Instance();
_dht = new DHT(DHT11_DATA_PIN, DHT11); _dht = new DHT(DHT11_DATA_PIN, DHT11);
_hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN); _hcsr04 = new HCSR04(HCSR04_TRIG_PIN, HCSR04_ECHO_PIN);
temperature = 0;
humidity = 0;
distance = 0;
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -38,40 +35,38 @@ void DoughSensors::setup() {
// loop // loop
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void DoughSensors::readTemperature() { Measurement* DoughSensors::readTemperature() {
float t = _dht->readTemperature(); float t = _dht->readTemperature();
if (isnan(t)) { if (isnan(t)) {
_ui->log("SENSORS", "s", "ERROR - Temperature measurement failed"); _ui->log("SENSORS", "s", "ERROR - Temperature measurement failed");
temperatureOk = false; return Measurement::Failed();
} else { } else {
temperature = int(t); _hcsr04->setTemperature(int(t));
temperatureOk = true; auto m = new Measurement(true, int(t));
_hcsr04->setTemperature(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(); int h = _dht->readHumidity();
if (h == 0) { if (h == 0) {
_ui->log("SENSORS", "s", "ERROR - Humidity measurement failed"); _ui->log("SENSORS", "s", "ERROR - Humidity measurement failed");
humidityOk = false; return Measurement::Failed();
} else { } else {
humidity = h;
humidityOk = true;
_hcsr04->setHumidity(h); _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(); int d = _hcsr04->readDistance();
if (d == -1) { if (d == -1) {
_ui->log("SENSORS", "s", "ERROR - Distance measurement failed"); _ui->log("SENSORS", "s", "ERROR - Distance measurement failed");
distanceOk = false; return Measurement::Failed();
} else { } else {
distanceOk = true; _ui->log("SENSORS", "sis", "Distance = ", d, "mm");
distance = d; return Measurement::Ok(d);
} }
_ui->log("SENSORS", "siss", "Distance = ", distance, "mm ", (distanceOk? "[OK]" : "[ERR]"));
} }

View File

@ -2,24 +2,18 @@
#define DOUGH_SENSORS_H #define DOUGH_SENSORS_H
#include <DHT.h> #include <DHT.h>
#include "HCSR04.h" #include "Sensors/HCSR04.h"
#include "DoughUI.h" #include "UI/DoughUI.h"
#include "Data/Measurement.h"
#include "config.h" #include "config.h"
class DoughSensors { class DoughSensors {
public: public:
static DoughSensors* Instance(); static DoughSensors* Instance();
void setup(); void setup();
void readAll(); Measurement* readTemperature();
void readTemperature(); Measurement* readHumidity();
int temperature = 0; Measurement* readDistance();
bool temperatureOk = false;
void readHumidity();
int humidity = 0;
bool humidityOk = false;
void readDistance();
int distance = 0;
bool distanceOk = false;
private: private:
DoughSensors(); DoughSensors();

View File

@ -1,4 +1,4 @@
#include "HCSR04.h" #include "Sensors/HCSR04.h"
HCSR04::HCSR04(int triggerPin, int echoPin) { HCSR04::HCSR04(int triggerPin, int echoPin) {
_triggerPin = triggerPin; _triggerPin = triggerPin;
@ -33,7 +33,6 @@ int HCSR04::readDistance() {
_sortSamples(); _sortSamples();
return _computeAverage(); return _computeAverage();
} }
DoughUI::Instance()->log("HCSR04", "s", "ERROR - Not enough samples for reading distance, returning NAN");
return -1; return -1;
} }

View File

@ -26,7 +26,6 @@
#define HCSR04_INIT_HUMIDITY 50.000 #define HCSR04_INIT_HUMIDITY 50.000
#include <Arduino.h> #include <Arduino.h>
#include "DoughUI.h"
#include "config.h" #include "config.h"
class HCSR04 { class HCSR04 {

View File

@ -18,11 +18,13 @@
* // Linking the function ot button interrupts. * // Linking the function ot button interrupts.
* myButton.onInterrupt(myButtonISR); * myButton.onInterrupt(myButtonISR);
*/ */
DoughButton::DoughButton(int pin) { DoughButton::DoughButton(int pin)
{
_pin = pin; _pin = pin;
} }
void DoughButton::setup() { void DoughButton::setup()
{
pinMode(_pin, INPUT_PULLUP); pinMode(_pin, INPUT_PULLUP);
} }
@ -31,7 +33,8 @@ void DoughButton::setup() {
* interrupts. The provided isr should relay interrupts to the * interrupts. The provided isr should relay interrupts to the
* handleButtonState() method of this class (see constructor docs). * handleButtonState() method of this class (see constructor docs).
*/ */
void DoughButton::onInterrupt(DoughButtonHandler isr) { void DoughButton::onInterrupt(DoughButtonHandler isr)
{
attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE); attachInterrupt(digitalPinToInterrupt(_pin), isr, CHANGE);
} }
@ -40,86 +43,111 @@ void DoughButton::onInterrupt(DoughButtonHandler isr) {
* When specific handlers for long and/or short presses are * When specific handlers for long and/or short presses are
* configured as well, those have precedence over this one. * configured as well, those have precedence over this one.
*/ */
void DoughButton::onPress(DoughButtonHandler handler) { void DoughButton::onPress(DoughButtonHandler handler)
{
_pressHandler = handler; _pressHandler = handler;
} }
/** /**
* Assign an event handler for long button presses. * Assign an event handler for long button presses.
*/ */
void DoughButton::onLongPress(DoughButtonHandler handler) { void DoughButton::onLongPress(DoughButtonHandler handler)
{
_longPressHandler = handler; _longPressHandler = handler;
} }
/** /**
* Assign an event handler for short button presses. * Assign an event handler for short button presses.
*/ */
void DoughButton::onShortPress(DoughButtonHandler handler) { void DoughButton::onShortPress(DoughButtonHandler handler)
{
_shortPressHandler = handler; _shortPressHandler = handler;
} }
void DoughButton::loop() { void DoughButton::loop()
{
handleButtonState(); handleButtonState();
if (_state == UP_AFTER_SHORT) { if (_state == UP_AFTER_SHORT)
if (_shortPressHandler != nullptr) { {
if (_shortPressHandler != nullptr)
{
_shortPressHandler(); _shortPressHandler();
} }
else if (_pressHandler != nullptr) { else if (_pressHandler != nullptr)
{
_pressHandler(); _pressHandler();
} }
_state = READY_FOR_NEXT_PRESS; _state = READY_FOR_NEXT_PRESS;
} }
else if (_state == DOWN_LONG || _state == UP_AFTER_LONG) { else if (_state == DOWN_LONG || _state == UP_AFTER_LONG)
if (_longPressHandler != nullptr) { {
if (_longPressHandler != nullptr)
{
_longPressHandler(); _longPressHandler();
} }
else if (_pressHandler != nullptr) { else if (_pressHandler != nullptr)
{
_pressHandler(); _pressHandler();
} }
_state = READY_FOR_NEXT_PRESS; _state = READY_FOR_NEXT_PRESS;
} }
else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr) { else if (_state == DOWN && _shortPressHandler == nullptr && _longPressHandler == nullptr)
if (_pressHandler != nullptr) { {
if (_pressHandler != nullptr)
{
_pressHandler(); _pressHandler();
} }
_state = READY_FOR_NEXT_PRESS; _state = READY_FOR_NEXT_PRESS;
} }
} }
void DoughButton::clearEvents() { void DoughButton::clearEvents()
{
_state = READY_FOR_NEXT_PRESS; _state = READY_FOR_NEXT_PRESS;
} }
void DoughButton::handleButtonState() { void DoughButton::handleButtonState()
{
bool buttonIsDown = digitalRead(_pin) == 0; bool buttonIsDown = digitalRead(_pin) == 0;
bool buttonIsUp = !buttonIsDown; bool buttonIsUp = !buttonIsDown;
// When the button state has changed since the last time, then // When the button state has changed since the last time, then
// start the debounce timer. // start the debounce timer.
if (buttonIsDown != _debounceState) { if (buttonIsDown != _debounceState)
{
_debounceTimer = millis(); _debounceTimer = millis();
_debounceState = buttonIsDown; _debounceState = buttonIsDown;
} }
unsigned long interval = (millis() - _debounceTimer); unsigned long interval = (millis() - _debounceTimer);
// Only when the last state change has been stable for longer than the // Only when the last state change has been stable for longer than the
// configured debounce delay, then we accept the current state as // configured debounce delay, then we accept the current state as
// a stabilized button state. // a stabilized button state.
if (interval < BUTTON_DEBOUNCE_DELAY) { if (interval < BUTTON_DEBOUNCE_DELAY)
{
return; return;
} }
// Handle button state changes. // Handle button state changes.
if (_state == READY_FOR_NEXT_PRESS && buttonIsUp) { if (_state == READY_FOR_NEXT_PRESS && buttonIsUp)
{
_state = UP; _state = UP;
} else if (_state == UP && buttonIsDown) { }
else if (_state == UP && buttonIsDown)
{
_state = DOWN; _state = DOWN;
} else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY) { }
else if (_state == DOWN && buttonIsDown && interval > BUTTON_LONGPRESS_DELAY)
{
_state = DOWN_LONG; _state = DOWN_LONG;
} else if (_state == DOWN && buttonIsUp) { }
else if (_state == DOWN && buttonIsUp)
{
_state = UP_AFTER_SHORT; _state = UP_AFTER_SHORT;
} else if (_state == DOWN_LONG && buttonIsUp) { }
else if (_state == DOWN_LONG && buttonIsUp)
{
_state = UP_AFTER_LONG; _state = UP_AFTER_LONG;
} }
} }

44
src/UI/DoughButton.h Normal file
View File

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

View File

@ -1,92 +1,115 @@
#include "DoughLED.h" #include "DoughLED.h"
DoughLED::DoughLED(int pin) { DoughLED::DoughLED(int pin)
{
_pin = pin; _pin = pin;
} }
void DoughLED::setup() { void DoughLED::setup()
{
pinMode(_pin, OUTPUT); pinMode(_pin, OUTPUT);
_state = OFF; _state = OFF;
_setPin(LOW); _setPin(LOW);
} }
void DoughLED::loop() { void DoughLED::loop()
{
unsigned long now = millis(); unsigned long now = millis();
bool tick = (now - _timer) > _time; bool tick = (now - _timer) > _time;
if (_state == FLASH) { if (_state == FLASH)
if (tick) { {
if (tick)
{
_setPin(LOW); _setPin(LOW);
_state = OFF; _state = OFF;
} }
} }
else if (_state == DIP) { else if (_state == DIP)
if (tick) { {
if (tick)
{
_setPin(HIGH); _setPin(HIGH);
_state = ON; _state = ON;
} }
} }
else if (_state == BLINK_ON) { else if (_state == BLINK_ON)
if (_blinkStep == _blinkOnStep) { {
if (_blinkStep == _blinkOnStep)
{
_setPin(HIGH); _setPin(HIGH);
} }
if (tick) { if (tick)
{
_setPin(LOW); _setPin(LOW);
_state = BLINK_OFF; _state = BLINK_OFF;
_timer = now; _timer = now;
} }
} }
else if (_state == BLINK_OFF) { else if (_state == BLINK_OFF)
if (tick) { {
if (tick)
{
_state = BLINK_ON; _state = BLINK_ON;
_timer = now; _timer = now;
_blinkStep++; _blinkStep++;
if (_blinkStep > _blinkOfSteps) { if (_blinkStep > _blinkOfSteps)
{
_blinkStep = 1; _blinkStep = 1;
} }
} }
} }
else if (_state == PULSE) { else if (_state == PULSE)
if (tick) { {
if (tick)
{
_timer = now; _timer = now;
_time = 1; _time = 1;
_brightness += _pulseStep; _brightness += _pulseStep;
if (_brightness <= 0) { if (_brightness <= 0)
{
_time = 200; _time = 200;
_brightness = 0; _brightness = 0;
_pulseStep = -_pulseStep; _pulseStep = -_pulseStep;
} }
else if (_brightness >= 100) { else if (_brightness >= 100)
{
_brightness = 100; _brightness = 100;
_pulseStep = -_pulseStep; _pulseStep = -_pulseStep;
} }
} }
analogWrite(_pin, _brightness); analogWrite(_pin, _brightness);
} }
else if (_state == OFF) { else if (_state == OFF)
{
_setPin(LOW); _setPin(LOW);
} }
else if (_state == ON) { else if (_state == ON)
{
_setPin(HIGH); _setPin(HIGH);
} }
} }
void DoughLED::_setPin(int high_or_low) { void DoughLED::_setPin(int high_or_low)
{
_pinState = 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; _state = ON;
loop(); loop();
} }
void DoughLED::off() { void DoughLED::off()
{
_state = OFF; _state = OFF;
loop(); loop();
} }
DoughLED* DoughLED::flash() { DoughLED *DoughLED::flash()
{
_setPin(HIGH); _setPin(HIGH);
_state = FLASH; _state = FLASH;
_timer = millis(); _timer = millis();
@ -95,11 +118,13 @@ DoughLED* DoughLED::flash() {
return this; return this;
} }
DoughLED* DoughLED::blink() { DoughLED *DoughLED::blink()
{
return blink(1, 1); return blink(1, 1);
} }
DoughLED* DoughLED::dip() { DoughLED *DoughLED::dip()
{
_setPin(LOW); _setPin(LOW);
_state = DIP; _state = DIP;
_timer = millis(); _timer = millis();
@ -108,7 +133,8 @@ DoughLED* DoughLED::dip() {
return this; return this;
} }
DoughLED* DoughLED::blink(int onStep, int ofSteps) { DoughLED *DoughLED::blink(int onStep, int ofSteps)
{
_blinkOnStep = onStep; _blinkOnStep = onStep;
_blinkOfSteps = ofSteps; _blinkOfSteps = ofSteps;
_blinkStep = 1; _blinkStep = 1;
@ -118,25 +144,30 @@ DoughLED* DoughLED::blink(int onStep, int ofSteps) {
return this; return this;
} }
void DoughLED::pulse() { void DoughLED::pulse()
{
_state = PULSE; _state = PULSE;
_brightness = 0; _brightness = 0;
_pulseStep = +8; _pulseStep = +8;
_time = 1; _time = 1;
} }
void DoughLED::slow() { void DoughLED::slow()
{
_time = LED_TRANSITION_TIME_SLOW; _time = LED_TRANSITION_TIME_SLOW;
} }
void DoughLED::fast() { void DoughLED::fast()
{
_time = LED_TRANSITION_TIME_FAST; _time = LED_TRANSITION_TIME_FAST;
} }
bool DoughLED::isOn() { bool DoughLED::isOn()
{
return _pinState == HIGH; return _pinState == HIGH;
} }
bool DoughLED::isOff() { bool DoughLED::isOff()
{
return _pinState == LOW; return _pinState == LOW;
} }

54
src/UI/DoughLED.h Normal file
View File

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

231
src/UI/DoughUI.cpp Normal file
View File

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

47
src/UI/DoughUI.h Normal file
View File

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

View File

@ -1,20 +1,20 @@
// The digital pin to which the DATA pin of the DHT11 // The digital pin to which the DATA pin of the DHT11
// temperature/humidity sensor is connected. // 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 digital pins to which the TRIG and ECHO pins of
// the HCSR04 distance sensor are connected. // the HCSR04 distance sensor are connected.
#define HCSR04_TRIG_PIN 4 #define HCSR04_TRIG_PIN 4
#define HCSR04_ECHO_PIN 5 #define HCSR04_ECHO_PIN 5
// The digital pins to which the three LEDs are connected. // The digital pins to which the three LEDs are connected.
#define LED1_PIN 8 #define LED1_PIN 8
#define LED2_PIN 7 #define LED2_PIN 7
#define LED3_PIN 6 #define LED3_PIN 6
// The digital pins to which the push buttons are connected. // The digital pins to which the push buttons are connected.
#define ONOFF_BUTTON_PIN 2 #define ONOFF_BUTTON_PIN 2
#define SETUP_BUTTON_PIN 3 #define SETUP_BUTTON_PIN 3
// The network configuration and possibly overrides for the above // The network configuration and possibly overrides for the above
// definitions are stored in a separate header file, which is // definitions are stored in a separate header file, which is

View File

@ -1,12 +1,12 @@
// WPA2 WiFi connection configuration. // WPA2 WiFi connection configuration.
#define WIFI_SSID "<SSID network name>" #define WIFI_SSID "<SSID network name>"
#define WIFI_PASSWORD "<network password>" #define WIFI_PASSWORD "<network password>"
// MQTT broker configuration. // MQTT broker configuration.
#define MQTT_BROKER "<IP or hostname>" #define MQTT_BROKER "<IP or hostname>"
#define MQTT_PORT 1883 #define MQTT_PORT 1883
#define MQTT_USERNAME "<mqtt username>" #define MQTT_USERNAME "<mqtt username>"
#define MQTT_PASSWORD "<mqtt password>" #define MQTT_PASSWORD "<mqtt password>"
// The prefix to use for the MQTT publishing topic. // The prefix to use for the MQTT publishing topic.
#define MQTT_TOPIC_PREFIX "sensors/doughboy" #define MQTT_TOPIC_PREFIX "sensors/doughboy"

View File

@ -1,4 +1,4 @@
#include "DoughBoy.h" #include "main.h"
// TOOD: implement the calibration logic // 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. // 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; DoughBoyState state = CONFIGURING;
void setup() { void setup()
{
DoughSensors::Instance()->setup(); DoughSensors::Instance()->setup();
DoughNetwork::Instance()->setup(); DoughWiFi::Instance()->setup();
DoughMQTT::Instance()->setup(); DoughMQTT::Instance()->setup();
DoughData::Instance()->setup(); DataController::Instance()->setup();
auto ui = DoughUI::Instance(); auto ui = DoughUI::Instance();
ui->setup(); ui->setup();
ui->onoffButton.onPress(handleOnoffButtonPress); ui->onoffButton.onPress(handleOnoffButtonPress);
@ -20,34 +21,41 @@ void setup() {
ui->log("MAIN", "s", "Initialization completed, starting device"); ui->log("MAIN", "s", "Initialization completed, starting device");
} }
void loop() { void loop()
{
auto ui = DoughUI::Instance(); auto ui = DoughUI::Instance();
auto data = DoughData::Instance(); auto data = DataController::Instance();
auto mqtt = DoughMQTT::Instance(); auto mqtt = DoughMQTT::Instance();
ui->processButtonEvents(); ui->processButtonEvents();
if (!setupNetworkConnection()) { if (!setupNetworkConnection())
{
return; return;
} }
mqtt->procesIncomingsMessages(); mqtt->procesIncomingsMessages();
if (state == CONFIGURING && data->isConfigured()) { if (state == CONFIGURING && data->isConfigured())
{
setStateToMeasuring(); setStateToMeasuring();
} }
else if (state == MEASURING && !data->isConfigured()) { else if (state == MEASURING && !data->isConfigured())
{
setStateToConfiguring(); setStateToConfiguring();
} }
else if (state == MEASURING) { else if (state == MEASURING)
DoughData::Instance()->loop(); {
DataController::Instance()->loop();
} }
else if (state == CALIBRATING) { else if (state == CALIBRATING)
{
delay(3000); delay(3000);
setStateToPaused(); setStateToPaused();
} }
else if (state == PAUSED) { else if (state == PAUSED)
DoughData::Instance()->clearHistory(); {
DataController::Instance()->clearHistory();
} }
} }
@ -56,18 +64,23 @@ void loop() {
* If not, then try to setup the connection. * If not, then try to setup the connection.
* Returns true if the connection was established, false otherwise. * Returns true if the connection was established, false otherwise.
*/ */
bool setupNetworkConnection() { bool setupNetworkConnection()
{
static auto connectionState = CONNECTING_WIFI; static auto connectionState = CONNECTING_WIFI;
auto ui = DoughUI::Instance(); auto ui = DoughUI::Instance();
auto network = DoughNetwork::Instance(); auto network = DoughWiFi::Instance();
auto mqtt = DoughMQTT::Instance(); auto mqtt = DoughMQTT::Instance();
if (!network->isConnected()) { if (!network->isConnected())
if (connectionState == CONNECTED) { {
if (connectionState == CONNECTED)
{
ui->log("MAIN", "s", "ERROR - Connection to WiFi network lost! Reconnecting ..."); 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; connectionState = CONNECTING_WIFI;
ui->led1.blink()->slow(); ui->led1.blink()->slow();
@ -75,20 +88,26 @@ bool setupNetworkConnection() {
ui->led3.off(); ui->led3.off();
network->connect(); network->connect();
} }
if (network->isConnected() && !mqtt->isConnected()) { if (network->isConnected() && !mqtt->isConnected())
if (connectionState == CONNECTED) { {
if (connectionState == CONNECTED)
{
ui->log("MAIN", "s", "ERROR - Connection to the MQTT broker lost! Reconnecting ..."); 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; connectionState = CONNECTING_MQTT;
ui->led1.blink()->fast(); ui->led1.blink()->fast();
ui->led2.off(); ui->led2.off();
ui->led3.off(); ui->led3.off();
mqtt->connect(); mqtt->connect();
} }
if (network->isConnected() && mqtt->isConnected()) { if (network->isConnected() && mqtt->isConnected())
if (connectionState != CONNECTED) { {
if (connectionState != CONNECTED)
{
ui->log("MAIN", "s", "Connection to MQTT broker established"); ui->log("MAIN", "s", "Connection to MQTT broker established");
ui->led1.on(); ui->led1.on();
ui->led2.off(); ui->led2.off();
@ -102,20 +121,25 @@ bool setupNetworkConnection() {
return connectionState == CONNECTED; return connectionState == CONNECTED;
} }
void handleOnoffButtonPress() { void handleOnoffButtonPress()
if (state == MEASURING) { {
if (state == MEASURING)
{
setStateToPaused(); setStateToPaused();
} }
else if (state == PAUSED) { else if (state == PAUSED)
{
setStateToMeasuring(); setStateToMeasuring();
} }
} }
void handleSetupButtonPress() { void handleSetupButtonPress()
{
setStateToCalibrating(); setStateToCalibrating();
} }
void setStateToConfiguring() { void setStateToConfiguring()
{
auto ui = DoughUI::Instance(); auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Waiting for configuration ..."); ui->log("MAIN", "s", "Waiting for configuration ...");
state = CONFIGURING; state = CONFIGURING;
@ -125,7 +149,8 @@ void setStateToConfiguring() {
DoughMQTT::Instance()->publish("state", "configuring"); DoughMQTT::Instance()->publish("state", "configuring");
} }
void setStateToMeasuring() { void setStateToMeasuring()
{
auto ui = DoughUI::Instance(); auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Starting measurements"); ui->log("MAIN", "s", "Starting measurements");
state = MEASURING; state = MEASURING;
@ -135,22 +160,24 @@ void setStateToMeasuring() {
DoughMQTT::Instance()->publish("state", "measuring"); DoughMQTT::Instance()->publish("state", "measuring");
} }
void setStateToPaused() { void setStateToPaused()
auto ui = DoughUI::Instance(); {
auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Pausing measurements"); ui->log("MAIN", "s", "Pausing measurements");
state = PAUSED; state = PAUSED;
ui->led1.on(); ui->led1.on();
ui->led2.on(); ui->led2.on();
ui->led3.pulse(); ui->led3.pulse();
DoughMQTT::Instance()->publish("state", "paused"); DoughMQTT::Instance()->publish("state", "paused");
} }
void setStateToCalibrating() { void setStateToCalibrating()
{
auto ui = DoughUI::Instance(); auto ui = DoughUI::Instance();
ui->log("MAIN", "s", "Requested device calibration"); ui->log("MAIN", "s", "Requested device calibration");
state = CALIBRATING; state = CALIBRATING;
ui->led1.on(); ui->led1.on();
ui->led2.blink()->slow(); ui->led2.blink()->slow();
ui->led3.off(); ui->led3.off();
DoughMQTT::Instance()->publish("state", "calibrating"); DoughMQTT::Instance()->publish("state", "calibrating");
} }

View File

@ -2,21 +2,23 @@
#define DOUGHBOY_H #define DOUGHBOY_H
#include <Arduino.h> #include <Arduino.h>
#include "DoughNetwork.h" #include "Network/DoughWiFi.h"
#include "DoughMQTT.h" #include "Network/DoughMQTT.h"
#include "DoughSensors.h" #include "Sensors/DoughSensors.h"
#include "DoughData.h" #include "Data/DataController.h"
#include "DoughButton.h" #include "UI/DoughButton.h"
#include "DoughUI.h" #include "UI/DoughUI.h"
#include "config.h" #include "config.h"
typedef enum { typedef enum
{
CONNECTING_WIFI, CONNECTING_WIFI,
CONNECTING_MQTT, CONNECTING_MQTT,
CONNECTED CONNECTED
} DoughBoyConnectionState; } DoughBoyConnectionState;
typedef enum { typedef enum
{
CONFIGURING, CONFIGURING,
MEASURING, MEASURING,
PAUSED, PAUSED,