Der Tomaten-Bewässerungswächter: Bild, Grünmaske, Ampel — Simulation, Display, Dokumentation

Fassung 1.0 · 29.09.2026 · Prof. Dr.-Ing. Ralph Wystup M.Sc. — erstellt mit KI und Agent (Claude Code, Anthropic)

Eine solarbetriebene ESP32-CAM fotografiert eine Tomatenpflanze, ein Raspberry Pi holt das Bild über Modbus TCP, trennt die Pflanze mit dem Excess-Green-Index vom Hintergrund und liest an Grünfläche und Schwerpunkt ab, ob die Blätter hängen. Hier läuft dieselbe Auswertung im Browser an einer gezeichneten Pflanze, dazu die drei Seiten des Displays und ein Tag im Zeitraffer mit Solarabschaltung. Alles rechnet ohne Netz.

Einstellen

Bewertung

Ablauf wie am Gerät: Pflanze gießen (Defizit auf 0), „Referenz setzen“, dann das Defizit wachsen lassen — die Ampel schaltet bei 8 % / 15 % Flächenabfall oder 2 % / 4 % Absinken des Schwerpunkts, jeweils auf dem Median der letzten fünf Messungen. Unter Helligkeit 40 ist Nacht, unter 2 % Grünanteil gilt die Pflanze als nicht erkannt.

Kamerabild und Grünmaske

Kamerabild (mit der gewählten Aufbereitungsstufe, nur Anzeige)

Kontrollbild: Pflanzenpixel (2G − R − B > 40 und G > 60) farbig, Rest abgedunkelt — analysiert wird immer das Original

Das Display (128 × 64), drei Seiten im Wechsel

Seite 1: die Antwort auf die einzige Frage — gießen?

Seite 2: läuft die Übertragung sauber?

Seite 3: Verlauf der Welke-Kennwerte

Ein Tag im Zeitraffer

288 Bilder à 5 min: Tageslicht, wachsendes Wasserdefizit, Referenz beim ersten hellen Bild nach 8:00, Gießen um 18:00; der Akku lädt bei Licht und entlädt nachts, die Versorgung schaltet die Kamera bei 10 % ab und bei 50 % wieder ein — der Wächter erkennt den Neustart am Rücksprung der Betriebszeit.

Vom Gerät

Rendervorschau der Displayseiten aus oled_anzeige.py

Bildaufbereitung gegen die Regenhaube: Original, wie durch die Folie, Stufen 1 bis 3 (Kontrastfaktor und Unschärfemaske)

Band 1 — Verfahren, Protokoll, Bildauswertung, Bedienung

Überblick: Was das System leistet

Eine ESP32-CAM beobachtet eine Tomatenpflanze. Ein Python-Programm auf dem PC holt in festem Takt Bilder von der Kamera, trennt die Pflanze rechnerisch vom Hintergrund und bestimmt zwei Kennwerte: die sichtbare Grünfläche und den Schwerpunkt der Blattmasse im Bild. Welkende Blätter hängen durch – die Grünfläche schrumpft und der Schwerpunkt sinkt ab. Beide Kennwerte werden mit einer Referenz verglichen, die einmal direkt nach dem Gießen gesetzt wird. Daraus entsteht eine einfache Ampel:

Ampel Bedeutung
GRÜN Wasser ausreichend
GELB beobachten – Pflanze wird schlapp
ROT (blinkend) GIESSEN! Blätter hängen deutlich
GRAU keine Aussage möglich (Nacht, Pflanze nicht erkannt, Referenz fehlt)

Die Besonderheit der Anlage: Die Kamera hängt im Freien an einem Solarmodul, dessen Laderegler die Versorgung bei 10 % Akkuladung hart abschaltet und erst bei 50 % wieder einschaltet. Die Kamera wird also regelmäßig stromlos und startet neu – mit allen Konsequenzen für IP-Adresse, Bildzählung und Gerätezustand. Das System ist so ausgelegt, dass es diese Zyklen ohne jeden Handgriff übersteht (Kapitel 6 und 7).

Beteiligte Komponenten

Komponente Aufgabe
ESP32-CAM (AI Thinker) nimmt auf Anforderung ein JPEG auf und stellt es als Modbus-TCP-Slave blockweise in Holding-Registern bereit
Solarmodul mit Laderegler versorgt die Kamera; Abschaltung bei 10 %, Wiedereinschaltung bei 50 % Akkuladung
ESP32_CAM_Modbus_Snapshot.ino Arduino-Sketch der Kamera: Kamera, WLAN, Modbus-TCP-Server, Statusregister, OTA-Update
pflanzen_dashboard.py Python-Programm auf dem PC: Bildabruf, Auswertung, Ampel, Web-Oberfläche (Port 8083), Protokollierung
Fritz!Box WLAN und Namensauflösung (tomatencam.fritz.box)
Browser Anzeige des Dashboards unter http://localhost:8083

Datenfluss

 Tomatenpflanze
      |
      | (Optik)
      v
 +-----------+   Modbus TCP, Port 502    +----------------------+   HTTP :8083   +---------+
 | ESP32-CAM |--------------------------->| pflanzen_dashboard.py|--------------->| Browser |
 | (Solar)   |   Bild in 240-B-Blöcken,   | Bildabruf + Analyse  |   Livebild,    | Ampel,  |
 |           |   CRC-gesichert            | ExG-Maske, Ampel     |   JSON-Daten   | Verlauf |
 +-----------+                            +----------------------+                +---------+
                                              |          |
                                              v          v
                                       pflanzen_log.csv  pflanzen_bilder/
                                       (Kennwerte)       (Bilder mit Zeitstempel)

Wichtig für das Verständnis: Die Kamera ist ein reiner Diener (Slave). Sie sendet nie von sich aus, sondern beantwortet nur Modbus-Anfragen des PC-Programms. Das PC-Programm ist der Meister (Master) und bestimmt den Takt. Der Browser wiederum fragt nur das PC-Programm ab, nie die Kamera – zur Kamera besteht stets genau eine TCP-Verbindung.

Das Zusammenspiel der Programme

Rollenverteilung

Der Arduino-Sketch auf der Kamera macht bewusst so wenig wie möglich: Bild aufnehmen, in Register packen, Fragen beantworten. Alles “Kluge” – Analyse, Bewertung, Speicherung, Anzeige – liegt auf dem PC. Das hat drei Gründe:

  1. Robustheit. Je weniger der Mikrocontroller tut, desto weniger kann nach einem harten Stromausfall in undefiniertem Zustand hängen bleiben.
  2. Rechenleistung. Die Bildauswertung mit numpy ist auf dem PC eine Sache von Millisekunden; auf dem ESP32 wäre sie mühsam und speicherkritisch.
  3. Wartbarkeit. Die Auswertung lässt sich am PC ändern und testen, ohne die Kamera neu zu flashen.

Der Registerplan als gemeinsame Sprache

Beide Programme kennen denselben Registerplan – er ist die vollständige Schnittstellenbeschreibung des Systems:

Register Inhalt Zugriff
0 Bildnummer (zählt je Aufnahme hoch, Reset bei Neustart!) lesen
1 Status: 0 = kein Bild, 2 = Bild bereit, 3 = Kamerafehler lesen
2/3 Bildgröße in Bytes (High-/Low-Word, 32 Bit) lesen
4 Blockgröße in Bytes (fest 240) lesen
5 Blockanzahl des aktuellen Bildes lesen
6 Blockindex: welcher Block in 100..219 liegt lesen + schreiben
7 reserviert –
8 Kommando: 1 schreiben = neue Aufnahme auslösen schreiben
9 Blitz-LED (GPIO 4): 0/1 lesen + schreiben
10 CRC16 über das gesamte JPEG (Polynom 0xA001) lesen
11/12 Betriebszeit seit Einschalten in Sekunden (High-/Low-Word) lesen
13 WLAN-Signalstärke (RSSI) in dBm, Zweierkomplement lesen
14 freier Arbeitsspeicher (Heap) in kB lesen
99 CRC16 des aktuell eingeblendeten Blocks lesen
100..219 Blockdaten: 120 Register = 240 Bytes, High-Byte zuerst lesen

Verwendet werden nur die Modbus-Funktionscodes FC03/FC04 (Register lesen) und FC06 (einzelnes Register schreiben). Die Register 11 bis 14 sind die “Gesundheitsdaten” der Kamera; sie wurden eigens für den Solarbetrieb ergänzt (Kapitel 6).

Die drei Schleifen

Im Betrieb laufen drei unabhängige Schleifen:

  1. Kamera-Schleife (loop() im Sketch): horcht auf OTA-Updates, nimmt Modbus-Telegramme entgegen, prüft alle 5 s die WLAN-Verbindung und verbindet bei Abriss selbsttätig neu.
  2. Abfrageschleife (Thread im Python-Programm): holt im eingestellten Intervall (Standard 300 s) ein Bild, liest die Statusregister, wertet aus, protokolliert, speichert. Jeder Fehler führt zum sauberen Verbindungsabbau und einem neuen Versuch im nächsten Takt.
  3. Web-Schleife (HTTP-Server im Python-Programm): beantwortet die Anfragen des Browsers (/daten als JSON alle 3 s, /bild.jpg, /maske.jpg, /referenz, /blitz). Sie greift nur auf die zuletzt abgelegten Ergebnisse zu und wartet nie auf die Kamera.

Abfrage- und Web-Schleife teilen sich die Daten über ein Schloss (threading.Lock), damit der Browser nie ein halb aktualisiertes Bild oder inkonsistente Kennwerte sieht.

Der Bild-Download im Detail

Ablauf einer Bildübertragung

Ein vollständiger Bildabruf besteht aus vier Phasen:

  1. Auslösen: Der PC schreibt eine 1 in Register 8. Die Kamera verwirft zunächst einen Frame (damit kein veraltetes Bild aus dem Puffer kommt) und nimmt dann auf. Die Modbus-Antwort kommt erst nach der Aufnahme – deshalb arbeitet der PC mit 8 s Timeout.
  2. Kopf lesen: Der PC liest Register 0 bis 10 in einem Rutsch: Bildnummer, Status, Größe, Blockanzahl, Gesamt-CRC.
  3. Blöcke holen: Für jeden Block b=0…n−1b = 0 \dots n-1: Blockindex in Register 6 schreiben, dann Register 99 bis 219 in einem FC03-Telegramm lesen (121 Register = Block-CRC + 240 Bytes Nutzdaten). Stimmt die CRC16 des Blocks nicht, wird derselbe Block bis zu fünfmal neu angefordert – nur dieser eine Block, nicht das ganze Bild.
  4. Gesamtprüfung: Länge vergleichen, CRC16 über das ganze JPEG prüfen, JPEG-Marker kontrollieren (Anfang FF D8, Ende FF D9). Erst wenn alles stimmt, gilt das Bild als gültig.

Diese doppelte CRC-Sicherung (je Block und übers Ganze) stammt aus dem Vorgängerprojekt und hat sich bewährt: Einzelne WLAN-Störungen kosten nur die Wiederholung eines 240-Byte-Blocks statt einer kompletten Übertragung.

Datenraten: was zu erwarten ist

Je Block sind zwei Modbus-Transaktionen nötig (Index schreiben, Daten lesen). Auf Telegrammebene fallen dabei an:

Telegramm Größe
FC06-Anfrage (Blockindex schreiben) 12 Bytes
FC06-Antwort (Echo) 12 Bytes
FC03-Anfrage (121 Register lesen) 12 Bytes
FC03-Antwort (Kopf + 242 Bytes Daten) 251 Bytes

Für 240 Nutzbytes werden also rund 287 Bytes übertragen – die Protokoll-Effizienz ist mit etwa 84 % ordentlich. Begrenzend ist aber nicht die Datenmenge, sondern die Umlaufzeit (Round-Trip-Zeit tRt_R) im WLAN, denn der Master wartet nach jeder Anfrage auf die Antwort:

tBlock≈2tR+tDatenv≈240BtBlockt_\text{Block} \approx 2\,t_R + t_\text{Daten} \qquad v \approx \frac{240\ \text{B}}{t_\text{Block}}

Mit typischen WLAN-Umlaufzeiten von 5 bis 20 ms ergibt das:

tRt_R Zeit je Block effektive Rate 30-kB-Bild (125 Blöcke)
5 ms ca. 11 ms ca. 21 kB/s ca. 1,5 s
10 ms ca. 21 ms ca. 11 kB/s ca. 3 s
20 ms ca. 41 ms ca. 6 kB/s ca. 5–6 s

Hinzu kommen etwa 0,5 bis 1 s für die Aufnahme selbst (Phase 1). In der Praxis sind einige Sekunden pro VGA-Bild (640 x 480, JPEG-Qualität 10, typisch 25 bis 40 kB) normal; bei schlechtem WLAN-Empfang draußen am Solarstandort entsprechend mehr. Die tatsächlich erreichte Dauer und Rate zeigt das Dashboard bei jeder Übertragung an (“Dauer”, “kB/s”) – zusammen mit dem RSSI-Wert (Register 13) lässt sich so beurteilen, ob der Standort funktechnisch taugt. Als Faustwerte: RSSI besser als −70-70 dBm ist gut, schlechter als −80-80 dBm wird zäh (viele Blockwiederholungen).

Das Abfrageintervall von 300 s ist von diesen Zeiten weit entfernt – die Übertragung lastet das System nur zu wenigen Prozent aus. Für Testzwecke kann das Intervall problemlos auf 10 s gesenkt werden; ist es kürzer als die Übertragungsdauer, holt das Programm einfach lückenlos Bild um Bild.

Warum überhaupt Modbus?

Ein HTTP-Streaming-Sketch wäre schneller. Modbus TCP wurde trotzdem bewusst gewählt: Das Protokoll ist dasselbe wie bei den übrigen Laboraufbauten (ADAM-Module, Waveshare-Module, ESP32-Slaves, Trendows), die Übertragung ist durch die Register-Semantik vollständig deterministisch, jedes Byte ist CRC-gesichert, und derselbe Sketch lässt sich unverändert von Trendows oder jedem anderen Modbus-Master abfragen. Für ein Bild alle 5 Minuten ist Geschwindigkeit schlicht kein Kriterium.

Namensvergabe: die Kamera ohne feste IP finden

Das Problem

