Files
vogelalbum/esp32cam/README.md
T
christian c11fe9ab04 Vogelalbum 1.0.0
Eine Kamera am Vogelhaus, ein Rechner der erkennt wer da war — und eine
Sammlung, die sich von allein fuellt.

Zwei neuronale Netze: eines findet den Vogel im Bild, eines bestimmt die
Art. 148 heimische Arten, auf Deutschland eingeschraenkt und neu normiert —
ohne diese Einschraenkung wird aus einer Kohlmeise eine nordamerikanische
Schwesterart. Daraus entsteht ein Sammelalbum: nummerierte Seiten,
gefundene Arten in Farbe, fehlende als Schatten, dazu Erstfang, Punkte,
Stufen und 15 Abzeichen.

Laeuft vollstaendig auf eigener Hardware. Kein Cloud-Dienst, kein Konto,
keine Telemetrie; die Bilder verlassen das eigene Netz nicht.

Zwei getrennt deploybare Teile: die Sammlung als Django-Anwendung, die
Erkennung als eigener Dienst. Der Erkennungs-Dienst fragt selbst an und
braucht deshalb weder eine erreichbare Adresse noch einen offenen Port —
er laeuft hinter einem gewoehnlichen Router.

Dabei: Firmware fuer Raspberry Pi und ESP32-S3, Anbindung an Home
Assistant ueber MQTT, Oberflaeche auf Deutsch und Englisch, Einstellungen
je Kamera aus dem Browser, und ein Weg fuer Fotos von unterwegs.

AGPL-3.0.
2026-10-15 14:30:02 +00:00

7.2 KiB
Raw Blame History

Vogelkamera auf ESP32-S3

Kameraseite für Vogelalbum auf einem ESP32-S3 mit Kameramodul — das Gegenstück zu ../pi/. Gleiche Pfade und gleiches JSON-Schema: der Detector merkt nicht, ob ein Pi Zero oder ein ESP32 am anderen Ende hängt.

http://<kamera>:8080/stream    MJPEG, ein Zuschauer
http://<kamera>:8080/          Vorschauseite

http://<kamera>:8081/status    JSON: fps, RSSI, Temperatur, Laufzeit
http://<kamera>:8081/temp      Chiptemperatur als Text
http://<kamera>:8081/gesund    200 oder 503

🔑 Die Diagnose liegt auf einem eigenen Port — das ist der eine Unterschied zum Pi. Der Webserver im ESP32 arbeitet Anfragen mit einem Faden ab, und der Stream gibt den nie zurück. Solange jemand zuschaut, käme auf 8080 niemand mehr an /status — also genau dann nicht, wenn man es braucht. Am 23.09.2026 wirkte die Kamera deshalb tot, während sie brav streamte. Seitdem hat die Diagnose ihren eigenen Port und ihren eigenen Faden.

⚠️ Bis zum 09.10.2026 stand hier für alles 8080. Wer die Zeile kopierte, bekam eine Anfrage, die stumm ins Zeitlimit lief — dasselbe Bild wie eine tote Kamera. Der Port ist VK_PORT + 1; wer im menuconfig den Hauptport ändert, verschiebt beide.

Warum ESP32 statt Pi

Am Vogelhaus ist die Bildrate bandbreitenbegrenzt, nicht rechenbegrenzt: der Pi Zero W drückt über seine Chip-Antenne rund 2 Mbit/s durchs WLAN, und jeder gemessene Einbruch hing an der Funkstrecke. Ein ESP32-S3-Board mit externer Antenne greift genau da an — und kostet ein Vielfaches weniger, was für einen Bausatz den Unterschied zwischen verkaufbar und nicht verkaufbar macht.

Hardware

Getestet wird gegen ein ESP32-S3-CAM mit OV3660 (N16R8-Modul: 16 MB Flash, 8 MB PSRAM, externe Antenne). Die Firmware läuft aber auf jedem Board, dessen Pinbelegung im menuconfig hinterlegt ist.

⚠️ Das Funkmodul muss ein ESP32-S3-WROOM-1U sein — das U steht für die Antennenbuchse. Bei manchen Klonen liegt eine Antenne bei, das Modul hat aber nur die Platinenantenne; dann ist das Kabel Deko.

⚠️ OV5640 nicht verwenden. Auf diesen Boards liefert er dokumentiert ein schwarzes Bild, und der Sensor überhitzt, weil die Versorgung für seinen internen 1,5-V-Regler zu hoch liegt. OV2640 und OV3660 laufen.

Bauen und flashen

. ~/esp/esp-idf/export.sh
cd esp32cam
idf.py set-target esp32s3
idf.py menuconfig      # Menü "Vogelkamera": WLAN, Board, Bildgröße
idf.py -p /dev/ttyACM0 flash monitor

Die WLAN-Daten stehen bewusst nicht im Repo — sie landen über menuconfig in sdkconfig, und das ist per .gitignore draußen.

Zwei USB-C-Buchsen am Board: eine hängt am USB-Seriell-Wandler (CH340), die andere am nativen USB des Chips. Findet idf.py flash nichts, ist es die andere Buchse. Je nach Board muss zum Flashen BOOT gehalten und RESET kurz gedrückt werden.

Wenn die Kamera nicht anspringt

