From 0cc485577c7c9da22ff06e9bb292cc106557a9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Mon, 22 Apr 2024 12:33:31 +0200 Subject: [PATCH] Greenhouse first throw --- lib/patrix/config.cpp | 14 +++ lib/patrix/config.h | 4 + lib/patrix/console.cpp | 21 ++++ lib/patrix/console.h | 4 + lib/patrix/sensors/DHT.h | 28 +++++ lib/patrix/sensors/Moisture.h | 22 ++++ lib/patrix/sensors/Output.h | 78 +++++++++++++ platformio.ini | 15 +++ src/Gewaechshaus/Greenhouse.cpp | 187 ++++++++++++++++++++++++++++++ src/Gewaechshaus/GreenhouseData.h | 95 +++++++++++++++ 10 files changed, 468 insertions(+) create mode 100644 lib/patrix/sensors/DHT.h create mode 100644 lib/patrix/sensors/Moisture.h create mode 100644 lib/patrix/sensors/Output.h create mode 100644 src/Gewaechshaus/Greenhouse.cpp create mode 100644 src/Gewaechshaus/GreenhouseData.h diff --git a/lib/patrix/config.cpp b/lib/patrix/config.cpp index 903bef8..3c22bec 100644 --- a/lib/patrix/config.cpp +++ b/lib/patrix/config.cpp @@ -65,6 +65,20 @@ 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(); + } +} + void configPrint() { info("Config (%s):", lastChangeMillis == 0 ? "PERSISTED" : "TRANSIENT"); for (JsonPair pair: config.as()) { diff --git a/lib/patrix/config.h b/lib/patrix/config.h index 3fee50a..2cd7307 100644 --- a/lib/patrix/config.h +++ b/lib/patrix/config.h @@ -19,6 +19,10 @@ 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); + void configPrint(); #endif diff --git a/lib/patrix/console.cpp b/lib/patrix/console.cpp index 6b0c94e..d091dd0 100644 --- a/lib/patrix/console.cpp +++ b/lib/patrix/console.cpp @@ -273,3 +273,24 @@ bool setDouble(const char *name, double *destinationPtr, double min, double max) } return true; } + +bool setUint64_T(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; +} diff --git a/lib/patrix/console.h b/lib/patrix/console.h index c9abd53..f53e0a5 100644 --- a/lib/patrix/console.h +++ b/lib/patrix/console.h @@ -1,6 +1,8 @@ #ifndef SENSOR3_CONSOLE_H #define SENSOR3_CONSOLE_H +#include + void consoleSetup(); void consoleLoop(); @@ -13,4 +15,6 @@ void consolePrintUsage(); bool setDouble(const char *name, double *destinationPtr, double min, double max); +bool setUint64_T(const char *name, uint64_t *destinationPtr, uint64_t min, uint64_t max); + #endif diff --git a/lib/patrix/sensors/DHT.h b/lib/patrix/sensors/DHT.h new file mode 100644 index 0000000..2d627d5 --- /dev/null +++ b/lib/patrix/sensors/DHT.h @@ -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 diff --git a/lib/patrix/sensors/Moisture.h b/lib/patrix/sensors/Moisture.h new file mode 100644 index 0000000..4e395c5 --- /dev/null +++ b/lib/patrix/sensors/Moisture.h @@ -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 diff --git a/lib/patrix/sensors/Output.h b/lib/patrix/sensors/Output.h new file mode 100644 index 0000000..5b7cc37 --- /dev/null +++ b/lib/patrix/sensors/Output.h @@ -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 diff --git a/platformio.ini b/platformio.ini index 73c00ff..d8716ed 100644 --- a/platformio.ini +++ b/platformio.ini @@ -61,3 +61,18 @@ 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 diff --git a/src/Gewaechshaus/Greenhouse.cpp b/src/Gewaechshaus/Greenhouse.cpp new file mode 100644 index 0000000..0f3d0b4 --- /dev/null +++ b/src/Gewaechshaus/Greenhouse.cpp @@ -0,0 +1,187 @@ +#if defined(GREENHOUSE) + +#include +#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 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 patrix_command(char *first) { + bool result = false; + if (strcmp(first, "tempHigh") == 0 || strcmp(first, "th") == 0) { + result = setDouble("tempHigh", &tempHigh, NAN, NAN); + } else if (strcmp(first, "tempLow") == 0 || strcmp(first, "tl") == 0) { + result = setDouble("tempLow", &tempLow, NAN, NAN); + } else if (strcmp(first, "tempMillis") == 0 || strcmp(first, "tm") == 0) { + result = setUint64_T("tempMillis", &tempMillis, NAN, NAN); + } else if (strcmp(first, "moistHigh") == 0 || strcmp(first, "mh") == 0) { + result = setDouble("moistHigh", &moistHigh, NAN, NAN); + } else if (strcmp(first, "moistLow") == 0 || strcmp(first, "ml") == 0) { + result = setDouble("moistLow", &moistLow, NAN, NAN); + } else if (strcmp(first, "moistMillis") == 0 || strcmp(first, "mm") == 0) { + result = setUint64_T("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 \ No newline at end of file diff --git a/src/Gewaechshaus/GreenhouseData.h b/src/Gewaechshaus/GreenhouseData.h new file mode 100644 index 0000000..c4ad70c --- /dev/null +++ b/src/Gewaechshaus/GreenhouseData.h @@ -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