Bewässerungsautomat - Automatische Zeitsteuerung - [Teil 2]

Im zweiten Teil wollen wir nun eine neue Software erstellen, sodass wir Bewässerungsprogramme über den Browser konfigurieren können. Zum Beispiel, die Bewässerung soll täglich um 8 Uhr morgens für 15 Minuten und um 19 Uhr für 20 Minuten eingeschaltet werden.

Benötigte Hardware

Wir verwenden dieselbe Hardware wie im ersten Teil.

Bibliothek WebConfig

Um uns die Programmierarbeit zu erleichtern, verwenden wir die Software-Bibliothek WebConfig, die über die Arduino Bibliotheksverwaltung installiert werden kann. Es sollte in jedem Fall die neueste Version eingesetzt werden. Mindestens die Version 1.3 ist notwendig, da diese neue Funktionen enthält, die im Sketch verwendet werden.


Die Klasse WebConfig stellt intern bis zu 20 frei definierbare Konfigurationsparameter bereit. Die Werte dieser Parameter können im SPIFFS gespeichert und von dort gelesen werden. Über ein HTML-Formular können die Werte der Parameter verändert werden. Zur Definition wird eine JSON Zeichenkette verwendet, die eine Liste von Objekten darstellt. Jedes Objekt in dieser Liste beschreibt einen Parameter.

[{
"name":"",
"label":"",
"type":0,
"default":""
"min":0,
"max":0,
"options":[
  {"v":"","l":""}
]
}]

 name String Gibt den Namen des Parameters an. Mit diesem Namen wird der eingestellte Wert im Konfigurationsfile gespeichert. Über diesen Namen kann auch auf den Wert zugegriffen werden
label String Definiert die Beschriftung des Eingabeelements im HTML Formular
type Integer Typ des Eingabeelements folgende Elemente sind möglich
  • INPUTTEXT Texteingabefeld
  • INPUTPASSWORD Passwort Eingabefeld
  • INPUTNUMBER Nummern Eingabefeld
  • INPUTDATE Datums Eingabefeld
  • INPUTTIME Zeit Eingabefeld
  • INPUTRANGE Slider zur Nummerneingabe
  • INPUTCHECKBOX Ja/Nein Auswahl
  • INPUTRADIO Mehrfachauswahl
  • INPUTSELECT Mehrfachauswahl aus Dropdown-Liste
  • INPUTCOLOR Farbauswahl
default String Vorgabewert
min Integer (optional) Minimalwert für Nummerneingaben
max Integer (optional) Maximalwert für Nummerneingaben
options Liste von Objekten (optional) Liste der möglichen Optionen für Mehrfachauswahl. Jeder Eintrag besteht aus einem Objekt mit den Eigenschaften v für den Wert und l für die Beschriftung.


Anhand dieser Konfiguration erzeugt eine Funktion der WebConfig Klasse ein HTML-Formular zum Ändern und Speichern der Parameter. Die Darstellung auf der HTML Seite ist in der Breite auf 320 Pixel begrenzt, damit die Seite auf einem Smartphone gut lesbar ist.

Es gibt zwei Formulartypen. Eines dient besonders zum Einstellen von Zugangsdaten. Dieses Formular hat zwei Knöpfe, einer mit der Beschriftung „SAVE“ und einer mit der Beschriftung „RESTART“. Beide Knöpfe sorgen dafür, dass die Konfiguration im SPI-Flash-Filesystem, kurz SPIFFS, gespeichert wird.

Der „RESTART“-Knopf löst zusätzlich einen Restart des Mikrocontrollers aus. Über eine Callback Funktion, die beim Klicken auf den „SAVE“-Knopf ausgelöst wird, kann auf diesen Umstand reagiert werden. In unserem Sketch werden wir diese Callback-Funktion nutzen, um von der Konfigurationsseite wieder auf die Hauptseite zurückzuschalten.

Die zweite Variante ist ein Formular mit drei Knöpfen „DONE“, „CANCEL“ und „DELETE“. Dieses Formular besitzt keine automatische Speicherung. In unserem Sketch werden wir dieses Formular zum Einstellen der Schaltzeiten benützen. Auch hier können Callback-Funktionen registriert werden, die aufgerufen werden, wenn auf einen Knopf geklickt wurde.

