Greenhouse webinterface

This commit is contained in:
Patrick Haßel 2025-03-03 11:25:58 +01:00
parent 118bef8f72
commit 75755ada63
12 changed files with 322 additions and 53 deletions

View File

@ -7,25 +7,205 @@
<style>
body {
font-family: sans-serif;
font-size: 4vw;
margin: 1em;
font-size: 12vw;
margin: 0;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #5c875c;
}
div {
overflow: hidden;
}
h1 {
.heading {
padding: 0.25em 0;
text-align: center;
background-color: #93da93;
border-bottom: 1px solid gray;
}
.content {
padding: 0.25em;
}
.section {
clear: both;
margin-bottom: 0.5em;
}
.title {
font-size: 50%;
font-style: italic;
}
.valueAndUnit {
clear: both;
float: right;
}
.value {
float: left;
}
.unit {
float: left;
margin-left: 0.25em;
text-align: left;
}
.inputNull {
color: gray;
}
.inputBlue {
color: blue;
}
.inputGreen {
color: #00ff00;
}
.inputRed {
color: #ba0000;
}
</style>
</head>
<body>
<h1>Gewächshaus</h1>
TODO
<div class="heading">Gewächshaus</div>
<div class="content">
<div class="section">
<div class="title">Temperatur</div>
<div class="valueAndUnit">
<div class="value" id="temperature"></div>
<div class="unit">&deg;C</div>
</div>
</div>
<div class="section">
<div class="title">Relative Luftfeuchte</div>
<div class="valueAndUnit">
<div class="value" id="relative"></div>
<div class="unit">%</div>
</div>
</div>
<div class="section">
<div class="title">Absolute Luftfeuchte</div>
<div class="valueAndUnit">
<div class="value" id="absolute"></div>
<div class="unit">g/m³</div>
</div>
</div>
<div class="section">
<div class="title">Helligkeit</div>
<div class="valueAndUnit">
<div class="value" id="illuminance"></div>
<div class="unit">lux</div>
</div>
</div>
</div>
<script>
const NO_VALUE = "- - -";
const TEMPERATURE_BLUE = 10;
const TEMPERATURE_RED = 35;
const RELATIVE_BLUE = 60;
const RELATIVE_RED = 80;
const htmlTemperature = document.getElementById('temperature');
const htmlRelative = document.getElementById('relative');
const htmlAbsolute = document.getElementById('absolute');
const htmlIlluminance = document.getElementById('illuminance');
function status() {
get("/status");
}
function get(path) {
const request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState === 4) {
update(request.responseText);
}
};
request.open('GET', (location.hostname === "localhost" ? "http://10.0.0.160" : "") + path);
request.send();
}
function format(value, decimals) {
return value?.toLocaleString(undefined, {minimumFractionDigits: decimals, maximumFractionDigits: decimals});
}
function update(response) {
try {
const data = JSON.parse(response);
htmlTemperature.innerText = format(data?.temperature, 1) || NO_VALUE;
htmlRelative.innerText = format(data?.relative, 0) || NO_VALUE;
htmlAbsolute.innerText = format(data?.absolute, 1) || NO_VALUE;
htmlIlluminance.innerText = format(data?.illuminance, 0) || NO_VALUE;
const relativeNull = !isSet(data?.relative);
const relativeBlue = !relativeNull && data?.relative < RELATIVE_BLUE;
const relativeRed = !relativeNull && data?.relative > RELATIVE_RED;
const relativeGreen = !relativeNull && !relativeBlue && !relativeRed;
setClass(htmlRelative.parentElement, "inputNull", relativeNull);
setClass(htmlRelative.parentElement, "inputRed", relativeBlue);
setClass(htmlRelative.parentElement, "inputGreen", relativeGreen);
setClass(htmlRelative.parentElement, "inputBlue", relativeRed);
const temperatureNull = !isSet(data?.temperature);
const temperatureBlue = !temperatureNull && data?.temperature < TEMPERATURE_BLUE;
const temperatureRed = !temperatureNull && data?.temperature > TEMPERATURE_RED;
const temperatureGreen = !temperatureNull && !temperatureBlue && !temperatureRed;
setClass(htmlTemperature.parentElement, "inputNull", temperatureNull);
setClass(htmlTemperature.parentElement, "inputBlue", temperatureBlue);
setClass(htmlTemperature.parentElement, "inputGreen", temperatureGreen);
setClass(htmlTemperature.parentElement, "inputRed", temperatureRed);
} catch (e) {
reset(e);
}
}
function isSet(v) {
return v !== null && v !== undefined;
}
function reset(e) {
console.error("Failed to handle data:", e);
htmlTemperature.innerText = NO_VALUE;
htmlRelative.innerText = NO_VALUE;
htmlAbsolute.innerText = NO_VALUE;
htmlIlluminance.innerText = NO_VALUE;
}
/**
* @param {HTMLElement} element
* @param {string} className
* @param {boolean} enabled
*/
function setClass(element, className, enabled) {
if (element.classList.contains(className) !== enabled) {
if (enabled) {
element.classList.add(className);
} else {
element.classList.remove(className);
}
}
}
status();
setInterval(() => status(), 2000);
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
#ifdef NODE_GREENHOUSE
#include "http.h"
#include "sensors.h"
void patrixSetup() {
sensorsSetup();
httpSetup2();
}
void patrixLoop() {
sensorsLoop();
}
#endif

