waterpi

zurück

1 Intention

Den eigenen Garten mit mehreren Sprengern oder Schläuchen zu bewässern soll automatisiert werden. Kein manuelles Wasser andrehen (oder Pumpe einschalten), Sprenger nacheinander schalten, bloß nicht vergessen auch wieder auszuschalten. Analog dem seit 5 Jahren bewährten Projekt pi230v dokumentiere ich hier ein Projekt mit Fokus auf Gartenbewässerung mit der Hilfe von Magnetventilen.

2 Vision

Ein Raspberry Pi mit 8-Kanal-Relais soll sowohl eine vorhandene Pumpe und zu erwerbende Magnetventile steuern. Wir taufen das Projekt waterpi.

IMG_20170507_173325.jpg

3 Abgrenzung

Folgendes wird es hier nicht geben: eine App, eine "Cloudanbindung" oder irgendeine Art von Internetfähigkeit. Wir wollen hier keine unnötige Angriffsfläche schaffen.

3.1 Ja, aber…

Nein. Der waterpi wird rein lokal erreichbar sein. Wenn tatsächlich mal aus dem Urlaub heraus etwas geschaltet werden soll, wird halt ein VPN-Zugang benötigt, oder der Nachbar erhält mal die Zugangsdaten, die hier vorübergehend anpassbar wären.

4 Ausblick

Später sollen die Sprenger auch automatisiert

  • zu festen Zeiten oder
  • nach manuellem Anstoß alle nacheinander

laufen können. Aber erstmal wird sich "der Kunde" mit manuellem Schalten gewöhnen und Vertrauen gewinnen.

Später kann der Plan dann auch eingesehen werden.

Später wird bei Betreten der Webseite hervorgehoben, was gerade aktiv ist.

5 Meilenstein 1

5.1 Ziel

Eine einfache Browseroberfläche, von der manuell über WLAN die Sprenger gewählt werden können, oder halt "alles aus". Die Pumpe soll im Hintergrund ein bzw. ausgeschaltet werden, und auch maximal ein Sprenger gleichzeitig laufen (da ansonsten der Wasserdruck nicht immer reicht).

5.2 Schema

2021-05-24-waterpi.svg

5.3 Kosten

  • ein Raspberry Pi
  • weiterhin: 12V-Netzteil für Ansteuern der Magnetventile über das Relais
  • Dichthanf für die Dichtungen
  • Anschlüsse sind jeweils 3/4"
  Preis Anzahl Gesamt
8-Kanal Relais 7.25 1 7.25
Magnetventil 8.99 6 53.94
Muffe 3.59 6 21.54
Y-Adapter 6.29 5 31.45
Kupplung 4.49 6 26.94

5.4 Brainstorming

Was brauche ich alles?

  • einen Raspberry Pi, den habe ich schon, und was zu installieren ist weiß ich, siehe raspberry-pi-os (Installation, Java)
  • gpio, um mit dem Raspberry pi das Relais zu schalten, ist bereits im default enthalten
    • nötige Parameter sind readall, mode, write
  • für die Weboberfläche und Steuerung baue ich ein Java-Projekt mit Spring-Boot
    • Backend bekommt eine Rest-API
      • die Serviceschicht kümmert sich um Initialisierung und Steuerung per gpio
      • es gäbe auch eine Java-Bibliothek, die sieht mir aber viel zu kompliziert aus für das, was ich hier brauche (pi4j)
    • Frontend wird eine statische HTML-Seite, die die Api-Aufrufe durchführt

5.5 Umsetzung

Das Java-Projekt erstelle ich mit start.spring.io, ergänze lediglich Security (damit nicht jeder "Gast" im WLAN versehentlich die Seite entdeckt und fröhlich schaltet).

Gpio 1 wird die Pumpe steuern, die Gpios 2-7 dann ganze 6 Sprenger/Schläuche. Das Beispiel hier ist verkürzt auf 2 Sprenger, es soll ja das Prinzip gezeigt werden.

Überraschend ist hier vielleicht, dass das Einschalten "0", und Ausschalten "1" ist. Das liegt an der default-Relais-Belegung im Zusammenhang mit dem Einschalten des Pi, bevor alles initialisiert ist. Ohne Strom am Raspberry Pi soll die Pumpe halt aus sein.