Bibliothek NTPClient

Da wir eine genaue Uhrzeit zum Ein- und Ausschalten des Ventils benötigen, bedienen wir uns eines Zeitservers; das sind Server im Internet, die über das Network-Time-Protokoll (NTP) die genaue Uhrzeit und das aktuelle Datum liefern. Auch diese Bibliothek kann über die Bibliotheksverwaltung installiert werden.


Bibliothek ArduinoJson

Java-Script-Object-Notation (JSON) ist ein Format, in dem Arrays und Objekte als String dargestellt werden können. Dieses Format wird auch in der WebConfig Bibliothek benutzt, um die Formulare zu beschreiben. In unserem Sketch werden wir das JSON Format benutzen, um die Liste der Schaltzeiten als Array von Objekten im SPI-Filesystem zu speichern. Auch diese Bibliothek kann über die Bibliotheksverwaltung installiert werden. Nähere Details zum JSON Format findet man im Internet.

Die Software

//Includes für den Webserver und DNS
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
//Includes für den Zeit Client
#include <NTPClient.h>
#include <WiFiUdp.h>
//Include für den Servo
#include <Servo.h>
//Include für die Konfiguration
#include <WebConfig.h>
//Includes für die Speicherung der
//Einstellungen im SPI Flash Filesystem
#include <ArduinoJson.h>
#include <FS.h>


#define SERVO 13      //pin für den Servo
#define MAXEVENTS 10  //maximale Anzahl der Ereignisse
#define EVENTFILE "/ereignisse.json"  //Filename zur Sicherung

//Instanz für Servo
Servo servo1;
//Instanzen von WebConfig
WebConfig conf;       //WLAN Zugangsdaten
WebConfig eventconf;  //Ereignisse konfigurieren

//Instanz für Webserver
ESP8266WebServer server(80);

//Instanz für NTP Client
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP,"europe.pool.ntp.org");

//Datenstruktur zur Ereignis-Definition
typedef
struct event {
  String name;        //Name des Ereignis
  uint16_t time;      //Zeitpunkt in Minuten
  uint16_t duration;  //Dauer in Minuten
  uint8_t value;      //Wert zum Einstellen
  uint8_t status;     //Status 0=frei, 1=bereit
                      //2= Ereignis aktiv 
};

//Globale Variable für die Ereignisse
event events[MAXEVENTS];
//Verbindungszustand ins WLAN
bool connected = false;
//Zeitstempel der letzten Überprüfung
uint32_t last;
//aktuelle Durchflussmenge in %
uint16_t durchfluss = 0;
//aktueller Winkel für den Servo
float winkel = 0;

//Konfigurationswerte für Netzwerkanmeldung
//und Sommerzeit
String param_wlan = "["
  "{"
  "'name':'ssid',"
  "'label':'SSID des WLAN',"
  "'type':"+String(INPUTTEXT)+","
  "'default':''"
  "},"
  "{"
  "'name':'pwd',"
  "'label':'WLAN Passwort',"
  "'type':"+String(INPUTPASSWORD)+","
  "'default':''"
  "},"
  "{"
  "'name':'sommerzeit',"
  "'label':'Sommerzeit',"
  "'type':"+String(INPUTCHECKBOX)+","
  "'default':'1'"
  "}]";

//Formular zum Konfigurieren
//der Ereignisse
String param_event = "["
  "{"
  "'name':'name',"
  "'label':'NAME',"
  "'type':"+String(INPUTTEXT)+","
  "'default':''"
  "},"
  "{"
  "'name':'time',"
  "'label':'Zeit',"
  "'type':"+String(INPUTTIME)+","
  "'default':'00:00'"
  "},"
  "{"
  "'name':'duration',"
  "'label':'Dauer',"
  "'type':"+String(INPUTNUMBER)+","
  "'default':'1',"
  "'min':'1',"
  "'max':'300'"
  "},"
  "{"
  "'name':'value',"
  "'label':'Durchflussmenge',"
  "'type':"+String(INPUTNUMBER)+","
  "'default':'100',"
  "'min':'10',"
  "'max':'100'"
  "}]";

