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:
christian committed 2026-10-14 18:55:23 +00:00
commit cc9e0786c2
189 files changed
+29792

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
{% extends "dex/base.html" %}
{% load i18n %}
{% block title %}{% trans "Sammlung" %} — Vogelalbum{% endblock %}
{% block content %}
<h2>{% trans "Die Sammlung" %}
<span class="count">
{% blocktrans with a=found_count b=total_count %}{{ a }} von {{ b }} Arten{% endblocktrans %}{% if habitat_label %} · {{ habitat_label }}{% endif %}
</span>
</h2>
{% comment %}
Lebensraum ist die Hauptachse und bleibt sichtbar. Seltenheit und Gruppe
sind Verfeinerungen und gehören in Auswahlfelder — 27 Typen als Pillen
waren auf dem Handy zwölf Zeilen, bevor der erste Vogel kam.
{% endcomment %}
<div class="filterleiste">
{% if habitate %}
<div class="filtergruppe">
<span class="filterlabel">{% trans "Lebensraum" %}</span>
<div class="pills">
{% for h in habitate %}
<a href="?lebensraum={{ h.code }}" {% if habitat == h.code %}class="on"{% endif %}>{{ h.label }} <b>{{ h.n }}</b></a>
{% endfor %}
<a href="?lebensraum=alle" {% if not habitat %}class="on"{% endif %}>{% trans "Alle" %} <b>{{ gesamt_alle }}</b></a>
</div>
</div>
{% endif %}
<form class="filtergruppe verfeinern" method="get" action="{% url 'dex' %}">
<input type="hidden" name="lebensraum" value="{{ habitat|default:'alle' }}">
<label>
<span class="filterlabel">{% trans "Stufe" %}</span>
<select name="seltenheit" onchange="this.form.submit()">
<option value="">{% trans "Alle Stufen" %}</option>
{% for value, label in rarities %}
<option value="{{ value }}" {% if rarity_filter == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<label>
<span class="filterlabel">{% trans "Gruppe" %}</span>
<select name="typ" onchange="this.form.submit()">
<option value="">{% trans "Alle Gruppen" %}</option>
{% for t in typen %}
<option value="{{ t.code }}" {% if typ_filter == t.code %}selected{% endif %}>{{ t.label }} ({{ t.n }})</option>
{% endfor %}
</select>
</label>
<noscript><button type="submit">{% trans "Anwenden" %}</button></noscript>
{% if rarity_filter or typ_filter %}
<a class="zuruecksetzen" href="?lebensraum={{ habitat|default:'alle' }}">{% trans "Zurücksetzen" %}</a>
{% endif %}
</form>
</div>
{% if cards %}
<div class="grid">
{% for card in cards %}
{% with s=card.species %}
<a class="card {% if not card.found %}locked{% endif %}"
href="{% url 'species' s.slug %}"
style="{{ s.typ_vars }}"
{% if not card.found %}title="{% trans "Noch nicht gesichtet" %}"{% endif %}>
<span class="nr">{{ s.dex_nr }}</span>
<div class="thumb">
{% if card.found and card.entry.best_sighting and card.entry.best_sighting.kachel_url %}
<img src="{{ card.entry.best_sighting.kachel_url }}" alt="{{ s.name }}" loading="lazy">
{% elif s.reference_image %}
<img src="{{ s.reference_image.url }}" alt="{{ s.name }}" loading="lazy">
{% endif %}
</div>
<div class="body">
<div class="name">{{ s.name }}</div>
<div class="meta">
{% if s.typ %}<span class="pill">{{ s.typ_label }}</span>{% endif %}
{% if card.found %}
<span class="times">×{{ card.entry.count }}</span>
{% else %}
<span class="times">+{{ s.xp }} XP</span>
{% endif %}
</div>
</div>
</a>
{% endwith %}
{% endfor %}
</div>
{% else %}
<div class="empty">{% trans "Keine Art passt zu dieser Auswahl." %}</div>
{% endif %}
{% endblock %}