Wenn man Zimmerpflanzen zuhause pflegt und mal einige Zeit unterwegs ist, so möchte man die Pflanzen natürlich besonders in den warmen Monaten nicht ohne Wasser auskommen lassen. Oft können Freunde, Nachbarn oder Familienmitglieder das Gießen in dieser Zeit übernehmen. Wenn aber diese Möglichkeit nicht in Frage kommt, dann bleibt noch eine weitere Option: ein automatisches Bewässerungssystem.
Im Folgenden beschreibe ich ein selbst entworfenes System (genannt: "Automatic Plant Watering System APWS") basierend auf dem ESP32, dass modular eine oder mehrere Pflanzen überwacht und gießt.
Im Gegensatz zu den Anleitungen zu ähnlichen Systemen, die man im WWW finden kann, so ist mein Ansatz ein modulares System zu bauen, welches genau eine Pflanze überwachen und gießen kann. Will man eine zweite oder noch mehr Pflanzen hinzufügen, so muss die Hardware für dieses Modul vervielfacht werden. Dies hat den Vorteil, dass pro Pflanze zwar mehr Hardware angeschafft werden muss. Der Vorteil des APWS ist jedoch, dass jedes Modul autark funktioniert und damit die Module auch eine größere räumliche Trennung haben können. Gemeinsam hat das APWS eine Server-Komponente, mit man als Mensch alle zugeschalteten Pflanzen überwachen und sogar manuell eingreifen kann...und dies über das WWW.
Die folgende Übersicht zeigt, wie ein APWS-Modul funktioniert und mit der zugehörigen Server-Komponente kommuniziert:
Mit einem APWS-Modul soll eine Pflanze bzw. ein Pflanztopf mit Wasser versorgt werden. Natürlich kann es auch ein Blumenkasten oder sogar ein ganzes Beet sein, dafür müssen nur die Schläuche evtl. verzweigt werden. Außerdem sollte der Sensor dann an der richtigen Stelle platziert werden.
Der folgende Schaltplan zeigt genau die Verdrahtung der einzelnen Komponenten für ein APWS-Modul (Bewässerung einer Pflanze bzw. eines Topfes). Wichtig: Die Ausgangsspannung des Abwärtswandlers muss vor dem Anschließen an den ESP32 auf die 5V eingestellt werden!
Es gibt etliche weit verbreitete Sensoren zur Messung der Bodenfeuchtigkeit oder man kann sich auch einen Selbstbau-Sensor verwenden. Doch diese Möglichkeiten haben meist das Problem, dass die blanken Metallsonden im Erdreich korrodieren und damit schnell unbrauchbar bzw. unzuverlässig werden. Daher habe ich mich für einen sog. kapazitativer Bodenfeuchtesensor entschieden, der keine offenen Metallteile im Erdreich benötigt und somit (hoffentlich) deutlich länger hält.
Nach dem Lesen einiger DIY-Anleitungen in WWW war mein erster Gedanke eine kleine Gleichstrom-Pumpe für 12V zu verwenden. Solche Pumpen werden gerne als Tauchpumpen angeboten und sind recht günstig. Jedoch sind Tauchpumpen nicht unbedingt ideal, da sie nicht trockenlaufen sollten (z.B. wegen Überhitzung) und außerdem sind die meisten von ihnen nicht selbstentlüftend. Darüber hinaus produzieren diese Pumpen einen vergleichsweise hohen Wasserdruck, was zur Folge haben könnte, dass bei einem Bewässerungsvorgang Wasser über den Topf auf den Teppich oder das Mobiliar verspritzt werden könnte.
Daher entschied ich mit für eine sog. Schlauchpumpe bzw. Peristaltik-Pumpe
mit 12V. Diese hat zwar einen recht geringen Durchsatz an Wassermenge (hier max. 150ml/min.), jedoch ist
diese zur Bewässerung von Zimmerpflanzen ausreichend und kann sehr gut dosiert werden. Außerdem hat der
hier verwendete Motor eine Stromaufnahme von nur 250-300 mA.
Wichtig ist, den passenden Schlauch für die Pumpe zu kaufen (hier: Innendurchmesser 3mm; Außendurchmesser: 5mm).
Als Material würde ich immer Silikon empfehlen, da dies Kälte, Hitze, Trockenheit, Feuchtigkeit und Korrosion
nicht anhaben kann und im Gegensatz zu PVC-Schläuchen nicht verhärtet und weiterhin flexibel bleibt. Allerdings
ist der Preis ein wenig höher als bei PVC.
Eine leere Kunststoff-Flasche wird als Wasservorrat für den ersten Prototypen verwendet. Zur späteren Bewässerung einer echten Zimmerpflanze wird vermutlich ein 5-Liter-Kanister zum Einsatz kommen, damit auch genug Wasser vorrätig ist. Um den Schlauch gut zu fixieren, habe ich in den Deckel ein Loch von 5mm Durchmesser gebohrt, welches dann den Schlauch aufnimmt. Ein zweites Loch soll den Luftausgleich gewährleisten.
Damit die Silikonschläuche gut am bzw. im Pflanztopf halten und kein Wasser auf den Teppichboden vergossen wird, habe ich zwei verschiedene Sorten von Halterungen in OpenSCAD entworfen, die man mit einem 3D-Drucker selbst fertigen kann.
Diese Halterung führt den Silikonschlauch auf der Oberseite und kann dann direkt in die Erde des Pflanztopfes gesteckt werden.
tube_mount1.scadDie zweite Variante ist eine Clip-Halterung, die man am Übertopf festklemmen kann. Für diese Halterung wird zusätzlich noch ein 2D-Pfad im SVG-Format benötigt, damit man die Halterung selbst erstellen kann.
clip_half.svg tube_mount2.scadHier ist es nur wichtig, dass die Eingangsspannung 12V betragen darf (um sicher zu gehen besser 16V). Die Ausgangsspannung muss entweder fest oder regelbar auf 5V eingerichtet sein, damit der ESP32 funktioniert.
Natürlich könnte das ganze Projekt auch über Batterie (Powerbank oder LiPo) betrieben werden, dann müssen allerdings die Pumpe und der Spannungswandler anders dimensioniert werden. Außerdem muss man vorher gut testen, wie lange solche eine Batterie hält, denn die Pumpe kann u.U. einiges an Energie verbrauchen. Da ich aus der gesamten Rechnung die Zeitspanne der automatischen Bewässerung nicht berücksichtigen wollte, habe ich mich für eine Lösung mit einem 12V-Netz-Adapter entschieden. Damit kann der Motor direkt und der ESP32 über einen einzigen Spannungswandler betrieben werden.
Zunächst habe ich den Sensor getestet bzw. die Daten analysiert, die dieser liefert. Dazu habe ich einen recht einfachen Sketch erstellt, der einmal pro Sekunde den ermittelten Wert des Sensors auf der seriellen Konsole ausgibt.
#define PIN_SENSOR 15
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.println(analogRead(PIN_SENSOR));
delay(1000);
}
Resultate des Sensors bei verschiedenen Messungen waren:
Hinweis: Eine Messung direkt nach dem Reset des ESP32 (oder auch Arduino) liefert immer den Wert 4095. Anscheinend benötigt der Sensor eine gewisse Zeit der Kalibrierung und daher sollten die ersten Messungen 30-60 Sekunden nach Einschalten des Mikrocontrollers ausgewertet werden.
Im späteren Sketch wird auch die WLAN-Verbindung des ESP32 genutzt. Ist diese Verbindung aktiv, sind nur die analogen Eingänge des ADC1 lesbar, d.h. die Pins 32, 33, 34, 35, 36 und 39!
Um zu testen, wie das Relais mit der Pumpe funktioniert, kann man den folgenden, einfachen Sketch verwenden,
der in regelmäßigen Abständen das Relais an- bzw. abstellt.
Sollte das Relais NICHT auf diesen Sketch reagieren, so ist die Spannung von 3,3V -die am digitalen Ausgang des
ESP32 anliegt- nicht genug, um das Relais zu steuern. Daher sollte man entweder ein auf anderes Relais-Modul oder
sogar einen MOSFET zurückgreifen (siehe unten).
#define PIN_RELAY 5
void setup()
{
pinMode(PIN_RELAY, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(PIN_RELAY, LOW);
delay(5000);
digitalWrite(LED_BUILTIN, HIGH);
digitalWrite(PIN_RELAY, HIGH);
delay(5000);
}
Der folgende Sketch ist das gesamte Programm, welches zur Analyse und automatischen Bewässerung des APWS notwendig ist. Vor der Benutzung müssen noch einige Werte auf die eigenen Bedürfnisse eingestellt werden:
DEVICE_ID
: Fortlaufende Nummer für jede zu bewässernde Pflanze bzw. für jedes APWS-Modul.
Am besten, man fängt bei 1
and und nummeriert dann fortlaufend durch.
PIN_SENSOR
PIN_RELAY
WIFI_SSID
und WIFI_PASSWORD
sind die Zugangsdaten des lokalen WLAN-Netzwerkes,
welches vom ESP32 zur Übertragung der Daten und Befehle benutzt wird.
SERVER_HOST
, SERVER_URI
und
SERVER_PORT
entsprechend angepasst werden.
SERVER_PSK
wird eine zufällige Zeichen- und Nummernfolge (=starkes Passwort) gewählt.Natürlich kann der Sketch auch angepasst werden, wenn die Server-Komponente gar nicht genutzt werden soll.
/**
Automatic Plant Watering System (APWS)
(c) 2021 Christian Grieger
*/
/**
* This is the unique IoT deviceId (1-255) which identifies the
* current plant on the server component
*/
#define DEVICE_ID 1
/**
Pins for the capacitity soil moisture sensor and the
relay for switching the water pump
Important: You can only use ADC1 pins (32, 33, 34, 35, 36, 39)
to use with analogRead() when WiFi is enabled!
*/
#define PIN_SENSOR 32
#define PIN_RELAY 5
#define PIN_LED_INDICATOR LED_BUILTIN
/**
Watering time (in seconds): Sets how long the pump
will spread water in one action.
(default: 10)
*/
#define WATERING_TIME 10
/**
Time interval (in seconds) how often new commands
are fetched from the server queue.
(default: 60)
*/
#define COMMAND_FETCH_INTERVAL 60
/**
Time interval (in seconds) how often the soil moisture
is been measured and possible automatic actions are taken.
(default: 1800; = 60 min.)
*/
#define MEASURING_INTERVAL 1800
/**
Threshold of the measured soil moisture when
the system begins with automatic watering.
*/
#define MOISTURE_THRESHOLD 2200
#define MOISTURE_THRESHOLD_STEP 100
/**
WiFi settings
*/
const char* WIFI_SSID = "mySSID";
const char* WIFI_PASSWORD = "myPassword";
/**
Server component settings
*/
const char* SERVER_HOST = "http://apws.example.com";
const char* SERVER_URI = "/";
const uint16_t SERVER_PORT = 80;
const char* SERVER_PSK = "mySecretPsk";
#include <WiFi.h>
#include <HTTPClient.h>
#define lmillis() ((long)millis())
#define _DEBUG_MODE
#ifdef _DEBUG_MODE
#define DEBUG_INIT(baudSpeed) Serial.begin(baudSpeed)
#define DEBUG_PRINT(value) Serial.print(value)
#define DEBUG_PRINTLN(value) Serial.println(value)
#else
// Empty macros bodies if debugging is not needed
#define DEBUG_INIT(baudSpeed)
#define DEBUG_PRINT(value)
#define DEBUG_PRINTLN(value)
#endif
#define PAUSE_WATERING 1
#define RESUME_WATERING 2
#define MANUAL_WATERING 3
#define MANUAL_MEASUREMENT 4
#define THRESHOLD_INCREASE 5
#define THRESHOLD_DECREASE 6
#define LOG_TYPE_MEASUREMENT 1
#define LOG_TYPE_ACTION 2
#define LOG_TYPE_COMMAND 3
#define LOG_TYPE_MESSAGE 4
bool automaticWateringEnabled = true;
long lastMeasurement, lastCommandFetched;
unsigned int moistureThreshold;
HTTPClient httpClient;
void setup()
{
DEBUG_INIT(9600);
pinMode(PIN_SENSOR, INPUT);
pinMode(PIN_RELAY, OUTPUT);
digitalWrite(PIN_RELAY, LOW);
pinMode(PIN_LED_INDICATOR, OUTPUT);
digitalWrite(PIN_LED_INDICATOR, LOW);
lastMeasurement = lmillis() + 60;
lastCommandFetched = lmillis() + 5;
moistureThreshold = MOISTURE_THRESHOLD;
}
void loop()
{
if (lmillis() - lastCommandFetched >= 0) {
lastCommandFetched = lmillis() + COMMAND_FETCH_INTERVAL * 1000;
String serverResult = requestServer("request", String(DEVICE_ID));
executeCommand(serverResult.toInt());
}
if (lmillis() - lastMeasurement >= 0) {
lastMeasurement = lmillis() + MEASURING_INTERVAL * 1000;
if (isSoilDry()) {
doWater();
}
}
}
bool isSoilDry()
{
return getSoilMoisture() > moistureThreshold;
}
int getSoilMoisture()
{
int moisture = analogRead(PIN_SENSOR);
String mStr = String(moisture);
DEBUG_PRINTLN("Measured moisture = " + mStr);
logToServer(LOG_TYPE_MEASUREMENT, mStr);
return moisture;
}
void doWater()
{
if (!automaticWateringEnabled) {
DEBUG_PRINTLN("Soil too dry, but automatic watering is paused.");
logToServer(LOG_TYPE_MESSAGE, "Soil too dry but automatic watering is paused.");
return;
}
logToServer(LOG_TYPE_COMMAND, "Watering " + String(WATERING_TIME) + "s (automatic)");
DEBUG_PRINTLN("Begin automatic watering.");
int timeWatering = WATERING_TIME * 1000;
digitalWrite(PIN_RELAY, HIGH);
// safetly check after every 5 sec. if moisture is above threshold, stop watering!
do {
if (!isSoilDry()) {
digitalWrite(PIN_RELAY, LOW);
DEBUG_PRINTLN("Safety stop watering!");
return;
}
delay(timeWatering);
timeWatering -= 5000;
} while (timeWatering > 0);
digitalWrite(PIN_RELAY, LOW);
DEBUG_PRINTLN("Ended automatic watering.");
}
void executeCommand(byte command)
{
DEBUG_PRINT("Executing command ");
switch (command) {
case PAUSE_WATERING:
DEBUG_PRINTLN("PAUSE_WATERING");
automaticWateringEnabled = false;
logToServer(LOG_TYPE_COMMAND, "Automatic watering paused");
break;
case RESUME_WATERING:
DEBUG_PRINTLN("RESUME_WATERING");
automaticWateringEnabled = true;
logToServer(LOG_TYPE_COMMAND, "Automatic watering resumed");
break;
case MANUAL_WATERING:
DEBUG_PRINTLN("MANUAL_WATERING");
digitalWrite(PIN_RELAY, HIGH);
delay(WATERING_TIME * 1000);
digitalWrite(PIN_RELAY, LOW);
logToServer(LOG_TYPE_COMMAND, "Watering " + String(WATERING_TIME) + "s (manual)");
break;
case MANUAL_MEASUREMENT:
DEBUG_PRINTLN("MANUAL_MEASUREMENT");
getSoilMoisture();
break;
case THRESHOLD_INCREASE:
DEBUG_PRINTLN("THRESHOLD_INCREASE");
if (moistureThreshold < (1023 - MOISTURE_THRESHOLD_STEP)) {
moistureThreshold += MOISTURE_THRESHOLD_STEP;
}
logToServer(LOG_TYPE_COMMAND, "Increased threshold by +" + String(MOISTURE_THRESHOLD_STEP));
break;
case THRESHOLD_DECREASE:
DEBUG_PRINTLN("THRESHOLD_DECREASE");
if (moistureThreshold > MOISTURE_THRESHOLD_STEP) {
moistureThreshold -= MOISTURE_THRESHOLD_STEP;
}
logToServer(LOG_TYPE_COMMAND, "Decreased threshold by -" + String(MOISTURE_THRESHOLD_STEP));
break;
default:
DEBUG_PRINTLN("NONE/UNKNOWN");
break;
}
}
void logToServer(byte logType, String message)
{
String logTypeStr;
switch (logType) {
case LOG_TYPE_MEASUREMENT:
logTypeStr = "MEASUREMENT";
break;
case LOG_TYPE_ACTION:
logTypeStr = "ACTION";
break;
case LOG_TYPE_COMMAND:
logTypeStr = "COMMAND";
break;
case LOG_TYPE_MESSAGE:
default:
logTypeStr = "MESSAGE";
break;
}
String logMessage = logTypeStr + "," + String(DEVICE_ID) + "," + message;
requestServer("log", logMessage);
}
String requestServer(String serverParam, String payload)
{
String auth = "?psk=" + String(SERVER_PSK);
String param = "&" + serverParam + "=" + payload;
String url = String(SERVER_HOST) + ":" + String(SERVER_PORT) + SERVER_URI + auth + param;
digitalWrite(PIN_LED_INDICATOR, HIGH);
connectWiFi();
httpClient.begin(url);
String httpBody = "";
int httpCode = httpClient.GET();
if (httpCode > 0) {
httpBody = httpClient.getString();
DEBUG_PRINT("httpCode=");
DEBUG_PRINT(httpCode);
DEBUG_PRINT(" - httpContent=");
DEBUG_PRINTLN(httpBody);
} else {
DEBUG_PRINT("ERROR requesting ");
DEBUG_PRINT(url);
DEBUG_PRINTLN(" - " + httpClient.errorToString(httpCode));
}
httpClient.end();
digitalWrite(PIN_LED_INDICATOR, LOW);
return httpBody;
}
void connectWiFi()
{
if (WiFi.status() == WL_CONNECTED) {
return;
}
DEBUG_PRINT("Connecting to WiFi ");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
DEBUG_PRINT(".");
}
DEBUG_PRINTLN("[ok]");
}
Die Server-Komponente besteht primär aus einem PHP-Script, welches sowohl die Messdaten empfängt und in einer Datenbank (MySQL) speichert, als auch Befehle an die angeschlossenen APWS-Module weitergibt. Generell dient die Server-Komponente als Polling-Dienst, welches das APWS-Modul anfragt. Der Server selbst kann keinen Kontakt zu den APWS-Modulen herstellen.
Um die Server-Komponente betreiben zu können sind folgende Voraussetzungen notwendig:
/var/www/apws
)
htdocs
verweist, hier ein Beispiel
für nginx (hier ist auch gleich die Konfiguration für PHP-FPM enthalten):
server {
server_name apws.example.com;
listen 80;
root /var/www/apws/htdocs;
index server.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
}
database/structure.sql
die benötigten
Tabellen und den Benutzer. Diese Daten müssen ebenfalls in der config.php abgeändert werden.
PSK
in denselben Wert abgeändert, der zuvor im Sketch als SERVER_PSK
gewählt wurde.
$devices
und $colors
festgelegt.
$commands
sind die gültigen Befehle hinterlegt, die der ESP32 ausführen kann und darf. Sollen diese sich ändern,
so ist darauf zu achten, sie auch (inkl. abgeänderter Logik) im Sketch zu ändern.
Nach dem Einrichten sollte nun unter der URL, die man im Webserver konfiguriert hat und mit Angabe des gültigen psk
(z.B. "http://apws.example.com/?psk=xxxxx") die Oberfläche der APWS Server-Komponente erscheinen:
Standardmäßig sind die folgenden Steuerbefehle implementiert und können sofort genutzt werden:
Nachdem der Sketch und das PHP-Script fertig programmiert und der Server eingerichtet war, konnte ein erster Prototyp gebaut werden, mit dem man nun alle eingebauten Funktionen testen kann.
Da viele der handelsüblichen Relais-Module 5V benötigen, um durchzuschalten, sind sie bei den max. 3,3V, die ein ESP32 liefern kann, nutzlos. Eine Alternative ist ein MOSFET, der als Schalter betrieben ebenfalls verwendet werden könnte. Allerdings muss bei der Auswahl des richtigen Transistors auf drei Werte geachtet werden:
Der IRF 1404 oder 1N60 könnten diese Anforderungen erfüllen. (siehe: Informationen wichtiger MOSFETs)
- noch in Arbeit -
zurück