Hilfe zum automatischen Bewässerungscode

Fortsetzung der Diskussion von Automatische Bewässerungsanlage:

Hey,

ich mache gerade ein kleines Arduino-Projekt (automatische Bewässerung) und wollte mal fragen, ob mein Code so okay ist oder ob man den besser / einfacher machen kann.

Ich messe die Bodenfeuchtigkeit über einen Sensor und wenn der Boden zu trocken ist, schalte ich eine Pumpe über ein Relais ein. Ich kenne mich mit Arduino noch nicht so gut aus und bin mir nicht sicher, ob ich das richtig oder sinnvoll gemacht habe.

Hier ist mein Code:


const int sensorPin = A0;     // Bodenfeuchtesensor
const int relayPin  = 4;      // Relais / Pumpe

int feuchtigkeit;             // Feuchtigkeit in Prozent

void setup() {
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);   // Pumpe aus (LOW-aktives Relais)

  Serial.begin(9600);
  Serial.println("Bodenfeuchtigkeit wird gemessen...");
  delay(2000);
}

void loop() {
  int sensorWert = analogRead(sensorPin);

  feuchtigkeit = map(sensorWert, 550, 10, 0, 100);

  Serial.print("Feuchtigkeit: ");
  Serial.print(feuchtigkeit);
  Serial.println(" %");

  if (feuchtigkeit < 60) {
    Serial.println("Boden zu trocken -> Bewässerung AN");
    digitalWrite(relayPin, LOW);
    delay(1000);
    digitalWrite(relayPin, HIGH);
  } else {
    Serial.println("Boden ausreichend feucht -> Bewässerung AUS");
    digitalWrite(relayPin, HIGH);
  }

  delay(3000);
}

Wäre cool, wenn jemand kurz drüber schauen könnte.
Danke schon mal :slightly_smiling_face:

Wenn Du im if- und im else-Block am Ende die Pumpe ausschaltest:
digitalWrite(relayPin, HIGH);
kannst Du das auch gleich zusammenfassen oder den else-Block gleich weglassen:

void loop() {
  int sensorWert = analogRead(sensorPin);

  feuchtigkeit = map(sensorWert, 550, 10, 0, 100);

  Serial.print("Feuchtigkeit: ");
  Serial.print(feuchtigkeit);
  Serial.println(" %");

  if (feuchtigkeit < 60) {
    Serial.println("Boden zu trocken -> Bewässerung AN");
    digitalWrite(relayPin, LOW);
    delay(1000);
    digitalWrite(relayPin, HIGH);
  }

  delay(3000);
}

Ich halte den 3- bzw. 4-Sekunden-Takt für deutlich zu schnell, aber das musst Du ausprobieren.
Vielleicht ist es sinnvoll, noch einen Zeitstempel zu generieren und diesen beim Einschalten der Pumpe mit zu senden.
Wenn Du alle 3 bzw. 4 Sekunden einen Messwert protokollieerst, sind das >= 1200 Werte pro Stunde, da kann Dir mal schnell das Einschalten der Pumpe durch die Lappen gehen.

Falscher Datentype: Hier reicht ein uint8_t

Um Speicher zu sparen: F-Makro nutzen

Serial.println(F("Boden zu trocken -> Bewässerung AN"));

Im Zeit zu sparen:

Serial.begin(115200);

Um die Werte zu begrenzen sollte noch ein https://docs.arduino.cc/language-reference/de/funktionen/math/constrain/ benutzt werden.

feuchtigkeit = constrain(map(sensorWert, 550, 10, 0, 100), 0, 100);

Mal unabhängig davon ob die Bewässerung wirklich funktioniert, habe ich den Code mal mit ein paar Anmerkungen und Links zum Nachlesen versehen:

// Warum constexpr? https://wiki.funkfreun.de/wissen/arduino_konstanten
constexpr uint8_t sensorPin {A0};   // Bodenfeuchtesensor
constexpr uint8_t relayPin {4};     // Relais / Pumpe

// Keine "magic numbers" im Code  https://de.wikipedia.org/wiki/Magische_Zahl_(Informatik)#Magische_Zahlen_in_Code
constexpr uint8_t gcHumidityThreshold {60};   // Limit value. If the value falls below this, watering is required.
constexpr uint16_t gcSensorMax {550};         // maximum Sensor Value
constexpr uint16_t gcSensorMin {10};          // minimum Sensor Value
constexpr unsigned long gcSetupDelay {2000};
constexpr unsigned long gcRelayDelay {1000};
constexpr unsigned long gcMeasureDelay {3000};