Nach jeder Solar-Abschaltung startet die Kamera neu und bezieht ihre IP-Adresse per DHCP von der Fritz!Box. In der Regel bekommt sie dieselbe Adresse wieder – garantiert ist das aber nicht, insbesondere nach längeren Ausfällen oder einem Router-Neustart. Eine fest im Python-Programm eingetragene IP kann also ins Leere laufen. Eine DHCP-Reservierung in der Fritz!Box würde das lösen, ist hier aber bewusst nicht vorausgesetzt.

Die Lösung: zwei Namen, ein Gerät

Der Sketch meldet die Kamera unter dem Namen tomatencam gleich auf zwei Wegen im Netz an:

  1. DHCP-Hostname (WiFi.setHostname("tomatencam"), vor WiFi.begin() gesetzt): Die Kamera nennt der Fritz!Box bei jeder Anmeldung ihren Namen. Die Fritz!Box trägt ihn automatisch in ihren lokalen DNS ein – die Kamera ist ab dann als tomatencam.fritz.box auflösbar, von jedem Gerät im Heimnetz, ganz ohne Konfiguration am Router.
  2. mDNS (durch ArduinoOTA.setHostname("tomatencam") + ArduinoOTA.begin()): Die Kamera beantwortet Multicast-DNS-Anfragen selbst und ist als tomatencam.local erreichbar. Auf diesem Weg findet auch die Arduino IDE den OTA-Netzwerk-Port (“tomatencam at 192.168.x.xxx” – dort ist die aktuelle IP übrigens jederzeit ohne Kabel ablesbar).

Im Python-Programm steht deshalb keine IP mehr, sondern:

ESP32CAM_IP = "tomatencam.fritz.box"

Entscheidend ist, wann aufgelöst wird: socket.create_connection() löst den Namen bei jedem Verbindungsaufbau neu auf. Nach jedem Verbindungsverlust (und damit nach jeder Solar-Abschaltung) fragt das Programm die Fritz!Box also erneut nach der aktuellen Adresse. Die Kette

Neustart→DHCP (neue IP + Name)→DNS-Eintrag aktualisiert→Dashboard löst neu auf→verbunden\text{Neustart} \rightarrow \text{DHCP (neue IP + Name)} \rightarrow \text{DNS-Eintrag aktualisiert} \rightarrow \text{Dashboard löst neu auf} \rightarrow \text{verbunden}

schließt sich vollautomatisch. Sollte tomatencam.fritz.box in einem fremden Netz (anderer Router) nicht funktionieren, ist tomatencam.local der Ausweichname; als letzte Rückfallebene kann weiterhin eine nackte IP eingetragen werden.

Die Stromabschaltung: Erkennung und sicherer Wiederanlauf

Was bei der Abschaltung passiert

Der Solar-Laderegler trennt die Versorgung bei 10 % Restladung hart – für die Kamera ist das wie Netzstecker ziehen. Dabei geht verloren:

Nicht verloren gehen: der Sketch selbst (Flash-Speicher), die Referenz und alle Messdaten – die liegen auf dem PC. Ein Datenverlust durch die Abschaltung ist ausgeschlossen, weil die Kamera nichts speichert; schlimmstenfalls entfällt das Bild, das gerade übertragen wurde (die CRC-Prüfung verwirft es, und der nächste Takt holt ein neues).

Wie das Dashboard den Ausfall erlebt

Während die Kamera aus ist, scheitert jeder Abrufversuch. Die Abfrageschleife fängt jede Störung nach demselben Muster ab:

  1. Fehler registrieren, Meldung im Dashboard anzeigen (“Störung: …, nächster Versuch läuft”), Fehlbild-Zähler erhöhen.
  2. Socket schließen und auf None setzen – kein halbtoter Zustand.
  3. Zwei Sekunden warten, dann regulär im Takt weiterversuchen.

Das Dashboard bleibt dabei voll bedienbar und zeigt das letzte gültige Bild samt Kennwerten. Ein tage- oder wochenlanger Ausfall ist für das Programm derselbe Fall wie ein kurzer WLAN-Schluckauf – es gibt keinen Zustand, aus dem es nicht von allein zurückfindet, und es muss nie neu gestartet werden.

Der Wiederanlauf, Schritt für Schritt

Sobald der Akku 50 % erreicht, schaltet der Laderegler die Versorgung wieder ein. Dann läuft folgende Kette ab, ohne jeden Eingriff:

  1. Kamera bootet, initialisiert die Kamera-Hardware, verbindet sich ins WLAN, meldet ihren Namen an (DHCP + mDNS), startet OTA und den Modbus-Server. Der WLAN-Watchdog im Sketch prüft danach dauerhaft alle 5 s die Verbindung.
  2. Beim nächsten Takt der Abfrageschleife gelingt der Verbindungsaufbau (Namensauflösung liefert die aktuelle IP).
  3. Direkt nach dem Verbinden stellt das Dashboard den Blitz-Zustand wieder her (Register 9 wird mit dem letzten Sollwert beschrieben) – ein eingeschalteter Blitz bleibt aus Nutzersicht also einfach an.
  4. Das Dashboard liest mit jedem Bild die Betriebszeit (Register 11/12). Ist der neue Wert kleiner als der zuletzt gesehene, kann das nur einen Grund haben: Die Kamera wurde zwischenzeitlich stromlos. Das Dashboard schreibt dann “Kamera-Neustart erkannt (Solar-Abschaltung?)” in die Meldungsliste. So sind alle Solar-Zyklen im Dashboard dokumentiert, ohne dass die Kamera dafür eine Uhr bräuchte.
  5. Bilder werden mit Zeitstempel im Dateinamen gespeichert (bild_20260807_183005_00042.jpg). Dass die Bildnummer nach dem Neustart wieder bei 1 beginnt, ist damit unschädlich – nichts wird überschrieben, und die zeitliche Reihenfolge der Dateien stimmt immer.
  6. Die Bewertung läuft mit der unveränderten Referenz weiter, denn die liegt in pflanzen_referenz.json auf dem PC. Einzige Empfehlung: Nach sehr langen Ausfällen (Tage) einmal prüfen, ob sich Lichtsituation oder Pflanze stark verändert haben, und die Referenz beim nächsten Gießen ohnehin neu setzen.

Zeitstempel: warum der PC stempelt und nicht die Kamera

Die ESP32-CAM besitzt keine Echtzeituhr, und selbst eine per NTP gestellte Uhr wäre nach jeder Solar-Abschaltung zunächst wieder falsch. Deshalb stempelt der PC beim Empfang Datum und Uhrzeit unten als schwarzen Balken ins Bild – zusammen mit Bildnummer, RSSI und Kamera-Betriebszeit, z. B.:

07.08.2026 18:30:05  Bild 42  |  -61 dBm  |  Kamera an seit 1 h 23 min

Da zwischen Aufnahme und Empfang nur Sekunden liegen, ist der PC-Stempel praktisch exakt. Wichtig: Gestempelt wird nur die Anzeige- und Speicherfassung des Bildes; die Bildauswertung (Kapitel 6) läuft immer auf dem unveränderten Original, damit der Balken die Kennwerte nicht verfälscht.

Software-Updates ohne Kabel (OTA)

Da die Kamera draußen montiert ist, wäre jedes Update per USB-Kabel ein Ärgernis – zumal der serielle Upload bei der ESP32-CAM erfahrungsgemäß störanfällig ist. Der Sketch enthält deshalb ArduinoOTA: Die Kamera erscheint in der Arduino IDE unter Werkzeuge -> Port als Netzwerk-Port “tomatencam at ”, und der Upload läuft komplett über WLAN.

Das Verfahren ist gegen Stromausfälle während des Updates gesichert: Der Flash enthält zwei Programm-Partitionen (Partition Scheme “… with OTA”). Das neue Programm wird in die inaktive Partition geschrieben, während das alte weiterläuft. Erst wenn die Übertragung vollständig und die Prüfsumme korrekt ist, wird der Boot-Vermerk umgeschaltet; der Bootloader startet ab dann das neue Programm. Bricht das Update ab – auch durch die Solar-Abschaltung mitten im Vorgang – bleibt der Vermerk auf der alten Partition und die Kamera läuft unverändert weiter. Ein “Zerflashen” über OTA ist damit ausgeschlossen.

Zwei Regeln dazu:

Die Bildauswertung: von Pixeln zur Gieß-Empfehlung

Schritt 1: Die Pflanze vom Hintergrund trennen (Excess-Green-Index)

Grundlage ist der in der Agrar-Bildverarbeitung etablierte Excess-Green-Index. Für jedes Pixel mit den Farbkanälen RR, GG, BB (je 0 bis 255) wird berechnet:

ExG=2G−R−B\mathrm{ExG} = 2G - R - B

Die Idee: Blattgrün hat einen deutlich höheren Grünkanal als Rot- und Blaukanal zusammen im Vergleich zu neutralen Flächen. Für Grautöne (Erde, Topf, Wand) gilt R≈G≈BR \approx G \approx B, also ExG≈0\mathrm{ExG} \approx 0; für sattes Blattgrün wird ExG\mathrm{ExG} deutlich positiv. Ein Pixel zählt zur Pflanze, wenn zwei Bedingungen erfüllt sind:

ExG>40undG>60\mathrm{ExG} > 40 \quad\text{und}\quad G > 60

Die erste Schwelle (EXG_SCHWELLE) trennt Grün von Neutral, die zweite (MIN_GRUEN) unterdrückt dunkles Rauschen, das sonst bei schwachem Licht als “grün” durchrutschen könnte. Das Ergebnis ist die Grünmaske – im Dashboard über das Häkchen “Grünmaske zeigen” einsehbar: Pflanzenpixel bleiben farbig, alles andere wird abgedunkelt. Dieser Kontrollblick ist das wichtigste Werkzeug bei der Inbetriebnahme (stimmt die Maske nicht, stimmen auch die Kennwerte nicht).

Schritt 2: Zwei Kennwerte je Bild

Aus der Maske werden zwei Zahlen gewonnen:

ys=∑yy⋅m(y)∑ym(y)⋅100Hy_s = \frac{\sum_y y \cdot m(y)}{\sum_y m(y)} \cdot \frac{100}{H}

mit m(y)m(y) = Anzahl Pflanzenpixel in Bildzeile yy und HH = Bildhöhe. Welkende, herabhängende Blätter verlagern Blattmasse nach unten – ysy_s steigt (größere Werte bedeuten “weiter unten”).

Zusätzlich wird die mittlere Bildhelligkeit bestimmt. Liegt sie unter der Schwelle (MIN_HELLIGKEIT = 40), ist es Nacht oder zu dunkel – die Ampel geht auf GRAU und es findet keine Bewertung statt, denn ohne Licht sind die Kennwerte bedeutungslos. Ebenso GRAU: Grünanteil unter 2 % (“Pflanze nicht erkannt”).

Schritt 3: Glättung

Einzelbilder streuen (Wind bewegt Blätter, Belichtungsautomatik pumpt). Deshalb wird nicht der Momentanwert bewertet, sondern der Median der letzten 5 Messungen beider Kennwerte. Der Median ist hier dem Mittelwert überlegen, weil ein einzelner Ausreißer (Vogel im Bild, Wolkenschatten) ihn gar nicht beeinflusst. Bei 5-Minuten-Takt bewertet die Ampel also das Pflanzenverhalten der letzten knapp halben Stunde – für den Wasserhaushalt einer Tomate genau die richtige Zeitskala.

Schritt 4: Vergleich mit der Referenz und Ampel

Beim Klick auf “Referenz setzen” (direkt nach dem Gießen!) merkt sich das Programm die aktuellen Medianwerte als ArefA_\text{ref} und ys,refy_{s,\text{ref}} in pflanzen_referenz.json. Danach werden je Messung zwei Abweichungen gebildet:

ΔA=max(0,100⋅(1−AAref))Δy=max(0,ys−ys,ref)\Delta A = \max\!\left(0,\; 100 \cdot \left(1 - \frac{A}{A_\text{ref}}\right)\right) \qquad \Delta y = \max\!\left(0,\; y_s - y_{s,\text{ref}}\right)

ΔA\Delta A ist der Flächenabfall in Prozent der Referenzfläche, Δy\Delta y das Absinken des Schwerpunkts in Prozentpunkten der Bildhöhe. Beide sind bei 0 gedeckelt – eine Pflanze, die “besser als die Referenz” dasteht, wird schlicht als GRÜN gewertet. Die Ampel schaltet nach festen Schwellen, wobei jeweils der schlechtere der beiden Kennwerte entscheidet:

Bewertung Bedingung
ROT ΔA≥15%\Delta A \geq 15\,\% oder Δy≥4%\Delta y \geq 4\,\%
GELB ΔA≥8%\Delta A \geq 8\,\% oder Δy≥2%\Delta y \geq 2\,\%
GRÜN sonst

Zwei unabhängige Kennwerte machen die Erkennung robuster: Ein reiner Flächenabfall kann auch durch eine Lichtänderung entstehen, ein reines Absinken auch durch Wachstum einer einzelnen Ranke – treten beide gemeinsam auf oder einer deutlich, ist Welke die wahrscheinlichste Erklärung.

Das Verlaufsdiagramm im Dashboard zeigt beide Größen über die letzten zwei Tage (576 Punkte bei 5-Minuten-Takt) mit den ROT-Schwellen als gestrichelte Linien – man sieht der Pflanze also beim “Schlappwerden” zu und erkennt auch das typische Muster nach dem Gießen: Beide Kurven fallen binnen Stunden wieder Richtung null.

Grenzen des Verfahrens

Das Verfahren ist ein Vergleichsverfahren – es steht und fällt mit der Vergleichbarkeit der Bilder:

Alle Messwerte werden zusätzlich in pflanzen_log.csv protokolliert (Semikolon-getrennt: Zeit, Bildnummer, Helligkeit, Fläche, Schwerpunkt, Flächenabfall, Absinken, Status) – die Datei lässt sich direkt in Excel öffnen, etwa um die Schwellen anhand der ersten Trockenzyklen nachzujustieren.

Bedienungsanleitung

Einmalige Einrichtung

