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.
117 lines
4.4 KiB
C
117 lines
4.4 KiB
C
// WLAN: Zugangsdaten im NVS, Verbindung als Station, Einrichtung als
|
|
// Zugangspunkt. Wer die Daten hat, verbindet sich; wer keine hat, macht
|
|
// sein eigenes Netz auf.
|
|
//
|
|
// Uebernommen aus Hey Fred (firmware/main/netz.c) am 23.09.2026. Dort ist
|
|
// es erprobt; hier geaendert wurden der NVS-Bereich und der Name des
|
|
// Einrichtungsnetzes.
|
|
|
|
#pragma once
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "esp_err.h"
|
|
|
|
#define NETZ_SSID_MAX 32
|
|
#define NETZ_PASS_MAX 64
|
|
|
|
// Name des Zugangspunkts zur Einrichtung. ⚠ Anders als bei Fred NICHT
|
|
// fest: Die letzten beiden Bytes der MAC haengen hinten dran, also etwa
|
|
// "Vogelkamera-A4F2". Zwei Kameras im selben Garten -- Futterhaus und
|
|
// Traenke -- wuerden sich sonst denselben Namen teilen, und man wuesste
|
|
// beim Einrichten nicht, welche man gerade vor sich hat.
|
|
#define NETZ_SETUP_PRAEFIX "Vogelkamera"
|
|
|
|
// Der tatsaechliche Name, erst zur Laufzeit bekannt.
|
|
const char *netz_setup_ssid(void);
|
|
|
|
// Das Geheimnis fuer Updates ueber WLAN.
|
|
//
|
|
// ⚠ Bewusst NICHT einkompiliert (so macht es Hey Fred in secrets.h). Fuer
|
|
// ein Geraet, das als Bausatz weitergegeben wird, waere das derselbe
|
|
// Fehler wie eingebackene WLAN-Daten: Jeder muesste die Firmware selbst
|
|
// uebersetzen, und alle Geraete haetten dasselbe Geheimnis.
|
|
//
|
|
// Stattdessen: Beim ersten Start wuerfelt sich jedes Geraet sein eigenes
|
|
// und legt es im NVS ab. Zu sehen bekommt man es im Einrichtungsnetz --
|
|
// wer dort steht, steht ohnehin vor dem Geraet.
|
|
const char *netz_ota_token(void);
|
|
|
|
typedef struct {
|
|
char ssid[NETZ_SSID_MAX + 1];
|
|
int8_t rssi;
|
|
bool verschluesselt;
|
|
} netz_fund_t;
|
|
|
|
// Einmal beim Start aufrufen, vor allem anderen.
|
|
void netz_init(void);
|
|
|
|
// Liegen Zugangsdaten im NVS?
|
|
bool netz_zugang_vorhanden(void);
|
|
|
|
// Der gespeicherte Netzname, oder "" wenn keiner da ist. Fuer die Anzeige
|
|
// auf der Einrichtungsseite: Erst damit sieht man, ob das Speichern
|
|
// ueberhaupt ankommt -- ohne Kabel, ohne Raten.
|
|
const char *netz_gespeicherte_ssid(void);
|
|
|
|
// Gibt false zurueck, wenn das Schreiben scheitert -- der Grund steht dann
|
|
// in *fehler. ⚠ Frueher brach das Geraet bei einem Fehler einfach ab
|
|
// (ESP_ERROR_CHECK): Es startete mitten im Absenden neu, der Browser blieb
|
|
// haengen, nichts war gespeichert, und niemand erfuhr warum.
|
|
bool netz_zugang_speichern(const char *ssid, const char *passwort,
|
|
esp_err_t *fehler);
|
|
void netz_zugang_loeschen(void);
|
|
|
|
// Verbindet mit den gespeicherten Daten. Blockiert bis zum Erfolg oder bis
|
|
// die Versuche aufgebraucht sind. Gibt false zurück, wenn es nicht klappt —
|
|
// dann gehört Fred in den Einrichtungsmodus.
|
|
bool netz_verbinden(void);
|
|
|
|
// Öffnet den Zugangspunkt für die Einrichtung. Danach ist Fred unter
|
|
// 192.168.4.1 erreichbar.
|
|
void netz_setup_ap_start(void);
|
|
|
|
// Sucht Netze in der Umgebung. Schreibt bis `max` Funde nach `ziel` und gibt
|
|
// die Anzahl zurück. Doppelte SSIDs werden übersprungen, der stärkste
|
|
// Fund gewinnt.
|
|
int netz_suchen(netz_fund_t *ziel, int max);
|
|
|
|
// Sucht EINMAL und merkt sich das Ergebnis.
|
|
//
|
|
// ⚠ Der Grund ist unangenehm und kostet sonst Stunden: Eine WLAN-Suche
|
|
// wechselt den Funkkanal. Laeuft gleichzeitig der eigene Zugangspunkt,
|
|
// fliegen die verbundenen Geraete dabei raus. Sucht man also, waehrend
|
|
// jemand das Formular betrachtet, verliert genau dieser Jemand die
|
|
// Verbindung -- und sein Absenden kommt nie an. Am 23.09.2026 genau so
|
|
// passiert, an Handy wie an Mac.
|
|
//
|
|
// Deshalb: einmal suchen, bevor der erste Besucher da ist, und danach
|
|
// nur noch die Liste ausliefern.
|
|
int netz_funde_holen(netz_fund_t *ziel, int max);
|
|
void netz_funde_auffrischen(void);
|
|
|
|
// IP als Zeichenkette, leer wenn nicht verbunden.
|
|
const char *netz_ip(void);
|
|
|
|
// Wie oft die Verbindung seit dem Start abgerissen ist, und warum zuletzt.
|
|
// Der Grund ist der Code des WLAN-Treibers: 15 und 204 stehen für ein
|
|
// falsches Passwort, 201 für „Netz nicht gefunden", 2 bis 4 zeigen auf
|
|
// Verschlüsselung und Management-Frames.
|
|
int netz_trennungen(void);
|
|
int netz_letzter_grund(void);
|
|
|
|
// Läuft die Verbindung gerade?
|
|
bool netz_verbunden(void);
|
|
|
|
// Holt die Uhrzeit per SNTP und stellt sie auf deutsche Zeit. Blockiert
|
|
// nicht — das Ergebnis trudelt im Hintergrund ein.
|
|
void zeit_holen(void);
|
|
|
|
// Steht die Uhr schon auf einer plausiblen Zeit?
|
|
bool zeit_steht(void);
|
|
|
|
// Empfangsstaerke zum Zugangspunkt in dBm (negativ; naeher an null ist
|
|
// besser). 0 heisst: nicht ermittelbar.
|
|
int netz_feldstaerke(void);
|