void setup() {
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);   // Pumpe aus (LOW-aktives Relais)

  Serial.begin(9600);
  Serial.println(F("Bodenfeuchtigkeit wird gemessen..."));
  delay(gcSetupDelay);
}

void loop() {
  int sensorWert = analogRead(sensorPin);
  // (int feuchtigkeit -> keine globale Variable erforderlich)
  // https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i2-avoid-non-const-global-variables
  int feuchtigkeit = map(sensorWert, gcSensorMax, gcSensorMin, 0, 100);   // Feuchtigkeit in Prozent

  Serial.print(F("Feuchtigkeit: "));
  Serial.print(feuchtigkeit);
  Serial.println(" %");

  if (feuchtigkeit < gcHumidityThreshold) {
    Serial.println(F("Boden zu trocken -> Bewässerung AN"));
    digitalWrite(relayPin, LOW);
    // Delay = i.d.R. schlecht ...
    // nicht blockierend wäre besser erfordert aber einen aufwendigeren Programmcode (zweiter Schritt)
    delay(gcRelayDelay);
    digitalWrite(relayPin, HIGH);
  } else {
    Serial.println(F("Boden ausreichend feucht -> Bewässerung AUS"));
  }
  // How to do multiple things at once
  // http://www.gammon.com.au/blink
  // http://www.gammon.com.au/statemachine

  delay(gcMeasureDelay);
}

Hallo @hubex638 ,

mir ist aufgefalllen, dass Dein Sketch aus Post 1 auch hier
https://forum.arduino.cc/t/automatische-bewasserungsanlage/1408929/14
aber von dem Forummitglied @eisbrenner
und eine nur unwesentlich abweichende Lösung bei
https://forum.arduino.cc/t/arduino-automatische-pflanzenbewasserung/1284330
bereits im Juli 2024 gepostet wurden.

Könntest Du die Zusämmenhänge kurz aufklären?

Gruß
ec2021

Hallo ec2021,

ja das stimmt. Den Sketch habe ich von eisbrenner genommen.

Ich kenne mich mit Arduino noch kaum aus und weiß oft nicht, wie ich selber anfangen soll. Deshalb schaue ich mir Beispiele aus dem Forum an und probiere damit rum.

Ich brauche einfach noch viel Hilfe und bin noch am Lernen.

Falls das falsch rübergekommen ist, war das nicht meine Absicht.

LG

"Mein Code" impliziert zumindest bei mir, dass Du den Sketch (weitgehend) selbst erstellt hast, so dass eine Hilfestellung dann i.d.R. anders aussehen muss, als wenn der Code von einem Dritten stammt.

@eisbrenner wiederum schreibt:

Hallo ihr lieben,Ich hab mir deinen Code nochmal genauer unter die Lupe genommen… da sind mir ein paar Verbesserungsmöglichkeiten aufgefallen:)

in einem Thread, wo dieser Code so überhaupt nicht vorkommt, stattdessen aber überraschend ähnlich in einem Thread vom Juli 2024, wiederum bei einem anderen Nutzer gepostet wurde.

Vielleicht kann @eisbrenner dieses Mysterium auch noch auflösen… ?! Im allgemeinen kann man hier im Forum den Werdegang eines Threads und der zugehörigen Software gut nachvollziehen, in diesem Fall bisher eher nicht…

Hey,

mir war ehrlich gesagt nicht klar, dass man jemanden extra markieren muss, wenn man seinen Code oder Teile davon nutzt. Ich hab den Code im Forum gesehen, weiter damit gearbeitet und hatte dann Fragen, die ich hier stellen wollte.

Ich bin noch neu hier und kenne die Regeln hier noch nicht so richtig. Arduino ist auch nicht mein Gebiet. Es tut mir leid, dass dadurch Verwirrung entstanden ist, das war echt keine Absicht.

Fürs nächste Mal hab ich daraus gelernt und werde besser drauf achten.

LG

Schönen Guten Abend,

Ich denke hier wird das Thema größer gemacht als nötig. Der Nutzer ist Anfänger hat sich an vorhandenem Code orientiert…. und wollte daraus lernen und sein Projekt umsetzen. Dass ihm nicht klar war dass man den ursprünglichen Autor extra markieren sollte, ist ihm einfach nicht bewusst gewesen.

Am Ende geht es hier doch darum zu helfen nicht darum, aus einer Kleinigkeit ein großes Thema zu machen…..

