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.
This commit is contained in:
commit
cc9e0786c2
189 files changed
+29792
No files matched your search
@@ -0,0 +1,150 @@
|
||||
# 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.
|
||||
|
||||
## Noch nicht getestet
|
||||
|
||||
Die Firmware **kompiliert** (ESP-IDF v5.3, 78 % der App-Partition frei), war
|
||||
aber noch auf keiner Hardware. Ungeprüft sind damit: die Pinbelegungen, die
|
||||
tatsächliche Bildrate über WLAN und die Sensorerkennung. Erwartet werden die
|
||||
Boards am 14.09.2026.
|
||||
|
||||
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.
|
||||
Reference in new issue
Block a user