Kamera flashen

  1. Arduino IDE öffnen, ESP32_CAM_Modbus_Snapshot.ino laden; oben im Sketch WLAN-Name und -Passwort kontrollieren.
  2. Unter Werkzeuge einstellen (die Einträge erscheinen erst nach der Board-Wahl):
    • Board: ESP32 Dev Module (hat garantiert alle Menüpunkte)
    • Upload Speed: 115200 (höhere Raten brechen erfahrungsgemäß ab)
    • Partition Scheme: Minimal SPIFFS (1.9MB APP with OTA/…) – entscheidend ist der Zusatz “with OTA”; die KB-Angabe dahinter ist egal
    • PSRAM: Enabled (sonst nur reduzierte Auflösung!)
    • Port: der COM-Port des USB-Adapters
  3. Hochladen. Bricht der Upload ab: stabile 5-V-Versorgung sicherstellen (nicht der 3,3-V-Pin des Adapters!), kurzes gutes USB-Kabel, notfalls Stützkondensator 470 bis 1000 µF an 5V/GND. Dieses erste Flashen ist das letzte per Kabel – danach geht alles über WLAN.
  4. Seriellen Monitor öffnen (115200 Baud), Reset drücken und die Startmeldungen prüfen. Erwartet werden nacheinander: PSRAM gefunden, Kamera initialisiert, WLAN verbunden mit IP-Angabe, OTA bereit (Netzwerk-Port 'tomatencam'), Modbus TCP auf Port 502.

PC vorbereiten

  1. Benötigte Pakete: pip install numpy pillow
  2. In pflanzen_dashboard.py prüfen: ESP32CAM_IP = "tomatencam.fritz.box". Zum schnellen Testen darf INTERVALL vorübergehend auf 10.0 stehen; für den Dauerbetrieb wieder 300.0 eintragen.

Montage

  1. Kamera fest montieren (Stativ/Halterung), Pflanze möglichst bildfüllend, Blickrichtung so, dass die Pflanze frei vor ruhigem Hintergrund steht. Möglichst gleichmäßige Beleuchtung.
  2. An die Solarversorgung anschließen. Fertig – an der Fritz!Box ist nichts einzustellen.

Täglicher Betrieb

Starten

python pflanzen_dashboard.py

Browser: http://localhost:8083 (die Adresse für Handy/Tablet im selben Netz nennt die Konsole beim Start). Oben links zeigt der Statuspunkt die Verbindung; nach spätestens einem Intervall erscheint das erste Bild mit Zeitstempel-Balken.

Referenz setzen – der wichtigste Handgriff

  1. Tomate gießen.
  2. Einige Messungen abwarten (im Normaltakt ca. 25 Minuten, denn die Glättung braucht 5 Bilder; im 10-s-Testtakt reicht eine Minute).
  3. Im Dashboard “Referenz setzen” klicken. Die Bestätigung nennt den Zeitpunkt; er wird dauerhaft unter der Ampel angezeigt.

Diesen Handgriff nach jedem Gießen wiederholen – er sagt dem System “so sieht die gut versorgte Pflanze aus”.

Die Dashboard-Elemente

Element Bedeutung
Ampel mit Statuszeile Gesamtbewertung (Kapitel 6.4); ROT blinkt
“Referenz setzen” speichert den Ist-Zustand als frisch-gegossen-Referenz
Kamerabild / Häkchen “Grünmaske zeigen” Livebild mit Zeitstempel bzw. Kontrollansicht der Pflanzenerkennung
Kennwerte Grünfläche, Schwerpunkt, Abweichungen zur Referenz, Helligkeit
Übertragung Bildnummer, Größe, Dauer, Wiederholungen, verworfene Bilder
Kamera an seit / WLAN-Signal / freier Speicher Betriebsdaten aus den Registern 11–14; “Kamera an seit” springt nach jeder Solar-Abschaltung zurück
Blitz-Knopf schaltet die weiße LED der Kamera (überlebt Neustarts)
Verlaufsdiagramm Flächenabfall und Absinken über ca. 2 Tage, Gieß-Schwellen gestrichelt
Meldungen letzte 10 Ereignisse, u. a. “Kamera-Neustart erkannt (Solar-Abschaltung?)”

Dateien, die das Programm anlegt

Datei/Ordner Inhalt
pflanzen_log.csv alle Messwerte, Semikolon-getrennt, Excel-tauglich
pflanzen_bilder/ jedes Bild als JPEG mit Zeitstempel im Namen und im Bild
pflanzen_referenz.json die aktuelle Referenz (übersteht Programm-Neustarts)

Software-Update der Kamera (über WLAN)

  1. Python-Dashboard mit Strg+C beenden (es hält sonst die einzige Modbus-Verbindung – das stört zwar OTA nicht, aber es ist sauberer, die Kamera in Ruhe zu lassen).
  2. Arduino IDE: Werkzeuge -> Port -> “tomatencam at …” wählen.
  3. Hochladen. Die Kamera startet danach von selbst neu.
  4. Dashboard wieder starten.

Erscheint der Netzwerk-Port nicht: Ist die Kamera gerade in der Solar-Abschaltung? PC im selben Netz? Notfalls Port-Menü erneut öffnen oder IDE neu starten.

Fehlerbehebung

Symptom Ursache und Abhilfe
Dauerhaft “Störung: …” im Dashboard Kamera stromlos (Solar-Abschaltung – einfach warten) oder Name nicht auflösbar: tomatencam.local bzw. IP eintragen; prüfen, ob ein zweites Programm die einzige Modbus-Verbindung belegt
“Kamera meldet Status 3” Kamerafehler bei der Aufnahme; tritt er wiederholt auf: Stromversorgung prüfen (Spannungseinbruch), Kamera einmal stromlos machen
“Pflanze nicht erkannt” Pflanze zu klein im Bild oder zu dunkel: näher heran, Blitz einschalten, ggf. EXG_SCHWELLE senken (z. B. 30)
Ampel springt hin und her wechselnde Beleuchtung: Standort/Beleuchtung vergleichmäßigen; Referenz bei repräsentativem Licht setzen; ggf. GLAETTUNG erhöhen
Ampel dauernd GELB/ROT trotz gegossener Pflanze Referenz veraltet (Pflanze gewachsen, Kamera verrutscht, Jahreszeit): neu gießen und Referenz neu setzen
Bilder kommen sehr langsam, viele Wiederholungen schwaches WLAN am Standort: RSSI im Dashboard prüfen (schlechter als −80-80 dBm ist kritisch), Antenne/Position optimieren
Serieller Upload bricht ab Upload Speed 115200? 5-V-Versorgung stabil? Stützkondensator; besser gleich OTA verwenden
“numpy/Pillow fehlen” pip install numpy pillow; ohne diese Pakete läuft nur die Bildübertragung ohne Auswertung

Einstellparameter im Überblick

Alle Stellschrauben stehen am Anfang von pflanzen_dashboard.py:

Parameter Standard Bedeutung
ESP32CAM_IP tomatencam.fritz.box Name oder IP der Kamera
INTERVALL 300 s Zeit zwischen zwei Bildern
EXG_SCHWELLE 40 ab diesem ExG-Wert gilt ein Pixel als grün
MIN_GRUEN 60 Mindest-Grünkanal gegen Dunkelrauschen
MIN_FLAECHE 2 % darunter “Pflanze nicht erkannt”
MIN_HELLIGKEIT 40 darunter “zu dunkel”, keine Bewertung
GELB_FLAECHE / ROT_FLAECHE 8 % / 15 % Ampelschwellen Flächenabfall
GELB_ABSINKEN / ROT_ABSINKEN 2 % / 4 % Ampelschwellen Schwerpunkt-Absinken
GLAETTUNG 5 Median über die letzten n Messungen
VERLAUF_MAX 576 Punkte im Verlaufsdiagramm (2 Tage bei 5 min)
ORDNER pflanzen_bilder Bildablage (leer = nicht speichern)

Im Sketch: BILD_GROESSE (FRAMESIZE_VGA), BILD_QUALITAET (10) sowie die WLAN-Zugangsdaten.

Zusammenfassung

Das System verbindet bewährte Bausteine zu einem wartungsarmen Ganzen: Die solarbetriebene ESP32-CAM liefert als Modbus-TCP-Slave CRC-gesicherte Bilder in 240-Byte-Blöcken (einige Sekunden je VGA-Bild, begrenzt durch die WLAN-Umlaufzeit, nicht durch die Datenmenge). Das PC-Dashboard trennt die Pflanze per Excess-Green-Index vom Hintergrund, verdichtet jedes Bild auf Grünfläche und Blatt-Schwerpunkt, glättet per Median und bewertet gegen eine nach dem Gießen gesetzte Referenz – als Ampel, als Verlaufsdiagramm und als CSV-Protokoll.

Die harten Stromabschaltungen des Solar-Ladereglers sind konstruktiv eingeplant: Namensauflösung statt fester IP (tomatencam.fritz.box, bei jedem Verbindungsaufbau neu), Zeitstempel vom PC statt von der uhrenlosen Kamera, Dateinamen mit Zeitstempel gegen das Zurückspringen der Bildnummer, automatische Wiederherstellung des Blitz-Zustands, Erkennung jedes Neustarts über das Betriebszeit-Register und ein Updateweg (OTA), der selbst bei Stromausfall mitten im Flashen nichts beschädigt. Der einzige regelmäßige Handgriff des Betreibers bleibt der, um den es eigentlich geht: gießen – und danach “Referenz setzen”.

Band 2 — vom Programm zum Gerät: Raspberry Pi, Display, RS485-Klemmenebene

Der Tomaten-Bewässerungswächter als eigenständiges Gerät

Band 2: Vom PC-Programm zum Dauerläufer mit Anzeige und Klemmenebene

Ralph Wystup, August 2026


Einordnung

Der erste Band beschreibt das Verfahren: eine ESP32-CAM beobachtet eine Tomatenpflanze, überträgt ihre Bilder blockweise und CRC-gesichert über Modbus TCP, und ein Python-Programm trennt die Pflanze über den Excess-Green-Index vom Hintergrund, um aus Grünfläche und Schwerpunktlage den Wasserbedarf abzuleiten. Das alles gilt unverändert und wird hier nicht wiederholt.

Dieser Band behandelt, was danach kam: aus dem Programm, das auf einem eingeschalteten Windows-Rechner lief, wurde ein Gerät. Es besteht aus einem Raspberry Pi 3B+, einer Anzeige vor Ort und einer Klemmenebene mit Relais und Analogein- und -ausgängen. Es läuft ohne Bedienung, ohne Bildschirm und ohne angemeldeten Benutzer, startet nach Stromausfall von selbst und ist über das Netz erreichbar.

Drei Themen prägen diesen Band, und alle drei sind übertragbar auf andere Vorhaben: die Frage, was ein Programm zu einem Dauergerät macht; ein Displaytreiber, der ohne jede Fremdbibliothek auskommt; und die Anbindung einer industriellen Klemmenebene über Modbus RTU.

   ESP32-CAM  ──WLAN──▶  Raspberry Pi 3B+  ──▶  Webseite (Port 8083)
   (Solar)     Modbus     Systemdienst      ──▶  OLED 128×64 (I2C)
               TCP        pflanzen_waechter ──▶  Protokoll pflanzen_log.csv
                                            ──▶  Modul A (Modbus RTU, RS485)
                                                 2 Relais, 2 DI, 2 AI, 2 AO

1. Was ein Programm zu einem Gerät macht

Ein Programm, das man startet, und ein Gerät, das läuft, unterscheiden sich nicht im Algorithmus, sondern in vier Eigenschaften. Sie klingen banal und entscheiden doch darüber, ob die Sache nach vier Wochen noch arbeitet.

Es muss ohne Anmeldung starten. Ein Programm im Autostart eines Benutzers läuft erst, wenn sich jemand anmeldet — auf einem Gerät ohne Bildschirm also nie. Der Wächter läuft deshalb als Systemdienst, gestartet von systemd, eingehängt in multi-user.target, das lange vor jeder Anmeldung erreicht wird.

Es muss Fehler überleben. Jeder Absturz, jede unbehandelte Ausnahme, jeder Speicherfehler beendet ein gewöhnliches Programm endgültig. Der Dienst trägt Restart=always und RestartSec=15: Endet der Prozess aus welchem Grund auch immer, wird er nach fünfzehn Sekunden neu gestartet. Das ist keine Entschuldigung für schlechte Fehlerbehandlung, sondern die letzte Rückfallebene.

Es muss mit fehlender Umgebung zurechtkommen. Die Kamera ist solarbetrieben und wochenweise stromlos, das Display kann abgezogen sein, der RS485-Wandler ebenso. Jede dieser Baugruppen ist im Programm so behandelt, dass ihr Fehlen eine Meldung erzeugt und keinen Abbruch. Der Grundsatz: Was Zugabe ist, darf den Kern nicht mitreißen.

Es darf sein Speichermedium nicht auffressen. Bei einem Bild alle fünf Minuten entstehen rund 300 Bilder oder vier Megabyte pro Tag. Ohne Gegenmaßnahme wäre die Speicherkarte in einem Jahr voll — mit dem unangenehmen Fehlerbild, dass das Gerät scheinbar grundlos den Dienst versagt. Eine tägliche Aufräumroutine löscht Bilder, die älter als eine einstellbare Frist sind.

Die Wahl des Unterbaus

Der Pi 3B+ hat 1 GB Arbeitsspeicher. Das ist die eigentliche Randbedingung. Sein Prozessor ist 64-bit-fähig, doch 64-Bit-Code belegt mehr Speicher, weil jede Adresse doppelt so breit ist; bei laufender Oberfläche führt das zu ständigem Auslagern auf die Speicherkarte. Gewählt wurde deshalb die 32-Bit-Ausgabe.

Ebenso begründet ist das Startverhalten: Das Gerät startet in die Textanzeige, nicht in den Desktop. Das spart rund 300 MB und ist zugleich die Voraussetzung dafür, dass der Fern-Desktop über xrdp zuverlässig arbeitet — eine automatisch angemeldete lokale Sitzung belegt sonst die Grafik, und die Verbindung bricht unmittelbar nach dem Aufbau wieder ab. Der Desktop entsteht auf diese Weise genau dann, wenn sich jemand verbindet.


2. Die Anzeige vor Ort

Ein Gerät im Gewächshaus soll seinen Zustand zeigen, ohne dass man einen Browser öffnet. Dafür sitzt ein einfarbiges OLED mit 128 × 64 Bildpunkten am I2C-Bus.

Warum ein eigener Treiber

Naheliegend wäre eine der verbreiteten Bibliotheken gewesen, luma.oled oder Adafruit-Blinka. Dagegen sprach der Unterbau: Raspberry Pi OS lässt seit Bookworm keine Installation von Python-Paketen ins System mehr zu — der Paketverwalter meldet „externally managed environment”. Bibliotheken müssten in eine virtuelle Umgebung, und der Systemdienst müsste diese Umgebung aktivieren. Für ein Gerät, das jahrelang unbeaufsichtigt laufen soll, ist das eine zusätzliche Bruchstelle.