Ich unterstelle keine böse Absicht, sondern möchte nur die Zusammenhänge nachvollziehen können.

Außerdem ist es hilfreich, wenn man Werdegang und Hintergründe eines Posts bzw. Threads erkennen kann, alleine schon, um zu erkennen, auf welchem Kenntnisstand man aufsetzen sollte.

Kann es sein, dass der Post von @hubex638 hier als Post 1 und Deine Antwort im inzwischen getrennten Thread gelandet ist? Das würde zumindest den ansonsten unverständlichen Zusammenhang erklären…

Gruß

ec2021

Hätte @eisbrenner genauer damals gelesen hätte der festgestellt das im #Post 4

@my_xy_projekt schönen Code geschrieben hat ohne den hässligen delay().
Ob der Code vom @eisbrenner schöner war weiß ich nicht.

Hi @fony ,

darum geht’s mir gar nicht … :slight_smile:

Wenn man weiß, woher ein Sketch kommt, kann man i.d.R. wesentlich schneller und effizienter unterstützen.

Nach der Rückmeldung von @eisbrenner vermute ich, dass der Post, auf den er geantwortet hat, durch die Trennung der Threads aus dem Zusammenhang genommen wurde. Das würde erklären, warum eine Verbesserung eines Sketches kommt, der im vorherigen Thread überhaupt nicht zu finden ist. So wirkt es befremdlich und in drei Monaten wundern sich dann andere über einen “in der Luft hängenden” Post,

Ich stelle auch nur Fragen und bitte um Antworten.

Die Klärung des Sachverhalts kann aufzeigen, ob jemand etwas falsch gemacht oder sich einfach nur missverständlich ausgedrückt oder unzureichend informiert hat. Das gilt auch für den, der nachfragt, in diesem Fall also für mich :wink:

Im Übrigen kann man auch Ursache sein, ohne “Schuld” zu haben …

Gruß
ec2021

Wenn Du noch aufzeigen könntest, woher Du den Sketch genommen hast, könnte man prüfen, für welches Umfeld und unter welchen Bedingungen er einmal erstellt worden ist.

Hier ein paar (längliche) Texte meinerseits ... :wink:, als Anregung:

Beschreibung zu Deinem Sketch
  • Bei korrekter Beschaltung und Verwendung ändert ein “Feuchtesensor” seinen Widerstand in Abhängigkeit von der ihn umgebenden Bodenfeuchte:
    • Je feuchter seine Umgebung, um so geringer der Widerstand und damit die über dem Sensor abfallende Spannung (der Messpunkt nähert sich dem Massepotential). Der Messwert wird kleiner, der gemappte Wert steigt an.
    • Unterschreitet die “gemappte” Feuchtigkeit einen bestimmten Wert, so wird ein Digitalpin auf LOW gezogen, und nach einer Sekunde wieder auf HIGH gesetzt. Außerdem wird eine entsprechende Meldung auf der seriellen Schnittstelle ausgegeben.
  • Der Messvorgang findet alle drei, wenn der Schwellwert unterschritten wird, alle vier Sekunden statt (aufgrund der zusätzlichen Ein-Sekunden-Pause im if-Zweig).
  • Ist der Wert der Variablen “feuchtigkeit” gleich oder größer als der Schwellwert, so wird regelmäßig eine entsprechende Meldung per Serial abgesetzt und der relayPin auf HIGH gesetzt.
  • Allgemeine Punkte
    • Die serielle Schnittstelle wird mit einer Baudrate von 9600 Bd betrieben.
    • Analogmesswerte außerhalb der Grenzen von 550 und 10 werden entsprechend der map() Funktion so abgebildet, dass ein unterer Wert von 0 zu 101 und der obere Wert von 1023 zu -87 wird.
    • Für die in 8 Bit passenden Pin-Nummern werden integer Konstante verwendet.
    • Die Parameter des Mappings sind als Zahlenwerte im Code programmiert.
