GrowLED-Controller kryptische Zeiche auf Display und WebServer

Ich wollte keine 150€ für einen Controller für meine Grow-LED kaufen. Die Lampe war teuer genug.

So dachte ich, ich hab doch noch einiges hier herum liegen.

Wenn mir jemand sagen könnte, wie ich die kyptischen Zeichen aus den WebServer und vom Display weg bekomme, wäre ich sehr dankbar.

Hier der Code, für Leutz die es nachbauen wollen:

#include "WiFi.h"
#include <WebServer.h>
#include <EEPROM.h>
#include <Arduino_GFX_Library.h>
#include <Wire.h>
#include "DHT.h"

// WiFi credentials
const char* ssid = "SSID";
const char* password = "Passwort";

// WebServer
WebServer server(80);

// Den benötigten Sensor definieren, die ander auskommentieren
// #define DHTTYPE DHT11   // DHT 11
// #define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

// DHT Sensor
#define DHTPin 4
DHT dht(DHTPin, DHTTYPE);

float Temperature;
float Humidity;
float leafTemperatureOffset = 0.0; // Blatttemperatur-Offset in °C
float VPD = 0.0; // VPD-Wert

// I2C Einstellungen für PCF8591
#define SDA_PIN 5
#define SCL_PIN 6
#define PCF8591_I2C_ADDRESS 0x48

int analogOutput = 0;  
float analogPercent = 0;  

// EEPROM Einstellungen
#define EEPROM_SIZE 1

// Display Einstellungen
#define TFT_BL 14
Arduino_DataBus *bus = new Arduino_ESP32SPI(11 /* DC */, 10 /* CS */, 12 /* SCK */, 13 /* MOSI */, GFX_NOT_DEFINED /* MISO */);
Arduino_GFX *gfx = new Arduino_ST7789(bus, 1 /* RST */, 1 /* rotation */, true /* IPS */, 170 /* width */, 320 /* height */, 35 /* col offset 1 */, 0 /* row offset 1 */, 35 /* col offset 2 */, 0 /* row offset 2 */);

// WebServer Hauptseite
void handleRoot() {
  String html = "<html><body>";
  html += "<h1>ESP32-GrowLED-Controller</h1>";
  html += "<br><br>";

  // Analoger Ausgang
  html += "<h2>Lichtintensität: <span id=\"analogValue\">" + String(analogPercent) + "</span>%</h2>";
  html += "<input type=\"range\" id=\"analog\" min=\"0\" max=\"100\" step=\"1\" value=\"" + String(analogPercent) + "\" oninput=\"updateAnalog(this.value)\" style=\"width: 100%;\">";

  // Schaltflächen für Änderungen mit vergrößertem Abstand
  html += "<div style=\"margin-top: 20px; display: flex; justify-content: center; gap: 60px;\">";
  html += "<button onclick=\"adjustAnalog(-0.1)\">-0.1%</button>";
  html += "<button onclick=\"adjustAnalog(0.1)\">+0.1%</button>";
  html += "</div>";
  html += "<div style=\"margin-top: 20px; display: flex; justify-content: center; gap: 60px;\">";
  html += "<button onclick=\"adjustAnalog(-1)\">-1%</button>";
  html += "<button onclick=\"adjustAnalog(1)\">+1%</button>";
  html += "</div>";

  html += "<script>";
  // Funktion zum direkten Setzen des Wertes über den Schieberegler
  html += "function updateAnalog(value) {";
  html += "  fetch('/setanalog', {";
  html += "    method: 'POST',";
  html += "    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },";
  html += "    body: 'analog=' + value";
  html += "  });";
  html += "}";

  // Funktion zur Anpassung des Analogwerts
  html += "function adjustAnalog(delta) {";
  html += "  let currentValue = parseFloat(document.getElementById('analogValue').innerText);";
  html += "  let newValue = Math.min(100, Math.max(0, currentValue + delta));"; // Begrenzung zwischen 0% und 100%
  html += "  document.getElementById('analog').value = newValue;";
  html += "  updateAnalog(newValue);"; // Wert setzen
  html += "}";

  // Funktion zum Abrufen und Aktualisieren der Werte
  html += "function fetchValues() {";
  html += "  fetch('/getvalues').then(response => response.json()).then(data => {";
  html += "    document.getElementById('analogValue').innerText = data.analogPercent;";
  html += "    document.getElementById('temperature').innerText = data.temperature;";
  html += "    document.getElementById('humidity').innerText = data.humidity;";
  html += "  });";
  html += "}";
  html += "setInterval(fetchValues, 1000);"; // Aktualisiert die Werte alle 1 Sekunde
  html += "</script>";

  html += "<h2>Temperatur: <span id=\"temperature\">" + String(Temperature) + "</span> °C</h2>";
  html += "<h2>Luftfeuchtigkeit: <span id=\"humidity\">" + String(Humidity) + "</span> % RLF</h2>";
  html += "<h2>VPD: <span id=\"vpd\">" + String(VPD, 2) + "</span> kPa</h2>";

  // Dropdown-Menü für Blatttemperatur-Offset
  html += "<h2>Blatttemperatur gegenüber Raumtemperatur:</h2>";
  html += "<select id=\"leafTemp\" onchange=\"updateLeafTemp(this.value)\">";
  for (float i = -5.0; i <= 5.0; i += 0.5) { // Werte von -5.0 bis 5.0 in 0.5er-Schritten
    html += "<option value=\"" + String(i, 1) + "\"";
    if (i == leafTemperatureOffset) {
      html += " selected"; // Aktuellen Offset vorauswählen
    }
    html += ">" + String(i, 1) + " °C</option>";
  }
  html += "</select>";

  html += "<script>";
  html += "function updateAnalog(value) {";
  html += "  fetch('/setanalog', {";
  html += "    method: 'POST',";
  html += "    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },"; 
  html += "    body: 'analog=' + value";
  html += "  });";
  html += "}";
  html += "function updateLeafTemp(value) {";
  html += "  fetch('/setLeafTemp', {";
  html += "    method: 'POST',";
  html += "    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },";
  html += "    body: 'leafTempOffset=' + value";
  html += "  });";
  html += "}";
  html += "function fetchValues() {";
  html += "  fetch('/getvalues').then(response => response.json()).then(data => {";
  html += "    document.getElementById('analogValue').innerText = data.analogPercent;";
  html += "    document.getElementById('temperature').innerText = data.temperature;";
  html += "    document.getElementById('humidity').innerText = data.humidity;";
  html += "    document.getElementById('vpd').innerText = data.vpd;";
  html += "  });";
  html += "}";
  html += "setInterval(fetchValues, 1000);";
  html += "</script>";

  html += "</body></html>";
  server.send(200, "text/html", html);
}