Die Alternative kostet etwa hundert Zeilen. Das Display braucht ausschließlich Schreibzugriffe, und die beherrscht der Kerneltreiber i2c-dev unmittelbar:

fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, 0x0703, adresse)        # I2C_SLAVE
os.write(fd, bytes([0x00]) + befehle)   # 0x00 = es folgen Befehle
os.write(fd, bytes([0x40]) + bilddaten) # 0x40 = es folgen Bilddaten

Damit hat die Anzeige keine einzige Zusatzabhängigkeit. Gezeichnet wird mit Pillow, das für die Bildauswertung ohnehin vorhanden ist.

Vom Bild zum Bildspeicher

Der SSD1306 verwaltet seinen Speicher in acht Seiten zu je 128 Byte. Ein Byte beschreibt eine Spalte von acht übereinanderliegenden Bildpunkten, Bit 0 ist der oberste. Aus einem Pillow-Bild wird der Bildspeicher also durch eine Umsortierung:

Byte(s,x)=∑i=07b(x,8s+i)⋅2i \text{Byte}(s, x) = \sum_{i=0}^{7} b(x,\; 8s + i)\cdot 2^{i}

mit b∈{0,1}b \in \{0,1\} als Bildpunkt und ss als Seitennummer. In numpy ist das eine Zeile, ohne numpy eine Schleife — beide Wege sind vorhanden und liefern nachweislich dasselbe Ergebnis.

Zwei Controller sind im Umlauf und äußerlich nicht zu unterscheiden. Der SSD1306 beherrscht waagerechte Adressierung, der Bildspeicher lässt sich in einem Zug schreiben. Der SH1106 verwaltet intern 132 Spalten, von denen die mittleren 128 sichtbar sind, kennt diese Adressierung nicht und wird deshalb Seite für Seite mit einem Versatz von zwei Spalten beschrieben. Beide Wege sind eingebaut, umschaltbar über eine Konstante. Das Fehlerbild bei falscher Wahl ist eindeutig: Streifen oder ein Versatz um genau zwei Bildpunkte.

Ein Layout, das in 64 Punkte passt

Auf so kleiner Fläche ist jede Zeile gezählt. Maßgeblich ist die Zeilenhöhe der Schrift, also die Summe aus Ober- und Unterlänge. Bei DejaVu Sans ergibt sich gemessen:

Schriftgröße Zeilenhöhe
7 9 Punkte
8 10 Punkte
10 13 Punkte
16 fett 19 Punkte

Daraus entstand die Aufteilung der ersten Seite: Kopfzeile 0 bis 9, Trennstrich auf 10, die große Aussage in einem Band von 12 bis 32, darunter drei Zeilen bei 34, 46 und 55. Die letzte endet damit genau auf 64. Der erste Entwurf hatte diese Rechnung nicht angestellt und schnitt die Fußzeile ab — ein Fehler, der auf dem Bildschirm des Entwicklers unsichtbar bleibt, wenn man nicht maßstäblich vorschaut.

Die eigentliche Aussage steht in einer großen Zeile: Wasser ok, bald giessen oder GIESSEN!. Da ein einfarbiges Display keine Ampelfarben kennt, wird der Gießbefehl invertiert dargestellt — heller Balken, dunkle Schrift. Das ist aus einigen Metern erkennbar und ersetzt das Rot.

Drei Seiten wechseln alle sechs Sekunden: Pflanze, Technik, Verlauf. Bei jedem Wechsel verschiebt sich der gesamte Inhalt um einen Bildpunkt. Diese Kleinigkeit beugt dem Einbrennen vor, dem einzigen Verschleiß, den ein OLED im Dauerbetrieb kennt.


3. Die Klemmenebene: Modbus RTU über RS485

Mit dem Waveshare „Modbus RTU Module (A)” bekommt das Gerät Hände und weitere Sinne: zwei Relais, zwei Digitaleingänge, zwei Analogeingänge und zwei Analogausgänge.

RS485 in drei Sätzen

RS485 überträgt symmetrisch über ein Adernpaar A und B; gewertet wird die Spannungsdifferenz, weshalb Störungen, die beide Adern gleich treffen, herausfallen. Der Bus ist ein Strang, an dem viele Teilnehmer hängen dürfen, aber immer nur einer sendet — es gibt genau einen Master, der fragt, und Teilnehmer, die antworten. Die Masse gehört mitverbunden, auch wenn es ohne sie oft zunächst funktioniert: Ohne gemeinsamen Bezug driften die Pegel, und das Fehlerbild sind sporadische Aussetzer, die man wochenlang der Software anlastet.

Aus der Ein-Master-Regel folgt eine Betriebsvorschrift für diese Anlage: Der Pi und das vorhandene RS485-TO-ETH-Gateway dürfen nicht gleichzeitig arbeiten. Zwei Master erzeugen keine saubere Fehlermeldung, sondern Antworten, die zufällig richtig oder falsch aussehen.

Der Vierfachwandler

Verwendet wird ein Waveshare „USB TO 4CH RS485” mit dem Baustein WCH CH344. Er meldet sich als ein USB-Gerät (1a86:55d5) und erzeugt vier voneinander unabhängige, galvanisch getrennte Schnittstellen. Unter Linux erscheinen sie als vier Einträge:

/dev/serial/by-id/usb-WCH.CN_USB_Quad_Serial_...-if00   = Kanal 1
                                              ...-if02   = Kanal 2
                                              ...-if04   = Kanal 3
                                              ...-if06   = Kanal 4

Die Endungen springen in Zweierschritten, weil die ungeraden Nummern zu den Steuerschnittstellen des Bausteins gehören. Wichtig ist der Weg über /dev/serial/by-id/ und nicht über /dev/ttyUSB0: Die laufende Nummer der tty-Geräte hängt von der Reihenfolge des Einsteckens ab und kann nach einem Neustart eine andere sein. Der Name unter by-id bleibt gleich.

Das Programm probiert alle gefundenen Schnittstellen der Reihe nach durch und verwendet die, an der ein Modul antwortet. Damit ist gleichgültig, an welchem Kanal geklemmt wurde.

Der Telegrammaufbau

Modbus RTU ist von entwaffnender Einfachheit. Ein Telegramm besteht aus Adresse, Funktionscode, Nutzdaten und einer Prüfsumme:

[Adresse][Funktion][Daten ...][CRC16 niederwertig][CRC16 höherwertig]

Die Prüfsumme ist ein CRC16 mit dem Polynom 0xA001, rückwärts gerechnet, mit 0xFFFF als Startwert — dieselbe Routine, die im Wächter schon für die Bildblöcke der Kamera dient:

def crc16(daten):
    crc = 0xFFFF
    for b in daten:
        crc ^= b
        for _ in range(8):
            crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
    return crc

Verwendet werden sechs Funktionscodes:

Code Bedeutung hier benutzt für
01 Coils lesen Zustand der Relais
02 Diskrete Eingänge lesen Digitaleingänge
03 Halteregister lesen Analogausgänge, Messbereich, Version
04 Eingangsregister lesen Analogeingänge
05 Einzelne Coil schreiben Relais schalten
06 Einzelnes Register schreiben Analogausgang, Messbereich

Ein Beispiel: Relais 1 einschalten heißt Adresse 1, Funktion 5, Coil 0x0000, Wert 0xFF00. Daraus wird die Bytefolge 01 05 00 00 FF 00 plus zwei Byte Prüfsumme. Der Wert 0xFF00 für „ein” und 0x0000 für „aus” ist eine Eigenheit von Modbus, die sich aus der Frühzeit des Protokolls erklärt und keine tiefere Bedeutung hat.

Registerplan des Moduls

Größe Adresse Zugriff Einheit
Relais 1 / 2 Coil 0x0000 / 0x0001 FC01 lesen, FC05 schreiben 0xFF00 = ein
Digitaleingang 1 / 2 0x0000, 2 Bit FC02 0 / 1
Analogeingang 1 / 2 0x0000, 2 Register FC04 mV oder µA, je Messbereich
Analogausgang 1 / 2 0x0000, 2 Register FC03 / FC06 µA, 0 bis 20000
Messbereich AI 1 / 2 0x3000 / 0x3001 FC03 / FC06 siehe unten
Firmware-Version 0x8000 FC03 Wert / 100

Die Analogausgänge können ausschließlich Strom, 0 bis 20 mA, angegeben in Mikroampere. Eine Spannungsausgabe gibt es nicht; wo sie gebraucht wird, erzeugt man sie mit einem Bürdewiderstand.

Die Analogeingänge dagegen sind umschaltbar:

Wert Messbereich Einheit des Rohwerts
0 0–5 / 0–10 V mV
1 1–5 / 2–10 V mV
2 0–20 mA µA
3 4–20 mA µA
4 ADC roh —

Zwei Dinge gehören zusammen: das Register und ein Schiebeschalter auf der Platine. Für Spannungsmessung muss der Schalter des Kanals auf OFF stehen, sonst liegt der Bürdewiderstand parallel zum Eingang und verfälscht die Messung. Das Programm schreibt den gewünschten Bereich beim Verbinden selbst ins Modul, liest ihn zur Kontrolle zurück und rechnet die Rohwerte entsprechend um — mV zu Volt, µA zu Milliampere. In der Anzeige steht der Bereich in Klammern hinter dem Messwert, damit kein Zweifel entsteht, was die Zahl bedeutet.

Abfragefaden und Sperre

Die Anzeige soll in Echtzeit mitlaufen, während gleichzeitig aus dem Browser geschaltet werden kann. Beides greift auf dieselbe serielle Leitung zu, und Modbus RTU verträgt keine Überlappung: Zwischen Frage und Antwort darf nichts dazwischenfunken.

Gelöst ist das mit einem eigenen Abfragefaden, der im Sekundentakt alle vier Größen liest, und einer Sperre, die jeden Zugriff umschließt — auch die Schaltbefehle aus dem Webserver. Die Wartezeit, die ein Bedienbefehl dadurch erfährt, liegt im Bereich einiger Millisekunden und ist nicht wahrnehmbar.

Fällt die Verbindung aus, wird die Schnittstelle geschlossen, im Zustand vermerkt und alle fünf Sekunden neu gesucht. Das Abziehen des Wandlers im laufenden Betrieb ist damit kein Störfall, sondern eine Meldung im Dashboard.

Die Gießfunktion und ihre Sicherungen

Relais 2 schaltet die Pumpe oder das Ventil. Eine Pumpe, die eingeschaltet bleibt, ist der einzige Fehler in diesem ganzen Programm, der wirklich Schaden anrichten kann — entsprechend ist die Funktion abgesichert.

Die Dauer ist auf zehn Minuten begrenzt, unabhängig davon, was übergeben wird. Ein zweiter Aufruf während eines laufenden Vorgangs wird abgewiesen, statt die Zeit zu verlängern. Das Ausschalten steht in einem finally-Block, wird also auch dann ausgeführt, wenn dazwischen etwas fehlschlägt, und wird bis zu fünfmal wiederholt, falls die Leitung gerade stört. Und der Vorgang läuft in einem eigenen Faden, damit der Webserver währenddessen bedienbar bleibt.

Bewusst nicht eingebaut ist die selbsttätige Auslösung durch die Ampel. Wann ein Gerät ohne Aufsicht Wasser laufen lässt, unter welchen Bedingungen und mit welcher Sperrzeit danach, ist eine Entscheidung, die der Betreiber treffen muss und die von der Bewässerungstechnik abhängt. Die Mechanik dafür steht bereit; die Regel fehlt noch.


4. Das erweiterte Dashboard

Die Webseite hat eine zusätzliche Karte bekommen, die den Zustand der Klemmenebene zeigt und bedienbar macht. Der Aufbau folgt dem, was schon vorhanden war: Die Seite fragt im Dreisekundentakt /daten ab und bekommt ein JSON-Paket, das nun einen Abschnitt io enthält.

Neue Endpunkte:

Aufruf Wirkung
/relais?nr=1&ein=1 Relais schalten
/ao?kanal=1&wert=12000 Analogausgang setzen, Wert in µA
/giessen?sekunden=20 Relais 2 zeitbegrenzt einschalten

Zwei Feinheiten der Bedienung verdienen Erwähnung. Der Zustand der Relais wird nicht mitgeschrieben, sondern aus dem Modul zurückgelesen — die Anzeige zeigt also, was tatsächlich geschaltet ist, nicht was befohlen wurde. Und die Eingabefelder der Analogausgänge werden zwar laufend mit dem gemessenen Wert nachgeführt, aber nicht, während der Mauszeiger darin steht; sonst würde die eigene Eingabe im Sekundentakt überschrieben.


5. Erfahrungen aus der Inbetriebnahme

Die Einrichtung kostete deutlich mehr Zeit als geplant, und zwar nicht wegen des Programms. Drei Punkte sind festgehalten, weil sie sich jederzeit wiederholen können und von außen nicht zu erkennen sind.

Der Raspberry Pi Imager in der Fassung 2.0.10 schrieb die Voreinstellungen — Benutzer, WLAN, Fernzugriff — nicht auf die Karte, meldete aber „Schreiben erfolgreich”. Der Pi startete daraufhin ohne Benutzer und ohne Netz. Von außen ist das nicht von einem Verdrahtungsfehler oder einem vertippten Kennwort zu unterscheiden. Der Fehler ist für mehrere Fassungen der 2.0-Reihe in den Fehlerberichten des Projekts belegt.

Dieselbe Fassung stellte zudem die Beschriftungen der Schaltflächen verstümmelt dar: Die eingebettete Schrift wurde mit einer um zwei Plätze verschobenen Zeichentabelle geladen, aus SPEICHERN wurde QNCGAF CPL. Auch das ist belegt und in der Nachfolgefassung behoben.

Und schließlich läuft die Ersteinrichtung eines Raspberry Pi genau ein Mal. Ausgelöst wird sie durch einen Eintrag in cmdline.txt, den das Einrichtungsprogramm anschließend selbst entfernt. Wer eine Konfigurationsdatei erst nach dem ersten Einschalten nachträgt, wartet vergeblich — sie wird nie gelesen. Die Reihenfolge ist zwingend: brennen, Datei kopieren, dann erst zum ersten Mal einschalten.

Aus allen dreien folgt dieselbe Lehre, die auch in anderen Projekten trägt: Sobald eine Diagnose von außen nicht mehr möglich ist, lohnt der Aufwand, sich Sicht zu verschaffen — hier ein Monitor und eine Tastatur für die Ersteinrichtung. Die Alternative ist das Durchprobieren von Vermutungen, und das kostet mehr Zeit, als das Kabel je gekostet hätte.


