waterpi
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
.
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
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
- nötige Parameter sind
- 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)
- die Serviceschicht kümmert sich um Initialisierung und Steuerung per
- Frontend wird eine statische HTML-Seite, die die Api-Aufrufe durchführt
- Backend bekommt eine Rest-API
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