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.
161 lines
6.7 KiB
Markdown
161 lines
6.7 KiB
Markdown
# Betrieb
|
|
|
|
<sub>[← zurück zum Überblick](../README.md) · [alle Kapitel](README.md)</sub>
|
|
|
|
Live zuschauen, mehrere Kameras, der Detector auf einem eigenen Host und das
|
|
Deployen der Web-App. Also alles, was nach dem ersten erkannten Vogel kommt.
|
|
|
|
## Live zuschauen
|
|
|
|
Eine Kamera liefert **einen** Stream. Wer direkt bei ihr zuschaut, nimmt ihn
|
|
der Erkennung weg — auf einem Pi Zero W ist das keine Einstellung, sondern
|
|
Physik: die Funkstrecke trägt rund 2 Mbit/s, ein zweiter Client sättigt sie.
|
|
|
|
Der Detector liest den Stream aber ohnehin rund um die Uhr. Also verteilt er
|
|
ihn weiter:
|
|
|
|
```bash
|
|
python -m detector watch --source http://kamera:8080/stream --live-port 8131
|
|
```
|
|
|
|
Damit schauen beliebig viele Leute zu, und die Kamera merkt nichts davon — sie
|
|
hat weiterhin genau einen Zuschauer. Solange niemand hinsieht, wird auch nichts
|
|
umkodiert: bei MJPEG-Quellen gehen die JPEG-Bytes unverändert durch, und das
|
|
Kodieren für andere Quellen passiert im HTTP-Thread, einmal je Bild statt
|
|
einmal je Zuschauer. Die Erkennung wird davon nicht langsamer.
|
|
|
|
| Adresse | wozu |
|
|
|---|---|
|
|
| `http://<detector>:8131/live` | MJPEG, für mehrere Zuschauer |
|
|
| `http://<detector>:8131/bild.jpg` | Einzelbild |
|
|
| `http://<detector>:8131/` | Seite mit Livebild |
|
|
|
|
### In der Web-App
|
|
|
|
Eingetragen wird das **im Django-Admin bei der Kamera**: `Live-Adresse` auf
|
|
`http://<detector>:8131/live`, dann `Live-Bild zeigen` ankreuzen. Danach gibt
|
|
es die Seite `/live/` und einen Eintrag in der Navigation. Ohne Freigabe
|
|
existiert beides nicht — kein Link, keine Seite, 404.
|
|
|
|
Das Bild lädt der Browser direkt beim Verteiler; die Web-App reicht nichts
|
|
durch. Sonst hinge an jedem Zuschauer eine offene Verbindung im Webserver.
|
|
|
|
> **Für eine öffentliche Instanz: `VOGELALBUM_LIVE_ERLAUBT=0`.** Dann ist Live
|
|
> nicht einschaltbar, auch nicht mit einem Fehlklick im Admin, und die
|
|
> Admin-Liste zeigt „gesperrt". Ein Livebild aus einem Privatgarten soll nicht
|
|
> an einem Häkchen hängen — für einen Showcase ist die Sammlung ohnehin das
|
|
> Interessantere.
|
|
|
|
Der Verteiler selbst kennt keine Anmeldung. Er gehört ins Heimnetz.
|
|
|
|
## Mehrere Kameras
|
|
|
|
Jede Kamera bekommt einen eigenen Token. Der Token identifiziert sie — die
|
|
Web-App muss nie wissen, wo sie steht, und ein kompromittierter Standort lässt
|
|
sich einzeln stilllegen, ohne die anderen anzufassen.
|
|
|
|
```bash
|
|
manage.py camera add futterhaus --name "Futterhaus Garten" --location "Garten"
|
|
manage.py camera add nistkasten --name "Nistkasten Apfelbaum"
|
|
manage.py camera list
|
|
manage.py camera rotate futterhaus # Token erneuern
|
|
manage.py camera disable nistkasten
|
|
```
|
|
|
|
Der Token wird **genau einmal** ausgegeben; gespeichert ist nur sein SHA-256.
|
|
Bei 256 Bit Entropie braucht es kein Salt, und wer die Datenbank in die Hand
|
|
bekommt, kann damit trotzdem nichts einliefern.
|
|
|
|
Pro Kamera läuft eine eigene `detector watch`-Instanz. Sie können auf
|
|
demselben Host laufen; rechne mit rund 40 % eines Kerns je Kamera bei
|
|
`--classify-every 2` und 4 fps.
|
|
|
|
Die Sammlung ist dabei **gemeinsam**: Findet die Gartenkamera die erste Amsel,
|
|
ist sie im Dex freigeschaltet — die Nistkastenkamera bekommt für dieselbe Art
|
|
keinen zweiten Erstfang. Das ist Absicht, es ist die Sammlung eines Menschen
|
|
und nicht die einer Kamera. Die Sichtungsliste lässt sich trotzdem nach Kamera
|
|
filtern.
|
|
|
|
`manage.py camera list` markiert Kameras, die sich über eine Stunde nicht
|
|
gemeldet haben — der schnellste Weg zu „warum kommt da nichts mehr".
|
|
|
|
## Betrieb
|
|
|
|
### Detector auf einem eigenen Host
|
|
|
|
Ein frischer Debian- oder Ubuntu-Container, dann:
|
|
|
|
```bash
|
|
sudo ./scripts/setup_detector_host.sh
|
|
```
|
|
|
|
Das Skript legt den Dienstbenutzer `vogelalbum` an, klont nach `/opt/vogelalbum`,
|
|
baut das venv, holt die Modelle, schreibt `/etc/vogelalbum.env` und installiert
|
|
die systemd-Unit. Idempotent — ein zweiter Lauf aktualisiert nur.
|
|
|
|
Danach `/etc/vogelalbum.env` ausfüllen und `systemctl enable --now
|
|
vogelalbum-detector`.
|
|
|
|
**Dimensionierung**
|
|
|
|
| | |
|
|
|---|---|
|
|
| CPU | 1 Kern je Kamera (bei `--classify-every 2` und ~4 fps rund 40 % Last) |
|
|
| RAM | 512 MB je Instanz, 1 GB für Luft |
|
|
| Platte | 4 GB fürs System, plus ~150 kB je gespeicherter Sichtung |
|
|
|
|
**Netz:** ausgehend zur Kamera und zur Web-App. **Eingehend nichts.**
|
|
|
|
Achtung, wenn Kamera und Erkennungs-Host in verschiedenen Netzsegmenten
|
|
liegen: Die Route muss stehen. Bei uns hängt die Kamera im WLAN-Segment und
|
|
der Server im LAN — ein Scan des falschen Segments findet sie schlicht nicht.
|
|
|
|
**Zweite Kamera:** eigene `/etc/vogelalbum-<name>.env`, die Unit als
|
|
`vogelalbum-detector@.service` ablegen, dann `systemctl enable --now
|
|
vogelalbum-detector@nistkasten`.
|
|
|
|
## Die Web-App deployen
|
|
|
|
Nur die Web-App wird deployt. Der Detector bleibt im Heimnetz — er braucht
|
|
Sichtverbindung zur Kamera.
|
|
|
|
1. DNS auf den Host zeigen lassen.
|
|
2. Anwendung aus dem Repo anlegen, Build über `docker-compose.yml`.
|
|
⚠️ **Der Eingang ist `medien` auf Port 8080, nicht `web` auf 8000.** Das
|
|
compose startet zwei Dienste: Gunicorn und einen nginx davor, der die
|
|
Vogelfotos vom Volume ausliefert. Zeigt der Reverse Proxy direkt auf
|
|
`web`, lädt die Seite — aber **jede Karte der Sammlung hat ein kaputtes
|
|
Bild**, denn Django liefert unter `DJANGO_DEBUG=0` keine Medien aus.
|
|
Begründung ausführlich in [deploy/nginx-media.conf](../deploy/nginx-media.conf).
|
|
3. Persistentes Volume auf `/data` mounten. **Das ist der Schritt, den man
|
|
vergisst** — ohne ihn löscht jeder Deploy Sammlung und Fotos.
|
|
4. Umgebungsvariablen setzen (siehe [Einstellungen](einstellungen.md)).
|
|
5. Denselben Token auf dem Erkennungs-Host eintragen und `VOGELALBUM_API` auf die
|
|
öffentliche Adresse setzen.
|
|
6. Auf einer öffentlichen Instanz `VOGELALBUM_LIVE_ERLAUBT=0` setzen — steht im
|
|
`docker-compose.yml` schon drin. Ohne die Angabe wäre das Livebild aus dem
|
|
eigenen Garten erlaubt, denn der Standard im Code ist `1`.
|
|
|
|
Der erste Start dauert ein paar Minuten: Der Entrypoint holt einmalig die
|
|
Referenzfotos. Danach kostet der Schritt nichts mehr. `migrate`,
|
|
`collectstatic` und `seed_dex` erledigt er ebenfalls — neue Arten im Katalog
|
|
landen also mit dem nächsten Deploy automatisch in der Datenbank.
|
|
|
|
📘 **Schritt für Schritt mit Coolify, inklusive Umzug einer bestehenden
|
|
Sammlung: [deploy/COOLIFY.md](../deploy/COOLIFY.md).** Dort steht auch, wie
|
|
Datenbank und Fotos als 1:1-Kopie auf das Volume kommen — Kamera-Tokens,
|
|
Handbestimmungen und Abzeichen bleiben dabei erhalten, und die Kameras zu
|
|
Hause liefern mit ihrem bisherigen Token weiter.
|
|
|
|
### Einbinden auf einer anderen Seite
|
|
|
|
`/widget/` ist dafür da — keine Navigation, transparenter Hintergrund:
|
|
|
|
```html
|
|
<iframe src="https://vogelalbum.example.org/widget/"
|
|
style="width:100%;height:110px;border:0" loading="lazy"></iframe>
|
|
```
|
|
|
|
Wer lieber selbst gestaltet, nimmt `/api/stats/` — Level, Artenzahl und die
|
|
letzte Sichtung als JSON.
|