6. Dateien

Alles, was für einen vollständigen Neuaufbau gebraucht wird. Diese Dateien gehören außerhalb des Geräts aufbewahrt — auf dem PC und zusätzlich auf einem Datenträger, den kein Kartenschaden erreicht.

Auf dem Raspberry Pi, Ordner /home/pi/tomate

Datei Inhalt
pflanzen_dashboard.py Hauptprogramm: Bildabholung, Auswertung, Webserver, Anbindung von Display und Klemmenebene
oled_anzeige.py Displaytreiber SSD1306/SH1106 und Seitengestaltung
waveshare_io.py Modbus-RTU-Anbindung des Moduls A, Abfragefaden, Gießfunktion
pflanzen_waechter.service Vorlage des Systemdienstes
install_pi.sh Einrichtungsskript: Pakete, I2C, Autostart

Werkzeuge zur Inbetriebnahme

Datei Zweck
waveshare_wandler_test.py prüft den RS485-Wandler allein, mit Schleifentest zwischen zwei Kanälen
waveshare_pi_test.py prüft das Modul A: Version, DI, AI, AO, Relais

Auf der Kamera

Datei Inhalt
ESP32_CAM_Modbus_Snapshot.ino Sketch der ESP32-CAM mit Blockübertragung, Statusregistern und OTA

Dokumentation

Datei Inhalt
MANUSKRIPT_Tomatenwaechter.md Band 1: Verfahren, Protokoll, Bildauswertung, Bedienung
MANUSKRIPT_Tomatenwaechter_Pi.md Band 2: dieses Dokument
ANLEITUNG_Pi_OLED.md Arbeitsanleitung: Installation, Betrieb, Fehlersuche, Wiederaufbau

Betriebsdaten, die das Gerät selbst anlegt

pflanzen_log.csv mit der Messreihe, pflanzen_referenz.json mit der gesetzten Referenz und der Ordner pflanzen_bilder. Die Messreihe ist der einzige unwiederbringliche Teil und sollte gelegentlich auf den PC geholt werden.


7. Ausblick

Drei Erweiterungen liegen nahe und sind vorbereitet.

Die selbsttätige Bewässerung braucht nur noch eine Regel: bei welcher Ampelfarbe, für wie lange, mit welcher Sperrzeit danach, und ob nur bei Tageslicht. Sinnvoll ist eine Sperrzeit von mehreren Stunden, denn eine Pflanze braucht Zeit, um auf Wasser zu reagieren — ohne diese Sperre würde das Gerät nachgießen, bevor die erste Gabe wirken konnte, und der Regelkreis schwingt.

Ein Bodenfeuchtesensor am Analogeingang gäbe der Entscheidung ein zweites, unabhängiges Kriterium. Die Blattstellung ist ein später Anzeiger — wenn die Blätter hängen, ist der Wassermangel bereits eingetreten. Die Bodenfeuchte zeigt ihn früher. Beide zusammen erlauben eine Aussage, die keiner von beiden allein liefert: hängende Blätter bei feuchtem Boden deuten nicht auf Durst, sondern auf Wurzelschaden oder Hitze.

Und schließlich ließe sich die zweite Ampel auf dem Display darstellen, sobald zwei Kriterien vorliegen. Platz dafür ist auf der Technikseite.


Anhang: Befehle im Betrieb

Zweck Befehl
Zustand des Dienstes systemctl status pflanzen_waechter
Meldungen mitlesen journalctl -u pflanzen_waechter -f
Nach Programmänderung sudo systemctl restart pflanzen_waechter
Modul einzeln prüfen Dienst anhalten, dann python3 ~/tomate/waveshare_pi_test.py
Display einzeln prüfen Dienst anhalten, dann python3 ~/tomate/oled_anzeige.py demo
I2C-Teilnehmer suchen i2cdetect -y 1
Serielle Kanäle auflisten ls /dev/serial/by-id/
Sauber ausschalten sudo poweroff

Der Dienst belegt die serielle Schnittstelle dauerhaft. Die beiden Prüfprogramme lassen sich deshalb nur bei angehaltenem Dienst verwenden.

Band 3 — Betrieb: Regenschutz, Bildaufbereitung, Stromversorgung

Der Tomaten-Bewässerungswächter im Betrieb

Band 3: Regenschutz, Bildaufbereitung und Stromversorgung

Ralph Wystup, August 2026


Einordnung

Die ersten beiden Bände beschreiben das Verfahren und das Gerät. Dieser dritte handelt von dem, was danach kommt und in keiner Entwurfszeichnung steht: Regen, der auf die Kamera fällt, und Strom, der nicht ausreicht.

Beide Themen haben eine Gemeinsamkeit, die sie lehrreich macht. Sie erzeugen Fehlerbilder, die auf etwas ganz anderes hindeuten. Eine Folie vor dem Objektiv sieht aus wie eine welkende Pflanze. Ein zu schwaches Netzteil sieht aus wie ein Software- oder Netzwerkfehler. Wer die Zusammenhänge nicht kennt, sucht an der falschen Stelle — und zwar lange.


1. Die Regenhaube

1.1 Was eine Folie mit dem Bild macht

Über die ESP32-CAM wurde bei Regen eine dünne, durchsichtige Tüte gezogen, etwa drei Zentimeter vor dem Objektiv, unten offen. Das Bild wurde daraufhin weich und kontrastarm, die Pflanze blieb aber erkennbar.

Der Grund ist nicht der Abstand, sondern die Beschaffenheit der Fläche. Zwei Wirkungen überlagern sich:

Eine faltige Folie besteht aus lauter gekrümmten Flächen. Jede Krümmung wirkt als kleine Linse mit eigener Brennweite und lenkt das Licht in eine andere Richtung. Was das Objektiv erreicht, ist ein Gemisch aus Strahlen, die aus verschiedenen Richtungen kommen — also ein verwaschenes Bild.

Dazu kommt Streulicht. Ein Teil des Lichts wird an der Folie diffus gestreut und legt sich als gleichmäßiger Schleier über das ganze Bild. Die Schwärzen werden angehoben, der Kontrast sinkt.

Daraus folgt die wichtigste Konstruktionsregel: Entscheidend ist die Ebenheit, nicht der Abstand. Eine glatte, straff gespannte Fläche stört kaum, selbst dicht vor der Linse — nach demselben Prinzip arbeitet jedes Schutzglas vor einem Objektiv. Eine faltige Tüte stört auch aus zehn Zentimetern.

1.2 Warum Unschärfe der Auswertung wenig ausmacht

Die Pflanzenauswertung braucht keine Schärfe. Sie zählt Pixel, deren Excess-Green-Index über einer Schwelle liegt:

ExG=2G−R−B \mathrm{ExG} = 2G - R - B

Ein weiches Bild verschiebt lediglich die Ränder der Grünmaske um wenige Pixel; die Fläche in der Mitte bleibt unverändert. Für Flächenanteil und Schwerpunkt ist das nahezu belanglos.

Was dagegen sehr wohl wirkt, ist der Schleier. Streulicht ist annähernd unbunt, es hebt also alle drei Farbkanäle um denselben Betrag ss an:

ExG′=2(G+s)−(R+s)−(B+s)=ExG \mathrm{ExG}' = 2(G+s) - (R+s) - (B+s) = \mathrm{ExG}

Rechnerisch bleibt der Index also gleich — solange die Streuung wirklich unbunt ist und additiv wirkt. In der Wirklichkeit kommt jedoch hinzu, dass die Kamera ihre Belichtung nachregelt und die Sättigung durch die Aufhellung insgesamt sinkt: Die Farben rücken näher zusammen, G−RG-R und G−BG-B werden kleiner, und damit sinkt ExG doch. Pixel, die vorher knapp über der Schwelle lagen, fallen aus der Maske.

Die Folge ist heimtückisch: Die gemessene Grünfläche schrumpft, ohne dass sich an der Pflanze etwas geändert hätte. Der Wächter deutet das als Welke.

Die zweite Gefahr ist Bewegung. Eine lose Tüte flattert im Wind, und damit ändert sich die Streuung von Bild zu Bild. Für die Auswertung ist ein gleichbleibend weiches Bild deutlich besser als ein mal scharfes, mal weiches — Rauschen in den Messwerten ist schlimmer als ein systematischer Versatz, denn den fängt die Referenz auf.

1.3 Was daraus folgt

Für den Aufbau, nach Wirksamkeit geordnet:

Ein starres, flaches Fenster ist die beste Lösung — ein Stück Acrylglas, das Glas aus einem Bilderrahmen oder der Deckel einer CD-Hülle, rechtwinklig zur Blickrichtung. Bequemer noch ist eine durchsichtige Dose, in der die Kamera sitzt und deren glatte Wand als Fenster dient; das ist zugleich der bessere Regenschutz, weil es formstabil ist.

Bleibt es bei der Folie, gehört sie straff und unbeweglich befestigt. Nicht wegen der Schärfe, sondern wegen der Gleichmäßigkeit.

Die Haube sollte unten offen bleiben. Regen kommt von oben; die Öffnung unten lässt warme, feuchte Luft entweichen und verhindert, dass sich beim Abkühlen alles beschlägt. Ein Beutelchen Trockenmittel hilft zusätzlich. Ein leicht geneigtes Fenster lässt Tropfen ablaufen, die sonst als helle Flecken die Grünerkennung stören.

Für die Auswertung:

Fällt der gemessene Grünanteil durch die Haube deutlich ab, wird die Schwelle EXG_SCHWELLE von 40 auf etwa 30 herabgesetzt. Prüfen lässt sich das, indem man den Wert im Dashboard mit und ohne Haube vergleicht.

Und in jedem Fall gilt: Nach jeder Änderung an der Optik muss die Referenz neu gesetzt werden. Sie ist der Maßstab, mit dem alle späteren Messungen verglichen werden, und muss aus demselben Aufbau stammen. Eine Referenz von vor dem Regen und Messwerte von nach dem Regen vergleichen zwei verschiedene Kameras.


2. Die elektronische Bildaufbereitung

2.1 Was Rechnen kann und was nicht

Für die Anzeige — nicht für die Auswertung — lässt sich das weiche Bild nachbessern. Die Grenze ist klar zu benennen: Rechnen kann nur betonen, was noch im Bild steckt. Was die Streuung vernichtet hat, ist verloren; kein Verfahren holt es zurück.

Zwei Schritte helfen trotzdem spürbar.

Die Kontrastanhebung wirkt gegen den Schleier. Sie spreizt die Helligkeiten um einen festen Faktor um den Mittelwert herum, hebt also die Unterschiede an, die die Streuung eingeebnet hat.

Die Unschärfemaske wirkt gegen die Weichheit. Ihr Prinzip ist eine Subtraktion: Vom Bild wird eine verwischte Fassung seiner selbst abgezogen, und die Differenz — das sind gerade die Kanten — wird verstärkt wieder aufaddiert:

B′=B+k(B−Gσ(B)) B' = B + k\,\bigl(B - G_\sigma(B)\bigr)

Dabei ist GσG_\sigma eine Glättung mit dem Radius σ\sigma und kk die Stärke. Der Name kommt daher, dass zur Schärfung ausgerechnet ein unscharfes Bild gebraucht wird.

Im Dashboard sind vier Stufen wählbar, von aus bis stark:

Stufe Kontrastfaktor Stärke Radius
1 sanft 1,10 60 % 1,2
2 mittel 1,25 110 % 1,6
3 stark 1,45 180 % 2,0

Die Vorgabe ist aus. Das Bild bleibt also unverändert, solange man es nicht ausdrücklich anders einstellt.

2.2 Ein Fehlversuch als Lehrstück

Der erste Entwurf verwendete statt der begrenzten Kontrastanhebung eine volle Kontrastspreizung, wie sie viele Bildbearbeitungen als „Autokontrast” anbieten: Der genutzte Helligkeitsbereich wird auf den vollen Umfang von 0 bis 255 gedehnt.

Das Ergebnis war unbrauchbar. Der übliche Autokontrast dehnt jeden Farbkanal für sich, und weil die Kanäle unterschiedlich weit reichen, verschieben sich dabei die Farben. Aus brauner Erde wurde Magenta.

Der zweite Versuch mit tonwerterhaltender Spreizung behob das Farbkippen, doch das Bild brannte aus: Die Dehnung bis an die Anschläge machte aus Blattgrün Neongrün und aus Erde ein dunkles Rot. Der Grund ist, dass eine steile Kennlinie die Unterschiede zwischen den Kanälen mitverstärkt und damit die Sättigung hochtreibt.

Erst der dritte Ansatz taugte: ein fester, begrenzter Kontrastfaktor. Er kann nicht ausreißen, weil er nicht vom Bildinhalt abhängt.

Die Lehre daraus ist allgemeiner Natur: Ein Verfahren, das sich selbst an den Bildinhalt anpasst, verhält sich bei ungewöhnlichem Inhalt ungewöhnlich. Für eine Anlage, die unbeaufsichtigt läuft und deren Bilder von Tageslicht, Wetter und Jahreszeit abhängen, ist das Vorhersehbare dem Optimalen vorzuziehen.

2.3 Die Trennung von Anzeige und Auswertung

Die Aufbereitung wirkt ausschließlich auf das angezeigte Bild. Die Pflanzenauswertung rechnet unverändert mit dem Originalbild der Kamera.

Diese Trennung ist keine Formsache. Würde die Auswertung auf dem aufbereiteten Bild rechnen, hinge jeder Messwert davon ab, welche Stufe gerade eingestellt ist — und ein Griff ins Dashboard würde die Messreihe unbrauchbar machen. Aus demselben Grund wird auch der Zeitstempel erst nach der Auswertung ins Bild gestempelt.

Technisch wird dafür das Originalbild getrennt vom Anzeigebild vorgehalten und das aufbereitete Ergebnis nach Bildnummer und Stufe zwischengespeichert, damit nicht bei jedem Abruf neu gerechnet wird.


3. Die Stromversorgung

3.1 Ein Fehlerbild, das in die Irre führt

Nach dem Anschluss der USB-Kamera trat folgendes auf: Das Dashboard war nicht mehr erreichbar, SSH lief in eine Zeitüberschreitung — aber das OLED zeigte weiterhin an, und die Uhr in seiner Kopfzeile lief. Ein Ping an den Pi wurde beantwortet.