esp_camera_init scheitert mit ESP_ERR_NOT_FOUND (0x105), wenn die Pinbelegung nicht zum Board passt — dann ist die Board-Auswahl im menuconfig falsch, nicht die Kamera defekt. Die Firmware bricht deshalb nicht ab: der HTTP-Server läuft weiter und /status nennt den Fehlercode. Ein Gerät im Garten, das bei einem Fehler stumm bleibt, kostet einen Weg nach draußen.

Zweiter Kandidat: CONFIG_SPIRAM_MODE_OCT in sdkconfig.defaults. Octal ist für die R8-Module richtig; ein R2- oder Quad-Modul braucht QUAD, sonst findet die Kamera keinen Framebuffer.

Ein Zuschauer, mit Absicht

Ein zweiter Client bekommt 503 statt eines halben Bildes. Der Grund steht im Kopf von main/vogelkamera.c: ein zweiter Zuschauer sättigt die Funkstrecke, der Detector fällt in Timeouts, und zwar ohne dass irgendwo ein Fehler auftaucht — die Bildrate halbiert sich einfach. Genau das ist bei der Entwicklung passiert: das Diagnosewerkzeug hat den Fehler erzeugt, den es messen sollte.

Deshalb gibt es /status und /temp. Beide fassen den Stream nicht an und sind der richtige Weg, die Kamera im Betrieb zu prüfen:

curl -s http://<kamera>:8081/status
{"kamera":"bereit","fehler":"0x0","laufzeit_s":615201,"bilder":3206804,
 "fps":5.25,"letztes_bild_s":0.1,"zuschauer":true,"rssi":-71,
 "temperatur_c":51.7,"speicher_frei":8104348,"psram_frei":7941536}

(Echte Ausgabe einer Kamera an einer Vogeltränke nach sieben Tagen Dauerbetrieb.) Port 8081, nicht 8080 — siehe oben.

ping taugt dafür nicht — ICMP fällt bei gesättigtem WLAN als Erstes aus, während TCP weiterläuft. Eine Entwarnung per ping ist Glück, keine Messung.

Was die Werte sagen:

Feld Gesund Woran es sonst liegt
fps stabil über Stunden fällt → Funkstrecke, zweiter Zuschauer, Hitze
rssi über −70 dBm darunter Antenne ausrichten oder näher ran
temperatur_c unter 70 °C geschlossenes Gehäuse in der Sonne drosselt
letztes_bild_s unter 1 s bei Zuschauer steht → Sensor hängt, siehe unten
zuschauer true im Normalbetrieb false, obwohl der Detector läuft → er kommt nicht durch

Bleibt der Sensor 30 Versuche am Stück stumm, startet das Gerät selbst neu. Zwei Sekunden Neustart sind billiger als ein Weg in den Garten.

Einstellungen, die wirklich etwas bringen

Bildgröße und Qualität sind Bandbreite, nicht Schönheit. Weniger Qualität heißt mehr Bilder pro Sekunde, und mehr Bilder finden mehr Vögel als schönere. Default ist XGA (1024x768) bei Qualität 12.

Der begrenzende Faktor ist der Bildwinkel, nicht die Auflösung. Am Futterhaus scheitert die Erkennung daran, dass der Vogel unter den nötigen 5–8 % Bildfläche bleibt. Mehr Megapixel ändern daran nichts, nähere Montage schon — 30–40 cm an die Landefläche, und die Landefläche in die untere Bildmitte, nicht an den Bildrand (der unterste Bildrand ist eine tote Zone, siehe python -m detector karte).

Kein WLAN-Stromsparen. WIFI_PS_NONE ist gesetzt: der Empfänger darf zwischen zwei Bildern nicht schlafen. Die Bildrate ist der Zweck des Geräts, nicht die Batterielaufzeit.

Feste Adresse

Der Detector braucht eine gleichbleibende Adresse. Der Dev-Server löst kein mDNS auf (kein avahi), .local-Namen helfen also nicht — im Router eine feste DHCP-Lease für die MAC eintragen.

Im Dauerbetrieb

Die Firmware läuft an einer Vogeltränke im Garten. Gemessen am laufenden Detector, drei aufeinanderfolgende Minuten:

Bilder/s
ESP32-S3-CAM mit OV3660 (dieses Projekt) 5,2
Raspberry Pi Zero W mit OV5647 (zum Vergleich) 4,4

Insgesamt hat der Detector an dieser Kamera bisher über 2,8 Millionen Bilder verarbeitet. Die Bildrate ist bandbreitenlimitiert, nicht rechenlimitiert — deshalb der Rat zur Modulvariante mit Antennenbuchse (ESP32-S3-WROOM-1U) und externer Antenne.

Ungetestet sind die übrigen Pinbelegungen: Gebaut und betrieben wurde bisher nur die S3-CAM-Variante. XIAO-S3 und AI-Thinker sind in menuconfig vorgesehen, aber nie auf Hardware gelaufen.

Was fehlt

OTA-Updates sind nicht eingebaut. Die Partitionstabelle lässt hinter der 4-MB-App genug Platz, um sie ohne Umbau nachzurüsten — für ein Gerät, das später im Garten festsitzt, wäre das der nächste sinnvolle Schritt.