//HTML Templates
//Erster Teil der Webseite
const char HTML1[] =
"<!DOCTYPE HTML>\n"
"<html lang='de'>\n"
"<head>\n"
"<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n"
"<meta name='viewport' content='width=380' />\n"
"<title>Ventilsteuerung</title>\n"
"<style>\n"
"body {\n"
"  background-color: #d2f3eb;\n"
"  font-family: Arial, Helvetica, Sans-Serif;\n"
"  Color: #000000;\n"
"  font-size:12pt;\n"
"}\n"
".titel {\n"
"font-size:18pt;\n"
"font-weight:bold;\n"
"text-align:center;\n"
"width:100%;\n"
"padding:20px;\n"
"}\n"
".zeile {\n"
"  width:100%;\n"
"  padding:5px;\n"
"  text-align: center;\n"
"}\n"
"button {\n"
"font-size:14pt;\n"
"width:150px;\n"
"border-radius:10px;\n"
"}\n"
"</style>\n"
"</head>\n"
"<body>\n"
"<div id='main_div' style='margin-left:15px;margin-right:15px;'>\n"
"<div class='titel'>Durchflussmenge %i %%</div>\n"
"<form action='./' method='get'>\n"
" <div class='zeile'>0%%<input type='range' min='0' max='100' value='%i' name='durchfluss' onchange='form.submit()'>100%%</div>\n"
"</form><br>\n";

//Endteil der Webseite
const char HTML2 [] =
"<br><form action='./event' method='get' >\n"
"   <div class='zeile'><button type='submit'>Neues Ereignis</button></div>\n"
"</form>\n"
"<form action='./config' method='get' >\n"
"   <div class='zeile'><button type='submit'>Konfiguration</button></div>\n"
"</form>\n"
"</div>\n"
"</body>\n"
"</html>\n";

//Template für die Ereignisanzeige
const char HTMLEV [] =
"<div class='zeile'><a href='./event?ev=%s'>%s %s Uhr %3i Minuten mit %3i %%</a></div>\n";


//Falls möglich am lokalen WLAN anmelden
boolean initWiFi() {
    boolean connected = false;
    //Stationsmodus
    WiFi.mode(WIFI_STA);
    //wenn eine SSID konfiguriert wurde versuchen wir uns anzumelden
    if (strlen(conf.getValue("ssid")) != 0) {
      Serial.print("Verbindung zu ");
      Serial.print(conf.getValue("ssid"));
      Serial.println(" herstellen");
      //Verbindungsaufbau starten
      WiFi.begin(conf.getValue("ssid"),conf.getValue("pwd"));
      uint8_t cnt = 0;
      //10 Sekunden auf erfolgreiche Verbindung warten
      while ((WiFi.status() != WL_CONNECTED) && (cnt<20)){
        delay(500);
        Serial.print(".");
        cnt++;
      }
      Serial.println();
      //Wenn die Verbindung erfolgreich war, wird die IP-Adresse angezeigt
      if (WiFi.status() == WL_CONNECTED) {
        Serial.print("IP-Adresse = ");
        Serial.println(WiFi.localIP());
        connected = true;
      }
    }
    if (!connected) {
      //keine Verbindung, wir starten einen Access Point
      //damit wir auf die Konfiguration zugreifen können
      WiFi.mode(WIFI_AP);
      WiFi.softAP(conf.getApName(),"",1);
    }

    return connected;
}

//wandelt Minuten in einen String mit Format hh:mm
String timeToString(uint16_t minutes) {
  char buf[10];
  uint8_t h = minutes / 60;
  uint8_t m = minutes % 60;
  sprintf(buf,"%02d:%02d",h,m);
  return String(buf);
}

//wandelt Zeit im Format hh:mm in Minuten um
uint16_t minutesFromString(String time) {
  uint16_t h = time.substring(0,2).toInt();
  uint16_t m = time.substring(3,5).toInt();
  return h * 60 + m;
}

//sucht einen freien Ereigniseintrag
//returniert den Index oder -1 wenn kein
//freier Eintrag existiert
int16_t findFree() {
  uint8_t i = 0;
  while ((i<MAXEVENTS) && (events[i].status != 0)) i++;
  if (i<MAXEVENTS) {
    return i;
  } else {
    return -1;
  }
}