Diese Kombination wirkt widersprüchlich und ist es nicht. Ping beantwortet der Betriebssystemkern selbst; dafür muss kein Programm laufen. Die Anzeige wird von einem Faden bedient, der bereits läuft und nur wenig Rechenzeit braucht. Was fehlschlug, war das Annehmen neuer Verbindungen — die aufwendigste dieser drei Tätigkeiten.

Die Ursache war Unterspannung. Sinkt die Versorgung unter etwa 4,63 Volt, drosselt der Pi seinen Takt; bei stärkeren Einbrüchen wird das System so zäh, dass es neue Verbindungen nicht mehr in vertretbarer Zeit bedient. Nach außen sieht das aus wie ein Netzwerk- oder Softwarefehler.

3.2 Der Beweis

Der Pi führt darüber Buch. Abzufragen mit:

vcgencmd get_throttled

Der zurückgegebene Wert ist ein Bitmuster. Die hinteren Stellen beschreiben den Zustand von jetzt, die vorderen sind Merker seit dem Einschalten:

Bit Wert Bedeutung
0 0x1 Unterspannung, jetzt
1 0x2 Takt begrenzt, jetzt
2 0x4 gedrosselt, jetzt
3 0x8 weiche Temperaturgrenze, jetzt
16 0x10000 Unterspannung ist vorgekommen
17 0x20000 Taktbegrenzung ist vorgekommen
18 0x40000 Drosselung ist vorgekommen
19 0x80000 Temperaturgrenze ist vorgekommen

Gemessen wurde throttled=0x50000: Unterspannung und Drosselung waren vorgekommen, aber nicht mehr aktuell. Damit war die Ursache belegt, ohne ein Messgerät anzulegen.

Die Merker lassen sich nur durch einen Neustart zurücksetzen. Wer prüfen will, ob eine Maßnahme geholfen hat, muss deshalb zuerst neu starten und dann unter Last messen — sonst sieht er weiterhin den alten Eintrag.

3.3 Warum das Kabel wichtiger ist als das Netzteil

Der Spannungsabfall auf der Zuleitung ist der meistunterschätzte Anteil. Er folgt schlicht dem ohmschen Gesetz, wobei Hin- und Rückleiter zählen:

ΔU=I⋅2⋅ρlA \Delta U = I \cdot 2 \cdot \frac{\rho\,l}{A}

Für ein Meter Kabel mit den üblichen dünnen Adern der Stärke 28 AWG beträgt der Widerstand je Leiter rund 0,21 Ohm, zusammen also 0,43 Ohm. Bei 1,5 Ampere ergibt das:

ΔU=1,5A⋅0,43Ω≈0,64V \Delta U = 1{,}5\ \mathrm{A} \cdot 0{,}43\ \Omega \approx 0{,}64\ \mathrm{V}

Aus 5,2 Volt am Netzteil werden damit 4,56 Volt am Pi — unterhalb der Erkennungsschwelle. Das Netzteil ist tadellos, das Gerät läuft trotzdem in Unterspannung.

Mit einer kräftigen Ader von 20 AWG sinkt der Widerstand auf 0,033 Ohm je Meter und Leiter, der Abfall bei denselben 1,5 Ampere auf etwa 0,1 Volt. Der Unterschied zwischen brauchbar und unbrauchbar liegt also allein im Kabel.

Praktische Folgerung: kurz und dick. Und im Zweifel am Pi messen, nicht am Netzteil.

3.4 Die Auslegung

Angeschlossen wurde ein Netzteil mit 5,2 Volt Klemmenspannung und 5 Ampere.

Zur Stromstärke: Reserve schadet nicht. Ein Netzteil drückt keinen Strom in das Gerät, es stellt ihn bereit; der Pi nimmt sich, was er braucht. Der Nutzen der Reserve liegt darin, dass die Spannung bei Lastspitzen nicht einbricht, weil das Netzteil weit von seiner Grenze entfernt arbeitet.

Zur Spannung: Der Pi ist auf 5 Volt mit ±5 Prozent ausgelegt, also 4,75 bis 5,25 Volt. Die 5,2 Volt liegen innerhalb und gleichen zugleich den Abfall auf der Leitung aus — aus demselben Grund liefert das Originalnetzteil 5,1 Volt statt 5,0. Nach oben ist bei 5,25 Volt am Gerät Schluss; einen Überspannungsschutz gibt es nicht, und angeschlossene USB-Geräte vertragen ebenfalls nur 5 Volt ±5 Prozent.

Wer über eine einstellbare Quelle einspeist, stellt nicht nach Gefühl ein, sondern misst am Pi unter Last. An der Klemme über 5,5 Volt zu gehen ist auch dann nicht vertretbar, denn beim Wegfall der Last liegt diese Spannung ungedämpft am Gerät an.

Ein Hinweis zur Einspeisung über die Stiftleiste: Wer die 5 Volt an Pin 2 oder 4 legt, umgeht die Sicherung und den Verpolungsschutz der Micro-USB-Buchse. Mechanisch bequem, im Fehlerfall tödlich für die Platine.

3.5 Das Ergebnis

Nach dem Wechsel wurde neu gestartet und unter Last geprüft — Livebild eingeschaltet und alle vier Rechenkerne beschäftigt:

Messung Wert
direkt nach dem Start throttled=0x0
unter voller Last throttled=0x80008
Temperatur dabei 61,2 °C

Die Unterspannungsbits sind verschwunden. Was übrig bleibt, ist Bit 3 und sein Merker Bit 19: die weiche Temperaturgrenze. Der Pi 3B+ nimmt oberhalb von etwa 60 Grad den Takt geringfügig zurück, von 1,4 auf 1,2 GHz. Das ist vorgesehenes Verhalten und für diese Anlage bedeutungslos — ernsthaft gedrosselt wird erst ab 80 Grad, und die künstliche Volllast liegt weit über allem, was im Betrieb auftritt. Der Wächter holt alle fünf Minuten ein Bild und ruht dazwischen.

Nebenbei ist damit auch die Speicherkarte sicherer: Unterspannung ist eine der häufigsten Ursachen für beschädigte Dateisysteme.


4. Prüfliste

Bei jedem unerklärlichen Verhalten des Geräts zuerst diese drei Fragen:

Läuft die Uhr auf dem Display? Dann arbeitet das Programm, und das Problem liegt im Netz oder in der Versorgung.

Was sagt vcgencmd get_throttled? Alles außer 0x0 weist auf Strom oder Wärme. Endet der Wert auf 1, 4 oder 5, ist es die Versorgung.

Was sagt ping auf den Pi? Antwortet er, während SSH nicht durchkommt, ist das kein Netzwerkfehler, sondern ein überlastetes oder unterversorgtes Gerät.

Bei jeder Änderung an der Optik der Kamera — Haube, Ausrichtung, Reinigung — gehört anschließend die Referenz neu gesetzt.

Anleitung — Aufbau, Installation, Betrieb, Fehlersuche, Wiederaufbau

Tomaten-Bewässerungswächter auf dem Raspberry Pi 3B+

Vollständige Dokumentation: Aufbau, Installation, Betrieb, Fehlersuche.

Stand 18.08.2026. Gerät in Betrieb genommen, im Kaltstart geprüft, Display angeschlossen und laufend.


1. Was das Gerät tut

Eine ESP32-CAM beobachtet eine Tomatenpflanze. Der Raspberry Pi holt alle fünf Minuten ein Bild über Modbus TCP ab, trennt die Pflanze über ihren Grünanteil vom Hintergrund und vergleicht zwei Kennwerte mit einer Referenz, die nach dem Gießen gesetzt wird: die sichtbare Grünfläche und die Höhe des Schwerpunkts der Grünmaske. Welke Blätter hängen durch — die Fläche schrumpft, der Schwerpunkt sinkt. Daraus entsteht eine Ampel.

   ESP32-CAM  ──WLAN──▶  Raspberry Pi 3B+  ──▶ Webseite (Port 8083)
   (Solar, Akku)          pflanzen_dashboard.py   ──▶ OLED 128×64 (I2C)
                          als Systemdienst        ──▶ pflanzen_log.csv

Der Pi ersetzt den PC, der diese Aufgabe vorher übernommen hatte. Er läuft unbeaufsichtigt durch, startet nach Stromausfall von selbst und braucht weder Monitor noch Tastatur.


2. Gerätedaten und Zugänge

Rechner Raspberry Pi 3 Model B+, 1 GB
Betriebssystem Raspberry Pi OS (32-bit) mit Desktop, Debian-Basis Trixie
Netzwerkname tomate
Adresse <IP-des-Pi> (per DHCP, kann sich ändern)
Benutzer pi
Kennwort xxxxxxxxx
WLAN <WLAN-Name>
Programmordner /home/pi/tomate
Dienst pflanzen_waechter
Dashboard http://<IP-des-Pi>:8083
Startverhalten Textanzeige (kein lokaler Desktop)
Fernzugriff SSH, xrdp (Windows-Remotedesktop), VNC eingeschaltet

Zum Kennwort: xxxxxxxxx ist die historische Werkseinstellung und damit das Erste, was automatische Suchprogramme durchprobieren. Solange kein Zugang von außen auf den Pi weitergeleitet wird, bleibt das Risiko im Heimnetz. Ändern lässt es sich jederzeit mit passwd.


3. Warum die Einrichtung so und nicht anders

Drei Entscheidungen prägen den Aufbau. Sie sind hier begründet, damit sie bei einer Neuinstallation nicht neu erarbeitet werden müssen.

32 Bit statt 64 Bit

Der Prozessor des 3B+ ist 64-bit-fähig, das Betriebssystem liefe. 64-Bit-Code belegt aber mehr Arbeitsspeicher, weil jede Adresse doppelt so breit ist. Bei 1 GB und laufendem Desktop führt das zu ständigem Auslagern auf die Speicherkarte: das Gerät wird zäh, die Karte verschleißt. Die Empfehlung der Raspberry Pi Foundation lautet für Geräte mit 1 GB entsprechend 32 Bit; 64 Bit lohnt ab dem Pi 4 mit mehr Speicher. Für Python, numpy und Pillow ist die Wahl ohne Belang.

Desktop-Ausgabe, aber Start in die Textanzeige

Die Desktop-Ausgabe wurde gewählt, weil ein Monitor zur Verfügung stand und die Ersteinrichtung damit sichtbar wird — jeder Fehler steht im Klartext auf dem Schirm, statt von außen erraten werden zu müssen.

Der Pi startet trotzdem nicht in den Desktop, sondern in die Textanzeige (raspi-config, Boot-Verhalten B2). Das hat zwei Gründe. Erstens bleiben rund 300 MB Arbeitsspeicher frei, solange niemand zuschaut. Zweitens — und das ist der zwingende Grund — kollidiert eine automatisch angemeldete Desktop-Sitzung mit xrdp: Der Fern-Desktop bricht dann unmittelbar nach dem Verbindungsaufbau wieder ab. Ohne lokale Sitzung läuft xrdp zuverlässig und erzeugt den Desktop genau dann, wenn man sich verbindet.

Eigener Displaytreiber statt Fremdbibliothek

oled_anzeige.py spricht das OLED unmittelbar über /dev/i2c-1 an, mit einem ioctl-Aufruf und os.write. Der Grund: Raspberry Pi OS lässt seit Bookworm keine pip install-Installationen mehr ins System (externally managed environment). Bibliotheken wie luma.oled oder Adafruit-Blinka müssten in einer virtuellen Umgebung installiert werden, was den Dienststart verkompliziert. Das Display braucht ohnehin nur Schreibzugriffe — die beherrscht der Kernel-Treiber i2c-dev direkt. Gezeichnet wird mit Pillow, das für die Bildauswertung sowieso vorhanden ist. Damit hat die Anzeige keine einzige Zusatzabhängigkeit.


4. Installation von Null

Diese Reihenfolge hat sich bewährt. Sie unterscheidet sich von der üblichen Anleitung, weil zwei Fehler im Raspberry Pi Imager umgangen werden (siehe Abschnitt 8).

4.1 Imager

Nicht Version 2.0.10 verwenden. Diese Fassung hat zwei belegte Fehler: sie stellt Schaltflächenbeschriftungen verstümmelt dar (jeder Buchstabe um zwei Stellen verschoben, aus SPEICHERN wird QNCGAF CPL), und sie schreibt die Voreinstellungen teilweise oder gar nicht auf die Karte, ohne das zu melden.

Verwendet wurde Version 1.9, zu beziehen über die Veröffentlichungsseite github.com/raspberrypi/rpi-imager/releases.

4.2 Karte brennen — ohne Voreinstellungen

Die Voreinstellungen werden nicht gebraucht, weil die Einrichtung am Monitor erfolgt. Damit ist die fehleranfälligste Stelle des Imagers umgangen.

Während des Schreibens fragt Windows möglicherweise, ob ein Datenträger formatiert werden soll. Immer Abbrechen. Die Frage bezieht sich auf die Linux-Partition, die Windows nicht lesen kann, und kommt bei jedem Einstecken.

4.3 Erster Start am Monitor

Monitor per HDMI, Tastatur und Maus anschließen, dann erst Karte einlegen und Strom anlegen. Netzteil mit mindestens 2,5 A verwenden.

Der Begrüßungsassistent führt durch:

Schritt Eingabe
Land, Sprache, Zeitzone Germany, German, Berlin — Häkchen „Use English language” weglassen
Benutzer Name pi, Kennwort xxxxxxxxx
WLAN <WLAN-Name> und Kennwort
Browser Chromium
Software aktualisieren kann übersprungen werden; bricht der Schritt ab, ist das folgenlos

Anschließend neu starten lassen.

4.4 Schnittstellen einschalten

Himbeer-Menü → Einstellungen → Raspberry-Pi-Konfiguration → Reiter Schnittstellen. Dort einschalten:

Danach neu starten.

4.5 Netzwerknamen setzen

Über SSH oder am Monitor:

sudo raspi-config nonint do_hostname tomate
sudo reboot

Damit heißt das Gerät tomate und ist als tomate.fritz.box ansprechbar, sofern die Fritz!Box den Namen weiterreicht. Die Zahlenadresse funktioniert immer.

4.6 Dateien übertragen

Auf dem Pi den Ordner anlegen:

mkdir -p ~/tomate

Auf dem Windows-PC eine zweite PowerShell öffnen — nicht die, in der die SSH-Sitzung läuft, denn dort würden die Befehle auf dem Pi ausgeführt. In den Ordner mit den Dateien wechseln, etwa:

cd $HOME\Downloads