View File

@ -0,0 +1,25 @@
#ifdef NODE_GREENHOUSE
#include <ArduinoJson.h>
#include "http.h"
#include "sensors.h"
#include "../../patrix/mqtt.h"
void httpStatus(AsyncWebServerRequest* request) {
JsonDocument json;
json["illuminance"] = greenhouseTSL.getIlluminance();;
json["temperature"] = greenhouseDHT22.getTemperature();
json["relative"] = greenhouseDHT22.getRelative();
json["absolute"] = greenhouseDHT22.getAbsolute();
AsyncResponseStream* stream = request->beginResponseStream("application/json");
serializeJson(json, *stream);
request->send(stream);
}
void httpSetup2() {
server.on("/status", httpStatus);
}
#endif

View File

@ -0,0 +1,16 @@
#ifdef NODE_GREENHOUSE
#ifndef HTTP_H
#define HTTP_H
#include "patrix/http.h"
void httpStatus(AsyncWebServerRequest* request);
void httpTargetAdd(AsyncWebServerRequest* request);
void httpSetup2();
#endif
#endif

View File

@ -1,18 +1,17 @@
#ifdef NODE_GREENHOUSE
#include "patrix/tsl2561.h"
#include "patrix/DHT22.h"
#include "sensors.h"
TSL2561 greenhouseTSL("greenhouse");
DHT22Sensor greenhouseDHT22("greenhouse", D5);
void patrixSetup() {
void sensorsSetup() {
greenhouseTSL.setup();
greenhouseDHT22.setup();
}
void patrixLoop() {
void sensorsLoop() {
greenhouseTSL.loop();
greenhouseDHT22.loop();
}

View File

@ -0,0 +1,19 @@
#ifdef NODE_GREENHOUSE
#ifndef SENSORS_H
#define SENSORS_H
#include "patrix/DHT22.h"
#include "patrix/tsl2561.h"
extern TSL2561 greenhouseTSL;
extern DHT22Sensor greenhouseDHT22;
void sensorsSetup();
void sensorsLoop();
#endif
#endif

View File

@ -1,8 +1,9 @@
#ifndef DHT22_H
#define DHT22_H
#include "mqtt.h"
#include "DHT_U.h"
#include "humidityRelative.h"
#include "mqtt.h"
class DHT22Sensor {
@ -12,6 +13,12 @@ class DHT22Sensor {
unsigned long last = 0UL;
double temperature = NAN;
double relative = NAN;
double absolute = NAN;
public:
const String name;
@ -29,26 +36,28 @@ public:
void loop() {
const auto now = max(1UL, millis());
float temperature = NAN;
if (now - last >= intervalMs) {
sensors_event_t event;
dht.temperature().getEvent(&event);
if (isnan(event.temperature)) {
temperature = event.temperature;
if (isnan(temperature)) {
absolute = NAN;
Log.error("Error reading temperature!");
} else {
temperature = event.temperature;
mqttPublishValue(name + "/temperature", temperature, "TEMPERATURE_C");
}
dht.humidity().getEvent(&event);
if (isnan(event.relative_humidity)) {
relative = event.relative_humidity;
if (isnan(relative)) {
absolute = NAN;
Log.error("Error reading humidity!");
} else {
mqttPublishValue(name + "/humidity/relative", event.relative_humidity, "HUMIDITY_RELATIVE_PERCENT");
mqttPublishValue(name + "/humidity/relative", relative, "HUMIDITY_RELATIVE_PERCENT");
if (!isnan(temperature)) {
double absHumid = calculateHumidityAbsolute(event.temperature, event.relative_humidity);
mqttPublishValue(name + "/humidity/absolute", absHumid, "HUMIDITY_ABSOLUTE_GM3");
absolute = calculateHumidityAbsolute(temperature, relative);
mqttPublishValue(name + "/humidity/absolute", absolute, "HUMIDITY_ABSOLUTE_GM3");
}
}
@ -56,16 +65,16 @@ public:
}
}
static double calculateHumidityAbsolute(const double temperature, const double humidityRelative) {
constexpr auto A = 6.112;
constexpr auto m = 17.67;
constexpr auto Tn = 243.5;
constexpr auto Mw = 18.01534;
constexpr auto R = 8.314462618;
const auto Tk = temperature + 273.15;
const auto P_sat = A * exp((m * temperature) / (temperature + Tn));
const auto P_act = P_sat * (humidityRelative / 100.0);
return (P_act * Mw) / (R * Tk);
[[nodiscard]] double getTemperature() const {
return temperature;
}
[[nodiscard]] double getRelative() const {
return relative;
}
[[nodiscard]] double getAbsolute() const {
return absolute;
}
};

View File

@ -2,6 +2,7 @@
#define BME680_H
#include "Adafruit_BME680.h"
#include "humidityRelative.h"
#include "mqtt.h"
class BME680 {
@ -59,18 +60,6 @@ public:
}
static double calculateHumidityAbsolute(const double temperature, const double humidityRelative) {
constexpr auto A = 6.112;
constexpr auto m = 17.67;
constexpr auto Tn = 243.5;
constexpr auto Mw = 18.01534;
constexpr auto R = 8.314462618;
const auto Tk = temperature + 273.15;
const auto P_sat = A * exp((m * temperature) / (temperature + Tn));
const auto P_act = P_sat * (humidityRelative / 100.0);
return (P_act * Mw) / (R * Tk);
}
};
#endif

View File

@ -3,6 +3,7 @@
#include "Adafruit_BMP280.h"
#include "Adafruit_AHTX0.h"
#include "humidityRelative.h"
#include "mqtt.h"
class BMP280_AHT20 {
@ -88,18 +89,6 @@ public:
mqttPublishValue(name + "/absolute", absolute, "HUMIDITY_ABSOLUTE_GM3");
}
static double calculateHumidityAbsolute(const double temperature, const double humidityRelative) {
constexpr auto A = 6.112;
constexpr auto m = 17.67;
constexpr auto Tn = 243.5;
constexpr auto Mw = 18.01534;
constexpr auto R = 8.314462618;
const auto Tk = temperature + 273.15;
const auto P_sat = A * exp((m * temperature) / (temperature + Tn));
const auto P_act = P_sat * (humidityRelative / 100.0);
return (P_act * Mw) / (R * Tk);
}
};
#endif

View File

@ -0,0 +1,15 @@
#include "humidityRelative.h"
#include <cmath>
double calculateHumidityAbsolute(const double temperatureCelsius, const double humidityRelativePercent) {
constexpr auto A = 6.112;
constexpr auto m = 17.67;
constexpr auto Tn = 243.5;
constexpr auto Mw = 18.01534;
constexpr auto R = 8.314462618;
const auto Tk = temperatureCelsius + 273.15;
const auto P_sat = A * exp((m * temperatureCelsius) / (temperatureCelsius + Tn));
const auto P_act = P_sat * humidityRelativePercent;
return (P_act * Mw) / (R * Tk);
}

View File

@ -0,0 +1,6 @@
#ifndef HUMIDITY_RELATIVE_H
#define HUMIDITY_RELATIVE_H
double calculateHumidityAbsolute(double temperatureCelsius, double humidityRelativePercent);
#endif

View File

@ -10,6 +10,8 @@ class TSL2561 {
unsigned long last = 0UL;
int64_t illuminance = -1;
public:
const String name;
@ -38,14 +40,19 @@ public:
}
}
[[nodiscard]] int64_t getIlluminance() const {
return illuminance;
}
private:
void read() {
uint16_t broadband;
uint16_t ir;
tsl.getLuminosity(&broadband, &ir);
const auto illuminance = tsl.calculateLux(broadband, ir);
illuminance = tsl.calculateLux(broadband, ir);
if (illuminance == 65536) {
illuminance = -1;
Log.error("TSL2561 \"%s\": Failed to read.", name.c_str());
setup();
} else {