//sucht einen Ereigniseintrag mit Namen
//returniert den Index oder -1 wenn
//der Name nicht gefunden wurde
int16_t findEvent(String name) {
  uint8_t i = 0;
  while ((i<MAXEVENTS) && (events[i].name != name)) i++;
  if (i<MAXEVENTS) {
    return i;
  } else {
    return -1;
  }
}

//zeigt die Hauptseite am Webserver an
void handleRoot() {
  char buf[2000];
  char n[40];
  char t[10];
  //Aktualisiert den Servo zur manuellen Einstellung
  if (server.hasArg("durchfluss")) {
    durchfluss = server.arg("durchfluss").toInt();
    sendServo(durchfluss);
  }
  //Vorbereitung der Antwort
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  //Im Template zum ertsen Teil %i mit der aktuellen 
  //Durchflussmenge ersetzen
  sprintf(buf,HTML1,durchfluss,durchfluss);
  //Und an den Browser senden
  server.send(200, "text/html", buf);
  //Nun folgt die Anzeige der konfigurierten Ereignisse
  for (uint16_t i = 0;i<MAXEVENTS;i++) {
    if (events[i].status != 0) {
      strlcpy(n,events[i].name.c_str(),40);
      strlcpy(t,timeToString(events[i].time).c_str(),10);
      //Variablen im Template durch die Werte des
      //jeweiligen Ereignis ersetzen
      sprintf(buf,HTMLEV,n,n,t,events[i].duration,events[i].value);
      //Daten an den Browser senden
      server.sendContent(buf);
    }
  }
  //und schließlich das Ende des HTML-Dokuments senden
  server.sendContent(HTML2);
}

//Anfrage für die Konfigurationsseite
//bearbeiten
void handleConfig() {
  //die Anfrage an die Konfigurations Instanz
  //weitergeben
  conf.handleFormRequest(&server);  
}

//Anfrage für das Formular zur Ereignis
//Eingabe bearbeiten
void handleEvent() {
  int16_t i = -1;
  //Eintrag mit dem im Argument "ev" angegebenen
  //Namen suchen
  if (server.hasArg("ev")) {
    i = findEvent(server.arg("ev"));
  }
  if (i>=0) {
    //Wurde ein Eintrag gefunden, werden die Formularwerte
    //auf die Werte des Eintrags gesetzt
    eventconf.setValue("name",events[i].name);
    eventconf.setValue("time",timeToString(events[i].time));
    eventconf.setValue("duration",String(events[i].duration));
    eventconf.setValue("value",String(events[i].value));
  } else {
    //Wurde kein Eintrag gefunden, wird das Formular auf 
    //die Default-Werte gesetzt
    eventconf.setValue("name","");
    eventconf.setValue("time","00:00");
    eventconf.setValue("duration","1");
    eventconf.setValue("value","100");
  }
  //Anfrage an die Ereigniskonfiguration
  //weitergeben
  eventconf.handleFormRequest(&server);
}

//Servo einstellen
void sendServo(uint16_t durchfluss) {
  //um das Ventil zu öffnen ist ein Winkel
  //von 90 Grad erforderlich
  float winkel = durchfluss * 90 / 100;
  //diese Korrektur berücksichtigt, dass der Servo 
  //einen gesmt Drehwinkel von 210 Grad und 
  //nicht 180 Grad hat
  winkel = winkel * 180 / 210;
  //Servo einschalten die Parameter min=540 
  //und max=2400 bestimmen die minimale und die maximale 
  //Impulsdauer. Durch vermindern des Wertes min kann eine
  //Feinjustierung vorgenommen werden, wenn das Ventil
  //nicht ganz schließt
  servo1.attach(SERVO,500,2400);
  servo1.write(winkel);
  delay(1000);
  //Nach einer Sekunde den Servo wieder abschalten
  servo1.detach();
}

//Das Konfigurationsformular wurde mit dem
//Button SAVE beendet
void onSave(String res) {
  //die Hauptseite wird angezeigt
  handleRoot();
}