Dann die Dateien einzeln übertragen. Einzeln deshalb, weil lange Zeilen beim Einfügen in die PowerShell umbrechen und dann als zwei unvollständige Befehle ausgeführt werden:

scp pflanzen_dashboard.py pi@<IP-des-Pi>:/home/pi/tomate/
scp oled_anzeige.py pi@<IP-des-Pi>:/home/pi/tomate/
scp waveshare_io.py pi@<IP-des-Pi>:/home/pi/tomate/
scp waveshare_pi_test.py pi@<IP-des-Pi>:/home/pi/tomate/
scp waveshare_wandler_test.py pi@<IP-des-Pi>:/home/pi/tomate/
scp pflanzen_waechter.service pi@<IP-des-Pi>:/home/pi/tomate/
scp install_pi.sh pi@<IP-des-Pi>:/home/pi/tomate/
scp ANLEITUNG_Pi_OLED.md pi@<IP-des-Pi>:/home/pi/tomate/

Jede Zeile fragt nach dem Kennwort. Wer lieber mit der Maus arbeitet, nimmt WinSCP (Protokoll SFTP, Rechner <IP-des-Pi>, Benutzer pi).

4.7 Einrichten

Im SSH-Fenster:

cd ~/tomate
chmod +x install_pi.sh
sudo ./install_pi.sh

Das Skript führt fünf Schritte aus:

  1. installiert python3-numpy, python3-pil, fonts-dejavu-core, i2c-tools über apt (nicht über pip),
  2. schaltet I2C ein und trägt 400 kHz Bustakt in /boot/firmware/config.txt,
  3. sucht das Display auf dem Bus und meldet das Ergebnis,
  4. richtet den Systemdienst pflanzen_waechter ein und schaltet ihn scharf,
  5. startet ihn und zeigt seinen Zustand.

Solange kein Display angeschlossen ist, meldet Schritt 3, dass keines gefunden wurde. Das ist kein Fehler — die Anzeige ist Zugabe, das Programm läuft ohne sie unverändert.

4.8 Start in die Textanzeige umstellen

sudo raspi-config nonint do_boot_behaviour B2
sudo reboot

Danach zeigt der Monitor nur noch eine Eingabeaufforderung. Der Wächter läuft davon unberührt, denn er ist ein Systemdienst und startet vor jeder Anmeldung.

4.9 Fern-Desktop

sudo apt install -y xrdp

Am Windows-PC mstsc starten, <IP-des-Pi> eingeben, im Anmeldefenster von xrdp Session Xorg, Benutzer pi, Kennwort xxxxxxxxx.

Wichtig: Ohne die Umstellung aus 4.8 bricht die Verbindung sofort wieder ab.


5. Betrieb

Das Dashboard ist im Browser jedes Geräts im Heimnetz erreichbar:

http://<IP-des-Pi>:8083

Ablauf im Alltag: Pflanze gießen, im Dashboard einmal Referenz setzen. Von da an meldet der Wächter, wenn die Blätter hängen. Die Referenz sollte bei ähnlicher Beleuchtung gesetzt werden wie später gemessen wird.

Befehle

Zweck Befehl
Läuft der Wächter? systemctl status pflanzen_waechter
Mitlesen, was er tut journalctl -u pflanzen_waechter -f (Ende mit Strg+C)
Anhalten sudo systemctl stop pflanzen_waechter
Starten sudo systemctl start pflanzen_waechter
Nach Änderung neu laden sudo systemctl restart pflanzen_waechter
Autostart abschalten sudo systemctl disable pflanzen_waechter
Adresse des Pi hostname -I
Sauber ausschalten sudo poweroff

Den Pi nicht einfach vom Strom trennen — dabei kann die Speicherkarte Schaden nehmen. Nach sudo poweroff blinkt die grüne Leuchtdiode zehnmal und bleibt dann dunkel; erst dann das Netzteil ziehen.

Einstellungen

Alle Stellschrauben stehen oben in pflanzen_dashboard.py. Ändern mit nano ~/tomate/pflanzen_dashboard.py, danach sudo systemctl restart pflanzen_waechter.

Größe Bedeutung
ESP32CAM_IP Name oder Adresse der Kamera
INTERVALL Sekunden zwischen zwei Bildern, Vorgabe 300
EXG_SCHWELLE, MIN_GRUEN Empfindlichkeit der Grünerkennung
GELB_FLAECHE, ROT_FLAECHE Flächenabfall in Prozent für Gelb und Rot
GELB_ABSINKEN, ROT_ABSINKEN Absinken des Schwerpunkts für Gelb und Rot
GLAETTUNG Median über die letzten n Messungen
OLED_AKTIV False schaltet das Display ab
OLED_TREIBER ssd1306 oder sh1106
OLED_KOPFSTEHEND True dreht die Anzeige um 180 Grad
OLED_SEITENZEIT Sekunden je Anzeigeseite
BILDER_TAGE Bilder älter als n Tage löschen, 0 = nie

Zu BILDER_TAGE: bei einem Bild alle fünf Minuten fallen rund 300 Bilder oder 4 MB pro Tag an. Ohne Aufräumen läuft die Karte in einem Jahr voll; die Vorgabe von 14 Tagen hält den Ordner bei etwa 60 MB.


6. OLED-Display

Angeschlossen und in Betrieb seit dem 18.08.2026. Verwendet wird ein weißes 128×64-Modul mit SSD1306-Controller, I2C, in der vierpoligen 0,96”-Bauform ohne Reset-Anschluss. Es war dafür nichts zu konfigurieren — I2C war bereits eingeschaltet, und das Programm sucht das Display beim Start auf beiden möglichen Adressen selbst.

Vor dem Anschließen den Pi sauber herunterfahren (sudo poweroff, grüne Leuchtdiode blinkt zehnmal, dann erst das Netzteil ziehen). Umstecken unter Spannung ist die häufigste Ursache für zerstörte Module.

Das Programm sucht das Display nur beim Start. Wird es im laufenden Betrieb angesteckt, bleibt es dunkel bis sudo systemctl restart pflanzen_waechter.

Display Pi-Stiftleiste Bezeichnung im Pinplan
VCC Pin 1 3V3 Power
SDA Pin 3 GPIO 2 (SDA)
SCL Pin 5 GPIO 3 (SCL)
GND Pin 9 Ground

So ausgeführt. Pin 1 ist die Ecke mit der eckigen Lötinsel, dem SD-Kartenschacht am nächsten. Die Masse liegt hier bewusst auf Pin 9 und nicht auf dem ebenfalls möglichen Pin 6: dann liegen alle vier Anschlüsse in derselben Reihe. Dabei die Zählweise beachten — in dieser Reihe folgen die ungeraden Nummern 1, 3, 5, 7, 9 aufeinander, Masse ist also der fünfte Stift, nicht der vierte. Pin 7 ist GPIO 4 und bleibt frei.

Die Stiftreihenfolge auf dem Displaymodul ist nicht einheitlich — verbreitet sind GND VCC SCL SDA und VCC GND SCL SDA. Die Beschriftung auf der Platine lesen, nicht nach Gewohnheit stecken. Vertauschte Versorgung überlebt das Modul meist nicht; vertauschte Datenleitungen sind harmlos, dann wird das Display lediglich nicht gefunden.

Nach dem Einschalten prüfen:

i2cdetect -y 1

In der Tabelle muss 3c erscheinen, bei manchen Modulen 3d. Beide findet das Programm von selbst. Anschließend:

sudo systemctl restart pflanzen_waechter

Was das Display zeigt

Drei Seiten wechseln sich alle sechs Sekunden ab. Der Inhalt verschiebt sich dabei um einen Bildpunkt, das beugt dem Einbrennen vor.

Seite 1, Pflanze:

Tomate                19:40
---------------------------
      [ GIESSEN! ]
Gruen 12.3 %
Abfall 9.4%  Sinkt 2.1%
Ref 14.08. 19:30

Die große Zeile ist die eigentliche Aussage: Wasser ok, bald giessen oder GIESSEN!. Der Gießbefehl wird hell hinterlegt dargestellt und ist dadurch aus einigen Metern erkennbar. Weitere Texte: Nacht (zu dunkel zum Auswerten), keine Referenz und keine Pflanze.

Seite 2, Technik: Bildnummer und -größe, Betriebszeit der Kamera, WLAN-Pegel, freier Speicher der Kamera, Wiederholungen und verworfene Bilder, unten die eigene Adresse.

Seite 3, Verlauf: Flächenabfall als durchgezogene Linie, Absinken gepunktet.

Ein (!) in der Kopfzeile bedeutet: gerade keine Verbindung zur Kamera. Im Solarbetrieb ist das normal und verschwindet von selbst.

Das Display einzeln prüfen, ohne den Wächter:

sudo systemctl stop pflanzen_waechter
python3 ~/tomate/oled_anzeige.py demo

Zeigt alle drei Seiten mit erfundenen Werten. Ende mit Strg+C, danach sudo systemctl start pflanzen_waechter.


6b. Klemmenebene: Waveshare Modbus RTU Module (A)

Angeschlossen über einen Waveshare „USB TO 4CH RS485” (Baustein WCH CH344) am USB des Pi. Das Modul hängt an Kanal 1, erkennbar an der Endung -if00 unter /dev/serial/by-id/. Verdrahtung A auf A, B auf B, Masse mitverbinden; Moduladresse 1, 9600 Baud, 8N1. Das Modul braucht eine eigene Spannungsversorgung.

Benötigt wird python3-serial (installiert das Einrichtungsskript nicht mit, daher bei einem Neuaufbau nachholen):

sudo apt install -y python3-serial

Auf einem RS485-Bus darf nur ein Master sprechen. Solange der Pi Master ist, müssen PC-Programme und das RS485-TO-ETH-Gateway () schweigen.

Im Dashboard erscheint eine eigene Karte: Analogeingänge im Sekundentakt mit Messbereich, Digitaleingänge als Anzeige, Relais 1 als Schaltknopf, Relais 2 als Gießaktor mit Zeitvorgabe, und die beiden Analogausgänge mit Eingabefeld in Mikroampere.

Der Messbereich der Analogeingänge wird beim Verbinden aus waveshare_io.py ins Modul geschrieben (AI_MODUS, Vorgabe 0 = 0–10 V). Für Spannungsmessung müssen zusätzlich die Schiebeschalter der beiden AI-Kanäle auf dem Modul auf OFF stehen. Änderungen an dieser Konstante wirken erst nach sudo systemctl restart pflanzen_waechter.

Einzeln prüfen lässt sich das Modul nur bei angehaltenem Dienst, weil dieser die serielle Schnittstelle dauerhaft belegt:

sudo systemctl stop pflanzen_waechter
python3 ~/tomate/waveshare_pi_test.py
sudo systemctl start pflanzen_waechter

7. Fehlersuche

Kamera

Immer „keine Verbindung zur Kamera”: Die ESP32-CAM nimmt nur eine einzige Modbus-Verbindung an. Solange das alte Wächterprogramm auf dem PC läuft, bleibt der Pi ausgesperrt — den PC-Wächter beenden. Im Solarbetrieb schaltet die Versorgung bei 10 % Akku ab und bei 50 % wieder ein; Aussetzer sind daher normal. Prüfen mit ping tomatencam.fritz.box.

Netzwerk

Der Pi ist unter seinem Namen nicht erreichbar: .local löst Windows ohne Zusatzdienste nicht auf. tomate.fritz.box versuchen, sonst die Zahlenadresse.

Adresse unbekannt: Raspberry-Pi-Geräte tragen eine Netzwerkkennung, die mit b8-27-eb beginnt. In der PowerShell erst das Netz abklopfen, dann die Antworten filtern:

1..254 | ForEach-Object { $null = (New-Object System.Net.NetworkInformation.Ping).SendPingAsync("192.168.x.$_", 500) }
arp -a | findstr b8-27-eb

Die Liste arp -a zeigt nur Geräte, mit denen der PC kürzlich Kontakt hatte — ohne das vorherige Abklopfen bleibt sie unvollständig.

Fern-Desktop

xrdp verbindet und bricht sofort ab: Es läuft eine lokale Desktop-Sitzung, die die Grafik belegt. Abhilfe ist die Umstellung auf Textanzeige (Abschnitt 4.8).

Display

i2cdetect -y 1 zeigt nichts: I2C eingeschaltet? Verdrahtung, besonders die Reihenfolge der Stifte am Modul. Nach dem ersten Einschalten von I2C ist ein Neustart nötig.

Streifen oder Versatz im Bild: falscher Controller, OLED_TREIBER = "sh1106" setzen und den Dienst neu starten.

Adresse wird gefunden, Anzeige bleibt dunkel: journalctl -u pflanzen_waechter -n 30 ansehen. Bei einem Rechtefehler auf /dev/i2c-1 hilft sudo usermod -aG i2c pi und ein Neustart.

Dienst

Dashboard nicht erreichbar: systemctl status pflanzen_waechter zeigt, ob der Dienst läuft; journalctl -u pflanzen_waechter -n 50 nennt den Grund. Nach einem Absturz startet er binnen 15 Sekunden von selbst neu.


8. Was bei dieser Installation schiefging

Festgehalten, weil beides bei einer Neuinstallation wieder auftreten kann und von außen nicht zu erkennen ist.

Der Imager schrieb die Voreinstellungen nicht