// Funktion für den analogen Ausgang
void handleSetAnalog() {
  if (server.hasArg("analog")) {
    analogPercent = server.arg("analog").toFloat(); // Prozentwert als Float
    analogOutput = round(analogPercent * 2.55);    // Umrechnung in den Bereich 0–255 (1 % = 2.55 Schritte)

    // Senden der DAC Daten zum PCF8591
    Wire.beginTransmission(PCF8591_I2C_ADDRESS);
    Wire.write(0x40);
    Wire.write(analogOutput);
    Wire.endTransmission();

    // Speichern des Werts im EEPROM
    EEPROM.write(0, analogOutput);
    EEPROM.commit();

    // Aktualisiere Anzeige auf dem Display
    gfx->fillScreen(BLACK);
    gfx->setCursor(5, 10);
    gfx->setTextColor(WHITE);
    gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
    gfx->print("Lichtintesität: ");
    gfx->print(analogPercent);
    gfx->println("%");

    gfx->setCursor(5, 30);
    gfx->setTextColor(WHITE);
    gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
    gfx->print("Temperatur: ");
    gfx->print(Temperature);
    gfx->println("°C");
    
    gfx->setCursor(5, 50);
    gfx->setTextColor(WHITE);
    gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
    gfx->print("Luftfeuchtigkeit: ");
    gfx->print(Humidity);
    gfx->println("%");
  }
  server.sendHeader("Location", "/");
  server.send(303);
}

void updateDisplay() {
  // Display aktualisieren
  gfx->fillScreen(BLACK);
  gfx->setCursor(5, 10);
  gfx->setTextColor(WHITE);
  gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
  gfx->print("Lichtintensität: ");
  gfx->print(analogPercent);
  gfx->println("%");

  gfx->setCursor(5, 30);
  gfx->setTextColor(WHITE);
  gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
  gfx->print("Temperatur: ");
  gfx->print(Temperature);
  gfx->println("°C");
  
  gfx->setCursor(5, 50);
  gfx->setTextColor(WHITE);
  gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
  gfx->print("Luftfeuchtigkeit: ");
  gfx->print(Humidity);
  gfx->println("%");

  gfx->setCursor(5, 70);
  gfx->setTextColor(WHITE);
  gfx->setTextSize(2 /* x scale */, 2 /* y scale */, 1 /* pixel_margin */);
  VPD = calculateVPD(Temperature, Humidity, leafTemperatureOffset);
  gfx->print("VPD: ");
  gfx->print(VPD, 4);
  gfx->println(" kPa");
}

void handleGetValues() {
  String json = "{";
  json += "\"analogPercent\":" + String(analogPercent) + ",";
  json += "\"temperature\":" + String(Temperature) + ",";
  json += "\"humidity\":" + String(Humidity) + ",";
  json += "\"vpd\":" + String(VPD, 2); // VPD hinzufügen
  json += "}";
  server.send(200, "application/json", json);
}


// Berechnung des VPD
float calculateVPD(float temperature, float humidity, float leafTempOffset) {
  float leafTemperature = temperature + leafTempOffset - 1.4;

  // Sättigungsdampfdruck berechnen (in kPa)
  float saturationVaporPressure = 0.6108 * exp((17.27 * leafTemperature) / (leafTemperature + 237.3));

  // Tatsächlicher Dampfdruck berechnen (in kPa)
  float actualVaporPressure = (humidity / 100.0) * saturationVaporPressure;

  // VPD ist die Differenz zwischen Sättigungsdampfdruck und tatsächlichem Dampfdruck
  float vpd = saturationVaporPressure - actualVaporPressure;

  // Negative Werte verhindern
  return vpd > 0 ? vpd : 0.0;
}


