Compare commits
10 Commits
c59281a525
...
1bde26489b
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bde26489b | |||
| 0cc485577c | |||
| 77890778d4 | |||
| de4bdfa98b | |||
| b7b9ea61b5 | |||
| b27986294c | |||
| dd654ee3ac | |||
| 93ed16dda0 | |||
| 0c77595ae5 | |||
| 110f454e01 |
@ -2,6 +2,9 @@
|
|||||||
#define SENSOR3_PATRIX_H
|
#define SENSOR3_PATRIX_H
|
||||||
|
|
||||||
#include "base.h"
|
#include "base.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "console.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
void patrixSetup();
|
void patrixSetup();
|
||||||
|
|
||||||
|
|||||||
@ -65,6 +65,34 @@ void configPutDouble(const char *name, double value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t configGetUint64_t(const char *name, uint64_t fallback) {
|
||||||
|
if (!config.containsKey(name)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return config[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
void configPutUint64_t(const char *name, uint64_t value) {
|
||||||
|
if (config[name] != value) {
|
||||||
|
config[name] = value;
|
||||||
|
lastChangeMillis = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t configGetUint8_t(const char *name, uint8_t fallback) {
|
||||||
|
if (!config.containsKey(name)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return config[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
void configPutUint8_t(const char *name, uint8_t value) {
|
||||||
|
if (config[name] != value) {
|
||||||
|
config[name] = value;
|
||||||
|
lastChangeMillis = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void configPrint() {
|
void configPrint() {
|
||||||
info("Config (%s):", lastChangeMillis == 0 ? "PERSISTED" : "TRANSIENT");
|
info("Config (%s):", lastChangeMillis == 0 ? "PERSISTED" : "TRANSIENT");
|
||||||
for (JsonPair pair: config.as<JsonObject>()) {
|
for (JsonPair pair: config.as<JsonObject>()) {
|
||||||
|
|||||||
@ -9,16 +9,27 @@ void configLoop();
|
|||||||
|
|
||||||
void configReset();
|
void configReset();
|
||||||
|
|
||||||
void configLoaded();
|
|
||||||
|
|
||||||
String configGetString(const char *name, const char *fallback, bool allowEmpty);
|
String configGetString(const char *name, const char *fallback, bool allowEmpty);
|
||||||
|
|
||||||
void configPutString(const char *name, const char *value);
|
void configPutString(const char *name, const char *value);
|
||||||
|
|
||||||
|
|
||||||
double configGetDouble(const char *name, double fallback);
|
double configGetDouble(const char *name, double fallback);
|
||||||
|
|
||||||
void configPutDouble(const char *name, double value);
|
void configPutDouble(const char *name, double value);
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t configGetUint64_t(const char *name, uint64_t fallback);
|
||||||
|
|
||||||
|
void configPutUint64_t(const char *name, uint64_t value);
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t configGetUint8_t(const char *name, uint8_t fallback);
|
||||||
|
|
||||||
|
void configPutUint8_t(const char *name, uint8_t value);
|
||||||
|
|
||||||
|
|
||||||
void configPrint();
|
void configPrint();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -67,11 +67,11 @@ void consoleLoop() {
|
|||||||
void consoleHandle(char *cmd) {
|
void consoleHandle(char *cmd) {
|
||||||
char *first = strtok(cmd, " ");
|
char *first = strtok(cmd, " ");
|
||||||
if (first == nullptr) {
|
if (first == nullptr) {
|
||||||
_usage();
|
consolePrintUsage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (strcmp(first, "help") == 0) {
|
if (strcmp(first, "help") == 0) {
|
||||||
_usage();
|
consolePrintUsage();
|
||||||
} else if (strcmp(first, "wifi") == 0) {
|
} else if (strcmp(first, "wifi") == 0) {
|
||||||
_wifi();
|
_wifi();
|
||||||
} else if (strcmp(first, "mqtt") == 0) {
|
} else if (strcmp(first, "mqtt") == 0) {
|
||||||
@ -84,12 +84,12 @@ void consoleHandle(char *cmd) {
|
|||||||
_debug();
|
_debug();
|
||||||
} else if (strcmp(first, "config_reset") == 0) {
|
} else if (strcmp(first, "config_reset") == 0) {
|
||||||
configReset();
|
configReset();
|
||||||
} else if (!patrix_command((char *) cmd)) {
|
} else if (!patrixCommand((char *) cmd)) {
|
||||||
info("Unknown command: %s", cmd);
|
info("Unknown command: %s", cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _usage() {
|
void consolePrintUsage() {
|
||||||
info(R"(USAGE:
|
info(R"(USAGE:
|
||||||
help
|
help
|
||||||
info
|
info
|
||||||
@ -120,7 +120,7 @@ void _reboot() {
|
|||||||
void _mqtt() {
|
void _mqtt() {
|
||||||
char *sub = strtok(nullptr, " ");
|
char *sub = strtok(nullptr, " ");
|
||||||
if (sub == nullptr) {
|
if (sub == nullptr) {
|
||||||
_usage();
|
consolePrintUsage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (strcmp(sub, "reconnect") == 0) {
|
if (strcmp(sub, "reconnect") == 0) {
|
||||||
@ -134,7 +134,7 @@ void _mqtt() {
|
|||||||
void _wifi() {
|
void _wifi() {
|
||||||
char *sub = strtok(nullptr, " ");
|
char *sub = strtok(nullptr, " ");
|
||||||
if (sub == nullptr) {
|
if (sub == nullptr) {
|
||||||
_usage();
|
consolePrintUsage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (strcmp(sub, "reconnect") == 0) {
|
if (strcmp(sub, "reconnect") == 0) {
|
||||||
@ -150,7 +150,7 @@ void _wifi() {
|
|||||||
void _setConfigString(const char *name, bool allowEmpty) {
|
void _setConfigString(const char *name, bool allowEmpty) {
|
||||||
char *value = strtok(nullptr, "");
|
char *value = strtok(nullptr, "");
|
||||||
if (value == nullptr) {
|
if (value == nullptr) {
|
||||||
_usage();
|
consolePrintUsage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!allowEmpty && strcmp(value, "") == 0) {
|
if (!allowEmpty && strcmp(value, "") == 0) {
|
||||||
@ -248,3 +248,49 @@ const char *getFlashChipMode() {
|
|||||||
return "[???]";
|
return "[???]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cmdReadDouble(const char *name, double *destinationPtr, double min, double max) {
|
||||||
|
const char *valueStr = strtok(nullptr, "");
|
||||||
|
if (valueStr == nullptr) {
|
||||||
|
consolePrintUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double value = strtod(valueStr, nullptr);
|
||||||
|
if (isnan(value)) {
|
||||||
|
error("Failed to parse double for \"%s\": %s", name, valueStr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((!isnan(min) && value < min) || (!isnan(max) && value > max)) {
|
||||||
|
error("Value out of range for \"%s\" [%f..%f]: %f", name, min, max, value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*destinationPtr != value) {
|
||||||
|
*destinationPtr = value;
|
||||||
|
info("Value for \"%s\" set to: %f", name, value);
|
||||||
|
configPutDouble(name, value);
|
||||||
|
} else {
|
||||||
|
info("Value for \"%s\" unchanged: %f", name, value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmdReadU64(const char *name, uint64_t *destinationPtr, uint64_t min, uint64_t max) {
|
||||||
|
const char *valueStr = strtok(nullptr, "");
|
||||||
|
if (valueStr == nullptr) {
|
||||||
|
consolePrintUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint64_t value = strtoll(valueStr, nullptr, 10);
|
||||||
|
if ((min > 0 && value < min) || (max > 0 && value > max)) {
|
||||||
|
error("Value out of range for \"%s\" [%f..%f]: %f", name, min, max, value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*destinationPtr != value) {
|
||||||
|
*destinationPtr = value;
|
||||||
|
info("Value for \"%s\" set to: %f", name, value);
|
||||||
|
configPutUint64_t(name, value);
|
||||||
|
} else {
|
||||||
|
info("Value for \"%s\" unchanged: %f", name, value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
#ifndef SENSOR3_CONSOLE_H
|
#ifndef SENSOR3_CONSOLE_H
|
||||||
#define SENSOR3_CONSOLE_H
|
#define SENSOR3_CONSOLE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
void consoleSetup();
|
void consoleSetup();
|
||||||
|
|
||||||
void consoleLoop();
|
void consoleLoop();
|
||||||
|
|
||||||
void consoleHandle(char *cmd);
|
void consoleHandle(char *cmd);
|
||||||
|
|
||||||
bool patrix_command(char *cmd);
|
bool patrixCommand(char *cmd);
|
||||||
|
|
||||||
void _usage();
|
void consolePrintUsage();
|
||||||
|
|
||||||
|
bool cmdReadDouble(const char *name, double *destinationPtr, double min, double max);
|
||||||
|
|
||||||
|
bool cmdReadU64(const char *name, uint64_t *destinationPtr, uint64_t min, uint64_t max);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -3,9 +3,17 @@
|
|||||||
|
|
||||||
#include "base.h"
|
#include "base.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include "wifi.h"
|
||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
struct IData {
|
struct IData {
|
||||||
|
|
||||||
|
time_t time;
|
||||||
|
|
||||||
|
explicit IData(time_t time) : time(time) {
|
||||||
|
// -
|
||||||
|
}
|
||||||
|
|
||||||
virtual void toJson(JsonObject &json) const = 0;
|
virtual void toJson(JsonObject &json) const = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -18,16 +26,13 @@ class Cache {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
struct Entry {
|
T buffer[size];
|
||||||
time_t timestamp = 0;
|
|
||||||
T data;
|
|
||||||
};
|
|
||||||
|
|
||||||
Entry buffer[size];
|
T *bufferRead = buffer;
|
||||||
|
|
||||||
Entry *bufferRead = buffer;
|
T *bufferWrite = buffer;
|
||||||
|
|
||||||
Entry *bufferWrite = buffer;
|
T last{};
|
||||||
|
|
||||||
size_t usage = 0;
|
size_t usage = 0;
|
||||||
|
|
||||||
@ -38,25 +43,26 @@ public:
|
|||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
|
|
||||||
bool add(const time_t timestamp, const T &data) {
|
bool add(T data) {
|
||||||
if (usage >= size) {
|
if (usage < size && data.needsPublish(last)) {
|
||||||
return false;
|
last = data;
|
||||||
}
|
*bufferWrite = data;
|
||||||
bufferWrite->timestamp = timestamp;
|
|
||||||
bufferWrite->data = data;
|
|
||||||
bufferWrite = (bufferWrite - buffer + 1) % size + buffer;
|
bufferWrite = (bufferWrite - buffer + 1) % size + buffer;
|
||||||
usage++;
|
usage++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (usage == 0 || !isTimeSet()) {
|
if (usage == 0 || !isTimeSet()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JsonDocument json;
|
JsonDocument doc;
|
||||||
json["timestamp"] = correctTime(bufferRead->timestamp);
|
JsonObject json = doc.to<JsonObject>();
|
||||||
JsonObject data = json["data"].to<JsonObject>();
|
correctTime(bufferRead->time);
|
||||||
bufferRead->data.toJson(data);
|
json["timestamp"] = bufferRead->time;
|
||||||
|
bufferRead->toJson(json);
|
||||||
if (mqttPublishData(json)) {
|
if (mqttPublishData(json)) {
|
||||||
bufferRead = (bufferRead - buffer + 1) % size + buffer;
|
bufferRead = (bufferRead - buffer + 1) % size + buffer;
|
||||||
usage--;
|
usage--;
|
||||||
|
|||||||
28
lib/patrix/sensors/DHT.h
Normal file
28
lib/patrix/sensors/DHT.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef SENSOR3_DHT_H
|
||||||
|
#define SENSOR3_DHT_H
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
|
||||||
|
class DHT {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const int pin;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit DHT(const int pin) : pin(pin) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(float *temperatureCelsius, uint8_t *humidityRelativePercent, float *humidityAbsoluteMgL) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -15,36 +15,22 @@ private:
|
|||||||
|
|
||||||
uint64_t address;
|
uint64_t address;
|
||||||
|
|
||||||
const double valueThreshold;
|
|
||||||
|
|
||||||
const time_t timeoutSec;
|
const time_t timeoutSec;
|
||||||
|
|
||||||
const time_t minIntervalMillis;
|
|
||||||
|
|
||||||
double lastSentValue = NAN;
|
|
||||||
|
|
||||||
unsigned long lastSentMillis = 0;
|
|
||||||
|
|
||||||
double lastValue = NAN;
|
double lastValue = NAN;
|
||||||
|
|
||||||
time_t lastTimestamp = 0;
|
time_t lastTimestamp = 0;
|
||||||
|
|
||||||
double lastValidValue = NAN;
|
|
||||||
|
|
||||||
time_t lastValidTimestamp = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DallasSensor(Dallas &sensors, const uint64_t address, const double valueThreshold, const time_t timeoutSec, const time_t minIntervalMillis) :
|
DallasSensor(Dallas &sensors, const uint64_t address, const time_t timeoutSec) :
|
||||||
sensors(sensors),
|
sensors(sensors),
|
||||||
address(address),
|
address(address),
|
||||||
valueThreshold(valueThreshold),
|
timeoutSec(timeoutSec) {
|
||||||
timeoutSec(timeoutSec),
|
|
||||||
minIntervalMillis(minIntervalMillis) {
|
|
||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loop() {
|
void loop() {
|
||||||
const time_t now = getTime();
|
const time_t now = getTime();
|
||||||
if (now - lastTimestamp > timeoutSec) {
|
if (now - lastTimestamp > timeoutSec) {
|
||||||
lastTimestamp = 0;
|
lastTimestamp = 0;
|
||||||
@ -53,21 +39,9 @@ public:
|
|||||||
if (sensors.isConverted()) {
|
if (sensors.isConverted()) {
|
||||||
const double value = sensors.read(address);
|
const double value = sensors.read(address);
|
||||||
const time_t timestamp = sensors.getTimestamp();
|
const time_t timestamp = sensors.getTimestamp();
|
||||||
const unsigned long millisNow = millis();
|
|
||||||
const bool doPublish = !isnan(value) && (abs(lastSentValue - value) >= valueThreshold || millisNow - lastSentMillis >= minIntervalMillis);
|
|
||||||
if (doPublish) {
|
|
||||||
lastSentValue = value;
|
|
||||||
lastSentMillis = millisNow;
|
|
||||||
}
|
|
||||||
lastValue = value;
|
lastValue = value;
|
||||||
lastTimestamp = timestamp;
|
lastTimestamp = timestamp;
|
||||||
if (!isnan(value)) {
|
|
||||||
lastValidValue = value;
|
|
||||||
lastValidTimestamp = timestamp;
|
|
||||||
}
|
}
|
||||||
return doPublish;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] double getLastValue() const {
|
[[nodiscard]] double getLastValue() const {
|
||||||
@ -78,14 +52,6 @@ public:
|
|||||||
return lastTimestamp;
|
return lastTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] double getLastValidValue() const {
|
|
||||||
return lastValidValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] time_t getLastValidTimestamp() const {
|
|
||||||
return lastValidTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
22
lib/patrix/sensors/Moisture.h
Normal file
22
lib/patrix/sensors/Moisture.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifndef SENSOR3_MOISTURE_H
|
||||||
|
#define SENSOR3_MOISTURE_H
|
||||||
|
|
||||||
|
class Moisture {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const int pin;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Moisture(const int pin) : pin(pin) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(float *value) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
78
lib/patrix/sensors/Output.h
Normal file
78
lib/patrix/sensors/Output.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#ifndef SENSOR3_OUTPUT_H
|
||||||
|
#define SENSOR3_OUTPUT_H
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
|
||||||
|
class Output {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const int pin;
|
||||||
|
|
||||||
|
const bool invert;
|
||||||
|
|
||||||
|
uint64_t onSince = 0;
|
||||||
|
|
||||||
|
uint64_t offSince = 0;
|
||||||
|
|
||||||
|
uint64_t totalOnMillis = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Output(const int pin, const bool invert, const bool initial) : pin(pin), invert(invert) {
|
||||||
|
pinMode(pin, OUTPUT);
|
||||||
|
set(initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
uint64_t now = getUptimeMillis();
|
||||||
|
static uint64_t last = now;
|
||||||
|
if (isOn()) {
|
||||||
|
totalOnMillis += now - last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool isOn() const {
|
||||||
|
return (digitalRead(pin) == HIGH) ^ invert;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on() {
|
||||||
|
set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void off() {
|
||||||
|
set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(const bool newState) {
|
||||||
|
if (isOn() != newState) {
|
||||||
|
digitalWrite(pin, newState ^ invert ? HIGH : LOW);
|
||||||
|
if (newState) {
|
||||||
|
onSince = getUptimeMillis();
|
||||||
|
} else {
|
||||||
|
offSince = getUptimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t getOnSince() const {
|
||||||
|
if (!isOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return getUptimeMillis() - onSince;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t getOffSince() const {
|
||||||
|
if (isOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return getUptimeMillis() - offSince;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t getTotalOnMillis() const {
|
||||||
|
return totalOnMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -195,18 +195,10 @@ time_t getTime() {
|
|||||||
return epochSeconds;
|
return epochSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t correctTime(const time_t value) {
|
void correctTime(time_t &value) {
|
||||||
if (!timeSet) {
|
if (timeSet && value < MIN_EPOCH_SECONDS) {
|
||||||
return value;
|
value = getTime() - preTimeOffset + value;
|
||||||
}
|
}
|
||||||
if (value < MIN_EPOCH_SECONDS) {
|
|
||||||
return getTime() - preTimeOffset + value;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isOlderThan(time_t time, time_t seconds) {
|
|
||||||
return getTime() > correctTime(time) + seconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void getDateTime(char *buffer, size_t size) {
|
void getDateTime(char *buffer, size_t size) {
|
||||||
|
|||||||
@ -15,9 +15,7 @@ bool isTimeSet();
|
|||||||
|
|
||||||
time_t getTime();
|
time_t getTime();
|
||||||
|
|
||||||
time_t correctTime(time_t value);
|
void correctTime(time_t &value);
|
||||||
|
|
||||||
bool isOlderThan(time_t time, time_t seconds);
|
|
||||||
|
|
||||||
void getDateTime(char *buffer, size_t size);
|
void getDateTime(char *buffer, size_t size);
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ lib_deps = https://github.com/milesburton/Arduino-Temperature-Control-Library
|
|||||||
Wire
|
Wire
|
||||||
https://github.com/phassel/ArduPID/
|
https://github.com/phassel/ArduPID/
|
||||||
https://github.com/wayoda/LedControl
|
https://github.com/wayoda/LedControl
|
||||||
|
https://github.com/me-no-dev/ESPAsyncWebServer
|
||||||
|
|
||||||
[env:TEST32]
|
[env:TEST32]
|
||||||
upload_port = 10.42.0.66
|
upload_port = 10.42.0.66
|
||||||
@ -30,7 +31,7 @@ monitor_port = ${COMMON.monitor_port}
|
|||||||
monitor_speed = ${COMMON.monitor_speed}
|
monitor_speed = ${COMMON.monitor_speed}
|
||||||
monitor_filters = ${COMMON.monitor_filters}
|
monitor_filters = ${COMMON.monitor_filters}
|
||||||
lib_deps = ${COMMON.lib_deps}
|
lib_deps = ${COMMON.lib_deps}
|
||||||
build_flags = -D TEST32 -D HOSTNAME=\"TEST32\" -D WIFI_SSID=\"${COMMON.WIFI_SSID}\" -D WIFI_PKEY=\"${COMMON.WIFI_PKEY}\" -D OTA_PASSWORD=\"OtaAuthPatrixTEST32\" -D BOOT_DELAY=false -D DEBUG_LOG=false
|
build_flags = -D TEST32 -D HOSTNAME=\"TEST32\" -D WIFI_SSID=\"${COMMON.WIFI_SSID}\" -D WIFI_PKEY=\"${COMMON.WIFI_PKEY}\" -D OTA_PASSWORD=\"OtaAuthPatrixTEST32\" -D BOOT_DELAY=false -D DEBUG_LOG=true
|
||||||
|
|
||||||
[env:TEST8266]
|
[env:TEST8266]
|
||||||
upload_port = 10.0.0.162
|
upload_port = 10.0.0.162
|
||||||
@ -60,4 +61,19 @@ monitor_port = ${COMMON.monitor_port}
|
|||||||
monitor_speed = ${COMMON.monitor_speed}
|
monitor_speed = ${COMMON.monitor_speed}
|
||||||
monitor_filters = esp8266_exception_decoder
|
monitor_filters = esp8266_exception_decoder
|
||||||
lib_deps = ${COMMON.lib_deps}
|
lib_deps = ${COMMON.lib_deps}
|
||||||
build_flags = -D FERMENTER -D HOSTNAME=\"Fermenter\" -D WIFI_SSID=\"${COMMON.WIFI_SSID}\" -D WIFI_PKEY=\"${COMMON.WIFI_PKEY}\" -D OTA_PASSWORD=\"OtaAuthPatrixFermenter\" -D BOOT_DELAY=true -D DEBUG_LOG=true
|
build_flags = -D FERMENTER -D HOSTNAME=\"Fermenter\" -D WIFI_SSID=\"${COMMON.WIFI_SSID}\" -D WIFI_PKEY=\"${COMMON.WIFI_PKEY}\" -D OTA_PASSWORD=\"OtaAuthPatrixFermenter\" -D BOOT_DELAY=true -D DEBUG_LOG=false
|
||||||
|
|
||||||
|
[env:Greenhouse]
|
||||||
|
;upload_port = 10.0.0.
|
||||||
|
;upload_flags = --auth=OtaAuthPatrixGreenhouse
|
||||||
|
;upload_protocol = ${COMMON.ota_protocol}
|
||||||
|
upload_port = ${COMMON.usb_port}
|
||||||
|
upload_speed = ${COMMON.usb_speed}
|
||||||
|
platform = espressif8266
|
||||||
|
board = esp12e
|
||||||
|
framework = ${COMMON.framework}
|
||||||
|
monitor_port = ${COMMON.monitor_port}
|
||||||
|
monitor_speed = ${COMMON.monitor_speed}
|
||||||
|
monitor_filters = esp8266_exception_decoder
|
||||||
|
lib_deps = ${COMMON.lib_deps}
|
||||||
|
build_flags = -D GREENHOUSE -D HOSTNAME=\"Greenhouse\" -D WIFI_SSID=\"${COMMON.WIFI_SSID}\" -D WIFI_PKEY=\"${COMMON.WIFI_PKEY}\" -D OTA_PASSWORD=\"OtaAuthPatrixGreenhouse\" -D BOOT_DELAY=true -D DEBUG_LOG=false
|
||||||
|
|||||||
29
src/Fermenter/FementerDisplay.cpp
Normal file
29
src/Fermenter/FementerDisplay.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "FementerDisplay.h"
|
||||||
|
|
||||||
|
#include "LedControl.h"
|
||||||
|
|
||||||
|
LedControl lc = LedControl(13, 14, 15, 1);
|
||||||
|
|
||||||
|
void writeDecimal(int *digit, double value) {
|
||||||
|
const int integer = (int) value;
|
||||||
|
const int decimal = (int) ((value - integer) * 10) % 10;
|
||||||
|
lc.setDigit(0, (*digit)++, decimal, false);
|
||||||
|
lc.setDigit(0, (*digit)++, integer % 10, true);
|
||||||
|
lc.setDigit(0, (*digit)++, integer / 10 % 10, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayLoop(double temperature, double target) {
|
||||||
|
static unsigned long lastDisplayInit = 0;
|
||||||
|
if (lastDisplayInit == 0 || millis() - lastDisplayInit > 60 * 60 * 1000) {
|
||||||
|
lastDisplayInit = millis();
|
||||||
|
lc.shutdown(0, true);
|
||||||
|
lc.shutdown(0, false);
|
||||||
|
lc.setIntensity(0, 4);
|
||||||
|
lc.clearDisplay(0);
|
||||||
|
}
|
||||||
|
int digit = 0;
|
||||||
|
writeDecimal(&digit, temperature);
|
||||||
|
digit++;
|
||||||
|
digit++;
|
||||||
|
writeDecimal(&digit, target);
|
||||||
|
}
|
||||||
6
src/Fermenter/FementerDisplay.h
Normal file
6
src/Fermenter/FementerDisplay.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#ifndef SENSOR3_FEMENTERDISPLAY_H
|
||||||
|
#define SENSOR3_FEMENTERDISPLAY_H
|
||||||
|
|
||||||
|
void displayLoop(double temperature, double target);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,192 +1,38 @@
|
|||||||
#if defined(FERMENTER) || defined(TEST8266)
|
#if defined(FERMENTER) || defined(TEST8266)
|
||||||
|
|
||||||
#include <Patrix.h>
|
#include <Patrix.h>
|
||||||
#include "sensors/Dallas.h"
|
|
||||||
#include "sensors/DallasSensor.h"
|
|
||||||
#include "ArduPID.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "console.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "LedControl.h"
|
|
||||||
#include "FermenterData.h"
|
#include "FermenterData.h"
|
||||||
|
#include "FementerDisplay.h"
|
||||||
|
#include "FermenterPID.h"
|
||||||
|
#include "FermenterSensor.h"
|
||||||
|
|
||||||
#define SENSOR_GPIO D4
|
Cache<FermenterData, 800> cache;
|
||||||
#define CONTROL_GPIO D2
|
|
||||||
#define CONTROL_PWM_BITS 10
|
|
||||||
#define CONTROL_PWM_MAX (pow(2, CONTROL_PWM_BITS) - 1)
|
|
||||||
|
|
||||||
#define PID_DEFAULT_P 1500
|
|
||||||
#define PID_DEFAULT_I 0
|
|
||||||
#define PID_DEFAULT_D 0
|
|
||||||
#define PID_DEFAULT_TARGET 31
|
|
||||||
#define PID_DEFAULT_OVER 5
|
|
||||||
|
|
||||||
Dallas dallas(SENSOR_GPIO);
|
|
||||||
|
|
||||||
DallasSensor sensor(dallas, 0x3D0417C1D740FF28, 0.1, 5, 60 * 1000);
|
|
||||||
|
|
||||||
LedControl lc = LedControl(13, 14, 15, 1);
|
|
||||||
|
|
||||||
ArduPID pid;
|
|
||||||
|
|
||||||
double proportional = PID_DEFAULT_P;
|
|
||||||
|
|
||||||
double integral = PID_DEFAULT_I;
|
|
||||||
|
|
||||||
double derivative = PID_DEFAULT_D;
|
|
||||||
|
|
||||||
double temperatureTarget = PID_DEFAULT_TARGET;
|
|
||||||
|
|
||||||
double temperatureOver = PID_DEFAULT_OVER;
|
|
||||||
|
|
||||||
double temperatureCurrent = NAN;
|
|
||||||
|
|
||||||
double heaterPWM = 0;
|
|
||||||
|
|
||||||
Cache<FermenterData, 500> cache;
|
|
||||||
|
|
||||||
void writeDecimal(int *digit, double value);
|
|
||||||
|
|
||||||
void displayLoop();
|
|
||||||
|
|
||||||
void pidLoop();
|
|
||||||
|
|
||||||
void patrixSetup() {
|
void patrixSetup() {
|
||||||
dallas.begin();
|
sensorSetup();
|
||||||
analogWriteResolution(CONTROL_PWM_BITS);
|
pidSetup(&temperature);
|
||||||
|
|
||||||
configLoaded();
|
|
||||||
|
|
||||||
pid.begin(&temperatureCurrent, &heaterPWM, &temperatureTarget, proportional, integral, derivative);
|
|
||||||
pid.setOutputLimits(0, CONTROL_PWM_MAX);
|
|
||||||
pid.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void patrixLoop() {
|
void patrixLoop() {
|
||||||
dallas.loop();
|
sensorLoop();
|
||||||
if (sensor.loop()) {
|
|
||||||
const FermenterData data = {sensor.getLastValue(), temperatureTarget, proportional, integral, derivative};
|
|
||||||
cache.add(sensor.getLastTimestamp(), data);
|
|
||||||
}
|
|
||||||
cache.loop();
|
|
||||||
displayLoop();
|
|
||||||
pidLoop();
|
pidLoop();
|
||||||
|
displayLoop(temperature, target);
|
||||||
|
cache.add({getTime(), (float) temperature, (float) target, getHeaterPercent(), (float) proportional, (float) integral, (float) derivative});
|
||||||
|
cache.loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pidLoop() {
|
bool patrixCommand(char *first) {
|
||||||
temperatureCurrent = sensor.getLastValue();
|
|
||||||
if (!isnan(temperatureCurrent)) {
|
|
||||||
pid.compute();
|
|
||||||
} else {
|
|
||||||
heaterPWM = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *emergencyStr = "";
|
|
||||||
if (heaterPWM > 0 && temperatureCurrent > temperatureTarget + temperatureOver) {
|
|
||||||
heaterPWM = 0;
|
|
||||||
emergencyStr = "[EMERGENCY CUTOFF]";
|
|
||||||
}
|
|
||||||
|
|
||||||
analogWrite(CONTROL_GPIO, (int) round(heaterPWM));
|
|
||||||
|
|
||||||
static unsigned long lastDebug = 0;
|
|
||||||
unsigned long now = millis();
|
|
||||||
if (now - lastDebug >= 1000) {
|
|
||||||
lastDebug = now;
|
|
||||||
int heaterPercent = (int) round(100.0 * heaterPWM / CONTROL_PWM_MAX);
|
|
||||||
debug(
|
|
||||||
"cache: %3d/%d | p: %f | i: %f | d: %f | target: %4.1f | heater: %3d%% | temperature: %5.2f %s",
|
|
||||||
cache.getUsage(),
|
|
||||||
cache.getSize(),
|
|
||||||
|
|
||||||
proportional == 0 ? NAN : proportional,
|
|
||||||
integral == 0 ? NAN : integral,
|
|
||||||
derivative == 0 ? NAN : derivative,
|
|
||||||
|
|
||||||
temperatureTarget,
|
|
||||||
heaterPercent,
|
|
||||||
temperatureCurrent,
|
|
||||||
|
|
||||||
emergencyStr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void displayLoop() {
|
|
||||||
static unsigned long lastDisplayInit = 0;
|
|
||||||
if (lastDisplayInit == 0 || millis() - lastDisplayInit > 60 * 60 * 1000) {
|
|
||||||
lastDisplayInit = millis();
|
|
||||||
lc.shutdown(0, true);
|
|
||||||
lc.shutdown(0, false);
|
|
||||||
lc.setIntensity(0, 4);
|
|
||||||
lc.clearDisplay(0);
|
|
||||||
}
|
|
||||||
int digit = 0;
|
|
||||||
writeDecimal(&digit, temperatureCurrent);
|
|
||||||
digit++;
|
|
||||||
digit++;
|
|
||||||
writeDecimal(&digit, temperatureTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeDecimal(int *digit, double value) {
|
|
||||||
const int integer = (int) value;
|
|
||||||
const int decimal = (int) ((value - integer) * 10) % 10;
|
|
||||||
lc.setDigit(0, (*digit)++, decimal, false);
|
|
||||||
lc.setDigit(0, (*digit)++, integer % 10, true);
|
|
||||||
lc.setDigit(0, (*digit)++, integer / 10 % 10, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool setDouble(const char *name, double *destinationPtr, double min, double max) {
|
|
||||||
const char *valueStr = strtok(nullptr, "");
|
|
||||||
if (valueStr == nullptr) {
|
|
||||||
_usage();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
double value = strtod(valueStr, nullptr);
|
|
||||||
if (isnan(value)) {
|
|
||||||
error("Failed to parse double for \"%s\": %s", name, valueStr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((!isnan(min) && value < min) || (!isnan(max) && value > max)) {
|
|
||||||
error("Value out of range for \"%s\" [%f..%f]: %f", name, min, max, value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (*destinationPtr != value) {
|
|
||||||
*destinationPtr = value;
|
|
||||||
info("Value for \"%s\" set to: %f", name, value);
|
|
||||||
configPutDouble(name, value);
|
|
||||||
} else {
|
|
||||||
info("Value for \"%s\" unchanged: %f", name, value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool patrix_command(char *first) {
|
|
||||||
bool result = false;
|
bool result = false;
|
||||||
if (strcmp(first, "over") == 0 || strcmp(first, "o") == 0) {
|
if (strcmp(first, "p") == 0) {
|
||||||
result = setDouble("over", &temperatureOver, 0, 20);
|
result = cmdReadDouble("p", &proportional, 0, NAN);
|
||||||
} else if (strcmp(first, "target") == 0 || strcmp(first, "t") == 0) {
|
} else if (strcmp(first, "i") == 0) {
|
||||||
result = setDouble("target", &temperatureTarget, -10, 60);
|
result = cmdReadDouble("i", &integral, 0, NAN);
|
||||||
} else if (strcmp(first, "proportional") == 0 || strcmp(first, "p") == 0) {
|
} else if (strcmp(first, "d") == 0) {
|
||||||
result = setDouble("p", &proportional, NAN, NAN);
|
result = cmdReadDouble("d", &derivative, 0, NAN);
|
||||||
} else if (strcmp(first, "integral") == 0 || strcmp(first, "i") == 0) {
|
} else if (strcmp(first, "t") == 0) {
|
||||||
result = setDouble("i", &integral, NAN, NAN);
|
result = cmdReadDouble("t", &target, -10, 60);
|
||||||
} else if (strcmp(first, "derivative") == 0 || strcmp(first, "d") == 0) {
|
|
||||||
result = setDouble("d", &derivative, NAN, NAN);
|
|
||||||
}
|
|
||||||
if (result) {
|
|
||||||
pid.setCoefficients(proportional, integral, derivative);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void configLoaded() {
|
|
||||||
proportional = configGetDouble("p", PID_DEFAULT_P);
|
|
||||||
integral = configGetDouble("i", PID_DEFAULT_I);
|
|
||||||
derivative = configGetDouble("d", PID_DEFAULT_D);
|
|
||||||
temperatureTarget = configGetDouble("target", PID_DEFAULT_TARGET);
|
|
||||||
temperatureOver = configGetDouble("over", PID_DEFAULT_OVER);
|
|
||||||
pid.setCoefficients(proportional, integral, derivative);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -1,35 +1,65 @@
|
|||||||
#ifndef SENSOR3_FERMENTERDATA_H
|
#ifndef SENSOR3_FERMENTER_DATA_H
|
||||||
#define SENSOR3_FERMENTERDATA_H
|
#define SENSOR3_FERMENTER_DATA_H
|
||||||
|
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
|
|
||||||
struct FermenterData : IData {
|
struct FermenterData : IData {
|
||||||
double temperature;
|
float currentCelsius;
|
||||||
double target;
|
float targetCelsius;
|
||||||
double p;
|
uint8_t heaterPercent;
|
||||||
double i;
|
float p;
|
||||||
double d;
|
float i;
|
||||||
|
float d;
|
||||||
|
|
||||||
FermenterData() {
|
FermenterData() : IData(0) {
|
||||||
temperature = NAN;
|
currentCelsius = NAN;
|
||||||
target = NAN;
|
targetCelsius = NAN;
|
||||||
|
heaterPercent = 0;
|
||||||
p = NAN;
|
p = NAN;
|
||||||
i = NAN;
|
i = NAN;
|
||||||
d = NAN;
|
d = NAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
FermenterData(double temperature, double target, double p, double i, double d) : temperature(temperature), target(target), p(p), i(i), d(d) {
|
FermenterData(
|
||||||
|
time_t time,
|
||||||
|
float currentCelsius,
|
||||||
|
float targetCelsius,
|
||||||
|
uint8_t heaterPercent,
|
||||||
|
float p,
|
||||||
|
float i,
|
||||||
|
float d
|
||||||
|
) :
|
||||||
|
IData(time),
|
||||||
|
currentCelsius(currentCelsius),
|
||||||
|
targetCelsius(targetCelsius),
|
||||||
|
heaterPercent(heaterPercent),
|
||||||
|
p(p),
|
||||||
|
i(i),
|
||||||
|
d(d) {
|
||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
|
|
||||||
void toJson(JsonObject &json) const override {
|
void toJson(JsonObject &json) const override {
|
||||||
json["temperature"] = temperature;
|
json["currentCelsius"] = currentCelsius;
|
||||||
json["target"] = target;
|
json["targetCelsius"] = targetCelsius;
|
||||||
|
json["heaterPercent"] = heaterPercent;
|
||||||
json["p"] = p;
|
json["p"] = p;
|
||||||
json["i"] = i;
|
json["i"] = i;
|
||||||
json["d"] = d;
|
json["d"] = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool needsPublish(FermenterData &last) {
|
||||||
|
correctTime(time);
|
||||||
|
correctTime(last.time);
|
||||||
|
return time - last.time >= 60
|
||||||
|
|| this->p != last.p
|
||||||
|
|| this->i != last.i
|
||||||
|
|| this->d != last.d
|
||||||
|
|| abs(this->currentCelsius - last.currentCelsius) >= 0.1
|
||||||
|
|| this->targetCelsius != last.targetCelsius
|
||||||
|
|| this->heaterPercent != last.heaterPercent;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
62
src/Fermenter/FermenterPID.cpp
Normal file
62
src/Fermenter/FermenterPID.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include "FermenterPID.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#define CONTROL_GPIO D2
|
||||||
|
#define CONTROL_PWM_BITS 10
|
||||||
|
#define CONTROL_PWM_MAX (pow(2, CONTROL_PWM_BITS) - 1)
|
||||||
|
|
||||||
|
ArduPID pid;
|
||||||
|
|
||||||
|
double proportional = 0;
|
||||||
|
|
||||||
|
double integral = 0;
|
||||||
|
|
||||||
|
double derivative = 0;
|
||||||
|
|
||||||
|
double target = 0;
|
||||||
|
|
||||||
|
double heater = 0;
|
||||||
|
|
||||||
|
void pidSetup(double *temperature) {
|
||||||
|
analogWriteResolution(CONTROL_PWM_BITS);
|
||||||
|
|
||||||
|
proportional = configGetDouble("p", 0);
|
||||||
|
integral = configGetDouble("i", 0);
|
||||||
|
derivative = configGetDouble("d", 0);
|
||||||
|
target = configGetDouble("t", 0);
|
||||||
|
pid.begin(temperature, &heater, &target, proportional, integral, derivative);
|
||||||
|
pid.setOutputLimits(0, CONTROL_PWM_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pidLoop() {
|
||||||
|
if (!isnan(*pid.input)) {
|
||||||
|
pid.setCoefficients(proportional, integral, derivative);
|
||||||
|
pid.compute();
|
||||||
|
} else {
|
||||||
|
heater = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
analogWrite(CONTROL_GPIO, (int) round(heater));
|
||||||
|
|
||||||
|
static unsigned long lastDebug = 0;
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now - lastDebug >= 1000) {
|
||||||
|
lastDebug = now;
|
||||||
|
debug(
|
||||||
|
"p: %f | i: %.12f | d: %.12f | target: %4.1f | heater: %3d%% | temperature: %5.2f",
|
||||||
|
proportional == 0 ? NAN : proportional,
|
||||||
|
integral == 0 ? NAN : integral,
|
||||||
|
derivative == 0 ? NAN : derivative,
|
||||||
|
|
||||||
|
*pid.setpoint,
|
||||||
|
getHeaterPercent(),
|
||||||
|
*pid.input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getHeaterPercent() {
|
||||||
|
return (int) round(100.0 * heater / CONTROL_PWM_MAX);
|
||||||
|
}
|
||||||
22
src/Fermenter/FermenterPID.h
Normal file
22
src/Fermenter/FermenterPID.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifndef SENSOR3_FERMENTERPID_H
|
||||||
|
#define SENSOR3_FERMENTERPID_H
|
||||||
|
|
||||||
|
#include "ArduPID.h"
|
||||||
|
|
||||||
|
extern ArduPID pid;
|
||||||
|
|
||||||
|
extern double proportional;
|
||||||
|
|
||||||
|
extern double integral;
|
||||||
|
|
||||||
|
extern double derivative;
|
||||||
|
|
||||||
|
extern double target;
|
||||||
|
|
||||||
|
void pidSetup(double *temperature);
|
||||||
|
|
||||||
|
void pidLoop();
|
||||||
|
|
||||||
|
uint8_t getHeaterPercent();
|
||||||
|
|
||||||
|
#endif
|
||||||
22
src/Fermenter/FermenterSensor.cpp
Normal file
22
src/Fermenter/FermenterSensor.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "FermenterSensor.h"
|
||||||
|
|
||||||
|
#include "sensors/Dallas.h"
|
||||||
|
#include "sensors/DallasSensor.h"
|
||||||
|
|
||||||
|
#define SENSOR_GPIO D4
|
||||||
|
|
||||||
|
Dallas dallas(SENSOR_GPIO);
|
||||||
|
|
||||||
|
DallasSensor sensor(dallas, 0x3D0417C1D740FF28, 5);
|
||||||
|
|
||||||
|
double temperature = NAN;
|
||||||
|
|
||||||
|
void sensorSetup() {
|
||||||
|
dallas.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sensorLoop() {
|
||||||
|
dallas.loop();
|
||||||
|
sensor.loop();
|
||||||
|
temperature = sensor.getLastValue();
|
||||||
|
}
|
||||||
10
src/Fermenter/FermenterSensor.h
Normal file
10
src/Fermenter/FermenterSensor.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef SENSOR3_FERMENTERSENSOR_H
|
||||||
|
#define SENSOR3_FERMENTERSENSOR_H
|
||||||
|
|
||||||
|
extern double temperature;
|
||||||
|
|
||||||
|
void sensorSetup();
|
||||||
|
|
||||||
|
void sensorLoop();
|
||||||
|
|
||||||
|
#endif
|
||||||
187
src/Gewaechshaus/Greenhouse.cpp
Normal file
187
src/Gewaechshaus/Greenhouse.cpp
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#if defined(GREENHOUSE)
|
||||||
|
|
||||||
|
#include <Patrix.h>
|
||||||
|
#include "GreenhouseData.h"
|
||||||
|
#include "sensors/DHT.h"
|
||||||
|
#include "sensors/Output.h"
|
||||||
|
#include "sensors/Moisture.h"
|
||||||
|
|
||||||
|
#define INSIDE_DHT_PIN 4
|
||||||
|
|
||||||
|
#define MOISTURE_PIN 5
|
||||||
|
|
||||||
|
#define OUTSIDE_DHT_PIN 6
|
||||||
|
|
||||||
|
#define VENTILATOR_PIN 7
|
||||||
|
|
||||||
|
#define SPRINKLER_PIN 8
|
||||||
|
|
||||||
|
#define TEMPERATURE_HIGH 35
|
||||||
|
|
||||||
|
#define TEMPERATURE_LOW 32
|
||||||
|
|
||||||
|
#define TEMPERATURE_MILLIS (5 * 60 * 1000)
|
||||||
|
|
||||||
|
#define MOISTURE_HIGH 70
|
||||||
|
|
||||||
|
#define MOISTURE_LOW 20
|
||||||
|
|
||||||
|
#define MOISTURE_MILLIS (5 * 60 * 1000)
|
||||||
|
|
||||||
|
GreenhouseData data{};
|
||||||
|
|
||||||
|
Cache<GreenhouseData, 800> cache;
|
||||||
|
|
||||||
|
DHT insideDHT(INSIDE_DHT_PIN);
|
||||||
|
|
||||||
|
Moisture insideMoisture(MOISTURE_PIN);
|
||||||
|
|
||||||
|
DHT outsideDHT(OUTSIDE_DHT_PIN);
|
||||||
|
|
||||||
|
Output ventilator(VENTILATOR_PIN, true, false);
|
||||||
|
|
||||||
|
Output sprinkler(SPRINKLER_PIN, true, false);
|
||||||
|
|
||||||
|
double tempHigh = 35;
|
||||||
|
|
||||||
|
double tempLow = 32;
|
||||||
|
|
||||||
|
uint64_t tempMillis = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
uint64_t tempHighSince = 0;
|
||||||
|
|
||||||
|
uint64_t tempLowSince = 0;
|
||||||
|
|
||||||
|
double moistHigh = 35;
|
||||||
|
|
||||||
|
double moistLow = 32;
|
||||||
|
|
||||||
|
uint64_t moistMillis = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
uint64_t moistHighSince = 0;
|
||||||
|
|
||||||
|
uint64_t moistLowSince = 0;
|
||||||
|
|
||||||
|
void insideLoop();
|
||||||
|
|
||||||
|
void outsideLoop();
|
||||||
|
|
||||||
|
void moistureLoop();
|
||||||
|
|
||||||
|
void patrixSetup() {
|
||||||
|
insideDHT.begin();
|
||||||
|
outsideDHT.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void patrixLoop() {
|
||||||
|
outsideLoop();
|
||||||
|
|
||||||
|
insideLoop();
|
||||||
|
moistureLoop();
|
||||||
|
|
||||||
|
ventilator.loop();
|
||||||
|
data.ventilatorSeconds = ventilator.getTotalOnMillis() / 1000;
|
||||||
|
|
||||||
|
sprinkler.loop();
|
||||||
|
data.sprinklerSeconds = sprinkler.getTotalOnMillis() / 1000;
|
||||||
|
|
||||||
|
static unsigned long now = millis();
|
||||||
|
static unsigned long last = now;
|
||||||
|
if (now - last >= 60 * 1000) {
|
||||||
|
last = now;
|
||||||
|
cache.add(getTime(), data);
|
||||||
|
cache.loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void outsideLoop() {
|
||||||
|
float temperature = NAN;
|
||||||
|
float absolute = NAN;
|
||||||
|
outsideDHT.loop(&temperature, &data.outsideRelativePercent, &absolute);
|
||||||
|
data.outsideTemperatureCenti = (int16_t) round(temperature * 100);
|
||||||
|
data.outsideAbsoluteCenti = (uint16_t) round(absolute * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insideLoop() {
|
||||||
|
float temperature = NAN;
|
||||||
|
float absolute = NAN;
|
||||||
|
insideDHT.loop(&temperature, &data.insideRelativePercent, &absolute);
|
||||||
|
data.insideTemperatureCenti = (int16_t) round(temperature * 100);
|
||||||
|
data.insideAbsoluteCenti = (uint16_t) round(absolute * 100);
|
||||||
|
|
||||||
|
if (temperature > tempHigh) {
|
||||||
|
tempLowSince = 0;
|
||||||
|
if (tempHighSince == 0) {
|
||||||
|
tempHighSince = getUptimeMillis();
|
||||||
|
}
|
||||||
|
if (tempHighSince != 0 && getUptimeMillis() - tempHighSince > tempMillis) {
|
||||||
|
ventilator.on();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temperature < tempLow) {
|
||||||
|
tempHighSince = 0;
|
||||||
|
if (tempLowSince == 0) {
|
||||||
|
tempLowSince = getUptimeMillis();
|
||||||
|
}
|
||||||
|
if (tempLowSince != 0 && getUptimeMillis() - tempLowSince > tempMillis) {
|
||||||
|
ventilator.off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void moistureLoop() {
|
||||||
|
float moisture = NAN;
|
||||||
|
insideMoisture.loop(&moisture);
|
||||||
|
data.insideMoisturePercent = (uint16_t) round(moisture * 100);
|
||||||
|
|
||||||
|
if (moisture > moistHigh) {
|
||||||
|
moistLowSince = 0;
|
||||||
|
if (moistHighSince == 0) {
|
||||||
|
moistHighSince = getUptimeMillis();
|
||||||
|
}
|
||||||
|
if (moistHighSince != 0 && getUptimeMillis() - moistHighSince > moistMillis) {
|
||||||
|
sprinkler.off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moisture < moistLow) {
|
||||||
|
moistHighSince = 0;
|
||||||
|
if (moistLowSince == 0) {
|
||||||
|
moistLowSince = getUptimeMillis();
|
||||||
|
}
|
||||||
|
if (moistLowSince != 0 && getUptimeMillis() - moistLowSince > moistMillis) {
|
||||||
|
sprinkler.on();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patrixCommand(char *first) {
|
||||||
|
bool result = false;
|
||||||
|
if (strcmp(first, "tempHigh") == 0 || strcmp(first, "th") == 0) {
|
||||||
|
result = cmdReadDouble("tempHigh", &tempHigh, NAN, NAN);
|
||||||
|
} else if (strcmp(first, "tempLow") == 0 || strcmp(first, "tl") == 0) {
|
||||||
|
result = cmdReadDouble("tempLow", &tempLow, NAN, NAN);
|
||||||
|
} else if (strcmp(first, "tempMillis") == 0 || strcmp(first, "tm") == 0) {
|
||||||
|
result = cmdReadU64("tempMillis", &tempMillis, NAN, NAN);
|
||||||
|
} else if (strcmp(first, "moistHigh") == 0 || strcmp(first, "mh") == 0) {
|
||||||
|
result = cmdReadDouble("moistHigh", &moistHigh, NAN, NAN);
|
||||||
|
} else if (strcmp(first, "moistLow") == 0 || strcmp(first, "ml") == 0) {
|
||||||
|
result = cmdReadDouble("moistLow", &moistLow, NAN, NAN);
|
||||||
|
} else if (strcmp(first, "moistMillis") == 0 || strcmp(first, "mm") == 0) {
|
||||||
|
result = cmdReadU64("moistMillis", &moistMillis, NAN, NAN);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void configLoaded() {
|
||||||
|
tempHigh = configGetDouble("tempHigh", TEMPERATURE_HIGH);
|
||||||
|
tempLow = configGetDouble("tempLow", TEMPERATURE_LOW);
|
||||||
|
tempMillis = configGetUint64_t("tempMillis", TEMPERATURE_MILLIS);
|
||||||
|
|
||||||
|
moistHigh = configGetDouble("moistHigh", MOISTURE_HIGH);
|
||||||
|
moistLow = configGetDouble("moistLow", MOISTURE_LOW);
|
||||||
|
moistMillis = configGetUint64_t("moistMillis", MOISTURE_MILLIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
95
src/Gewaechshaus/GreenhouseData.h
Normal file
95
src/Gewaechshaus/GreenhouseData.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#ifndef SENSOR3_GREENHOUSE_DATA_H
|
||||||
|
#define SENSOR3_GREENHOUSE_DATA_H
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
|
||||||
|
struct GreenhouseData : IData {
|
||||||
|
int16_t insideTemperatureCenti;
|
||||||
|
uint8_t insideRelativePercent;
|
||||||
|
uint16_t insideAbsoluteCenti;
|
||||||
|
uint8_t insideMoisturePercent;
|
||||||
|
|
||||||
|
int16_t outsideTemperatureCenti;
|
||||||
|
uint8_t outsideRelativePercent;
|
||||||
|
uint16_t outsideAbsoluteCenti;
|
||||||
|
|
||||||
|
bool ventilatorState;
|
||||||
|
uint32_t ventilatorSeconds;
|
||||||
|
|
||||||
|
bool sprinklerState;
|
||||||
|
uint32_t sprinklerSeconds;
|
||||||
|
|
||||||
|
GreenhouseData() {
|
||||||
|
insideTemperatureCenti = 0;
|
||||||
|
insideRelativePercent = 0;
|
||||||
|
insideAbsoluteCenti = 0;
|
||||||
|
insideMoisturePercent = 0;
|
||||||
|
|
||||||
|
outsideTemperatureCenti = 0;
|
||||||
|
outsideRelativePercent = 0;
|
||||||
|
outsideAbsoluteCenti = 0;
|
||||||
|
|
||||||
|
ventilatorState = false;
|
||||||
|
ventilatorSeconds = 0;
|
||||||
|
|
||||||
|
sprinklerState = false;
|
||||||
|
sprinklerSeconds = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
GreenhouseData(
|
||||||
|
int16_t insideTemperatureCenti,
|
||||||
|
uint8_t insideRelativePercent,
|
||||||
|
uint8_t insideAbsoluteCenti,
|
||||||
|
uint8_t insideMoisturePercent,
|
||||||
|
|
||||||
|
int16_t outsideTemperatureCenti,
|
||||||
|
uint8_t outsideRelativePercent,
|
||||||
|
uint8_t outsideAbsoluteCenti,
|
||||||
|
|
||||||
|
bool ventilatorState,
|
||||||
|
uint32_t ventilatorSeconds,
|
||||||
|
|
||||||
|
bool sprinklerState,
|
||||||
|
uint32_t sprinklerSeconds
|
||||||
|
) :
|
||||||
|
insideTemperatureCenti(insideTemperatureCenti),
|
||||||
|
insideRelativePercent(insideRelativePercent),
|
||||||
|
insideAbsoluteCenti(insideAbsoluteCenti),
|
||||||
|
insideMoisturePercent(insideMoisturePercent),
|
||||||
|
|
||||||
|
outsideTemperatureCenti(outsideTemperatureCenti),
|
||||||
|
outsideRelativePercent(outsideRelativePercent),
|
||||||
|
outsideAbsoluteCenti(outsideAbsoluteCenti),
|
||||||
|
|
||||||
|
ventilatorState(ventilatorState),
|
||||||
|
ventilatorSeconds(ventilatorSeconds),
|
||||||
|
|
||||||
|
sprinklerState(sprinklerState),
|
||||||
|
sprinklerSeconds(sprinklerSeconds) {
|
||||||
|
// -
|
||||||
|
}
|
||||||
|
|
||||||
|
void toJson(JsonObject &json) const override {
|
||||||
|
JsonObject inside = json["inside"];
|
||||||
|
inside["temperature"] = insideTemperatureCenti / 100;
|
||||||
|
inside["relative"] = insideRelativePercent;
|
||||||
|
inside["absolute"] = insideAbsoluteCenti / 100;
|
||||||
|
inside["moisture"] = insideMoisturePercent;
|
||||||
|
|
||||||
|
JsonObject outside = json["outside"];
|
||||||
|
outside["temperature"] = outsideTemperatureCenti / 100;
|
||||||
|
outside["relative"] = outsideRelativePercent;
|
||||||
|
outside["absolute"] = outsideAbsoluteCenti / 100;
|
||||||
|
|
||||||
|
JsonObject ventilator = json["ventilator"];
|
||||||
|
ventilator["state"] = ventilatorState;
|
||||||
|
ventilator["totalSeconds"] = ventilatorSeconds;
|
||||||
|
|
||||||
|
JsonObject sprinkler = json["sprinkler"];
|
||||||
|
sprinkler["state"] = sprinklerState;
|
||||||
|
sprinkler["totalSeconds"] = sprinklerSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user