//Das Formular zur Ereigniseingabe wurde
//mit dem Button DONE beendet
void saveEvent(String data) {
  Serial.println(data);
  String t;
  int16_t i;
  //wurde das Formular für ein bestimmtes 
  // Ereignis aufgerufen, suchen wir  den
  //Index für dieses Ereignis. 
  //Ansonsten wird ein freier Eintrag gesucht
  if (server.hasArg("ev")) {
    i = findEvent(server.arg("ev"));
  } else {
    i = findFree();
  }
  if (i >= 0) {
    //wenn wir einen Eintrag gefunden haben,
    //füllen wir dessen Daten mit den
    //Werten aus dem Formular
    events[i].name=eventconf.getString("name");
    t=eventconf.getString("time");
    events[i].time = minutesFromString(t);
    events[i].duration = eventconf.getInt("duration");
    events[i].value = eventconf.getInt("value");
    events[i].status=1;
  }
  //die geänderten Werte werden im Filesystem gespeichert
  writeEvents(EVENTFILE);
  //die Hauptseite wird wieder angezeigt
  handleRoot();
}

//Das Formular zur Ereigniseingabe wurde
//mit dem Button CANCEL beendet
void cancelEvent() {
  //die Hauptseite wird wieder angezeigt
  handleRoot();
}

//Das Formular zur Ereigniseingabe wurde
//mit dem Button DELETE beendet
void deleteEvent(String name) {
  int16_t i;
  //der Index des bearbeiteten Eintrags
  //wird gesucht und falls gefunden durch
  //das Setzen des Status auf 0 gelöscht
  if (server.hasArg("ev")) {
    i = findEvent(server.arg("ev"));
    if (i >= 0) events[i].status = 0;
    Serial.print("Delete ");
    Serial.println(server.arg("ev"));
    //die Änderungen werden gespeichert
    writeEvents(EVENTFILE);
  }
  //die Hauptseite wird wieder angezeigt
  handleRoot();
}

//alle Einträge mit Status <> 0 werden gespeichert
//Zum Speichern der Daten wird das JSON Format
//verwendet
void writeEvents(String filename) {
  //File zum Schreiben öffnen
  File f = SPIFFS.open(filename,"w");
  Serial.println("Ereignisse speichern");
  //Ein JSON Dokument anlegen
  DynamicJsonDocument doc(2000);
  //wir benötigen hier ein Array
  JsonArray array = doc.to<JsonArray>();
  //Nun iterieren wir über alle Ereignisse
  for (uint16_t i = 0;i<MAXEVENTS;i++) {
    if (events[i].status != 0) {
      //ist der Status <> 0, fügen wir ein neues
      //Objekt zum Array hinzu und füllen es mit
      //den Daten aus dem Eintrag
      JsonObject ev = array.createNestedObject();
      Serial.println(events[i].name);
      ev["name"] = events[i].name;
      ev["time"] = events[i].time;
      ev["duration"] = events[i].duration;
      ev["value"] = events[i].value;
    }
  }
  //Zum Schluss wird das JSON Dokument in einen
  //String serialisiert und ins File geschrieben
  serializeJson(array, f);
  f.close();
}

//Die gespeicherten Ereignisse werden eingelesen
void readEvents(String filename) {
  uint16_t i;
  char n[40];
  //Zuerst werden alle Einträge gelöscht
  for (i = 0; i<MAXEVENTS; i++) events[i].status = 0;
  //wenn ein File mit dem gegebenen Namen existiert
  //wird es zum Lesen geöffnet
  if (SPIFFS.exists(filename)) {
    File f = SPIFFS.open(filename,"r");
    if (f) {
      Serial.println("Ereignisse einlesen");
      //wir benötigen wieder ein JSON Dokument
      DynamicJsonDocument doc(2000);
      //in diese Dokument werden die Daten aus dem 
      //File eingelesen und in ein Array von
      //Objekten umgewandelt
      deserializeJson(doc,f);
      JsonArray evs = doc.as<JsonArray>();
      i=0;
      //wir iterieren über alle Objekte in diesem Array
      for (JsonVariant v : evs) {
        if (i<MAXEVENTS) {
          //ist die Liste der Einträge noch nicht voll
          //wird der nächste Eintrag mit den Daten 
          //aus dem JSON Objekt gefüllt und der
          //Status auf 1 gesetzt
          JsonObject ev = v.as<JsonObject>();
          if (ev.containsKey("name")) {
            strlcpy(n,ev["name"],15);
            Serial.println(n);
            events[i].name = String(n);
          }
          if (ev.containsKey("time")) events[i].time = ev["time"];
          if (ev.containsKey("duration")) events[i].duration = ev["duration"];
          if (ev.containsKey("value")) events[i].value = ev["value"];
          events[i].status=1;
          i++;
        }
      }
      f.close();
    }
  }
}

