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
c11fe9ab04
189 files changed
+29806
No files matched your search
@@ -0,0 +1,159 @@
|
||||
{% extends "dex/base.html" %}
|
||||
{% load i18n dex_extras %}
|
||||
|
||||
{% block title %}{% trans "Vogelalbum — Übersicht" %}{% endblock %}
|
||||
|
||||
{% comment %}
|
||||
Der Fortschrittsbalken steht auf allen Seiten oben. Hier nicht: er ist Teil
|
||||
des Blickfangs geworden. Zweimal dieselbe Leiste untereinander sähe nach
|
||||
Versehen aus.
|
||||
{% endcomment %}
|
||||
{% block progress %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% comment %}
|
||||
🔑 Die Startseite begann vorher mit vier Kacheln, von denen an einem ruhigen
|
||||
Tag zwei auf null standen — "0 Besuche heute, 0 Arten heute". Das ist die
|
||||
erste Seite, die ein Fremder sieht, und sie sagte ihm: hier passiert nichts.
|
||||
Jetzt steht oben der letzte Gast in groß. Die Zahlen sind nicht weg, sie
|
||||
stehen dort, wo sie etwas erklären: neben der Überschrift des Abschnitts,
|
||||
zu dem sie gehören.
|
||||
{% endcomment %}
|
||||
<section class="held">
|
||||
<div class="held-text">
|
||||
{% comment %}
|
||||
🔑 Die Überschrift kommt aus ``views.blickfang_titel`` und richtet sich
|
||||
nach dem letzten Besuch. Vorher stand hier fest „Wer war heute im
|
||||
Futterhaus?" — fünf Tage nach dem letzten Gast behauptete die Seite damit
|
||||
etwas, das das Bild daneben widerlegte.
|
||||
{% endcomment %}
|
||||
<h1>{{ blickfang_titel }}</h1>
|
||||
<p>{% trans "Jeder Vogel wird bestimmt, gezählt und gesammelt — aus dem Kamerabild oder vom Handyfoto. Alles auf dem eigenen Server." %}</p>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="level-badge">{{ progress.level }}</div>
|
||||
<div style="flex:1 1 200px; min-width:170px">
|
||||
<div class="rank">{{ progress.rank }}</div>
|
||||
<div class="xp-track" style="margin:8px 0 6px">
|
||||
<div class="xp-fill" style="width: {% widthratio progress.level_progress 1 100 %}%"></div>
|
||||
</div>
|
||||
<div class="sub">{% blocktrans with xp=progress.xp rest=progress.xp_to_next lvl=progress.level|add:1 %}{{ xp }} XP · noch {{ rest }} XP bis Level {{ lvl }}{% endblocktrans %}</div>
|
||||
</div>
|
||||
<div style="text-align:right">
|
||||
<div class="rank">{{ progress.species_seen }} / {{ progress.species_total }}</div>
|
||||
<div class="sub">{% trans "Arten" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with letzte=recent.0 %}
|
||||
{% if letzte %}
|
||||
<a class="held-bild" href="{% url 'species' letzte.effective_species.slug %}">
|
||||
{% if letzte.image %}
|
||||
<img src="{{ letzte.image.url }}" alt="{{ letzte.effective_species.name }}">
|
||||
{% elif letzte.kachel_url %}
|
||||
<img src="{{ letzte.kachel_url }}" alt="{{ letzte.effective_species.name }}">
|
||||
{% endif %}
|
||||
<span class="frisch">{% trans "ZULETZT" %}</span>
|
||||
<div class="schild">
|
||||
<b>{{ letzte.effective_species.name }}</b>
|
||||
<small>{{ letzte.started_at|zeitpunkt }} · {{ letzte.duration_s|floatformat:0 }}s · {% widthratio letzte.confidence 1 100 %}%</small>
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="held-leer">
|
||||
{% trans "Noch kein Gast aufgezeichnet." %}<br>
|
||||
{% trans "Der erste bestimmte Vogel steht hier." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</section>
|
||||
|
||||
{% if new_entries %}
|
||||
<h2>{% trans "Neu in der Sammlung" %} <span class="count">{% trans "letzte 7 Tage" %}</span></h2>
|
||||
<div class="grid">
|
||||
{% for entry in new_entries %}
|
||||
<a class="card" href="{% url 'species' entry.species.slug %}"
|
||||
style="{{ entry.species.typ_vars }}">
|
||||
<span class="nr">{{ entry.species.dex_nr }}</span>
|
||||
<div class="thumb">
|
||||
{% if entry.best_sighting and entry.best_sighting.kachel_url %}
|
||||
<img src="{{ entry.best_sighting.kachel_url }}" alt="{{ entry.species.name }}" loading="lazy">
|
||||
{% elif entry.species.reference_image %}
|
||||
<img src="{{ entry.species.reference_image.url }}" alt="{{ entry.species.name }}" loading="lazy">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="name">{{ entry.species.name }}</div>
|
||||
<div class="meta">
|
||||
{% if entry.species.typ %}<span class="pill">{{ entry.species.typ_label }}</span>{% endif %}
|
||||
<span class="tag-new">{% trans "Neu" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>{% trans "Zuletzt zu Besuch" %}
|
||||
<span class="count">{% blocktrans with n=progress.sightings %}{{ n }} Besuche insgesamt{% endblocktrans %}</span></h2>
|
||||
{% if recent %}
|
||||
{% comment %}
|
||||
⚠ Bildkarten statt Zeilen. Die Zeilen liefen über die ganze Breite, mit dem
|
||||
Namen links und dem Prozentwert rechts — dazwischen auf einem großen
|
||||
Bildschirm tausend Pixel Nichts. Eine Karte ist so breit wie ihr Inhalt.
|
||||
|
||||
Der erste Eintrag fehlt hier mit Absicht: der steht oben im Blickfang.
|
||||
{% endcomment %}
|
||||
<div class="besuche">
|
||||
{% for s in recent|slice:"1:5" %}
|
||||
<a class="besuch" href="{% url 'species' s.effective_species.slug %}">
|
||||
{% if s.kachel_url %}
|
||||
<img src="{{ s.kachel_url }}" alt="" loading="lazy">
|
||||
{% else %}<div class="leer"></div>{% endif %}
|
||||
<div class="f">
|
||||
{% if s.xp_awarded %}<span class="xp">+{{ s.xp_awarded }} XP</span>{% endif %}
|
||||
<b>{{ s.effective_species.name }}</b>
|
||||
<small>
|
||||
{% if s.is_first_of_species %}<span class="tag-new">{% trans "Erstfang" %}</span>
|
||||
{% else %}{{ s.started_at|zeitpunkt }}{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty">
|
||||
{% trans "Noch keine Sichtung aufgezeichnet." %}<br>
|
||||
{% trans "Sobald der Detector einen Vogel erkennt oder das erste Foto eingeliefert ist, erscheint hier der erste Gast." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if top %}
|
||||
<h2>{% trans "Stammgäste" %}</h2>
|
||||
<div class="grid">
|
||||
{% for entry in top %}
|
||||
<a class="card" href="{% url 'species' entry.species.slug %}"
|
||||
style="{{ entry.species.typ_vars }}">
|
||||
<span class="nr">{{ entry.species.dex_nr }}</span>
|
||||
<div class="thumb">
|
||||
{% if entry.best_sighting and entry.best_sighting.kachel_url %}
|
||||
<img src="{{ entry.best_sighting.kachel_url }}" alt="{{ entry.species.name }}" loading="lazy">
|
||||
{% elif entry.species.reference_image %}
|
||||
<img src="{{ entry.species.reference_image.url }}" alt="{{ entry.species.name }}" loading="lazy">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="name">{{ entry.species.name }}</div>
|
||||
<div class="meta">
|
||||
{% if entry.species.typ %}<span class="pill">{{ entry.species.typ_label }}</span>{% endif %}
|
||||
<span class="times">×{{ entry.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user