5.5.1 Java-Code

@RestController
@Slf4j
public class GpioService {
    private BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(20);
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.SECONDS, blockingQueue);

    private void executeProcess(String was, List<String> cmd) {
        executor.execute(() -> {
            final ProcessBuilder pb = new ProcessBuilder(cmd);
            pb.directory(new File(System.getProperty("user.dir")));
            Process process;
            try {
                log.info("starte: " + was);
                process = pb.start();
                process.waitFor();
            } catch (IOException | InterruptedException e) {
                log.warn("fehlgeschlagen: " + was);
            }
        });
    }

    private void gpioWrite(String was, Integer key, Boolean val) {
        Assert.isTrue(key >= 1 && key <= 32, "bad key");
        executeProcess(was, Arrays.asList("sudo", "gpio", "write", "" + key, (val ? "1" : "0")));
    }

    private void gpioSetModeOut(String was, Integer key) {
        Assert.isTrue(key >= 1 && key <= 32, "bad key");
        executeProcess(was, Arrays.asList("sudo", "gpio", "mode", "" + key, "out"));
    }

    @PostConstruct
    private void initRelayGpios() {
        IntStream.rangeClosed(1, 8).forEach(gpio -> {
            gpioWrite("init off " + gpio, gpio, Boolean.TRUE);
            gpioSetModeOut("init mode " + gpio, gpio);
        });
    }

    private void pumpeAn() {
        gpioWrite("pumpe an", 1, Boolean.FALSE);
    }

    private void pumpeAus() {
        gpioWrite("pumpe aus", 1, Boolean.TRUE);
    }

    private void pumpeAnUndAlleAusserDiesemVentilZu(Integer anlassen) {
        IntStream.rangeClosed(2, 7)
            .filter(gpio -> !anlassen.equals(gpio))
            .forEach(gpio -> gpioWrite("andere aus " + gpio, gpio, Boolean.TRUE));
        pumpeAn();
    }

    @GetMapping("api/feierabend")
    void feierabend() {
        pumpeAus();
        IntStream.rangeClosed(2, 7)
            .forEach(gpio -> gpioWrite("feierabend aus " + gpio, gpio, Boolean.TRUE));
    }

    @GetMapping("api/wasserFeuerkorbAn")
    void wasserFeuerkorbAn() {
        gpioWrite("wasserFeuerkorb an", 2, Boolean.FALSE);
        pumpeAnUndAlleAusserDiesemVentilZu(2);
    }

    @GetMapping("api/wasserSaunaAn")
    void wasserSaunaAn() {
        gpioWrite("WasserSauna an", 3, Boolean.FALSE);
        pumpeAnUndAlleAusserDiesemVentilZu(3);
    }

    //weitere Methoden für weitere Sprenger/Schläuche
}

5.5.2 Weboberfläche

Agil first, hier ein Minimum an Funktionalität:

<form>
  <input type="radio" name="rad" id="rad_feierabend" value="feierabend" onclick="fetch('api/feierabend')">       <label for="rad_feierabend">Feierabend</label>
  <input type="radio" name="rad" id="rad_feuerkorb"  value="feuerkorb"  onclick="fetch('api/wasserFeuerkorbAn')"><label for="rad_feuerkorb">Feuerkorb</label>
  <input type="radio" name="rad" id="rad_sauna"      value="sauna"      onclick="fetch('api/wasserSaunaAn')">    <label for="rad_sauna">Sauna</label>
  <!-- weitere Radios für weitere Sprenger/Schläuche -->
</form>

5.5.3 Autostart

Damit das Programm automatisch hochfährt und die Webseite erreichbar ist, wenn der waterpi im Keller angeschlossen wird, hier noch ein guter, alter cronjob. Ja, systemd-timer wäre inzwischen angesagt, ist mir hierfür aber zu kompliziert.

Der Verzeichniswechsel ist nötig, damit eine potentiell vorhandene application.properties mit eigenen Zugangsdaten eingelesen werden kann. (Ja, geht alles schöner, reicht hier aber).

@reboot cd /home/pi/Documents/waterpi; java -jar waterpi.ja