Compare commits

..

No commits in common. "1bde26489be476a9a7884c4dec9230a74c8e0a99" and "c59281a5257afd9c58f5d01661287a5946d251ff" have entirely different histories.

23 changed files with 268 additions and 777 deletions

View File

@ -2,9 +2,6 @@
#define SENSOR3_PATRIX_H
#include "base.h"
#include "config.h"
#include "console.h"
#include "log.h"
void patrixSetup();

View File

@ -65,34 +65,6 @@ 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() {
info("Config (%s):", lastChangeMillis == 0 ? "PERSISTED" : "TRANSIENT");
for (JsonPair pair: config.as<JsonObject>()) {

View File

@ -9,27 +9,16 @@ void configLoop();
void configReset();
void configLoaded();
String configGetString(const char *name, const char *fallback, bool allowEmpty);
void configPutString(const char *name, const char *value);
double configGetDouble(const char *name, double fallback);
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();
#endif

View File

@ -67,11 +67,11 @@ void consoleLoop() {
void consoleHandle(char *cmd) {
char *first = strtok(cmd, " ");
if (first == nullptr) {
consolePrintUsage();
_usage();
return;
}
if (strcmp(first, "help") == 0) {
consolePrintUsage();
_usage();
} else if (strcmp(first, "wifi") == 0) {
_wifi();
} else if (strcmp(first, "mqtt") == 0) {
@ -84,12 +84,12 @@ void consoleHandle(char *cmd) {
_debug();
} else if (strcmp(first, "config_reset") == 0) {
configReset();
} else if (!patrixCommand((char *) cmd)) {
} else if (!patrix_command((char *) cmd)) {
info("Unknown command: %s", cmd);
}
}
void consolePrintUsage() {
void _usage() {
info(R"(USAGE:
help
info
@ -120,7 +120,7 @@ void _reboot() {
void _mqtt() {
char *sub = strtok(nullptr, " ");
if (sub == nullptr) {
consolePrintUsage();
_usage();
return;
}
if (strcmp(sub, "reconnect") == 0) {
@ -134,7 +134,7 @@ void _mqtt() {
void _wifi() {
char *sub = strtok(nullptr, " ");
if (sub == nullptr) {
consolePrintUsage();
_usage();
return;
}
if (strcmp(sub, "reconnect") == 0) {
@ -150,7 +150,7 @@ void _wifi() {
void _setConfigString(const char *name, bool allowEmpty) {
char *value = strtok(nullptr, "");
if (value == nullptr) {
consolePrintUsage();
_usage();
return;
}
if (!allowEmpty && strcmp(value, "") == 0) {
@ -248,49 +248,3 @@ const char *getFlashChipMode() {
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;
}

View File

@ -1,20 +1,14 @@
#ifndef SENSOR3_CONSOLE_H
#define SENSOR3_CONSOLE_H
#include <cstdint>
void consoleSetup();
void consoleLoop();
void consoleHandle(char *cmd);
bool patrixCommand(char *cmd);
bool patrix_command(char *cmd);
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);
void _usage();
#endif

View File

@ -3,17 +3,9 @@
#include "base.h"
#include <ArduinoJson.h>
#include "wifi.h"
#include "mqtt.h"
struct IData {
time_t time;
explicit IData(time_t time) : time(time) {
// -
}
virtual void toJson(JsonObject &json) const = 0;
};
@ -26,13 +18,16 @@ class Cache {
private:
T buffer[size];
struct Entry {
time_t timestamp = 0;
T data;
};
T *bufferRead = buffer;
Entry buffer[size];
T *bufferWrite = buffer;
Entry *bufferRead = buffer;
T last{};
Entry *bufferWrite = buffer;
size_t usage = 0;
@ -43,26 +38,25 @@ public:
// -
}
bool add(T data) {
if (usage < size && data.needsPublish(last)) {
last = data;
*bufferWrite = data;
bufferWrite = (bufferWrite - buffer + 1) % size + buffer;
usage++;
return true;
bool add(const time_t timestamp, const T &data) {
if (usage >= size) {
return false;
}
return false;
bufferWrite->timestamp = timestamp;
bufferWrite->data = data;
bufferWrite = (bufferWrite - buffer + 1) % size + buffer;
usage++;
return true;
}
void loop() {
if (usage == 0 || !isTimeSet()) {
return;
}
JsonDocument doc;
JsonObject json = doc.to<JsonObject>();
correctTime(bufferRead->time);
json["timestamp"] = bufferRead->time;
bufferRead->toJson(json);
JsonDocument json;
json["timestamp"] = correctTime(bufferRead->timestamp);
JsonObject data = json["data"].to<JsonObject>();
bufferRead->data.toJson(data);
if (mqttPublishData(json)) {
bufferRead = (bufferRead - buffer + 1) % size + buffer;
usage--;

View File

@ -1,28 +0,0 @@
#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

View File

@ -15,22 +15,36 @@ private:
uint64_t address;
const double valueThreshold;
const time_t timeoutSec;
const time_t minIntervalMillis;
double lastSentValue = NAN;
unsigned long lastSentMillis = 0;
double lastValue = NAN;
time_t lastTimestamp = 0;
double lastValidValue = NAN;
time_t lastValidTimestamp = 0;
public:
DallasSensor(Dallas &sensors, const uint64_t address, const time_t timeoutSec) :
DallasSensor(Dallas &sensors, const uint64_t address, const double valueThreshold, const time_t timeoutSec, const time_t minIntervalMillis) :
sensors(sensors),
address(address),
timeoutSec(timeoutSec) {
valueThreshold(valueThreshold),
timeoutSec(timeoutSec),
minIntervalMillis(minIntervalMillis) {
// -
}
void loop() {
bool loop() {
const time_t now = getTime();
if (now - lastTimestamp > timeoutSec) {
lastTimestamp = 0;
@ -39,9 +53,21 @@ public:
if (sensors.isConverted()) {
const double value = sensors.read(address);
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;
lastTimestamp = timestamp;
if (!isnan(value)) {
lastValidValue = value;
lastValidTimestamp = timestamp;
}
return doPublish;
}
return false;
}
[[nodiscard]] double getLastValue() const {
@ -52,6 +78,14 @@ public:
return lastTimestamp;
}
[[nodiscard]] double getLastValidValue() const {
return lastValidValue;
}
[[nodiscard]] time_t getLastValidTimestamp() const {
return lastValidTimestamp;
}
};
#endif

View File

@ -1,22 +0,0 @@
#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

View File

@ -1,78 +0,0 @@
#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

View File

@ -195,10 +195,18 @@ time_t getTime() {
return epochSeconds;
}
void correctTime(time_t &value) {
if (timeSet && value < MIN_EPOCH_SECONDS) {
value = getTime() - preTimeOffset + value;
time_t correctTime(const time_t value) {
if (!timeSet) {
return 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) {

View File

@ -15,7 +15,9 @@ bool isTimeSet();
time_t getTime();
void correctTime(time_t &value);
time_t correctTime(time_t value);
bool isOlderThan(time_t time, time_t seconds);
void getDateTime(char *buffer, size_t size);

View File

@ -16,7 +16,6 @@ lib_deps = https://github.com/milesburton/Arduino-Temperature-Control-Library
Wire
https://github.com/phassel/ArduPID/
https://github.com/wayoda/LedControl
https://github.com/me-no-dev/ESPAsyncWebServer
[env:TEST32]
upload_port = 10.42.0.66
@ -31,7 +30,7 @@ monitor_port = ${COMMON.monitor_port}
monitor_speed = ${COMMON.monitor_speed}
monitor_filters = ${COMMON.monitor_filters}
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=true
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
[env:TEST8266]
upload_port = 10.0.0.162
@ -61,19 +60,4 @@ monitor_port = ${COMMON.monitor_port}
monitor_speed = ${COMMON.monitor_speed}
monitor_filters = esp8266_exception_decoder
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=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
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

View File

@ -1,29 +0,0 @@
#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);
}

View File

@ -1,6 +0,0 @@
#ifndef SENSOR3_FEMENTERDISPLAY_H
#define SENSOR3_FEMENTERDISPLAY_H
void displayLoop(double temperature, double target);
#endif

View File

@ -1,38 +1,192 @@
#if defined(FERMENTER) || defined(TEST8266)
#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 "FementerDisplay.h"
#include "FermenterPID.h"
#include "FermenterSensor.h"
Cache<FermenterData, 800> cache;
#define SENSOR_GPIO D4
#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() {
sensorSetup();
pidSetup(&temperature);
dallas.begin();
analogWriteResolution(CONTROL_PWM_BITS);
configLoaded();
pid.begin(&temperatureCurrent, &heaterPWM, &temperatureTarget, proportional, integral, derivative);
pid.setOutputLimits(0, CONTROL_PWM_MAX);
pid.start();
}
void patrixLoop() {
sensorLoop();
pidLoop();
displayLoop(temperature, target);
cache.add({getTime(), (float) temperature, (float) target, getHeaterPercent(), (float) proportional, (float) integral, (float) derivative});
dallas.loop();
if (sensor.loop()) {
const FermenterData data = {sensor.getLastValue(), temperatureTarget, proportional, integral, derivative};
cache.add(sensor.getLastTimestamp(), data);
}
cache.loop();
displayLoop();
pidLoop();
}
bool patrixCommand(char *first) {
void pidLoop() {
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;
if (strcmp(first, "p") == 0) {
result = cmdReadDouble("p", &proportional, 0, NAN);
} else if (strcmp(first, "i") == 0) {
result = cmdReadDouble("i", &integral, 0, NAN);
} else if (strcmp(first, "d") == 0) {
result = cmdReadDouble("d", &derivative, 0, NAN);
} else if (strcmp(first, "t") == 0) {
result = cmdReadDouble("t", &target, -10, 60);
if (strcmp(first, "over") == 0 || strcmp(first, "o") == 0) {
result = setDouble("over", &temperatureOver, 0, 20);
} else if (strcmp(first, "target") == 0 || strcmp(first, "t") == 0) {
result = setDouble("target", &temperatureTarget, -10, 60);
} else if (strcmp(first, "proportional") == 0 || strcmp(first, "p") == 0) {
result = setDouble("p", &proportional, NAN, NAN);
} else if (strcmp(first, "integral") == 0 || strcmp(first, "i") == 0) {
result = setDouble("i", &integral, NAN, NAN);
} 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;
}
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

View File

@ -1,65 +1,35 @@
#ifndef SENSOR3_FERMENTER_DATA_H
#define SENSOR3_FERMENTER_DATA_H
#ifndef SENSOR3_FERMENTERDATA_H
#define SENSOR3_FERMENTERDATA_H
#include "data.h"
struct FermenterData : IData {
float currentCelsius;
float targetCelsius;
uint8_t heaterPercent;
float p;
float i;
float d;
double temperature;
double target;
double p;
double i;
double d;
FermenterData() : IData(0) {
currentCelsius = NAN;
targetCelsius = NAN;
heaterPercent = 0;
FermenterData() {
temperature = NAN;
target = NAN;
p = NAN;
i = NAN;
d = NAN;
}
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) {
FermenterData(double temperature, double target, double p, double i, double d) : temperature(temperature), target(target), p(p), i(i), d(d) {
// -
}
void toJson(JsonObject &json) const override {
json["currentCelsius"] = currentCelsius;
json["targetCelsius"] = targetCelsius;
json["heaterPercent"] = heaterPercent;
json["temperature"] = temperature;
json["target"] = target;
json["p"] = p;
json["i"] = i;
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

View File

@ -1,62 +0,0 @@
#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);
}

View File

@ -1,22 +0,0 @@
#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

View File

@ -1,22 +0,0 @@
#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();
}

View File

@ -1,10 +0,0 @@
#ifndef SENSOR3_FERMENTERSENSOR_H
#define SENSOR3_FERMENTERSENSOR_H
extern double temperature;
void sensorSetup();
void sensorLoop();
#endif

View File

@ -1,187 +0,0 @@
#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

View File

@ -1,95 +0,0 @@
#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