//es wird geprüft ob im Augenblick ein Ereignis aktiv ist
//und falls nötig wird der Servo geändert
//Diese Funktion wird in regelmäßigen Abständen
//in der Hauptschleife aufgerufen
void checkEvents() {
  uint16_t m;
  uint8_t s;
  Serial.println(timeClient.getFormattedTime());
  //vom Time Client holen wir die aktuelle Uhrzeit 
  //in Minuten
  m = timeClient.getHours()*60+timeClient.getMinutes();
  //Wir iterieren über alle Ereigniseinträge
  for (uint16_t i = 0; i<MAXEVENTS; i++) {
    //Nur Einträge mit Status <> 0 werden verarbeitet
    if (events[i].status != 0) {
      s=1;
      if((events[i].time <= m) && ((events[i].time+events[i].duration) > m)) s=2;
      if (events[i].status != s) {
        if (s==2) {
          durchfluss=events[i].value;
        } else {
          durchfluss = 0;
        }
        Serial.printf("Status geändert auf %i Durchfluss %i%%\n",s,durchfluss);
        sendServo(durchfluss);
        events[i].status = s;
      }
    }
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(74880);
  Serial.println();
  bool initok = false;
  initok = SPIFFS.begin();
  if (!(initok)) // Format SPIFFS
  {
    Serial.println("Format SPIFFS");
    SPIFFS.format();
    initok = SPIFFS.begin();
  }
  Dir dir = SPIFFS.openDir("");
  while (dir.next()) {
      Serial.print(dir.fileName());
      Serial.print("    ");
      File f = dir.openFile("r");
      Serial.println(f.size());
  }

  //Die JSON Zeichenkette für das Konfigurations-Formular
  //wird gesetzt
  conf.setDescription(param_wlan);
  //Falls vorhanden wird die Konfiguration aus dewm SPIFFS geladen
  conf.readConfig();
  
  //Die JSON Zeichenkette für das Konfigurations-Formular
  //wird gesetzt
  eventconf.setDescription(param_event);
  //freies Formular definieren
  eventconf.setButtons(BTN_DONE+BTN_CANCEL+BTN_DELETE);
  //Falls vorhanden werden die Ereignisse aus dem SPIFFS geladen
  readEvents(EVENTFILE);
  //Die Netzwerk-Verbindung wird hergestellt
  connected = initWiFi();
  conf.registerOnSave(onSave);
  eventconf.registerOnDone(saveEvent);
  eventconf.registerOnCancel(cancelEvent);
  eventconf.registerOnDelete(deleteEvent);
  //readEvents(EVENTFILE);
  
  //Webserver vorbereiten und starten
  server.on("/", handleRoot);
  server.on("/config", handleConfig);
  server.on("/event",handleEvent);
  server.begin();
  Serial.println("WebServer gestartet");
  char dns[30];
  sprintf(dns,"%s.local",conf.getApName());
  uint16_t offset = conf.getBool("sommerzeit")?7200:3600;
  timeClient.setTimeOffset(offset);
  if (connected) timeClient.begin();
  if (MDNS.begin(dns)) {
    Serial.println("MDNS responder gestartet");
  }
  //Servo nach dem Einschalten auf 0%
  sendServo(durchfluss);
  last = millis();
}

void loop() {
  if (connected) timeClient.update();
  server.handleClient();
  MDNS.update();
  if ((millis()-last) > 10000) {
    checkEvents();
    last = millis();
  }
  
}

Vor dem Hochladen des Sketches muss noch sichergestellt werden, dass im Flash Speicher Platz für das SPI Filesystem reserviert wurde. Das erfolgt in der Arduino-IDE im Menü Werkzeuge.


1MB ist in unserem Fall ausreichend, dann bleiben noch 3MB für das Programm.

Viel Spaß beim Basteln.

Projekte für fortgeschrittene

4 Kommentare

Wolfgang Rothhaupt

Wolfgang Rothhaupt

Hallo,
habe das Projekt ausprobiert funktioniert mir Wemos mini ohne Probleme,
Wird der Sketch in einem NodeMCU esp8266 (esp12-e) modul geladen kommt keine Fehlermeldung aber es wird nicht mit dem WLAN verbunden an was kann das liegen?
Kann man den Beitrag als pdf irgendwo finden?

Viele Grüße
W. Rothhaupt

Christoph

Christoph

zum Lernen ist das Projekt ganz in Ordnung, wer aber wirklich eine Bewässerungslösung selber bauen möchte, der sollte sich das hier nicht antun.
Ich bin auf eine Open-Source Lösung mit dem Raspberry gekommen.
Es muss nur eine Lochrasterplatine mit einem 74HC595 aufgebaut werden und von AZ_Delivery zB ein 8x Relaisboard daran angeschlossen werden. Als Raspberry kann ein günstiger Zero-W genommen werden. Die Bewässerung erfolgt über Niedervoltventile. Oder wie bei mir aus ausgeschlachteten Waschmaschinenventile. Alle diese sind im ausgeschalteten Zustand verschlossen, was kein Risiko bei Stromausfall oder Defekt bedeutet!
Ein Relais lasse ich parallel zu allen anderen eingeschaltet, hiermit wird die Pumpe angesteuert, geht auch direkt am Wasserhahn, wer das möchte.

Die komplette Software ist via GIT erhältlich:
https://openthings.freshdesk.com/support/solutions/articles/5000631599-installing-and-updating-the-unified-firmware
Der Source-Code ist für Raspi und ESP8266 gleichermassen geschrieben.

Der HW-Schaltplan beinhaltet eine Echtzeituhr und einen AD-Wandler – beide können weggelassen werden, wie auch die Treiber für die Niedervoltrelais:
https://github.com/OpenSprinkler/OpenSprinkler-Hardware/tree/master/OSPi/1.50

Die komplette Bedienungsanleitung:
https://openthings.freshdesk.com/support/solutions/articles/5000716364-opensprinkler-user-manuals

Wer zB Feuchtigkeitssensoren für den Boden oder einen Regensensor verwenden möchte, der kann auch den AD-Wandler aus dem Schaltplan übernehmen.

Die Bedienung via WebSeite ist sehr übersichtlich und auch gut via Tablet oder Handy nutzbar. Sie unterstütz verschiedene Beregnungsszenarien und berücksichtig für den jeweiligen Ort die Wetterprognose.
Einfacher und trotzdem selbst gemacht, finde ich, geht es nicht.
Trotzdem finde ich diese Reihe hier ein gelungenes Trainingsprojekt, da es HW- und SW-Themen nutzvoll miteinander verbindet.

Gerald

Gerald

Hallo Manfred
Danke für den Hinweis. Ich habe die WebConfig Bibliothek korrigiert. Die neue Version ist 1.3.1

Manfred

Manfred

Hallo Gerald,
bei mir lässt sich der Sketch nicht compilieren.
Configuration —> ESP8266 + WebConfig 1.3.0.

Abbruch mit der Fehlermeldung:

/home/ms/Arduino/libraries/WebConfig/src/WebConfig.cpp:25:32: fatal error: ESP8266Webserver.h: No such file or directory
#include

Ursache ist die Datei “WebConfig.cpp” in Zeile 25:
hier steht:
#include
muss geändert werden in (Server groß geschrieben):
#include

Wenn’s hilfreich war, auf die Seite posten, ansonsten verwerfen.
MfG
Manfred

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert

Post di blog consigliati

  1. Installa ESP32 ora dal gestore del consiglio di amministrazione
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - Programmazione ESP tramite WLAN