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.
165 lines
7.2 KiB
Markdown
165 lines
7.2 KiB
Markdown
# 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.
|