Bewertung
  • Aus Programmiersicht handelt es sich um einen grundsätzlich funktionsfähigen Sketch.
  • Die Baudrate kann auf 115200 Bd gesetzt werden, da dies heutzutage als gängige Übertragungsrate auf der USB-Schnittstelle verwendet wird. Zeitkritisch ist das jedoch in der vorliegenden Anwendung nicht.
  • Der Einsatz von delays innerhalb der loop() ist vielleicht nicht "schön", aber unschädlich, solange der Sketch keine weiteren, ggf. zeitkritischen Funktionen bedienen muss.
  • Die Grenzwerte von 550 und 10 in der map()-Funktion werden in diversen Tutorials verwendet und basieren auf empirischen Messungen (können sich also im Einzelfall unterscheiden).
  • Die von der map() Funktion für Messwerte oberhalb von 550 und unterhalb von 10 gelieferten Werte sind zwar kleiner als 0 bzw. größer als 100, bedienen aber die if-Abfrage korrekt. Die Verwendung von constrain(), um die Daten auf den Bereich 0 ... 100 zu begrenzen, ist daher nicht erforderlich, solange man sich nicht darauf verlässt, dass nur Werte zwischen 0 und 100 auftauchen.
  • Die programmierte Abfolge führt dazu, dass die über das Relais angesteuerte Pumpe u.U. mehrfach hintereinander jeweils für nur eine Sekunde eingeschaltet wird, solange der Messwert nicht dafür sorgt. dass feuchtigkeit >= Schwellwert ist.
  • Das regelmäßige Setzen des relayPin auf HIGH, wenn genügend Feuchte gemessen wird, ist nicht erforderlich, aber auch nicht schädlich.
  • Die regelmäßigen seriellen Ausgaben sind vermutlich nur beim Testen sinnvoll; es sei denn die Bewässerungsanlage hängt ständig an einem Rechner.
  • Die Grenzwerte aus dem Mapping hängen im Detail von der jeweiligen Anwendung ab. Vermutlich decken die empirischen Werte aus den Tutorials die meisten Fälle ausreichend ab, aber verschiedene Faktoren können eine Rolle spielen:
    • Wie tief reicht der Sensor in das "Erdreich"?
    • Welche Beschaffenheit liegt vor: Pflanzerde? Granulat?
    • Leitfähigkeit des Boden/Wasser-Gemisches?
    • Übergangswiderstand Sensor-Umgebung
    • Wie weit sind Sensor und Wasserzuführung voneinander entfernt?
    • Wie schnell und wie gleichmäßig verteilt sich das Wasser im Boden?
Verbesserungspotential
  • Anstelle der delays() kann man millis()-basierendes Timing verwenden, um die loop() für ggf. später interessante zeitkritische Funktionen nicht zu blockieren.
  • Die optimalen Sensorwerte für die individuelle Anwendung kann man mittels Trocknung und manueller Befeuchtung durch einen Test ermitteln. Dazu gibt man einfach regelmäßig den Sensorwert aus, notiert sich die Werte, die als "trocken" und "ausreichend feucht" gelten sollen und benutzt diese dann im Code.
  • Zudem könnte man die Pumpe solange durchlaufen lassen, wie dies zum Erreichen der Wunschfeuchte sinnvoll ist. Diese Zeit müsste man ebenfalls durch manuelle Tests ermitteln. Dies könnte u.U. die Lebensdauer der Pumpe und des Relais verlängern ...
  • Es empfiehlt sich, anstatt fester Zahlenwerte innerhalb des Codes mit Konstanten zu arbeiten. Das vereinfacht es, diese Daten entsprechend den individuellen Messergebnissen anzupassen, ohne die Stellen innerhalb des Codes zu identifizieren und anzupassen.
  • Datentypen sollten möglichst gut zum Verwendungszweck ausgewählt werden. Pins also als (vorzeichenloser) Byte-Type deklariert werden.
  • Die serielle Ausgabe ist vermutlich in der Entwicklungsphase sinnvoll, wird im Einsatz eher nicht helfen. Hier könnte man stattdessen eine Led-Anzeige ergänzen, die den Feuchtezustand anzeigt und den Messzyklus signalisiert: Grün für "ok" und Rot für "zu trocken". Beide Led blinken kurz, wenn die Messung erfolgt, so dass erkennbar ist, dass der Controller nicht "steht". Zudem zeigt eine rote Led an, dass die Pumpe im Einsatz sein muss. Wenn Rot und kein Pumpeinsatz => Fehler!

Und hier noch ein Sketch, der das meiste davon berücksichtigt, eine nicht-blockierende Lösung aufzeigt und über eine Led-Anzeige zum Zustand inkl. kurzem "Blinken" bei jeder Messung verfügt. Muss man nicht so machen, aber vielleicht als weitere Anregung von Interesse ...