Version 2.0.10 meldete „Schreiben erfolgreich”, legte aber keine custom.toml auf der Karte an. Der Pi startete dadurch ohne Benutzer, ohne WLAN und ohne SSH — von außen nicht von einem Verdrahtungs- oder Kennwortfehler zu unterscheiden. Der Fehler ist für mehrere Fassungen der 2.0-Reihe belegt (rpi-imager, Issues #1439, #1567 und weitere).

Verstümmelte Beschriftungen

Ebenfalls 2.0.10 unter Windows: die eingebettete Schrift wird mit verschobener Zeichentabelle geladen, jeder Buchstabe erscheint zwei Stellen versetzt. Nur Schaltflächen betroffen, Überschriften korrekt. Belegt in den Issues #1648, #1653, #1684, behoben in 2.0.11. Kein Virus und ohne Einfluss auf die geschriebenen Daten.

Die Ersteinrichtung läuft nur einmal

Ausgelöst wird sie durch einen Eintrag in cmdline.txt (init=/usr/lib/raspberrypi-sys-mods/firstboot), den das Einrichtungsprogramm anschließend selbst entfernt. Wer eine custom.toml erst nach dem ersten Einschalten nachträgt, wartet vergeblich — sie wird nie gelesen. Die Reihenfolge ist zwingend: brennen, Datei kopieren, dann erst zum ersten Mal einschalten.

Diese drei Punkte zusammen sind der Grund, warum die Einrichtung hier am Monitor erfolgte statt kopflos. Mit Bildschirm ist jeder dieser Fehler in Sekunden sichtbar.


9. Dateien

Im Ordner /home/pi/tomate auf dem Pi:

Datei Inhalt
pflanzen_dashboard.py Hauptprogramm: Bildabholung, Auswertung, Webserver, Anbindung von Display und Klemmenebene
oled_anzeige.py Displaytreiber (SSD1306/SH1106) und Seitengestaltung
waveshare_io.py Modbus RTU: Relais, Digital- und Analogein-/-ausgänge, Gießfunktion
waveshare_pi_test.py Prüfprogramm für das Modul A (nur bei angehaltenem Dienst)
waveshare_wandler_test.py Prüfprogramm für den RS485-Wandler allein
pflanzen_waechter.service Vorlage des Systemdienstes
install_pi.sh Einrichtungsskript
ANLEITUNG_Pi_OLED.md dieses Dokument

Im Betrieb entstehen zusätzlich:

Datei Inhalt
pflanzen_log.csv Messreihe: Zeit, Bildnummer, Helligkeit, Fläche, Schwerpunkt, Bewertung
pflanzen_referenz.json die gesetzte Referenz
pflanzen_bilder/ gespeicherte Bilder mit Zeitstempel im Namen

Der eingerichtete Dienst liegt unter /etc/systemd/system/pflanzen_waechter.service.


10. Wiederaufbau von Null — wenn die Karte stirbt

Eine Speicherkarte ist ein Verschleißteil. Fällt sie aus, ist das kein Unglück, sondern eine knappe Stunde Arbeit — vorausgesetzt, die folgenden Dinge liegen außerhalb des Pi.

10.1 Was vorher gesichert sein muss

Was Wo aufbewahren Warum
die Programmdateien aus Abschnitt 9 auf dem PC, zusätzlich USB-Stick oder Cloud sie liegen sonst nur auf der defekten Karte
dieses Dokument ebenda enthält den ganzen Weg
pflanzen_log.csv gelegentlich auf den PC holen die Messreihe ist nicht wiederherstellbar

Die Messreihe holt man mit einem Befehl auf den PC (in einer PowerShell auf dem PC, nicht im SSH-Fenster):

scp pi@<IP-des-Pi>:/home/pi/tomate/pflanzen_log.csv .

Verloren gehen bei einem Kartenschaden ohne Sicherung: die Messreihe, die gespeicherten Bilder und die gesetzte Referenz. Die Referenz ist der einzige Punkt, der Handlung erfordert — sie wird nach dem Wiederaufbau beim nächsten Gießen neu gesetzt. Das Programm selbst und alle Einstellungen entstehen aus den gesicherten Dateien neu.

10.2 Zeitbedarf und Material

Rund vierzig Minuten, davon zehn Minuten Warten beim Brennen. Gebraucht werden eine neue microSD-Karte (16 GB genügen, Klasse 10, für Dauerbetrieb gern eine High-Endurance-Karte), ein Kartenleser am PC und für die Ersteinrichtung Monitor, Tastatur und Maus am Pi.

Die Ersteinrichtung ohne Monitor ist möglich, aber nach den Erfahrungen aus Abschnitt 8 nicht zu empfehlen: Schlägt sie fehl, ist der Pi stumm und der Grund von außen nicht erkennbar.

10.3 Ablauf

Schritt 1 bis 3 sind ausführlich in Abschnitt 4 beschrieben, hier nur die Kurzfassung zum Abhaken.

  1. Karte brennen mit Imager 1.9: Gerät Raspberry Pi 3, Betriebssystem Raspberry Pi OS (32-bit) mit Desktop, Voreinstellungen NEIN.

  2. Monitor, Tastatur, Maus anschließen, Karte einlegen, Strom anlegen.

  3. Begrüßungsassistenten durchgehen: Germany / German / Berlin, Benutzer pi mit Kennwort xxxxxxxxx, WLAN <WLAN-Name>, Browser Chromium, Aktualisierung überspringen. Neu starten lassen.

  4. Auf dem Pi ein Terminalfenster öffnen und die Grundeinstellungen in einem Rutsch setzen — das ersetzt das Klicken in der Konfigurationsoberfläche:

sudo raspi-config nonint do_hostname tomate
sudo raspi-config nonint do_ssh 0
sudo raspi-config nonint do_vnc 0
sudo raspi-config nonint do_i2c 0
sudo raspi-config nonint do_boot_behaviour B2
sudo reboot

Die 0 bedeutet bei diesen Befehlen jeweils „einschalten”. B2 stellt den Start auf die Textanzeige um; ohne das läuft der Fern-Desktop später nicht (Abschnitt 7).

  1. Adresse feststellen. Nach dem Neustart zeigt der Monitor eine Eingabeaufforderung; dort anmelden mit pi / xxxxxxxxx und eingeben:
hostname -I

Erscheint keine Adresse mit 192.168.x., hat das WLAN nicht verbunden; dann sudo raspi-config → System Options → Wireless LAN.

  1. Vom PC aus verbinden und den Ordner anlegen:
ssh pi@ADRESSE
mkdir -p ~/tomate
  1. In einer zweiten PowerShell auf dem PC, im Ordner mit den Dateien, jede einzeln übertragen (eine je Zeile, siehe Abschnitt 4.6):
scp pflanzen_dashboard.py pi@ADRESSE:/home/pi/tomate/
scp oled_anzeige.py pi@ADRESSE:/home/pi/tomate/
scp waveshare_io.py pi@ADRESSE:/home/pi/tomate/
scp waveshare_pi_test.py pi@ADRESSE:/home/pi/tomate/
scp waveshare_wandler_test.py pi@ADRESSE:/home/pi/tomate/
scp pflanzen_waechter.service pi@ADRESSE:/home/pi/tomate/
scp install_pi.sh pi@ADRESSE:/home/pi/tomate/
scp ANLEITUNG_Pi_OLED.md pi@ADRESSE:/home/pi/tomate/
  1. Im SSH-Fenster einrichten:
cd ~/tomate
chmod +x install_pi.sh
sudo ./install_pi.sh
  1. Fern-Desktop nachrüsten, falls gewünscht:
sudo apt install -y xrdp
  1. Prüfen: http://ADRESSE:8083 im Browser, dann Monitor und Tastatur abziehen, Strom trennen, wieder anstecken. Kommt das Dashboard nach zwei Minuten von selbst wieder, ist der Wiederaufbau abgeschlossen.

  2. Nach dem nächsten Gießen im Dashboard Referenz setzen.

10.4 Wenn die Adresse eine andere ist

Der Pi bekommt seine Adresse von der Fritz!Box und kann nach einer Neuinstallation eine andere erhalten. Ermitteln lässt sie sich am Gerät mit hostname -I oder vom PC aus über die Netzwerkkennung (Abschnitt 7, Unterpunkt Netzwerk). Wer Ruhe haben will, vergibt in der Fritz!Box unter Heimnetz eine feste Adresse für tomate.


11. Offene Punkte

Anleitung — Umgebungskamera (USB) als Livebild

Umgebungskamera am Tomatenwächter

Eine gewöhnliche USB-Webcam am Raspberry Pi liefert ein flüssiges Livebild der Umgebung. Eigenständige Anleitung; die übrigen Unterlagen werden nicht gebraucht.

Stand 19.08.2026, eingerichtet und in Betrieb.


1. Wozu

Die ESP32-CAM beobachtet die Pflanze und liefert alle fünf Minuten ein Einzelbild — sie ist solarbetrieben, funkt über WLAN und ist auf Sparsamkeit ausgelegt, nicht auf Bewegtbild.

Für den Blick in die Umgebung ist das zu wenig: Steht die Gießkanne noch da, läuft Wasser, ist jemand im Gewächshaus, hat der Wind etwas umgeworfen? Dafür hängt eine USB-Webcam unmittelbar am Pi und zeigt ein Livebild im selben Dashboard.

Beide Kameras stören einander nicht. Die Pflanzenauswertung arbeitet unverändert mit den Bildern der ESP32-CAM.


2. Wie es funktioniert

Der entscheidende Kunstgriff heißt: nichts anfassen.

Fast jede USB-Kamera kann ihre Bilder bereits fertig gepackt als JPEG liefern, im Format MJPG. Diese Bilder werden nicht entpackt und nicht neu gepackt, sondern unverändert an den Browser durchgereicht. Der Pi sortiert nur Bytes, und die Rechenlast bleibt vernachlässigbar — auf einem 3B+ mit 1 GB ist das der Unterschied zwischen flüssig und unbrauchbar.

Beherrscht eine Kamera kein MJPG, packt ffmpeg die Bilder selbst. Das funktioniert ebenfalls, kostet aber deutlich Rechenzeit. Das Programm merkt das selbst und zeigt es im Dashboard an.

Übertragen wird als „motion JPEG”: eine HTTP-Antwort, die nie endet und in der ein Bild nach dem anderen steht. Jeder Browser stellt das ohne Zusatzsoftware dar, es genügt ein Bildelement auf der Seite.

Der Datenstrom läuft nur, solange jemand zusieht. Wird das Häkchen im Dashboard entfernt oder die Seite geschlossen, schaltet sich die Kamera nach zehn Sekunden Nachlauf wieder ab. Das spart Rechenzeit, Strom und bei Mobilfunk Datenvolumen.


3. Voraussetzungen

Auf dem Pi:

sudo apt install -y ffmpeg v4l-utils

Die Datei livecam.py gehört nach /home/pi/tomate, und pflanzen_dashboard.py muss die Fassung mit der Kamerakarte sein. Nach dem Kopieren:

sudo systemctl restart pflanzen_waechter

Eine USB-Webcam beliebiger Herkunft genügt. Sie zieht bis zu 500 mA aus dem USB-Anschluss — zusammen mit dem RS485-Wandler ist ein Netzteil mit 2,5 A angebracht.


4. Kamera prüfen

Welche Videogeräte gibt es?

v4l2-ctl --list-devices

Der Raspberry Pi meldet ein gutes Dutzend, von denen die meisten interne Bausteine sind: bcm2835-codec für das Kodieren, bcm2835-isp für die Bildverarbeitung. Gesucht ist der Eintrag mit dem Namen der Kamera. Das Programm siebt die internen Geräte selbst aus.

Was kann sie?

v4l2-ctl -d /dev/video0 --list-formats-ext

Gesucht ist MJPG und darunter die Auflösungen mit ihren Bildraten. Die hier verwendete „HD USB Camera” kann in MJPG:

Auflösung Bilder je Sekunde
320×240, 640×480, 800×600, 1024×768 30
1280×960, 1600×1200, 2048×1536 15
2592×1944, 3264×2448 15

5. Einstellungen

Oben in livecam.py:

Größe Bedeutung
GERAET None = erste echte Kamera nehmen, sonst z.B. "/dev/video0"
BREITE, HOEHE Auflösung, Vorgabe 800 × 600
BILDRATE angeforderte Bilder je Sekunde, Vorgabe 15
QUALITAET nur wenn ffmpeg selbst packen muss: 2 (gut) bis 15 (grob)
NACHLAUF Sekunden Weiterlauf nach dem letzten Zuschauer

Nach jeder Änderung sudo systemctl restart pflanzen_waechter.

Zur Wahl der Auflösung: Sie kostet keine Rechenzeit, weil die Bilder ja nur durchgereicht werden — sie kostet Datenmenge. Als Anhaltspunkt liefert 800 × 600 rund 25 bis 30 kB je Bild, bei 15 Bildern je Sekunde also etwa 400 kB/s oder 3 Mbit/s. Im WLAN ist das belanglos, über Mobilfunk merklich. Wer von unterwegs zusieht, fährt mit 640 × 480 sparsamer.


6. Bedienung

Im Dashboard gibt es die Karte Umgebungskamera mit einem Häkchen Livebild einschalten. Es ist bewusst nicht von selbst eingeschaltet, damit nicht jeder Seitenaufruf ungefragt einen Datenstrom auslöst.

Über der Bildfläche steht der Zustand: Gerätename, tatsächliche Bildrate und Größe je Bild. Erscheint dort der Zusatz „ffmpeg packt selbst”, liefert die Kamera kein MJPG und die Rechenlast ist deutlich höher.

Ein Einzelbild ohne Datenstrom gibt es unter:

http://<IP-des-Pi>:8083/kamera2.jpg

Das eignet sich für schnelle Blicke von unterwegs oder um es in andere Seiten einzubinden.


7. Wenn etwas nicht geht

Die Karte meldet „keine USB-Kamera angeschlossen”: Prüfen mit ls /dev/video* und lsusb. Wurde die Kamera erst nach dem Programmstart angesteckt, muss der Dienst neu gestartet werden — er sucht die Kamera nur beim Start.

Es kommt kein Bild, obwohl die Kamera erkannt wird: Auf dem Pi den Dienst anhalten und die Kamera einzeln prüfen:

sudo systemctl stop pflanzen_waechter
python3 ~/tomate/livecam.py
sudo systemctl start pflanzen_waechter

Das Programm nimmt fünf Sekunden auf und zählt die Bilder. Dabei zeigt es auch, ob ffmpeg selbst packen musste.

Das Bild ruckelt: Auflösung oder Bildrate verringern. Über Mobilfunk ist die Leitung der Engpass, nicht der Pi.

Der Zusatz „ffmpeg packt selbst” erscheint, obwohl die Kamera MJPG kann: Dann passt die eingestellte Kombination aus Auflösung und Bildrate nicht zu dem, was die Kamera in MJPG anbietet. Die Liste aus Abschnitt 4 heranziehen und eine dort aufgeführte Auflösung eintragen.

Zwei Programme gleichzeitig gehen nicht: Eine Kamera lässt sich nur von einem Programm öffnen. Solange der Dienst läuft und jemand zusieht, ist sie belegt.


8. Zur Beachtung

Das Dashboard hat keine Anmeldung. Wer es erreicht, sieht auch das Livebild. Erreichbar ist es für alle Geräte im Heimnetz und, sofern eingerichtet, über den Fernzugriff.

Bei einer Kamera wiegt das schwerer als bei Messwerten. Wenn sie einen Bereich zeigt, in dem sich Menschen aufhalten, gehört das bedacht — und spätestens dann eine Anmeldung vor die Seite.