void setup() {
  Serial.begin(115200);

  // Initialisierung des DHT22
  dht.begin();

  // Initialisierung EEPROM
  EEPROM.begin(EEPROM_SIZE);
  analogOutput = EEPROM.read(0);
  analogPercent = map(analogOutput, 0, 255, 0, 100);

  // Initialisierung der I2C-Pins
  Wire.begin(SDA_PIN, SCL_PIN);

  // Schreibe gespeicherte DAC-Daten in den PCF8591
  Wire.beginTransmission(PCF8591_I2C_ADDRESS);
  Wire.write(0x40);
  Wire.write(analogOutput);
  Wire.endTransmission();

  // Mit W-LAN verbinden
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Verbinde mit W-LAN...");
  }

  Serial.println("Verbunden mit W-LAN. IP Addresse: ");
  Serial.println(WiFi.localIP());

  // Initialisierung des Displays
  gfx->begin();
  gfx->fillScreen(BLACK);
#ifdef TFT_BL
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
#endif
  updateDisplay();

  // Webserver-Endpunkte konfigurieren
  server.on("/", handleRoot); // **Hier wird die Hauptseite registriert**
  server.on("/setanalog", HTTP_POST, handleSetAnalog);
  server.on("/getvalues", HTTP_GET, handleGetValues);

  server.on("/setLeafTemp", HTTP_POST, []() {
    if (server.hasArg("leafTempOffset")) {
      leafTemperatureOffset = server.arg("leafTempOffset").toFloat();

      // VPD neu berechnen
      VPD = calculateVPD(Temperature, Humidity, leafTemperatureOffset);

      Serial.println("Neuer Blatttemperatur-Offset: " + String(leafTemperatureOffset));
      Serial.println("Aktualisierter VPD: " + String(VPD));

      // Weiterleitung zur Hauptseite
      server.sendHeader("Location", "/");
      server.send(303);
    } else {
      server.send(400, "text/plain", "Fehler: Kein Offset angegeben.");
    }
  });

  // Webserver starten
  server.begin();
}


void loop() {
  server.handleClient();  // WebServer-Anfragen verarbeiten

  // Sensorwerte regelmäßig aktualisieren
  static unsigned long lastUpdate = 0;
  unsigned long now = millis();
  if (now - lastUpdate >= 2000) {  // Alle 2 Sekunden aktualisieren
    lastUpdate = now;

    // DHT22 auslesen
    float temp = dht.readTemperature();
    float hum = dht.readHumidity();

    // Nur gültige Werte übernehmen
    if (!isnan(temp) && !isnan(hum)) {
      Temperature = temp;
      Humidity = hum;

      // VPD berechnen
     VPD = calculateVPD(Temperature, Humidity, leafTemperatureOffset);

     // Display aktualisieren
      updateDisplay();
    } else {
      Serial.println("Fehler beim Lesen des DHT22-Sensors.");
    }
  }
}

Bibliotheken:
GFX Library for Arduino (Version 1.5.1 von moononournation
DHT kxn (Version 3.4.4) von Adafruit
WiFiManager (Version 2.0.17) von tzapu
ESP Async WebServer (Version 3.4.5) von Me-No-Dev

Hallo thirty-thirty

Schaue mal genauer wie deutsche Umlaute richtig umgesetzt werden.

Wo finde ich da etwas? Auch Google hilft da nicht. Oder habe ich hier im Forum etwas übersehen?

Willkommen in der wunderschönen Welt der Zeichenkodierung, Codepages und Unicode...

Es sieht so aus, das du UTF-8 codierten Text an das TFT schickst, dieses jedoch eine andere Codierung erwartet.

Das selbe im HTML. Hier kannst du die Zeichen anders codieren. Siehe Zeichenreferenz – SELFHTML-Wiki
Alternativ gibst du ein meta charset mit der benutzten Kodierung an.

1 Like

Angabe der Zeichencodierung in HTML

1 Like
<!doctype html>
<head>
  <meta charset="utf-8">
</head>
<body>
1 Like
String html = "<!doctype html><head><meta charset=\"utf-8\"></head><body>";
1 Like

Auch hier geht es darum, daß Bibliothek und Font UTF-8 darstellen können, siehe Beispiel Arduino_GFX/tree/master/examples/U8g2Font/U8g2FontPrintUTF8/U8g2FontPrintUTF8.ino

Meine Empfehlung ist ein Umstieg auf TFT_eSPI.

1 Like

Habe es jetzt hinbekommen.

Aber ein kleiner Hinweis, wie das geht, wäre sehr nett gewesen. Meine letzten Programmierarbeiten waren in der Ausbildung. Das ist inzwischen über 20Jahre her und in der Zwischenzeit hatte ich kaum Berührung damit.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.