Non_blocking Sketch
/*
  Forum: https://forum.arduino.cc/t/hilfe-zum-automatischen-bewasserungscode/1426822
  Wokwi: https://wokwi.com/projects/454040248062179329

  Angepasster Sketch zur Bewässerungsanlage

    Statt der Pumpe -> Blaue Led
    Statt des Feuchtesensors -> Schiebepotentiometer

  Zusätzlich:
    Rote Led: Zeigt den Zustand "zu trocken" an
    Grüne Led: Zeigt den Zustand "ausreichend feucht" an
    Beide Led blinken kurz, wenn eine Feuchtemessung erfolgt ist
    Magenta Led: Wird regelmäßig ein/ausgeschaltet, um das Nicht-Blockieren der loop() zu demonstrieren

  Die Parameter für das Mapping des Sensorwertes sowie der Schwellwert für "zu trocken" sind als Konstanten
  im Header deklariert. Ebenso die Werte für den Messzyklus und die Dauer des Pumpens. Die Ausschaltzeit der
  Anzeige-Led ist ebenfalls dort zu finden.

  Bei Einsatz der Pumpe wird die folgende Messung um die Pumpdauer nach hinten verschoben, um so das im
  Originalsketch durch delay() erzeugte Timing nachzubilden. Dazu wird die Variable letzteMessung mit dem
  Ausschalten der Pumpe neu gesetzt.

  2026/01/24
  ec2021

*/

constexpr uint8_t sensorPin {A0};             // Bodenfeuchtesensor
constexpr uint8_t relayPin   {4};             // Relais / Pumpe
constexpr uint8_t greenPin   {5};             // Anzeige "ausreichend feucht"
constexpr uint8_t redPin     {6};             // Anzeige "zu trocken/Pumpeneinsatz"
constexpr uint16_t sensorDry {550};           // Schwellwert Analogsensor für "zu trocken"
constexpr uint16_t sensorWet { 10};           // Schwellwert Analogsensor für "ausreichend feucht"
constexpr  int16_t dryThreshold {60};         // Schwellwert der umgerechneten Feuchte für "zu trocken"
constexpr unsigned long measureInterval {3000};    // Messintervall in ms
constexpr unsigned long pumpDuration {1000};  // Pumpdauer in ms
constexpr unsigned long blinkDuration {250};   //  Dauer der Aus-Zeit der Led-Anzeige in ms

enum class  STATUS {MESSEN, PUMPEAN, PUMPEN};
STATUS Status = STATUS::MESSEN;

unsigned long startZeit;
unsigned long letzteMessung;
boolean istZuTrocken = false;
boolean messungErfolgt = false;

void setup() {
  Serial.begin(115200);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);   // Pumpe aus (LOW-aktives Relais)
  Serial.println("Bewässerungsanlage gestartet ...");
}

void loop() {
  ledAnzeige();
  bewaesserungsMaschine();
  flackern();  // Nur zur Demo: Für die Bewässerungsanlage nicht benötigt!
}

void bewaesserungsMaschine() {
  switch (Status) {
    case STATUS::MESSEN:
      if (istTrocken()) {
        Serial.println("Boden zu trocken -> Bewässerung AN");
        Status = STATUS::PUMPEAN;
      }
      break;
    case STATUS::PUMPEAN:
      startZeit = millis();
      startePumpe();
      Status = STATUS::PUMPEN;
      break;
    case STATUS::PUMPEN:
      if (millis() - startZeit >= pumpDuration) {
        stoppePumpe();
        letzteMessung = millis(); // Setzt die nächste Messung auf Jetztzeit + measureInterval
        Status = STATUS::MESSEN;
      }
      break;
  }
}

boolean istTrocken() {
  if (millis() - letzteMessung >= measureInterval) {
    letzteMessung = millis();
    int sensorWert = constrain(analogRead(sensorPin), sensorWet, sensorDry);
    int feuchtigkeit = map(sensorWert, sensorDry, sensorWet, 0, 100);
    Serial.print("Feuchtigkeit: ");
    Serial.print(feuchtigkeit);
    Serial.println(" %");
    istZuTrocken = (feuchtigkeit < dryThreshold);
    messungErfolgt = true;
    return istZuTrocken;
  }
  return false;
}

void startePumpe() {
  Serial.println("Starte Pumpe");
  digitalWrite(relayPin, LOW);
}

void stoppePumpe() {
  Serial.println("Stoppe Pumpe");
  digitalWrite(relayPin, HIGH);
}

void ledAnzeige() {
  static unsigned long lastBlink;
  if (messungErfolgt) {
    messungErfolgt = false;
    lastBlink = millis();
    digitalWrite(greenPin, LOW);
    digitalWrite(redPin, LOW);
  }
  if (millis() - lastBlink > blinkDuration) {
    digitalWrite(greenPin, !istZuTrocken);
    digitalWrite(redPin, istZuTrocken);
  }
}


