Files
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

165 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Vogelkamera auf ESP32-S3
Kameraseite für Vogelalbum auf einem ESP32-S3 mit Kameramodul — das Gegenstück zu
[`../pi/`](../pi/README.md). 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-1*U* 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
```sh
. ~/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`](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:
```sh
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-1**U**)
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.