// Dient nur der Demo der nicht-blockierenden StateMachine
void flackern() {
  constexpr uint8_t magentaPin   {7};
  constexpr unsigned long magentaBlink {150};
  static boolean firstCall = true;
  if (firstCall) {
    pinMode(magentaPin, OUTPUT);
    firstCall = false;
  }
  static unsigned long lastBlinking;
  if (millis() - lastBlinking > magentaBlink) {
    lastBlinking = millis();
    byte state = !digitalRead(magentaPin);
    digitalWrite(magentaPin, state);
  }
}

Zum Testen auf Wokwi: https://wokwi.com/projects/454040248062179329

Und Dein Originalsketch hier https://wokwi.com/projects/454040210678341633

Viel Erfolg!
ec2021

[Edit] Und hier noch eine einfacher gehaltene nicht-blockierende Lösung, die ohne die Enumerations arbeitet:
Siehe bei Wokwi: https://wokwi.com/projects/454058836291026945

Sketch
/*
  Forum: https://forum.arduino.cc/t/hilfe-zum-automatischen-bewasserungscode/1426822
  Wokwi: https://wokwi.com/projects/454058836291026945

  Angepasster Sketch zur Bewässerungsanlage

    Statt der Pumpe -> Blaue Led
    Statt des Feuchtesensors -> Schiebepotentiometer

  Einfache non-blocking Lösung

  2026/01/24
  ec2021

*/


constexpr byte sensorPin {A0};               // Bodenfeuchtesensor
constexpr byte relayPin   {4};               // Relais / Pumpe
constexpr uint16_t sensorTrocken {550};      // Schwellwert Analogsensor für "zu trocken"
constexpr uint16_t sensorFeucht { 10};       // Schwellwert Analogsensor für "ausreichend feucht"
constexpr  int16_t schwellWert {60};         // Schwellwert der umgerechneten Feuchte für "zu trocken"
constexpr unsigned long messIntervall {3000};// Zeitintervall zwischen zwei Messungen in ms
constexpr unsigned long pumpDauer   {1000};  // Dauer des Pumpens in ms

int feuchtigkeit;             // Feuchtigkeit in Prozent
unsigned long letzteMessung;
unsigned long pumpeEinZeit;
boolean pumpeAn = false;

void setup() {
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);   // Pumpe aus (LOW-aktives Relais)
  Serial.begin(115200);
  Serial.println("Bodenfeuchtigkeit wird gemessen...");
}

void loop() {
  if (pumpeAn) {
    pumpen();
  }  else {
    messenUndAuswerten();
  }
  flackern();
}

void messenUndAuswerten() {
  static boolean ersterAufruf = true;
  if (millis() - letzteMessung > messIntervall || ersterAufruf) {
    letzteMessung = millis();
    ersterAufruf = false;
    int sensorWert = constrain(analogRead(sensorPin), sensorFeucht, sensorTrocken);
    feuchtigkeit = map(sensorWert, sensorTrocken, sensorFeucht, 0, 100);
    Serial.print("Feuchtigkeit: ");
    Serial.print(feuchtigkeit);
    Serial.println(" %");
    if (feuchtigkeit < schwellWert)     {
      Serial.println("Boden zu trocken -> Bewässerung AN");
      digitalWrite(relayPin, LOW);
      pumpeAn = true;
      pumpeEinZeit = millis();
    }
  }
}

void pumpen() {
  if (millis() - pumpeEinZeit > pumpDauer) {
    Serial.println("Pumpe Aus");
    digitalWrite(relayPin, HIGH);
    pumpeAn = false;
    letzteMessung = millis(); // Neue Startzeit für die nächste Messung setzen,
    // nachdem die Pumpe wieder ausgeschaltet wurde
  }
}



// Dient nur der Demo der nicht-blockierenden StateMachine
void flackern() {
  constexpr uint8_t magentaPin   {7};
  constexpr unsigned long magentaBlink {150};
  static boolean firstCall = true;
  if (firstCall) {
    pinMode(magentaPin, OUTPUT);
    firstCall = false;
  }
  static unsigned long lastBlinking;
  if (millis() - lastBlinking > magentaBlink) {
    lastBlinking = millis();
    byte state = !digitalRead(magentaPin);
    digitalWrite(magentaPin, state);
  }
}

Ist vielleicht besser nachzuvollziehen als die StateMachine ...

@hubex638 Eine wichtige Frage, zeige link zu deinem sensoren.