diff --git a/AGENTS.md b/AGENTS.md
index 29aceab..e937663 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,8 +7,10 @@ Gewünscht sind ein kräftiger Schreckeffekt, Kamera, Weboberfläche und lokal
gespeicherte Bilder/Videoclips. Der Nutzer hat nun eine erste Pi-Software mit
Liveansicht und simuliertem Effektablauf beauftragt; das allgemeine
Uploadskript und die Blender-Konzeptvorschau wurden ebenfalls freigegeben.
-Hardwarebau und Anschluss echter Aktoren folgen nach Klärung der Mechanik,
-Versorgung und Sicherheit im Plan.
+Die beiden Kameraservos sind inzwischen laut Nutzer verdrahtet; Farbpunktmodus
+und manuelle Einrichtung sind implementiert. Hardwarebau/Anschluss der
+Grabaktoren folgen nach Klärung von Mechanik, Versorgung und Sicherheit.
+Aktueller Stand und offene Pi-Prüfungen stehen am Anfang von `PLAN.md`.
## Arbeitsweise
@@ -68,25 +70,63 @@ Versorgung und Sicherheit im Plan.
Pi-Pinübersicht `RASPI_PINS.svg`; Änderungen an betroffenen Darstellungen
synchron pflegen und in `PLAN.md` erfassen. Geplant: vorhandener PCA9685 am
Pi 5, CH0=PAN (unten), CH1=TILT (oben), VCC=3,3 V, eigene vorläufige
- 5-V-Servoversorgung. Platinenfoto und Reihenbeschriftung dokumentiert; Netzteil,
- Servosteckerpolung, Kameraport und reale Verdrahtung noch ungeprüft. 6-V-Handversorgung nicht auf dieselbe
+ 5-V-Servoversorgung. Platinenfoto und Reihenbeschriftung dokumentiert.
+ Verkabelungsfotos in `VERDRAHTUNG.md`: sichtbare Anordnung plausibel,
+ Netzteildaten, elektrische Prüfung, Kameraport und vollständiger Funktionstest offen.
+ 6-V-Handversorgung nicht auf dieselbe
V+-Schiene legen. Nutzer meldet Verdrahtung fertig; aktuell Powerbank für
Kameraservos, Modell/Stromreserve und reale Funktion noch ungeprüft.
- Neuer Farbpunkt-Test (`halloween/follow.py`): lokale HSV-Farbwahl per
flüchtigem Beispielfoto oder Rot/Grün, Vorschau ohne Hardware; echte Ausgabe
nur bewusst und außerhalb der automatischen Auslösung am PCA9685 CH0/CH1.
- 1500-µs-Startstellung vorab entkoppelt prüfen, enge konfigurierbare Grenzen,
- langsame Nachführung. Globaler Stopp/Reset beendet PWM. Heartbeat-/Bild-/
- Auswertungs- und Zielverlust beenden die Sitzung; kein automatischer Neustart.
+ Startstellung je Achse aus (Bewegungsminimum + Bewegungsmaximum)/2, halbe µs
+ aufrunden; vorab entkoppelt prüfen. Enge konfigurierbare Grenzen,
+ einstellbare Nachführung 40–800 µs/s, neuer Standard 400, alte Dateien ohne
+ Tempo behalten 80. Follow-Zeitschritt maximal 100 ms; manuelle Einrichtung
+ bleibt langsam. Pi-Kamera-Zielrate 15 Bilder/s, Demo ca. 5; gemessene Bildrate,
+ Auswertungsrate und Farberkennungsdauer im UI, reale Pi-Leistung noch prüfen.
+ „Alles stoppen“ beendet PWM. Heartbeat-/Bild-/
+ Auswertungsverlust beenden die Sitzung; kein automatischer Neustart.
+ Optionale persistierte Farbpunktsuche (Default aus): nur echte Nachführung,
+ erst nach erkanntem Ziel und 1,5 s Verlust. Einmalige Schwenks auf aktueller
+ Höhe und an beiden TILT-Grenzen, nur Follow-Endpunkte, max. min(Tempo,200) µs/s.
+ Nach einem Durchlauf/60 s an letzter Position auf denselben Farbpunkt warten;
+ zwei aufeinanderfolgende Erkennungen übernehmen Nachführung. Erste Erkennung
+ hält Suchbewegung an. Ohne Suche/Ersterkennung ebenfalls warten, PWM hält
+ Position. Farbe bleibt dauerhaft bis neuer Farbwahl gespeichert. Bild-/Heartbeatwatchdogs gelten weiter.
Farb-/Pulsparameter bleiben in data/follow.json; keine Bilder/Trainingsdaten.
Noch keine unabhängige OE-/Hardwareabschaltung; I²C-Ausfall oder harter
Prozessabbruch kann PWM stehen lassen. Nur beaufsichtigter Test, echte
Servos und Versorgung noch zu prüfen. Details in SOFTWARE.md.
+- WebUI getrennt: `/` oder `/grab` für Grab/Ereignisse/Profile, `/kamera` für
+ Farbpunkt/Endpunkte mit sofortigem Livebild. Eigenständige Bedienelemente und
+ Poller, ein gemeinsamer Kameraproduzent. Grab-Stopp/-Reset wirkt nur auf Grab,
+ Kamerastopp nur auf Follow; „Alles stoppen“ auf beide. Farbvorschau parallel
+ erlaubt, echte Kamerabewegung bei aktiver Grabautomatik weiterhin gesperrt.
+ Kamera-Start/-Stopp verändert keinen gespeicherten Grabmodus/Controllerstopp.
+- WebUI nutzt breite Desktopansicht: Bild links, Bedienung rechts; schmal
+ untereinander. Kamera-Endpunkte/manuelle Einrichtung und Detailparameter
+ aufklappbar, Status/Start/Stopp unmittelbar sichtbar. Keine Änderung an
+ Servo-/Speicherverträgen durch Layoutänderungen.
- Manueller Kamera-Einrichtmodus: Richtungstasten halten statt automatischer
Anschlagfahrt; beide Achsen vor Anschlag stoppen. 40 µs/s, 0,35-s-Bewegungslease,
Loslassen hält Position, Stopp beendet PWM. Kein Farbzielsuchen; Grenzen aus
Sollwerten mit 10 µs Innenabstand speichern. Keine Endschalter/Strommessung;
- absolute Testgrenzen 1100–1900 µs bleiben. Kein realer Anschlagtest bestätigt.
+ Einrichtbereich pro Achse gestoppt im WebUI einstellbar, bisherige Werte
+ 1100–1900 bleiben als Standard. Technische Eingabehülle 500–2500 µs ist
+ kein bestätigter Servoweg; nur schrittweise erweitern. Nachführgrenzen liegen
+ darin; Minimum darf Maximum nicht überschreiten. Kein realer Anschlagtest bestätigt.
+ Startmitte je Sitzung als festen Bezug für Grenzspeicherung halten; erst
+ beim nächsten bewussten Start neu berechnen. Speichern bewegt keine Servos.
+ „In Mittelstellung fahren“ nur in aktiver Einrichtsitzung: beide Achsen
+ höchstens 40 µs/s zur aktuellen Endpunktmitte, ohne Grenzen zu speichern.
+ Stopp/Richtungstaste bricht ab, Kamera-/Heartbeat-Watchdog bleibt aktiv.
+ Endpunkte in vier Richtungs-Karten mit Serverwert und Speicherkandidat zeigen;
+ zusätzliche Bewegungs-/Einricht-Min-/Max-Eingabe mit gemeinsamem
+ Speichern/Verwerfen nur gestoppt; alte Konfiguration erhält für die
+ Einrichtung 1100–1900, bestehende Endpunkte bleiben erhalten.
+ Entwurf und Serverwerte deutlich trennen. Start speichert nichts. Farbe/Richtung
+ nur separat und gestoppt speichern; Speicherfehler direkt im Einrichtbereich.
- Aktueller Nutzerumfang: zwei Antriebe, einer für die Hand und einer
für die Atemrotation. Nutzer sieht nun 230 V mit Relais für Rotation
vor; CHANCS 60KTYZ (Amazon B07WXFWYBZ, 220–240 V, ca. 2,5 U/min)
diff --git a/PCA9685_VERKABELT_FOTO.jpg b/PCA9685_VERKABELT_FOTO.jpg
new file mode 100644
index 0000000..10afc02
Binary files /dev/null and b/PCA9685_VERKABELT_FOTO.jpg differ
diff --git a/PLAN.md b/PLAN.md
index 25da93d..2e26ff3 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -1,4 +1,43 @@
-# Halloween – Projektregeln
+# Halloween – Arbeitsplan
+
+
+## Aktueller Stand · 16. Oktober 2026
+
+- Implementiert: getrennte Grab-/Kameraseiten, Farbpunktwahl, manuelle
+ Servoeinrichtung, vier Endpunktkarten sowie numerische Bewegungs- und
+ Einrichtgrenzen pro Achse. Bedienung in [SOFTWARE.md](SOFTWARE.md).
+- Aktueller Zielverlustvertrag: Farbe dauerhaft gespeichert; Hardware-Sitzung
+ wartet bei Punktverlust weiter. Optionale Suche nach 1,5 s, nach einem
+ Durchlauf/60 s ohne Suchbewegung warten, bei zwei Funden weiter folgen.
+ Stopp/Fehler/Neustart beenden weiterhin die Freigabe.
+- Einrichtbereich standardmäßig 1100–1900 µs, im WebUI gestoppt innerhalb
+ 500–2500 µs einstellbar. Nachführgrenzen liegen darin. Startmitte je Achse = (Minimum + Maximum)/2.
+ Technische Eingabehülle ist keine Bestätigung des mechanischen Fahrwegs.
+- Zuletzt lokal geprüft: 104 Python-Tests (89 bestanden, 15 optionale
+ übersprungen) und drei JS-Prüfungen. Optionale begrenzte Farbpunktsuche
+ implementiert; echter Suchtest und Upload stehen noch aus.
+- Pi-API am 16. Oktober: Kamera 15,0 Bilder/s, Nachführung inaktiv, Tempo noch
+ 80 µs/s. Aktuelle Grenzen PAN 588–2448, TILT 715–1620; nur gelesen.
+ SSH-Batchzugang weiterhin abgelehnt; kein Deployment durch diese Prüfung.
+- Farberkennung beschleunigt (JPEG-Reduktion, Pillow-Maske, 10-ms-Takt): lokal
+ ca. 11,6 → 2,2 ms, gleiche Ergebnisse; Pi-Messung und Upload offen.
+- Nutzer meldet Pendeln bei ruhigem Punkt (Tempo 800). Gegenmaßnahme: Verstärkung
+ auf 600 µs/s je Bildanteil begrenzt; Wirkung am Pi noch zu prüfen.
+- Nutzer meldet Zielverlust: Screenshot zeigt blauen Punkt auf bläulichem Papier
+ (Papier Hue 152/S 80–100, Punkt 168/157); Toleranz 18 ließ das Papier als zweite
+ Fläche mitzählen → kein Ziel. `locate()` nutzt jetzt Stufen streng→locker;
+ lokal mit dem Screenshot und Regressionstest geprüft, Pi-Test offen.
+- Nutzerstand: echtes Kameralivebild bestätigt, Aufbau laut Nutzer verdrahtet,
+ separate Powerbank; Nutzer erprobt die Servoeinrichtung. Keine vollständig
+ protokollierten realen Endpunkte oder bestätigte Fehlerabschaltung vorhanden.
+- Offen: letzte Version auf den Pi übertragen, Achsrichtungen/Mittelstellung,
+ Endpunkte, Versorgung und Stoppverhalten am Aufbau prüfen und dokumentieren.
+- Breites responsives Layout lokal umgesetzt: Bild/Steuerung nebeneinander,
+ Details aufklappbar. UI-Prüfungen siehe Abschnitt vom 16. Oktober unten.
+- Anschlussplan Revision 14; Softwareänderungen erfordern keine neue Verdrahtung.
+
+Die folgenden datierten Abschnitte dokumentieren auch frühere Zwischenstände.
+Bei abweichenden Grenzen oder Bedienabläufen gilt der aktuelle Stand oben.
## Verdrahtungsplan vom 15. Oktober 2026
@@ -396,3 +435,249 @@ Bildsequenzen eines Blender-Schnittprojekts bei Weitergabe mitnehmen.
Loslassen/Stopp bei echtem Aufbau prüfen. Beide Achsen vor Anschlag stoppen.
- Abschlussprüfung lokal: 87 Python-Tests, davon 72 bestanden, 15 optionale
OpenCV-/Modelltests übersprungen. Bisherige JS-Tests und JS-Syntaxprüfung bestanden.
+
+
+## Sichtprüfung der Verkabelung vom 15. Oktober 2026
+
+- Nutzerfoto ohne angeschlossene Servoversorgung als PCA9685_VERKABELT_FOTO.jpg
+ abgelegt und in VERDRAHTUNG.md Revision 7 verlinkt. Sichtbare Servosteckrichtung
+ und Anordnung der Steuerstecker erscheinen passend zum Plan.
+- Nicht bestätigt: Pi-seitige Pins (nicht vollständig im Foto), genaue
+ Kanalnummern/Zuordnung zu PAN/TILT, elektrische Durchgängigkeit,
+ Versorgungspolung und echte Funktion. Kein Einschalt- oder Bewegungstest.
+
+- Ergänzendes Pi-Foto RASPI_VERKABELT_FOTO.jpg in VERDRAHTUNG.md Revision 8:
+ sichtbare Anordnung passt zu Grün=1, Blau=3, W03=5 (violett wirkend), Grau=6.
+ Frühere Lücke der Pi-Sichtprüfung ergänzt; keine elektrische Messung oder
+ Bestätigung der Versorgungspolung/Servofunktion daraus ableiten.
+
+
+## Getrennte Grab und Kameraseiten vom 15. Oktober 2026
+
+- Erledigt: `/` und `/grab` mit Grabsteuerung, Ereignissen und Profilen;
+ `/kamera` mit Livebild, Farbpunkt und Endpunkteinrichtung. Jede Seite hat
+ eigene Bedienelemente und Poller; gemeinsamer Kameraproduzent bleibt erhalten.
+- Erledigt: Vorschau verändert den Grabzustand nicht und darf parallel zur
+ Automatik laufen. Nur echte Kamerabewegung bleibt bei aktiver Automatik
+ gesperrt. Keine automatische Umschaltung gespeicherter Einstellungen.
+- Erledigt: Grab-Stopp/-Reset wirkt nur auf Grab; Kamerastopp nur auf Kamera.
+ Explizites „Alles stoppen“ bleibt auf beiden Seiten, alte globale APIs bleiben
+ für bestehende Clients erhalten. Livebild auf Kameraseite auch ohne Sitzung.
+- Lokal geprüft: Seiteninhalt/Pollertrennung, unabhängige API-Aktionen,
+ Hardware-Interlock und globale Abschaltung mit künstlichem Treiber.
+- Offen am Pi: Upload, Navigation und getrennte Bedienung am Gerät prüfen;
+ gemeinsame Kamera erfordert nach Bewegung neue Bereichs-/Linienkontrolle.
+- Abschluss lokal: 89 Python-Tests, 74 bestanden, 15 optionale OpenCV-/
+ Modelltests übersprungen; beide bestehenden JS-UI-Tests und Syntaxprüfung
+ beider Seiten bestanden. Beide Seiten und Navigation im Demo-Browser
+ visuell geprüft; sofortiges Kameralivebild bestätigt. Kein Pi-Upload erfolgt.
+
+
+## Verständliche Endpunktspeicherung vom 15. Oktober 2026
+
+- Befund aus Nutzer-Screenshot: eingegebene 500/2500 µs widersprechen den
+ gespeicherten 1400–1600 µs. Oberer Speichern-Knopf wird während aktiver
+ Einrichtung abgelehnt; Fehleranzeige war vom Bedienort entfernt.
+- Erledigt: editierbare Grenzfelder durch vier Richtungs-Karten mit tatsächlichem
+ Serverwert, Kandidat (10 µs nach innen) und eindeutigem Speicherknopf ersetzt.
+ Unpassende Seite/Mittelstellung sperrt den Knopf mit Hinweis. Bestätigung und
+ Fehler im Einrichtbereich; gespeicherte Werte bleiben dauerhaft sichtbar.
+- Erledigt: Start speichert keine Formularwerte; Farb-/Richtungsspeicherung
+ übernimmt keine Grenzwerte aus alten Feldern. Aktive Einstellungen/Startknöpfe
+ gesperrt. Veraltete Statusantworten nach Speicherung verworfen.
+- Damaliger Zwischenstand: feste Testgrenzen 1100–1900 µs; durch den
+ späteren einstellbaren Einrichtbereich unten abgelöst.
+- Lokal geprüft: Endpoint-UI-Regressionen und Backendtests; Browserdemo mit
+ Links-Bewegung und bestätigtem Speichern auf 1460 µs. Dies ist ein künstlicher
+ Testwert, keine Endposition der echten Halterung. Upload und Pi-Prüfung offen.
+- Abschluss: 89 Python-Tests, davon 74 bestanden und 15 optionale OpenCV-/
+ Modelltests übersprungen. Drei JS-UI-Prüfungen inklusive neuer Endpunkt-
+ Regression und JS-Syntaxprüfung bestanden. Zweispaltiges Kartenlayout und
+ persistente Werte nach Demo-Neustart im Browser geprüft.
+
+
+## Manuelle Grenzfelder wiederhergestellt am 15. Oktober 2026
+
+- Auf Nutzerwunsch: sichtbare PAN-/TILT-Min-/Max-Felder zusätzlich zu den
+ Endpunktkarten. Eigener Speichern-/Verwerfen-Knopf; nur gestoppt editierbar.
+- Entwurf und gespeicherte Serverwerte getrennt anzeigen, ungespeicherte Werte
+ nicht durch Polling überschreiben; Start verlangt Speichern oder Verwerfen.
+ Separate Speicherung erhält Farbe/Drehrichtung. Damals noch feste Grenzen
+ 1100–1900 µs; inzwischen durch den einstellbaren Einrichtbereich abgelöst.
+- Lokale JS-Regression für korrekte Werte, getrennte Speicherung, ungültige
+ Grenzen und Startblockade ergänzt. Echte Pi-Prüfung nach Upload offen.
+
+
+## Einstellbarer Einrichtbereich am 15. Oktober 2026
+
+- Nutzerkorrektur: feste 1100–1900 µs reichen zum Ermitteln realer Endpunkte
+ möglicherweise nicht. Jetzt pro Achse separate Einricht-Min-/Max-Felder,
+ zusammen mit Nachführgrenzen nur gestoppt explizit speichern/verwerfen.
+- Jog begrenzt auf gespeicherten Einrichtbereich, Nachführung auf Endpunkte.
+ Technische Hülle 500–2500 µs, keine Aussage über mechanisch erlaubte Winkel.
+ Bestehende Konfigurationen behalten 1100–1900 und ihre bisherigen Endpunkte.
+- Lokal geprüft: Migration, Persistenz, aktive Änderung abgelehnt, erweitertes
+ Jog/Begrenzen und Endpunktspeichern, getrennte Followgrenzen; JS-Formtests.
+- Offen: Upload, Anzeige und beaufsichtigtes Einrichten am echten Pi.
+
+- Abschlussprüfung lokal: 90 Python-Tests (75 bestanden, 15 optionale übersprungen),
+ drei JS-Prüfungen bestanden; Browserdemo bestätigt erweiterten Einrichtwert
+ und Erhalt nach Neuladen sowie Verwerfen. Keine echte Servobewegung getestet.
+
+
+## Dokumentationsabgleich am 15. Oktober 2026
+
+- README, Bedienanleitung, Uploadhinweise, Anschlussplan und Projekt-/Modulregeln
+ auf getrennte Seiten und einstellbare Einrichtgrenzen abgeglichen.
+- Frühere feste Grenzen im Verlauf als abgelöste Zwischenstände gekennzeichnet.
+ Lokale Prüfung, Nutzerbeobachtungen und offene Pi-Tests getrennt dargestellt.
+- Nur Dokumentation geändert. Verifiziert: 24 lokale Links einschließlich
+ Überschriftenzielen, Revision 12 in Plan/Zeichnung, gültige SVG-Dateien,
+ AGENTS.md unter 400 Zeilen und git diff --check ohne Fehler.
+ Keine erneute Hardware-/Softwareprüfung erforderlich.
+
+
+## Startmitte aus gespeicherten Endpunkten · 15. Oktober 2026
+
+- Bewusster Start von Einrichtung/Nachführung berechnet PAN/TILT getrennt aus
+ gespeicherten Bewegungsgrenzen, halbe µs aufgerundet. Dienststart bleibt
+ inaktiv. Startwerte werden im WebUI angezeigt; kein automatischer Resume.
+- Bereiche müssen nicht mehr 1500 enthalten, aber geordnet im Einrichtbereich
+ liegen. Speicherknöpfe beziehen sich auf feste Startmitte der Sitzung;
+ Speichern bewegt nichts, nächster Start verwendet die neuen Endpunkte.
+- Dokumentation und Anschlussplan Revision 13 nachgeführt. Upload und echte
+ Zentrierfahrt am Pi offen; keine Hardware im lokalen Test angesteuert.
+
+- Verifiziert lokal: 92 Python-Tests (77 bestanden, 15 optionale übersprungen),
+ drei JS-Prüfungen bestanden. Browserdemo startet bei PAN 1450/TILT 1600 µs
+ aus gespeicherten 1200–1700/1400–1800; keine reale Servoausgabe.
+
+
+## Mittelstellungsbutton · 15. Oktober 2026
+
+- Im aktiven Einrichtmodus „In Mittelstellung fahren“: beide Achsen bis 40 µs/s
+ zur Mitte der zuletzt gespeicherten Bewegungsgrenzen. Kein neuer Hardwarestart
+ oder Umschalten aus der Nachführung durch diesen Button. Keine Endpunktänderung.
+- „Bewegung anhalten“/Richtungstaste bricht ab; alte Sequenzen können keine
+ spätere Stoppentscheidung aufheben. Watchdogs und Kamerastopp bleiben aktiv.
+- Dokumentation und Anschlussplan Revision 14 aktualisiert. Upload und echte
+ Fahrt am Pi weiterhin offen; lokale Prüfung mit künstlichem Treiber/Demo.
+
+- Verifiziert: 95 Python-Tests (80 bestanden, 15 optionale übersprungen),
+ drei JS-Prüfungen und JS-Syntaxprüfung bestanden. Browserdemo: PAN von 1450
+ auf 1508 verfahren und per Mittelstellungsbutton auf 1450 zurückgeführt,
+ TILT blieb bei 1600. Anschließend Kamerastopp geprüft, keine echte Hardware.
+
+## FPS- und Nachführungsdiagnose · 15. Oktober 2026
+
+- Nur lesend am laufenden Pi über Heimnetz geprüft; keine Bewegung, kein Start,
+ kein Deployment und keine Änderung der Laufzeiteinstellungen.
+- MJPEG-Messung über 15,09 s: 71 empfangene Bilder, 4,72 Bilder/s zwischen
+ erstem und letztem Bild. Median Bildabstand 201,4 ms, größter Abstand 310,9 ms.
+ Gemessen ist die ausgelieferte Bildrate über WLAN, nicht die Sensorbildrate.
+- API meldet echte Pi-Kamera ohne Fehler, Detektionsauswertung ca. 5/s.
+ Nachführung inaktiv; letzter Fehler „Farbpunkt verloren; erneut starten“.
+- Vom Pi gelieferte camera.html ist bytegleich zur lokalen Datei. SSH ohne
+ interaktive Anmeldung abgelehnt; Python-Dateien auf dem Pi nicht abgeglichen.
+- Lokaler Code: camera.py wartet nach jedem Bild 0,2 s; stream.mjpg zusätzlich
+ 0,1 s zwischen Prüfungen. Kamera-Follow-Canvas wird über Statuspolling mit
+ mindestens 0,3 s Abstand erneuert (höchstens ca. 3,3 Anzeigen/s).
+- follow.py wertet nur neue Kamerabilder aus. Nachführung: normierter
+ Bildfehler × 160 µs/s, höchstens ca. 80 µs/s, Totzone ±6 % der Bildbreite/-höhe.
+ Einrichtung/Mittelstellung bleiben absichtlich auf 40 µs/s begrenzt.
+- Live gespeicherte Bewegungsbereiche: PAN 588–2448 µs, TILT 715–1620 µs.
+ Falls der laufende Python-Code dem lokalen entspricht, dauert bereits die
+ Sollwertänderung über den ganzen Bereich selbst bei durchgehend maximalem
+ Tempo mindestens ca. 23,3 s bzw. 11,3 s. Keine gemessene mechanische Fahrzeit.
+- Ergebnis: geringe Auslieferungs-/Anzeigebildrate und sehr langsame
+ Sollwertnachführung sind konkrete Softwarebremsen. Die reale Servogeschwindigkeit,
+ Versorgung unter Last und tatsächliche Nachführleistung bleiben ungeprüft.
+- Möglicher nächster Schritt: höhere kontrollierte Kamerabildrate und einstellbare
+ Nachführgeschwindigkeit, anschließend beaufsichtigter Vergleich mit echten
+ Bewegungen. Bestehende Endpunkte, langsames Einrichten und Watchdogs erhalten.
+
+## Schnellere Farbnachführung · 15. Oktober 2026
+
+- Nutzer meldet schnelle Grundstellungsfahrt und wünscht vergleichbar zügige
+ Nachführung. Das bestätigt keine gemessene mechanische Geschwindigkeit.
+- Lokal umgesetzt: Nachführtempo 40–800 µs/s, Standard bei neuer Konfiguration
+ 400. Vorhandene Dateien ohne diesen Wert behalten 80 und alle Endpunkte;
+ nach Upload zum Test ausdrücklich 400 im WebUI speichern. Nah am Bildzentrum
+ proportional langsamer, Totzone und Endpunkte bleiben erhalten.
+- Pi-Kamera-Zielrate 15 Bilder/s bei 640×480, keine gespeicherte Altbild-Queue;
+ bisherige feste 200-ms-Pause ersetzt. MJPEG-Prüfung alle 20 ms, Follow-UI
+ 100 ms nach Antwort. Demo bleibt ca. 5 Bilder/s. Gemessene Producer-FPS,
+ Follow-Auswertungsrate und Farberkennungsdauer jetzt sichtbar.
+- Bei verzögerten Bildern höchstens 100 ms Sollwertänderung nachholen.
+ Einrichtung/Mittelstellungsbutton weiter 40 µs/s, Stopp-/Watchdog-Regeln
+ unverändert. Kein zusätzlicher Hardwarestart oder automatisches Wiederanlaufen.
+- Verifiziert: 98 Python-Tests, davon 83 bestanden und 15 optionale übersprungen;
+ drei JS-Prüfungen und git diff --check bestanden. Regressionen prüfen Tempo
+ bei 15/20 Hz, Bildlücken, Altdateien und Endpunkterhalt.
+- Lokale Browserdemo: Tempo 500 und anschließend 400 gespeichert, Endpunkte
+ unverändert. Vorschau ca. 4,8 Auswertungen/s, Producer ca. 4,9 Bilder/s,
+ Farberkennung rund 18–20 ms auf Entwicklungsrechner. Stopp und Sperrung der
+ Eingaben während Vorschau geprüft. Screenshot-Aufnahme scheiterte zweimal
+ am Browser-Timeout; sichtbare Zustände über Browser-Zugänglichkeitsbaum geprüft.
+- README, SOFTWARE, Modul-/Testanweisungen und AGENTS aktualisiert.
+- Offen: Upload, echte Pi-Bildrate/Last und Nachführung mit bewegtem Farbpunkt.
+ Kein Deployment und keine echte Servobewegung durch diese Änderung.
+
+## Begrenzte Farbpunktsuche · 16. Oktober 2026
+
+- Nutzer beauftragt Suchbewegung innerhalb gespeicherter Endpunkte.
+- Lokal umgesetzt: gespeicherte Option (anfangs aus), erst nach erkanntem
+ Farbpunkt und 0,5 Sekunden Verlust; zunächst letzte Bildrichtung, dann
+ Schwenks auf aktueller Höhe und beiden TILT-Grenzen. Grenzen bleiben erhalten.
+ Maximal min(Nachführtempo,200) µs/s, ein Durchlauf oder 60 Sekunden; ohne Fund
+ Sitzung/PWM beenden. Zwei aufeinanderfolgende Funde beenden Suche; erster
+ Fund hält Suchfahrt an. Preview/Setup suchen nicht. Kein automatischer Start.
+- Verifiziert: 102 Python-Tests (87 bestanden, 15 übersprungen), drei JS-Tests.
+ Grenzen, umgekehrte Achsen, Zeitlimit, Durchlaufende, Wiedererkennung, erneuter
+ Verlust, gesperrte Achse, Stopp und Kamera-/Heartbeat-/Auswertungsfehler simuliert.
+- Live nur gelesen: 15,0 Producer-Bilder/s, Suchoption noch nicht auf Pi,
+ Nachführtempo 80, Achsumkehr jeweils aus. Keine Servos angesteuert.
+- Offen: Upload per Nutzeranmeldung, Suchoption aktivieren und realen Punkt
+ erst erkennen lassen, dann aus dem Bild führen. Tatsächliche Suchabdeckung,
+ Wiedererkennung, Richtung und Geschwindigkeit am Aufbau verifizieren.
+- Browserdemo verifiziert: Suchoption eingeschaltet, gespeichert und nach
+ Neuladen weiterhin aktiv; Endpunkte unverändert. Screenshot unter
+ `/tmp/halloween-suchoption.png` (lokale Demo, keine echte Bewegung).
+ JS-Syntax und git diff --check ebenfalls bestanden.
+
+## Übersichtliche WebUI · 16. Oktober 2026
+
+- Auf Nutzerwunsch breite Ansicht bis 1800 px, ab 1000 px zwei Spalten.
+ Kamera: Bild links, Status/Start/Stopp rechts, Detailparameter aufklappbar.
+ Endpunkte/manuelle Einrichtung gemeinsamer Klappbereich, darin Zahlenwerte
+ und Richtungstasten nebeneinander. Grabseite entsprechend verbreitert;
+ Auslöseeinstellungen, Ereignisse und Profile aufklappbar.
+- Lesbare Schrift/Bedienelemente beibehalten; Desktopkopf mit Alles-Stopp
+ bleibt sichtbar. Mobil normale einspaltige Seite ohne seitlichen Überlauf.
+ IDs, JavaScript-Abläufe, API, Servo- und Speicherlogik unverändert.
+- Verifiziert: zwei Seitentests, drei bestehende JS-Tests, JS-Syntax, eindeutige
+ HTML-IDs und git diff --check. Kein neuer Volltest der unveränderten Steuerung.
+ Lokale Browserdemo bei normaler Desktopgröße, 1920×1080 und 390×844:
+ Bild/Bedienung nebeneinander bzw. untereinander, kein horizontaler Überlauf
+ auf beiden schmalen Seiten, Ausklappen erreichbar. Farbvorschau gestartet
+ und gestoppt; Demo-Einrichtung PAN 1500→1534 und Mittelstellung→1500 geprüft.
+ Keine echten Aktoren angesteuert, kein Pi-Upload.
+- Dokumentation/Anweisungen aktualisiert. Upload und Kontrolle auf Nutzer-Pi
+ noch offen; vorhandene Endpunkte bleiben erhalten.
+
+## Farbpunkt bei Bildverlust behalten · 16. Oktober 2026
+
+- Ursache: Farbe war bereits gespeichert; bisheriger 1-s-Zielverluststopp bzw.
+ Suchende beendete jedoch die Servositzung. Nutzer wünscht dauerhaftes Behalten.
+- Geändert: Ohne Fund aktive gesunde Sitzung an letzter Position warten lassen;
+ keine neuen Bewegungsbefehle, PWM hält Position. Nach zwei Funden wieder folgen.
+ Suchbeginn von 0,5 auf 1,5 s verzögert; ein Durchlauf/60 s endet im Wartezustand
+ statt Sitzungsabbruch. Keine endlosen Suchrunden. UI benennt Wartezustand.
+- Farbe bleibt wie bisher atomar in follow.json bis neuer Farbwahl, auch nach
+ Stopp/Neustart. Kein gespeichertes Foto oder eindeutige Objektidentität.
+ Nach explizitem Stopp, Fehler oder Neustart weiterhin bewusst neu starten.
+- Verifiziert: 104 Python-Tests (89 bestanden, 15 optionale übersprungen), drei
+ JS-Tests. Unter anderem drei Minuten simulierte Punktabwesenheit, kein
+ wiederholtes Suchen, Wiederaufnahme, Farbe nach Neukonstruktion sowie
+ bestehende Stopp-/Watchdog-Regeln. JS-Syntax und git diff --check bestanden.
+- Nur lokale Umsetzung; Upload und echter Wiedererkennungstest noch offen.
diff --git a/RASPI_VERKABELT_FOTO.jpg b/RASPI_VERKABELT_FOTO.jpg
new file mode 100644
index 0000000..cff6499
Binary files /dev/null and b/RASPI_VERKABELT_FOTO.jpg differ
diff --git a/README.md b/README.md
index 5aab64c..631071f 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,48 @@ GPIO-Steuerung der Grabaktoren, Besucher-Wiedererkennung und Clip-Aufnahmen blei
Der separate [Farbpunkt-Test](SOFTWARE.md#kamera-folgt-einem-farbpunkt) kann nach
bewusstem Start die beiden Kameraservos am PCA9685 bewegen; echte Hardwareprüfung steht aus.
+Stand der Dokumentation: **16. Oktober 2026**. Die neue Grenzeneinstellung ist
+lokal einschließlich Browserdemo geprüft; Upload und Prüfung dieser Version
+an der echten Halterung stehen noch aus.
+
+Die WebUI hat zwei Seiten: **Grab** (`/grab`, auch Startseite `/`) und
+**Kamera / Finger** (`/kamera`), mit eigener Bedienung und gemeinsamem Livebild.
+Auf breiten Bildschirmen stehen Bild und Steuerung nebeneinander. Detailwerte
+sind aufklappbar; Endpunkte und Mittelstellung findest du unter
+**Endpunkte & manuelle Einrichtung**. Auf kleinen Bildschirmen wird einspaltig angezeigt.
+Grab-Stopp und Kamerastopp wirken auf ihren jeweiligen Bereich; **Alles stoppen**
+beendet beide. Farbvorschau und Grabautomatik können parallel laufen; echte
+Kamerabewegung erfordert ausgeschaltete Grabautomatik.
+
+Auf **Kamera / Finger** lässt sich ein Farbpunkt im Beispielfoto auswählen.
+Der Name bezeichnet keine automatische Erkennung eines unmarkierten Fingers.
+Für die Servoeinrichtung ist kein Farbpunkt nötig: Richtungstaste halten,
+vor dem Anschlag loslassen und die Position als Links/Rechts/Oben/Unten speichern.
+**In Mittelstellung fahren** bringt beide Achsen im Einrichtmodus langsam zurück
+zur gespeicherten Mitte; **Bewegung anhalten** bricht die Fahrt ab.
+**Nachführtempo** ist separat einstellbar (40–800 µs/s). Als ersten schnelleren
+Test 400 bei gestoppten Servos speichern; alte Konfigurationen behalten zunächst
+80. Neue Kamera-Zielrate: 15 Bilder/s, tatsächliche Bild-/Auswertungsrate steht
+über der Vorschau. Die Pi-API meldet am 16. Oktober 15 Bilder/s ohne aktive Nachführung.
+Die optionale **Suche bei Punktverlust** ist lokal ergänzt: nach erster
+Erkennung nur innerhalb der gespeicherten Bewegungsgrenzen suchen, bei Fund
+weiter folgen; ohne Fund nach einem Durchlauf/spätestens 60 Sekunden anhalten
+und auf denselben Punkt warten. Die gewählte Farbe bleibt dauerhaft gespeichert.
+Unter **Farbe, Drehrichtung, Tempo und Suche einstellen** ausdrücklich aktivieren
+und speichern. Upload und echter Suchtest stehen noch aus.
+
+Die [Grenzeneinstellung](SOFTWARE.md#eindeutige-speicherung-der-endpositionen)
+bietet zwei Bereiche pro Achse:
+
+- **Bewegungsgrenzen** begrenzen die spätere Farbnachführung.
+- **Einrichtbereich** begrenzt die Richtungstasten beim Ermitteln der Endpunkte.
+ Er lässt sich bei gestoppten Servos schrittweise erweitern und speichern.
+
+Standard: Nachführung 1400–1600 µs, Einrichtung 1100–1900 µs. Die technische
+Eingabehülle 500–2500 µs ist kein bestätigter mechanischer Fahrweg. Beim bewussten Start fährt jede Achse
+in die Mitte ihrer gespeicherten Bewegungsgrenzen: (Minimum + Maximum) / 2. Bestehende Einstellungen bleiben beim Update
+erhalten; eine Kamerasitzung startet immer bewusst.
+
## Lokal ohne Hardware starten
```bash
@@ -29,12 +71,13 @@ manuelle Start für direkten Heimnetzzugriff steht in [SOFTWARE.md](SOFTWARE.md)
## Dokumentation
-- [Verdrahtung von Kamera und Schwenkarm](VERDRAHTUNG.md) · [Anschlusszeichnung](VERDRAHTUNG.svg)
+- [Verdrahtung von Kamera und Schwenkarm](VERDRAHTUNG.md) · [Anschlusszeichnung](VERDRAHTUNG.svg) · [Große Pi-Pinübersicht](RASPI_PINS.svg)
- [Software, Pi-Einrichtung und lokale Prüfungen](SOFTWARE.md)
- [Projektdateien auf den Pi übertragen](UPLOAD.md)
- [Arbeitsplan, Hardwarestand und offene Entscheidungen](PLAN.md)
- [Blender-Konzeptvorschau](preview/README.md)
- [Projektregeln](AGENTS.md)
+- Modulanweisungen: [Software](halloween/instructions.md), [Tests](tests/instructions.md), [Werkzeuge](tools/instructions.md), [Vorschau](preview/instructions.md)
Code, Planung und bearbeitbare Blender-Szenen werden versioniert.
Laufzeitdaten unter `data/`, `.venv`, Referenzclips und gerenderte
diff --git a/SOFTWARE.md b/SOFTWARE.md
index 15d6a2d..952359a 100644
--- a/SOFTWARE.md
+++ b/SOFTWARE.md
@@ -27,8 +27,9 @@ python3 -m venv --system-site-packages .venv
Picamera2 wird über die Raspberry-Pi-Pakete installiert; die virtuelle
Umgebung kann dieses Systempaket durch `--system-site-packages` verwenden.
-Der Server bindet nur an `127.0.0.1` auf dem Pi. So ist das Livebild nicht
-direkt im WLAN sichtbar.
+Beim Start über `run.py` bindet der Server nur an `127.0.0.1` auf dem Pi.
+Der versionierte Dienst nutzt dagegen die beim Upload gewählte Bind-Adresse
+(siehe unten); mit `0.0.0.0` ist die WebUI direkt im Heimnetz erreichbar.
### WebUI beim Hochfahren starten
@@ -238,7 +239,7 @@ ein manueller Testbutton umgeht keine Fehlersperre.
Kamera-/Modellfehler und mehr als zwei Sekunden ohne gültige Auswertung
sperren die aktivierte Automatik und stoppen den simulierten Ablauf. Nach
-Beheben des Fehlers „Nach Stopp zurücksetzen“ verwenden und Bereich
+Beheben des Fehlers „Grab zurücksetzen“ verwenden und Bereich
freihalten. Modellprobleme bei deaktivierter Automatik verhindern keine
manuellen Tests. Der Demo-Livefeed und manuelle Tests brauchen weder OpenCV
noch Modell; der Bewegungs-Test benötigt auch im Demo-Modus OpenCV. Synthetische
@@ -303,7 +304,7 @@ Sperrzeit und erneut mindestens zwei Sekunden ruhiges Bild. Ein ruhiges Bild
beweist **keinen menschenfreien Bereich**. Stopp, Fehler-Watchdog, bewusstes
Rücksetzen und erneute Freigabe nach Start/Konfigurationswechsel bleiben wirksam.
Bei bereits gesetzter Fehlersperre nach Auswahl und Speichern bewusst
-„Nach Stopp zurücksetzen“ drücken.
+„Grab zurücksetzen“ drücken.
Dies ist ein **Bewegungs-Test für die Simulation**, keine Personenerkennung
für den späteren Gartenbetrieb. Auch Kamera-/Hintergrundbewegung kann auslösen.
@@ -343,8 +344,8 @@ die Linien-/API-/Installer-Tests benötigen weder Modell noch OpenCV.
## Kamera folgt einem Farbpunkt
-In der WebUI gibt es einen eigenen Abschnitt **Kamera folgt einem Farbpunkt ·
-Testmodus**. Er verfolgt einen deutlich gefärbten Aufkleber oder Punkt, auch
+Auf der Seite **Kamera / Finger** (`/kamera`) gibt es den Abschnitt
+**Kamera folgt einem Farbpunkt · Testmodus**. Er verfolgt einen deutlich gefärbten Aufkleber oder Punkt, auch
am Finger. Eine nackte Hand beziehungsweise ein Finger wird nicht als solcher
erkannt. Die Farbe wird lokal mit Pillow ausgewertet, ohne Modelltraining,
OpenCV oder Internet. NoIR-Farben, IR-Licht und wechselnder Weißabgleich können
@@ -372,8 +373,10 @@ Vor Updates/Neustarts die Servoversorgung abziehen.
### Farbe auswählen und ohne Bewegung prüfen
-1. **Automatisch auslösen** ausschalten und **Linie und Bereich speichern**
- drücken. Farbtest und automatische Auslösung schließen sich gegenseitig aus.
+1. Oben **Kamera / Finger** öffnen. Das Livebild erscheint sofort. Für eine
+ Farbvorschau muss die Grabautomatik nicht ausgeschaltet werden. Nur vor
+ echter Servobewegung auf **Grab** die automatische Auslösung ausschalten
+ und **Linie und Bereich speichern** drücken.
2. **Grün wählen** oder **Rot wählen** drücken. Alternativ **Beispielfoto
aufnehmen** und im eingefrorenen Foto auf die Mitte des Farbpunkts tippen.
Ein ausreichend heller und gesättigter Farbton ist nötig. Das Foto und die
@@ -388,7 +391,12 @@ Vor Updates/Neustarts die Servoversorgung abziehen.
### Erste echte Bewegung
-Der Start der echten Nachführung setzt beide Servos sofort auf **1500 µs**.
+Der bewusste Start der echten Nachführung oder des Einrichtmodus setzt jede
+Achse auf **(gespeichertes Minimum + gespeichertes Maximum) / 2** ihrer
+Bewegungsgrenzen. Halbe Mikrosekunden werden aufgerundet. Beispiel: PAN
+1000–1800 → 1400 µs, TILT 1300–1800 → 1550 µs. Die WebUI zeigt die Werte unter
+**Beim nächsten Start**. Ohne angepasste Grenzen ergibt sich weiterhin 1500 µs.
+Beim Hochfahren des Pi wird keine Kamerasitzung gestartet.
Das kann aus einer unbekannten bisherigen Stellung eine größere Bewegung
sein. Für die erste Zentrierung Servohörner/Gestänge lösen, lose Teile sichern
und freie Mittelstellung prüfen. Danach spannungsfrei montieren. Die anfänglichen
@@ -397,25 +405,96 @@ Halterung bestätigten Winkel. Das Kamerakabel darf nicht unter Zug geraten.
Nach Prüfung den Hinweis zum freien Bewegungsraum bestätigen und **Echte
Kameranachführung starten** drücken. PAN- und TILT-Pulsbreiten werden angezeigt.
-Bewegt sich eine Achse vom Punkt weg, stoppen, unter **Servogrenzen und Richtung
+Bewegt sich eine Achse vom Punkt weg, stoppen, unter **Farbe, Drehrichtung, Tempo und Suche
einstellen** die betreffende Richtung umkehren und speichern. Grenzen pro Achse
-schrittweise ermitteln; absolut erlaubt sind 1100–1900 µs mit 1500 innerhalb
-des Bereichs. Gleiche Minimum-/Maximumwerte von 1500 sperren eine Achse auf die
-Mittelstellung. Diese Werte sind Pulsbreiten, keine garantierten Winkel.
+schrittweise ermitteln; die Nachführgrenzen müssen im gespeicherten
+Einrichtbereich liegen; Minimum darf nicht größer als Maximum sein. Gleiche
+Minimum-/Maximumwerte sperren eine Achse bei der Nachführung auf diesen Wert. Diese Werte sind Pulsbreiten, keine garantierten Winkel.
+
+Die Regelung hat eine Totzone um die Bildmitte. **Nachführtempo** stellt die
+maximale Pulsänderung zwischen 40 und 800 µs/s ein; nahe der Mitte wird langsamer
+nachgeführt. Zum ersten schnelleren Test: Kameraservos stoppen, **400** eintragen,
+**Farbe, Richtung, Tempo und Suche speichern**, anschließend bewusst neu starten.
+400 ist der Standard für neue Konfigurationen. Alte Einstellungen behalten beim
+Update zunächst 80; Endpunkte bleiben erhalten. Die Regelverstärkung ist auf 600 µs/s je Bildbreite/-höhe begrenzt (Bildverzögerung
+lässt höhere Werte pendeln); das Tempo wirkt als Obergrenze, mehr als ca. 300 µs/s
+wird dadurch bei Zielen nahe der Bildmitte/-rand nicht mehr erreicht. Bei Pendeln Tempo reduzieren.
+Einrichtung und Mittelstellungsbutton bleiben auf 40 µs/s begrenzt. Der bewusste
+Sitzungsstart gibt hingegen unmittelbar die gespeicherte Mitte als Sollwert aus;
+dessen reale Servogeschwindigkeit wurde nicht gemessen.
+
+Die Pi-Kamera zielt jetzt auf **15 Bilder/s** bei 640×480, die synthetische Demo
+bleibt bei etwa 5. Die Konfiguration nutzt Picamera2-`FrameDurationLimits`
+([Herstellerhandbuch](https://datasheets.raspberrypi.com/camera/picamera2-manual.pdf)).
+Über dem Bild stehen gemessene Kamerabilder/s, Nachführ-Auswertungen/s und die
+Dauer der Farberkennung. Das sind Softwaremessungen, keine CPU-Auslastung oder
+Bestätigung der realen mechanischen Geschwindigkeit. Die Vorschau fragt etwa
+alle 100 ms plus Antwort-/Darstellungszeit ab; die Regelung läuft unabhängig davon.
+Bei verzögerten Bildern werden maximal 100 ms Bewegung nachgeholt. Ob der Pi
+15 Bilder/s unter jeder Last erreicht, bleibt zu prüfen. Am 16. Oktober meldet
+seine Status-API 15,0 Bilder/s bei inaktiver Nachführung.
+
+### Übersichtliche Bedienung
+
+Die Seiten nutzen bis zu 1800 Pixel Breite. Ab 1000 Pixel Bildschirmbreite
+stehen Kamerabild und Bedienung nebeneinander; darunter untereinander.
+Auf der Kameraseite sind Status, Vorschau, echte Nachführung und Kamerastopp
+rechts direkt sichtbar. **Farbe, Drehrichtung, Tempo und Suche einstellen**
+öffnet die Detailparameter. **Endpunkte & manuelle Einrichtung** unter den
+beiden Hauptbereichen öffnet Grenzwerte, Richtungstasten und Mittelstellung.
+Am Desktop bleibt **Alles stoppen** im Seitenkopf beim Scrollen erreichbar.
+
+Auf der Grabseite sind **Automatische Auslösung einstellen**, **Gespeicherte
+Ereignisse** und **Personenprofile** aufklappbar. Die Auswertungs- und
+Betriebsstatus bleiben sichtbar. Auf-/Zuklappen speichert keine Einstellungen
+und startet oder stoppt keine Sitzung.
+
+### Farbpunkt nach Verlust suchen
+
+Unter **Farbe, Drehrichtung, Tempo und Suche einstellen** die Option
+**Bei Punktverlust automatisch suchen** aktivieren und **Farbe, Richtung,
+Tempo und Suche speichern** drücken. Danach den Punkt vor die Kamera halten
+und **Echte Kameranachführung starten**. Vorschau bewegt keine Servos.
+Die gewählte Farbe bleibt in `data/follow.json` gespeichert, bis du bewusst
+Rot, Grün oder einen neuen Punkt im Beispielfoto wählst; auch ein Neustart
+löscht sie nicht. Gespeichert wird die Farbe, keine eindeutige Objektidentität.
+Die Suchoption ist zunächst aus, bleibt nach dem Speichern erhalten und
+verändert keine Endpunkte. Auch mit gespeicherter Option startet die Sitzung nie selbst.
+
+Nach erster eindeutiger Erkennung hält die Kamera bei Punktverlust kurz still.
+Nach 1,5 Sekunden beginnt sie in der zuletzt gesehenen Richtung zu suchen,
+unter Berücksichtigung der Achsumkehr. Sie schwenkt einmal auf der aktuellen
+Höhe und an den beiden gespeicherten TILT-Grenzen hin und her. Nur gespeicherte
+**Bewegungsgrenzen** gelten, niemals der größere Einrichtbereich. Suchtempo:
+höchstens 200 µs/s beziehungsweise das kleinere eingestellte Nachführtempo.
+Die tatsächliche Sichtfeldabdeckung hängt von Optik und Mechanik ab; der
+Suchweg garantiert keine lückenlose Erfassung des gesamten Bereichs.
+
+Bei einem wieder sichtbaren Punkt hält die Suche sofort an. Zwei aufeinander-
+folgende eindeutige Erkennungen übernehmen wieder die normale Nachführung.
+Ohne Fund endet nur die Suchfahrt nach einem Durchlauf oder spätestens
+60 Sekunden. Die Kamera wartet danach unbegrenzt auf die gespeicherte Farbe,
+solange Kamerabild und Bedienverbindung funktionieren. Die Servos halten ihre
+letzte Position; es werden keine weiteren Suchrunden gestartet. Sobald der
+Punkt in zwei aufeinanderfolgenden Bildern wieder eindeutig erkannt wird,
+folgt die Kamera weiter. Gesperrte Achsen bleiben unverändert.
+**Kameraservos stoppen**, **Alles stoppen** und die bisherigen Bild-/Verbindungs-
+und Auswertungswatchdogs brechen auch die Suche ab. Ohne aktivierte Suche oder
+wenn seit Sitzungsstart noch kein Punkt erkannt wurde, wartet die Kamera
+an ihrer Position ebenfalls weiter. Im manuellen Einrichtmodus
+findet keine Farbpunktsuche statt.
-Die Regelung hat eine Totzone um die Bildmitte und ändert die Pulse um maximal
-80 µs/s. Bei fehlendem oder uneindeutigem Ziel wird nicht weiter nachgeführt;
-nach einer Sekunde wird die Hardware-Sitzung beendet. Es gibt keine Suchfahrt.
Sehr schnelle Bewegung oder ein enger eingestellter Bereich kann dazu führen,
dass der Punkt aus dem Bild läuft. Der Modus ersetzt keine zuverlässige
Personen- oder Fingererkennung.
### Stopp und Fehler
-**Kameraservos stoppen**, der allgemeine **Stopp** und **Nach Stopp
-zurücksetzen** beenden die Nachführung und setzen CH0/CH1 auf PWM aus.
-Der Grabablauf bleibt nach dem Farbtest bis zum bewussten Zurücksetzen
-angehalten. Schließen/Neuladen des Browserfensters oder ein Wechsel in den
+**Kameraservos stoppen** und **Alles stoppen** beenden die Nachführung und
+setzen CH0/CH1 auf PWM aus. **Grab stoppen** und **Grab zurücksetzen** wirken
+nur auf den Grabablauf. Die Kamera verändert dessen Modus und Stoppzustand
+nicht. Die Farbvorschau darf parallel zur Grabautomatik laufen; nur echte
+Kamerabewegung bleibt bei eingeschalteter Grabautomatik gesperrt. Schließen/Neuladen des Browserfensters oder ein Wechsel in den
Hintergrund beendet die Sitzung über Stopp beziehungsweise den Heartbeat:
nach 1,5 Sekunden ohne die startende Bedienseite wird abgeschaltet.
Die Nachführung benötigt neue Kamerabilder und neue Auswertungen innerhalb
@@ -448,9 +527,9 @@ Technische Referenzen: [Pillow HSV](https://pillow.readthedocs.io/en/stable/hand
## Endpunkte der Kamerahalterung manuell einrichten
Im Farbpunkt-Abschnitt **Endpunkte einrichten · manuell** öffnen. Die
-Servomittelstellung muss wie oben beschrieben vorab geprüft sein. Die
-Automatik bleibt ausgeschaltet. Bewegungsraum bestätigen und **Einrichtmodus
-starten** drücken. Der Start setzt wieder beide Achsen auf 1500 µs. Im Demo-
+Servomittelstellung muss wie oben beschrieben vorab geprüft sein. Für echte Bewegung muss die
+Grabautomatik ausgeschaltet sein. Bewegungsraum bestätigen und **Einrichtmodus
+starten** drücken. Der Start fährt jede Achse in die Mitte ihrer gespeicherten Bewegungsgrenzen. Im Demo-
Betrieb heißt der Knopf **Einrichtmodus als Demo starten** und bewegt nur
virtuelle Werte; es gibt keinen I²C-Zugriff.
@@ -462,17 +541,31 @@ virtuelle Werte; es gibt keinen I²C-Zugriff.
Tastensignal endet die Verstellung nach 0,35 s; bei Verbindungsverlust gilt
zusätzlich die bisherige Abschaltung der ganzen Sitzung. Die Position wird
nach dem Loslassen weiter per PWM gehalten, bis **Kameraservos stoppen**
- beziehungsweise der allgemeine Stopp die Ausgabe beendet.
-- **Links-Grenze speichern**, **Rechts-Grenze speichern**, **Oben-Grenze
- speichern** oder **Unten-Grenze speichern** übernimmt den zuletzt
- angeforderten Wert mit 10 µs Abstand nach innen. Die gespeicherten Grenzen
- sind unter den Tasten dauerhaft sichtbar und gelten für die Farbnachführung.
+ beziehungsweise **Alles stoppen** die Ausgabe beendet.
+- **In Mittelstellung fahren** ist im aktiven Einrichtmodus verfügbar. Einmal
+ klicken: beide Achsen fahren mit maximal 40 µs/s zur Mitte ihrer aktuell
+ gespeicherten Bewegungsgrenzen. Die Endpunkte bleiben unverändert. Kein
+ Gedrückthalten nötig; Kamera und Bedienverbindung müssen verfügbar bleiben.
+ **Bewegung anhalten** bricht die Fahrt ab und hält die erreichte Sollposition.
+ Eine Richtungstaste übernimmt ebenfalls die Bedienung. Kamerastopp beendet PWM.
+ Während der Fahrt sind Endpunktspeicherknöpfe gesperrt. Zum Zentrieren aus der
+ Farbnachführung zuerst Kameraservos stoppen und Einrichtmodus starten.
+- Unter den Richtungstasten zeigt jede der vier Karten **Links**, **Rechts**,
+ **Oben** und **Unten** den tatsächlich gespeicherten Wert. **Diese Position
+ als … speichern** übernimmt den aktuellen Sollwert dieser Achse mit 10 µs
+ Abstand nach innen. Der vorgesehene neue Wert steht schon vor dem Speichern
+ dabei; die Bestätigung nennt danach Richtung und gespeicherten Wert.
+- Ein Speicherknopf ist erst freigegeben, wenn die Achse ausreichend in die
+ zugehörige Richtung von der Mittelstellung gefahren ist. Bei falscher Seite
+ steht direkt an der Karte, was zu tun ist. Fehler beim Speichern erscheinen
+ im Einrichtbereich. Die Bestätigung bleibt sichtbar, bis eine neue Aktion
+ sie ersetzt. Die Grenzen gelten für die Farbnachführung.
Richtungsumkehr wird bei Bewegung und Speicherung gleich berücksichtigt.
- Der Einrichtmodus darf die bisher gespeicherten Farbtest-Grenzen bewusst
- überschreiten; die absoluten Softwaregrenzen bleiben **1100–1900 µs**.
- Ist damit nicht genug Weg verfügbar, stoppen und Mechanik/Servodaten klären,
- nicht die Halterung gewaltsam verstellen. Die Grenzen müssen weiterhin die
- 1500-µs-Startposition einschließen.
+ überschreiten; er hält sich an den **Einrichtbereich** pro Achse. Dieser
+ beginnt bei 1100–1900 µs und kann gestoppt über die WebUI geändert werden.
+ Nur schrittweise erweitern, vor dem mechanischen Anschlag loslassen. Die
+ Nachführgrenzen müssen innerhalb des Einrichtbereichs liegen.
Es gibt keine automatische Anschlagfahrt und keine Endschalterauswertung.
Angezeigte Werte sind **Sollwerte**, keine gemessene Position. Ohne Encoder,
@@ -482,7 +575,71 @@ Sicherheitsgarantie. Die Einrichtung braucht keinen Farbpunkt; frische Kamera,
Sitzungs-Heartbeat und Abschaltregeln bleiben erforderlich.
API-Ergänzung: `start` nimmt `operation=setup` an; `jog` benötigt den privaten
-Sitzungsschlüssel, eine Richtung und eine steigende Sequenznummer. Verspätete
+Sitzungsschlüssel, eine Richtung und eine steigende Sequenznummer. `center`
+beauftragt die Mittelstellungsfahrt nur in einer bestehenden Einrichtsitzung
+und verwendet dieselbe Sequenzfolge wie `jog`. Verspätete
Tastenbefehle dürfen einen neueren Loslassbefehl nicht überholen. `remember`
speichert eine Grenze erst nach beendeter Bewegung. Einstellungen und
Verdrahtung sind weiterhin im selben Plan dokumentiert.
+
+
+## Getrennte Seiten für Grab und Kamera
+
+- **Grab** unter `/` oder `/grab`: Livebild mit Erkennungsbereich, Betriebsmodus,
+ Auslösung, Ereignisse und Profile. Die Knöpfe **Grab stoppen** und **Grab
+ zurücksetzen** beeinflussen nur diesen Bereich.
+- **Kamera / Finger** unter `/kamera`: eigenes Livebild, Farbwahl,
+ Nachführung und manuelle Endpunkte. Farbvorschau und Einrichtung haben
+ getrennte Startknöpfe; beides gehört zur Kamerasitzung.
+- Oben auf beiden Seiten: Navigation und **Alles stoppen** für Grab und
+ Kameraservo-PWM gemeinsam. Die Stromversorgung wird damit nicht getrennt.
+
+Kamera und Grab speichern ihre bisherigen Einstellungen weiterhin getrennt.
+Die Seiten laden nur ihre eigenen Abfragen und Bedienelemente. Beide verwenden
+physisch dieselbe Kamera: Farbvorschau parallel ist möglich, echte Bewegung
+bei aktivierter Grabautomatik wird abgelehnt, ohne den Grabzustand umzustellen.
+Nach einer Kamerabewegung vor erneutem Aktivieren der Automatik Bildausschnitt,
+Linie und Bereich prüfen.
+
+Beim Verlassen der Kameraseite endet die Kamerasitzung über Stopp/Heartbeat,
+spätestens nach Ausbleiben der Bedienverbindung gemäß den bisherigen Regeln.
+Navigation startet keine Servos und ändert keinen gespeicherten Grabmodus.
+Die alten API-Aufrufe `/api/stop` und `/api/reset` bleiben als globale
+Abschaltung für ältere Clients bestehen; neue Grabknöpfe verwenden
+`/api/grave/stop` beziehungsweise `/api/grave/reset`.
+
+
+## Eindeutige Speicherung der Endpositionen
+
+Zusätzlich zu den vier Endpunktkarten gibt es auf **Kamera / Finger** den
+sichtbaren Bereich **Bewegungsgrenzen manuell eingeben** mit PAN Minimum,
+PAN Maximum, TILT Minimum und TILT Maximum. Nur bei gestoppter Kamerasitzung
+bearbeiten, dann **Bewegungs- und Einrichtgrenzen speichern** drücken. Darunter stehen
+immer die tatsächlichen Serverwerte; geänderte Eingaben werden ausdrücklich
+als ungespeichert markiert und bleiben beim Aktualisieren der Anzeige erhalten.
+**Eingaben verwerfen** stellt die gespeicherten Werte wieder her.
+
+Die manuelle Grenzspeicherung verändert Farbe/Drehrichtung nicht. Deren
+separater Speichern-Knopf verändert umgekehrt keine Grenzen. Ein Start speichert
+nichts; offene Grenzänderungen müssen zuerst gespeichert oder verworfen werden.
+Die Endpunktkarten und ihre Speicherung im aktiven Einrichtmodus bleiben
+zusätzlich erhalten. Manuelle Zahlenfelder sind während einer Sitzung gesperrt.
+Unter **Einrichtbereich zum Anfahren der Endpunkte** stehen zusätzlich PAN/TILT
+Einrichtung Minimum und Maximum. Diese Werte begrenzen die Richtungstasten,
+auch wenn die bisherigen Nachführgrenzen enger sind. Bei zu wenig Weg: stoppen,
+den betreffenden Einrichtwert in kleinen Schritten erweitern, speichern und
+Einrichtmodus erneut starten. Der Start berechnet die Mitte aus den gespeicherten Endpunkten neu. Dann die reale
+gewünschte Endposition langsam anfahren, loslassen und in der Richtungskarte
+speichern. Das übernimmt den Sollwert mit 10 µs Innenabstand für die Nachführung.
+
+Technische Eingabehülle: **500–2500 µs**, kein bestätigter mechanischer Bereich
+für diese Servos/Halterung. Die Nachführgrenzen müssen im Einrichtbereich liegen;
+bei einer direkten Zahleneingabe gegebenenfalls beide zusammen ändern. Speichern
+löst keine Bewegung aus. Alte Konfigurationen behalten alle Endpunkte und den
+bisherigen Einrichtbereich 1100–1900 µs; keine automatische Erweiterung.
+
+
+Die Startmitte einer Einrichtsitzung bleibt während dieser Sitzung der Bezug
+für die Links-/Rechts-/Oben-/Unten-Speicherknöpfe, auch nach dem Speichern eines
+Endpunkts. Speichern löst keine Zentrierfahrt aus. Erst beim nächsten bewussten
+Start wird die Mitte aus den dann gespeicherten Endpunkten neu berechnet.
diff --git a/UPLOAD.md b/UPLOAD.md
index a22c5af..74a70ae 100644
--- a/UPLOAD.md
+++ b/UPLOAD.md
@@ -4,7 +4,7 @@
bei jedem angewendeten Update den systemd-Autostart der WebUI ein. Ohne
`--apply` zeigt es nur eine lokale Vorschau und verbindet sich nicht zum Pi.
-## Auf dem Laptop ausführen
+## Auf dem Laptop im Projektordner ausführen
Das folgende Beispiel verwendet den vom Nutzer bestätigten SSH-Benutzer,
Hostnamen und Projektordner. Der absolute Home-Pfad muss auf dem Pi stimmen:
@@ -102,13 +102,28 @@ der Rückfall gelungen ist. Ein unvollständiger Rückfall wird ausdrücklich ge
Abrupter Stromverlust/SIGKILL während des Updates ist nicht automatisch abgedeckt.
`data/` wird niemals vom Laptop übertragen: Datenbank, Bilder, Modelle,
-Modus und Erkennungseinstellungen bleiben auf dem Pi. `.git`, `.venv`,
+Modus und Erkennungseinstellungen sowie `follow.json` mit Farbe, Richtungen,
+Nachführ- und Einrichtgrenzen bleiben auf dem Pi. `.git`, `.venv`,
Vorschauen, Referenzen, `.env`, Schlüssel, Versionsordner und Sicherungen werden
ebenfalls ausgeschlossen. Eigene Dateien außerhalb von `data/` bleiben im
Basisordner, werden aber nicht automatisch in die neue Version übernommen.
Das Skript löscht keine alten Pi-Dateien. Systempakete werden nicht geändert.
Nach Start gelten die bisherigen Freigabe-/Resetbedingungen der Effektsteuerung.
+
+## Nach einem WebUI-Update
+
+Vor dem Update die Kameraservos stoppen und ihre separate Versorgung abziehen.
+Nach erfolgreichem Upload die Browserseite mit **Strg+F5** neu laden. Auf der
+Kameraseite `/kamera` stehen die Bewegungs- und Einrichtgrenzen unter dem Bild.
+`halloween/camera.html` wird zusammen mit dem übrigen Projekt übertragen;
+es ist kein zusätzlicher Einzeldatei-Upload nötig.
+
+Beim Laden alter `follow.json`-Dateien bleiben die Nachführgrenzen erhalten;
+fehlende Einrichtgrenzen werden mit dem bisherigen Bereich 1100–1900 µs ergänzt.
+Ein Update erweitert keinen Bewegungsbereich und startet keine Kamerasitzung.
+Vor einem neuen Bewegungstest die gespeicherten Werte im WebUI prüfen.
+
## Auf dem Pi prüfen
```bash
diff --git a/VERDRAHTUNG.md b/VERDRAHTUNG.md
index 1ac6093..8d9bb36 100644
--- a/VERDRAHTUNG.md
+++ b/VERDRAHTUNG.md
@@ -1,6 +1,6 @@
# Verdrahtung von Kamera und Schwenkarm
-Stand: 15. Oktober 2026 · Revision 6 · Anschlussentwurf
+Stand: 15. Oktober 2026 · Revision 14 · Anschlussentwurf
Dieser Plan dokumentiert Pi 5, Camera Module 3 NoIR und die beiden
Kameraservos am vorhandenen PCA9685. Er dient später auch dem Wiederaufbau.
@@ -41,6 +41,31 @@ ist mit abgewinkelten Stiften bestückt. Immer nach dem Aufdruck anschließen.
Die Polarität der grünen Schraubklemme ist in dieser Aufnahme nicht eindeutig
ablesbar; vor Versorgung am Board prüfen.
+## Foto der gesteckten Anschlüsse
+
+
+
+Nutzerfoto vom 15. Oktober 2026, laut Nutzer ohne angeschlossene
+Servoversorgung. Die sichtbare Steckrichtung der beiden Servostecker am
+Kondensator-Ende passt zu Signal auf Gelb, Plus auf Rot und Minus auf Schwarz.
+Die Logikstecker erscheinen entsprechend VCC/SDA/SCL/GND angeordnet, OE und
+V+ bleiben frei. Die Nummern der Servoanschlüsse und die Zuordnung der Kabel
+zum unteren/oberen Servo sind am Board beziehungsweise durch Nachverfolgen
+zu bestätigen. Pi-Enden der Steuerleitungen sind auf diesem Modul-Foto nicht vollständig
+sichtbar; das folgende Pi-Foto ergänzt sie. Dies ist eine Sichtprüfung, keine elektrische Durchgangs- oder
+Funktionsprüfung. Schraubklemmenpolung und Versorgung bleiben zu prüfen.
+
+## Foto der gesteckten Pi-Pins
+
+
+
+Nutzerfoto vom 15. Oktober 2026. Soweit auf dem Foto erkennbar: W01 Grün an
+Pin 1, W02 Blau an Pin 3 und W03 (auf dem Foto violett wirkend, im Plan Braun)
+an Pin 5 in der inneren Reihe; W04 Grau gegenüber Pin 5 an Pin 6 in der äußeren
+Reihe. Die sichtbare Anordnung passt zum Plan. Pins 2 und 4 erscheinen frei.
+Dies ergänzt die Sichtprüfung; elektrische Durchgängigkeit, Versorgungspolung,
+I²C-Kommunikation und echte Servofunktion sind weiterhin nicht gemessen.
+
## Pi mit dem PCA9685 verbinden

@@ -136,10 +161,10 @@ Die neue Software bietet einen eigenen Farbpunkt-Test für S1/S2, siehe
[SOFTWARE.md](SOFTWARE.md#kamera-folgt-einem-farbpunkt). Nur der bewusst
aktivierte Hardwaremodus öffnet I²C-Bus 1 / Adresse 0x40 und steuert CH0/CH1.
Die anderen Kanäle für diesen Test unbestückt lassen: die PWM-Frequenz ist
-für die ganze Platine gemeinsam. Die erste Aktivierung setzt beide Servos
-auf 1500 µs; vorab ohne angekoppeltes Gestänge zentrieren und montieren.
+für die ganze Platine gemeinsam. Die bewusste Aktivierung setzt jede Achse
+auf die Mitte ihrer gespeicherten Bewegungsgrenzen; vorab ohne angekoppeltes Gestänge zentrieren und montieren.
-Der WebUI-Stopp und der eigene Kamerastopp beenden nun die Softwareausgabe
+„Alles stoppen“ und „Kameraservos stoppen“ beenden die Softwareausgabe
auf CH0/CH1. Die Servoversorgung bleibt an. Bei verlorenem Ziel, Kamerabild,
Auswertung oder Bedien-Heartbeat wird die Sitzung beendet; sie startet nicht
selbstständig neu. Diese Regeln sind lokal mit künstlichem I²C-Bus geprüft,
@@ -156,10 +181,21 @@ D1 übertragen. Nur beaufsichtigter Test.
Die WebUI bietet jetzt einen manuellen Einrichtmodus für S1/S2. Langsam mit
gehaltener Richtungstaste annähern, vor dem Anschlag loslassen, Grenze speichern.
+„In Mittelstellung fahren“ bringt beide Achsen im Einrichtmodus langsam
+zur Mitte der gespeicherten Grenzen; „Bewegung anhalten“ bricht ab.
Auch PAN nicht gegen den Anschlag drücken. Loslassen hält die Sollposition,
-der Kamerastopp beendet PWM. Es gibt keine Erkennung mechanischer Endlagen;
+der Kamerastopp beendet PWM. Grab-Stopp wirkt nur auf den Grabablauf. Es gibt keine Erkennung mechanischer Endlagen;
Sollwerte sind keine gemessenen Winkel. Die gespeicherten Grenzen stehen in
-`data/follow.json`; die tatsächlichen Werte nach dem Pi-Test auch im obigen
+`data/follow.json`. Auf **Kamera / Finger → Bewegungsgrenzen manuell eingeben**
+begrenzen die oberen Min-/Max-Felder die Nachführung. Die Felder unter
+**Einrichtbereich zum Anfahren der Endpunkte** begrenzen die Richtungstasten.
+Beide nur gestoppt ändern und mit **Bewegungs- und Einrichtgrenzen speichern**
+übernehmen. Der Einrichtbereich beginnt bei 1100–1900 µs und ist schrittweise
+innerhalb 500–2500 µs einstellbar; das bestätigt keinen mechanischen Fahrweg.
+Die Startstellung ergibt sich aus den gespeicherten Bewegungsgrenzen. Endpunktkarten speichern
+10 µs nach innen. Ausführliche Schritte stehen in
+[SOFTWARE.md](SOFTWARE.md#endpunkte-der-kamerahalterung-manuell-einrichten).
+Die tatsächlichen Werte nach dem Pi-Test auch im folgenden
Aufbauprotokoll festhalten. Keine zusätzlichen GPIOs/Endschalter angeschlossen.
## Noch nicht anschlussfertige Erweiterungen
@@ -212,3 +248,11 @@ zusätzlich durch das oben abgelegte Nutzerfoto dokumentiert.
| 15.10.2026 | 4 | Große Pinübersicht RASPI_PINS.svg mit Boardausrichtung und Nutzerkabelfarben ergänzt |
| 15.10.2026 | 5 | Nutzer meldet verdrahteten Aufbau mit Powerbank; Farbpunkt-Softwaretest mit bewusstem Hardwarestart und PWM-Stopp dokumentiert; echte Hardwareprüfung offen |
| 15.10.2026 | 6 | Manuellen Endpunktmodus ohne zusätzliche Verdrahtung dokumentiert; echte mechanische Grenzen weiter offen |
+| 15.10.2026 | 7 | Foto des gesteckten Moduls ohne Servoversorgung abgelegt; sichtbare Steckrichtung plausibel, Pi-Enden/Versorgung/Funktion offen |
+| 15.10.2026 | 8 | Pi-Verkabelungsfoto ergänzt; sichtbare Anordnung passt zu Pins 1/3/5/6, elektrische und funktionale Prüfung offen |
+| 15.10.2026 | 9 | Getrennte Grab-/Kameraseiten dokumentiert; eigene Stoppknöpfe und explizite gemeinsame Abschaltung, Verdrahtung unverändert |
+| 15.10.2026 | 10 | Endpunktkarten statt editierbarer Pulsgrenzen; Speicherkandidat und Bestätigung direkt an der Einrichtung, Verdrahtung unverändert |
+| 15.10.2026 | 11 | Manuelle Min-/Max-Eingabe zusätzlich zu Endpunktkarten, eigenes Speichern/Verwerfen nur gestoppt; Verdrahtung unverändert |
+| 15.10.2026 | 12 | Einrichtbereich pro Achse im WebUI einstellbar, Standard weiter 1100–1900 µs; technische Eingabehülle 500–2500 ist kein bestätigter mechanischer Weg. Verdrahtung unverändert |
+| 15.10.2026 | 13 | Bewusster Start nutzt die Mitte der gespeicherten Bewegungsgrenzen je Achse; Dienststart bleibt inaktiv, Verdrahtung unverändert |
+| 15.10.2026 | 14 | Mittelstellungsbutton im Einrichtmodus mit langsamer, abbrechbarer Fahrt; Verdrahtung unverändert |
diff --git a/VERDRAHTUNG.svg b/VERDRAHTUNG.svg
index ad928eb..0921063 100644
--- a/VERDRAHTUNG.svg
+++ b/VERDRAHTUNG.svg
@@ -2,7 +2,7 @@
Kamera und Schwenkarm · Anschlussentwurf
-Revision 6 · 15.10.2026 · Boardfoto dokumentiert; Servokabel und Netzteil noch prüfen
+Revision 14 · 15.10.2026 · Boardfoto dokumentiert; Servokabel und Netzteil noch prüfenR1 · Raspberry Pi 5Pin 1 · 3V3
diff --git a/halloween/camera.html b/halloween/camera.html
new file mode 100644
index 0000000..8a0d42a
--- /dev/null
+++ b/halloween/camera.html
@@ -0,0 +1,510 @@
+
+
+
+
+
+ Kamera / Finger · Halloween
+
+
+
+
+
Kamera / Finger
+
Farbpunkt verfolgen und Kameraservos einrichten
+
+
+
+
+
+
Kamerabild & Farbpunkt
+
+
+
+
+
Eigene Farbe: Foto aufnehmen und den Farbpunkt antippen. Das Foto bleibt ungespeichert; die gewählte Farbe bleibt bis zu deiner nächsten Farbwahl gespeichert.
+
+
+
+
+
+
+
+
Nachführung starten & stoppen
+
Farbtest lädt …
+
Vorschau zeigt die Erkennung. Echte Nachführung bewegt die Kamera. Dafür muss die Grabautomatik ausgeschaltet sein.
+
+
+
+
+
+
+
+ Farbe, Drehrichtung, Tempo und Suche einstellen
+
+
+
+
+
+
+
+
+
+
+
Nachführtempo zum Testen: 400 µs/s. Bei Pendeln reduzieren; manuelle Einrichtung bleibt langsam.
+
Endpositionen separat über „Bewegungsgrenzen“ oder die vier Endpunktkarten speichern. Änderungen hier sind nur bei gestoppter Kamera möglich.
+ Hinweise zu Tempo und Suche
Nach erster Erkennung: bei Punktverlust nach 1,5 Sekunden innerhalb der gespeicherten Bewegungsgrenzen suchen. Höchstens 200 µs/s und 60 Sekunden, danach ohne Suchbewegung auf denselben Punkt warten. Wiedererkennung beendet die Suche. Kameraservos stoppen bricht jederzeit ab.
+
+
+ Hinweise zum sicheren Start und Stopp
+
Einheit µs = Pulsbreite, keine Winkel. Der Start setzt jede Achse auf die Mitte ihrer gespeicherten Bewegungsgrenzen; dabei kann die Halterung springen. Beim ersten Test Servohörner/Gestänge lösen, Mittelstellung prüfen und danach spannungsfrei wieder montieren. Grenzen nur schrittweise erweitern.
+
Nur Kameraservos CH0/CH1. Kein automatischer Start nach Neustart. Im Farbtest: Suchbewegung nur mit gespeicherter Option nach erster Punkterkennung. Bei Punktverlust bleibt die Farbe gespeichert. Ohne Suche wartet die Kamera an ihrer Position. Nach einem Suchdurchlauf oder spätestens 60 Sekunden wartet sie ebenfalls; bei Wiedererkennung folgt sie weiter. Die Servos halten dabei ihre letzte Position. Bei verlorener Bedienverbindung oder Kamerafehler werden die PWM-Signale abgeschaltet, soweit die Verbindung zum Treiber funktioniert. Die Stromversorgung bleibt eingeschaltet. Bei einem Hardwarefehler Servoversorgung abziehen.
Erst Kameraservos stoppen, Werte ändern und ausdrücklich speichern. Die Bewegungsgrenzen gelten für die Nachführung; den erlaubten Fahrweg zum Ermitteln der Endpunkte stellst du darunter ein.
+
+
+
+
+
+
+
+
+
Einrichtbereich zum Anfahren der Endpunkte
+
Hier kannst du die bisherige Grenze von 1100–1900 µs erweitern. Nur schrittweise erweitern und mit den Richtungstasten prüfen. 500–2500 µs ist nur die technische Eingabegrenze, kein bestätigter Servoweg. Vor mechanischem Anschlag loslassen.
+
+
+
+
+
+
Werte geladen; keine ungespeicherten Änderungen.
+
Einheit µs, keine Winkel. Nachführgrenzen müssen innerhalb des Einrichtbereichs liegen. Minimum darf nicht größer als Maximum sein. Die Startmitte ergibt sich je Achse aus den gespeicherten Bewegungsgrenzen. Die vier Endpunktkarten unten bleiben ebenfalls nutzbar.
+
+
+
Endpunkte einrichten · manuell
+
Langsam annähern, vor dem Anschlag loslassen und die Grenze speichern. Auch links/rechts nicht gegen den Anschlag drücken. Keine automatische Endpunkterkennung.
+
+
+
Im Einrichtmodus: einmal klicken, langsam zur gespeicherten Mitte fahren. „Bewegung anhalten“ bricht ab.
+
1. Einrichtmodus starten. 2. Richtung halten und loslassen. 3. Bei dieser Richtung die Position speichern.
+
Sollposition: lädt …
+
+
+
+
+
+
+
+
Taste gedrückt halten: langsame Bewegung. Loslassen: Zielposition bleibt stehen. Bei fehlendem Tastensignal endet die Bewegung spätestens nach 0,35 s, sofern Pi und Treiber reagieren.
+
+
+
Links
+
Gespeichert: lädt …
+
+
+
+
+
Rechts
+
Gespeichert: lädt …
+
+
+
+
+
Oben
+
Gespeichert: lädt …
+
+
+
+
+
Unten
+
Gespeichert: lädt …
+
+
+
+
+
Speichert den angeforderten Pulswert mit 10 µs Abstand nach innen, keine gemessene Position. Beim Einrichten gilt der oben gespeicherte Einrichtbereich, auch außerhalb der Nachführgrenzen. Mehr Weg nötig? Stoppen, den Einrichtbereich schrittweise erweitern und erneut starten. Zum Beenden „Kameraservos stoppen“ drücken. Neue Sitzung startet in der Mitte der dann gespeicherten Bewegungsgrenzen.
+
+
+
+
+
+
+
+
diff --git a/halloween/camera.py b/halloween/camera.py
index 6286ebd..5c7afe1 100644
--- a/halloween/camera.py
+++ b/halloween/camera.py
@@ -1,5 +1,6 @@
"""Ein Kameraproduzent für mehrere Livebild-Betrachter."""
+from collections import deque
from io import BytesIO
import threading
import time
@@ -9,6 +10,7 @@ from PIL import Image, ImageDraw
class CameraStream:
+ TARGET_FPS = 15
def __init__(self, demo: bool = False):
self.demo = demo
self._condition = threading.Condition()
@@ -16,6 +18,7 @@ class CameraStream:
self._stop = threading.Event()
self._frame = None
self._number = 0
+ self._frame_times = deque(maxlen=45)
self._time = None
self.error = None
@@ -43,6 +46,7 @@ class CameraStream:
self._frame = frame
self._number += 1
self._time = time.monotonic()
+ self._frame_times.append(self._time)
self._condition.notify_all()
def _run(self):
@@ -75,15 +79,17 @@ class CameraStream:
try:
# BGR888 liefert laut Picamera2-Dokumentation RGB-Arrays für PIL.
config = camera.create_video_configuration(
- main={"size": (640, 480), "format": "BGR888"}
+ main={"size": (640, 480), "format": "BGR888"},
+ controls={"FrameDurationLimits": (66667, 66667)}, queue=False
)
camera.configure(config)
camera.start()
started = True
while not self._stop.is_set():
+ began = time.monotonic()
pixels = camera.capture_array("main")
self._publish(self._jpeg(Image.fromarray(pixels, "RGB")))
- self._stop.wait(0.2)
+ self._stop.wait(max(0., 1/self.TARGET_FPS-(time.monotonic()-began)))
finally:
try:
if started:
@@ -114,7 +120,12 @@ class CameraStream:
def status(self):
with self._condition:
age = None if self._time is None else round(time.monotonic() - self._time, 1)
+ times = self._frame_times
+ fps = ((len(times)-1)/(times[-1]-times[0])
+ if len(times)>1 and times[-1]>times[0] and age is not None and age<1 else 0.)
return {
+ "fps": round(fps, 1),
+ "target_fps": 5 if self.demo else self.TARGET_FPS,
"source": "Demo" if self.demo else "Pi-Kamera",
"ready": self._frame is not None and age is not None and age < 3,
"frame_age_seconds": age,
diff --git a/halloween/follow.py b/halloween/follow.py
index c44ba10..d365290 100644
--- a/halloween/follow.py
+++ b/halloween/follow.py
@@ -6,18 +6,32 @@ import base64
import json
import math
import os
+import re
import secrets
import tempfile
import threading
import time
-from PIL import Image
+from PIL import Image, ImageChops
-DEFAULT = dict(hue=85, tolerance=18, pan_min=1400, pan_max=1600,
- tilt_min=1400, tilt_max=1600, pan_reverse=False, tilt_reverse=False)
+FOLLOW_GAIN = 600 # µs/s per unit of image offset (= old behaviour at 300 µs/s)
+DEFAULT = dict(hue=85, tolerance=18, follow_speed=400, search_enabled=False, pan_min=1400, pan_max=1600,
+ tilt_min=1400, tilt_max=1600, pan_reverse=False, tilt_reverse=False,
+ pan_setup_min=1100, pan_setup_max=1900,
+ tilt_setup_min=1100, tilt_setup_max=1900)
def validate(value):
+ if not isinstance(value, dict):
+ raise ValueError('Ungültige Nachführungseinstellungen')
+ # Preserve the previous speed when loading existing configurations.
+ value = dict(value)
+ value.setdefault('follow_speed', 80)
+ value.setdefault('search_enabled', False)
+ # Old saved configurations retain their tested setup envelope.
+ setup_keys = {a+"_setup_"+b for a in ("pan", "tilt") for b in ("min", "max")}
+ if isinstance(value, dict) and set(value) == set(DEFAULT)-setup_keys:
+ value = {**{k: DEFAULT[k] for k in setup_keys}, **value}
if set(value) != set(DEFAULT):
raise ValueError('Unvollständige Nachführungseinstellungen')
result = {}
@@ -33,21 +47,48 @@ def validate(value):
result[key] = v
if not 0 <= result['hue'] <= 255 or not 5 <= result['tolerance'] <= 35:
raise ValueError('Ungültige Farbe oder Farbtoleranz')
+ if not 40 <= result['follow_speed'] <= 800:
+ raise ValueError('Nachführtempo muss zwischen 40 und 800 µs/s liegen')
for axis in ('pan', 'tilt'):
- if not 1100 <= result[axis+'_min'] <= 1500 <= result[axis+'_max'] <= 1900:
- raise ValueError('Grenzen müssen zwischen 1100 und 1900 µs liegen und 1500 einschließen')
+ if not 500 <= result[axis+'_setup_min'] <= result[axis+'_min'] <= result[axis+'_max'] <= result[axis+'_setup_max'] <= 2500:
+ raise ValueError('Einrichtbereich: 500–2500 µs; Minimum muss kleiner oder gleich Maximum sein; Bewegungsgrenzen müssen im Einrichtbereich liegen')
return result
+def midpoints(config):
+ """Stored follow endpoints determine each start, rounded to whole µs."""
+ return {axis: (config[axis+'_min'] + config[axis+'_max'] + 1)//2
+ for axis in ('pan', 'tilt')}
+
+
def locate(jpeg, config):
- """Largest unambiguous saturated blob, circular hue distance (also red)."""
- image = Image.open(BytesIO(jpeg)).convert('RGB').resize((160, 120))
- pixels = list(image.convert('HSV').getdata())
- mask = set()
- for i, (h, s, v) in enumerate(pixels):
- delta = abs(h-config['hue'])
- if min(delta, 256-delta) <= config['tolerance'] and s >= 100 and v >= 60:
- mask.add(i)
+ """Largest unambiguous saturated blob, circular hue distance (also red).
+
+ Strict hue/saturation masks run first so a strongly coloured marker wins
+ over similar but paler surroundings (e.g. bluish paper); weaker masks only
+ apply when the strict ones find no unambiguous blob.
+ """
+ image = Image.open(BytesIO(jpeg))
+ image.draft('RGB', (160, 120)) # JPEG decodes directly at reduced size
+ image = image.convert('RGB')
+ if image.size != (160, 120):
+ image = image.resize((160, 120))
+ planes = image.convert('HSV').split()
+ tolerance = config['tolerance']
+ for factor, min_saturation in ((.5, 140), (.75, 120), (1, 100)):
+ result = _find_blob(planes, config['hue'], max(5, tolerance*factor), min_saturation)
+ if result:
+ return result
+ return None
+
+
+def _find_blob(planes, centre, tolerance, min_saturation):
+ hue, saturation, value = planes
+ hue_table = [255 if min(abs(h-centre), 256-abs(h-centre)) <= tolerance else 0 for h in range(256)]
+ mask_image = ImageChops.darker(
+ ImageChops.darker(hue.point(hue_table), saturation.point([255 if i >= min_saturation else 0 for i in range(256)])),
+ value.point([255 if i >= 60 else 0 for i in range(256)]))
+ mask = {m.start() for m in re.finditer(b'\xff', mask_image.tobytes())}
blobs = []
while mask:
seed = mask.pop()
@@ -97,7 +138,7 @@ class PCA9685:
def move(self, pan, tilt):
for channel, pulse in enumerate((pan, tilt)):
- if not 1100 <= pulse <= 1900:
+ if not math.isfinite(pulse) or not 500 <= pulse <= 2500:
raise ValueError('Puls außerhalb der absoluten Testgrenze')
count = round(pulse * 25 / 122) # microseconds -> oscillator ticks / prescale
self.bus.write_i2c_block_data(0x40, 0x06+4*channel,
@@ -140,10 +181,15 @@ class FollowService:
self.jog_direction = None
self.jog_until = 0.
self.jog_sequence = -1
+ self.center_target = None
self.driver = None
- self.pan = self.tilt = 1500.
+ self.setup_origin = midpoints(self.config)
+ self.pan, self.tilt = self.setup_origin['pan'], self.setup_origin['tilt']
self.last_ping = self.clock()
self.last_result = self.last_seen = None
+ self.result_times = deque(maxlen=45)
+ self.analysis_ms = None
+ self._reset_search()
self.number = 0
self.target = None
self.jpeg = None
@@ -154,6 +200,52 @@ class FollowService:
self.thread = None
self.quit = threading.Event()
+ def _reset_search(self):
+ self.search_started = None
+ self.search_path = deque()
+ self.last_target = None
+ self.reacquire_count = 0
+ self.waiting_for_target = False
+
+ def _wait_for_target(self):
+ # Keep the selected colour and the armed session, but issue no motion.
+ # Existing pulses hold position; halt/watchdogs still release the driver.
+ self.search_started = None
+ self.search_path.clear()
+ self.reacquire_count = 0
+ self.waiting_for_target = True
+
+ def _search_step(self, now, dt):
+ """Finite sweep of stored follow limits; never use the setup envelope."""
+ if self.search_started is None:
+ self.search_started = now
+ # First look towards the last image position, respecting reversal.
+ first = {}
+ other = {}
+ for axis, coordinate in (('pan', 'x'), ('tilt', 'y')):
+ sign = 1 if self.last_target[coordinate] >= .5 else -1
+ if self.config[axis+'_reverse']: sign *= -1
+ first[axis] = self.config[axis+('_max' if sign > 0 else '_min')]
+ other[axis] = self.config[axis+('_min' if sign > 0 else '_max')]
+ self.search_path = deque([
+ (first['pan'], self.tilt), (other['pan'], self.tilt),
+ (other['pan'], first['tilt']), (first['pan'], first['tilt']),
+ (first['pan'], other['tilt']), (other['pan'], other['tilt'])])
+ if now-self.search_started >= 60:
+ self._wait_for_target()
+ return
+ while self.search_path and (self.pan, self.tilt) == self.search_path[0]:
+ self.search_path.popleft()
+ if not self.search_path:
+ self._wait_for_target()
+ return
+ step = min(200, self.config['follow_speed'])*dt
+ for axis, goal in zip(('pan', 'tilt'), self.search_path[0]):
+ position = getattr(self, axis)
+ value = position+max(-step, min(step, goal-position))
+ setattr(self, axis, max(self.config[axis+'_min'], min(self.config[axis+'_max'], value)))
+ self.driver.move(self.pan, self.tilt)
+
def _save(self, value):
config = validate(value)
with self.lock:
@@ -197,21 +289,33 @@ class FollowService:
if sequence <= self.jog_sequence:
return {'accepted': False}
self.jog_sequence = sequence
+ self.center_target = None
self.jog_direction = direction
self.jog_until = self.clock()+.35 if direction else 0.
return {'accepted': True}
+ def center(self, session, sequence):
+ """Explicit slow recentering within an already armed setup session."""
+ with self.lock:
+ accepted = self.jog(session, None, sequence)
+ if accepted['accepted']:
+ self.center_target = midpoints(self.config)
+ return accepted
+
def remember(self, session, direction):
with self.lock:
self._require_setup(session)
+ if self.center_target is not None:
+ raise ValueError('Mittelstellungsfahrt zuerst anhalten oder abwarten')
if self.jog_direction and self.clock() < self.jog_until:
raise ValueError('Richtungstaste vor dem Speichern loslassen')
axis, sign = self._direction(direction)
# Store a slightly inward commanded limit, never an inferred physical stop.
value = round(getattr(self, axis)) + (10 if sign < 0 else -10)
key = axis+('_min' if sign < 0 else '_max')
- if sign < 0 and value > 1500 or sign > 0 and value < 1500:
- raise ValueError('Position liegt zu nahe an oder auf der anderen Seite der Mitte')
+ origin = self.setup_origin[axis]
+ if sign < 0 and value > origin or sign > 0 and value < origin:
+ raise ValueError('Für diese Grenze zuerst in die zugehörige Richtung von der Startmitte dieser Sitzung weg fahren; mindestens 10 µs Abstand zur Mitte nötig')
return self._save(dict(self.config, **{key:value}))
def capture(self):
@@ -258,7 +362,8 @@ class FollowService:
raise ValueError('Kein frisches Kamerabild')
if hardware and (self.camera.demo or not acknowledged):
raise ValueError('Echte Kamera und bestätigter freier Servotest erforderlich')
- self.pan = self.tilt = 1500.
+ self.setup_origin = midpoints(self.config)
+ self.pan, self.tilt = self.setup_origin['pan'], self.setup_origin['tilt']
try:
if hardware:
self.driver = self.factory()
@@ -269,9 +374,13 @@ class FollowService:
self.active = True
self.operation = operation
self.jog_direction, self.jog_until, self.jog_sequence = None, 0., -1
+ self.center_target = None
self.session = secrets.token_hex(16)
self.last_ping = self.clock()
self.last_result = self.last_seen = self.clock()
+ self._reset_search()
+ self.result_times.clear()
+ self.analysis_ms = None
self.jpeg = None
self.target = None
self.revision += 1
@@ -286,7 +395,9 @@ class FollowService:
def halt(self, error=None):
with self.lock:
self.active = False
+ self._reset_search()
self.session = None
+ self.center_target = None
self.jog_direction = None
self.jog_until = 0.
self.revision += 1
@@ -311,45 +422,89 @@ class FollowService:
elif self.last_result is not None and now-self.last_result > 1:
self.halt('Bildauswertung ausgefallen; erneut starten')
- def process(self, number, jpeg, captured, revision, target):
+ def process(self, number, jpeg, captured, revision, target, analysis_ms=None):
with self.lock:
now = self.clock()
if not self.active or revision != self.revision or captured is None or now-captured > 1:
return
self.watchdog()
if not self.active: return
- dt = min(.25, max(0, now-(self.last_result or now)))
+ # Do not catch up a delayed frame with a large movement jump.
+ previous = self.last_result if self.last_result is not None else now
+ max_dt = .10 if self.operation == 'follow' else .25
+ dt = min(max_dt, max(0, now-previous))
+ self.result_times.append(now)
+ self.analysis_ms = analysis_ms
self.last_result, self.number, self.jpeg, self.target = now, number, jpeg, target
if self.operation == 'setup':
self.target = None
+ if self.center_target is not None:
+ for axis, goal in self.center_target.items():
+ position = getattr(self, axis)
+ setattr(self, axis, position + max(-40*dt, min(40*dt, goal-position)))
+ if self.driver: self.driver.move(self.pan, self.tilt)
+ if all(getattr(self, a) == goal for a, goal in self.center_target.items()):
+ self.center_target = None
+ return
if self.jog_direction and now < self.jog_until:
axis, sign = self._direction(self.jog_direction)
- value = max(1100, min(1900, getattr(self,axis)+sign*40*dt))
+ value = max(self.config[axis+'_setup_min'], min(self.config[axis+'_setup_max'], getattr(self,axis)+sign*40*dt))
setattr(self,axis,value)
if self.driver: self.driver.move(self.pan,self.tilt)
else:
self.jog_direction = None
return
+ if self.search_started is not None and now-self.search_started >= 60:
+ self._wait_for_target()
+ return
if target:
+ if self.search_started is not None or self.waiting_for_target:
+ # Stop searching immediately; require two fresh detections
+ # before returning control to ordinary tracking.
+ self.reacquire_count += 1
+ if self.reacquire_count < 2: return
+ self.search_started = None
+ self.search_path.clear()
+ self.waiting_for_target = False
+ self.reacquire_count = 0
+ self.last_target = dict(target)
self.last_seen = now
for axis, coordinate in (('pan','x'), ('tilt','y')):
error = target[coordinate]-.5
if abs(error) < .06: continue
sign = -1 if self.config[axis+'_reverse'] else 1
- value = getattr(self, axis) + sign*error*160*dt
+ # The tempo is a speed limit; the loop gain stays capped because
+ # camera/servo delay makes a higher gain swing back and forth.
+ speed = self.config['follow_speed']
+ value = getattr(self, axis) + sign*max(-speed, min(speed, error*FOLLOW_GAIN))*dt
setattr(self, axis, max(self.config[axis+'_min'], min(self.config[axis+'_max'], value)))
if self.driver: self.driver.move(self.pan, self.tilt)
- # No searching or recentring when the marker disappears. End the
- # hardware session after one second so old pulses do not hold forever.
- elif self.driver and self.last_seen is not None and now-self.last_seen > 1:
- self.halt('Farbpunkt verloren; erneut starten')
+ elif self.driver and self.last_seen is not None:
+ self.reacquire_count = 0
+ if self.waiting_for_target:
+ return
+ if self.config['search_enabled'] and self.last_target is not None:
+ if now-self.last_seen >= 1.5:
+ self._search_step(now, dt)
+ else:
+ self._wait_for_target()
def status(self):
with self.lock:
- return {'active': self.active, 'hardware': self.driver is not None,
+ times = self.result_times
+ fps = ((len(times)-1)/(times[-1]-times[0])
+ if self.active and len(times)>1 and times[-1]>times[0] and self.clock()-times[-1]<1 else 0.)
+ return {'fps': round(fps, 1), 'analysis_ms': self.analysis_ms,
+ 'searching': self.search_started is not None,
+ 'reacquiring': (self.search_started is not None or self.waiting_for_target) and self.reacquire_count > 0,
+ 'waiting_for_target': self.waiting_for_target,
+ 'search_seconds': round(self.clock()-self.search_started, 1) if self.search_started is not None else 0,
+ 'active': self.active, 'hardware': self.driver is not None,
'operation': self.operation, 'demo': self.camera.demo,
'error': self.error, 'config': dict(self.config),
'pan_us': round(self.pan), 'tilt_us': round(self.tilt),
+ 'setup_origin': dict(self.setup_origin),
+ 'centering': self.center_target is not None,
'target': self.target, 'frame_number': self.number,
'image_base64': base64.b64encode(self.jpeg).decode() if self.active and self.jpeg else None}
@@ -359,7 +514,7 @@ class FollowService:
def _run(self):
last = 0
- while not self.quit.wait(.05):
+ while not self.quit.wait(.01):
self.watchdog()
with self.lock:
if not self.active: continue
@@ -368,8 +523,10 @@ class FollowService:
if number == last or jpeg is None: continue
last = number
try:
+ began = time.monotonic()
target = locate(jpeg, config) if operation == 'follow' else None
- self.process(number, jpeg, captured, revision, target)
+ analysis_ms = round((time.monotonic()-began)*1000, 1)
+ self.process(number, jpeg, captured, revision, target, analysis_ms)
except Exception as exc:
with self.lock:
if revision == self.revision: self.halt(f'Nachführungsfehler: {exc}')
diff --git a/halloween/index.html b/halloween/index.html
index 07e75f0..cdbe191 100644
--- a/halloween/index.html
+++ b/halloween/index.html
@@ -27,6 +27,7 @@
button.danger:hover { background: #9c352c; }
button:disabled { opacity: .5; cursor: not-allowed; }
a.download { display: inline-block; text-decoration: none; }
+ nav a[aria-current="page"] { background:#46695b; border:2px solid #c4e0cb; font-weight:700; }
.value { font-weight: 650; color: #ffca78; }
.notice { border-left: 4px solid #ffb44f; padding: .55rem .8rem; background: #302b21; margin: .8rem 0; }
.muted { color: #bfcbc6; }
@@ -40,12 +41,44 @@
.event-image { display: block; max-width: 200px; max-height: 150px; border-radius: 7px; margin: .5rem 0; }
.event-tools { margin: .3rem 0 .8rem; }
@media (max-width: 760px) { main { grid-template-columns: 1fr; } }
+ [hidden] { display: none !important; }
+ header { padding: .8rem 2vw; display: grid; grid-template-columns: 1fr auto; gap: .25rem 1rem; align-items: center; position: sticky; top: 0; z-index: 20; }
+ header h1, header > p { grid-column: 1; }
+ header nav { grid-column: 2; grid-row: 1 / span 2; margin: 0 !important; }
+ #all-stop-status:empty { display: none; }
+ #all-stop-status { grid-column: 1 / -1; }
+ main { width: 100%; max-width: 1800px; padding: 1rem 2vw; grid-template-columns: minmax(0,1.35fr) minmax(360px,1fr); align-items: start; }
+ section { min-width: 0; }
+ p { line-height: 1.45; }
+ summary { cursor: pointer; font-weight: 650; padding: .65rem 0; }
+ summary:focus-visible { outline: 3px solid #ffb44f; outline-offset: 3px; }
+
+ .camera-wrap { max-width: 720px; margin: auto; }
+ .events { grid-column: auto; }
+ details > summary { font-size: 1.1rem; }
+ @media (max-width: 999px) {
+ header { position: static; }
+ main { grid-template-columns: minmax(0,1fr); }
+ }
+ @media (max-width: 580px) {
+ header { display: block; padding: .8rem; }
+ header nav { margin-top: .8rem !important; }
+ main { padding: .7rem; }
+ section { padding: .8rem; }
+ button, select, input { max-width: 100%; }
+ }
Halloween-Grab
-
Lokale Kameraansicht und Steuerung
+
Auslösung, Ereignisse und Profile
+
+
@@ -54,7 +87,7 @@
-
Automatische Auslösung
+ Automatische Auslösung einstellen
Endpunkte der orangefarbenen Linie und Ecken des grünen Bereichs ziehen. Der Pfeil zeigt zum Grab. Änderungen werden erst nach dem Speichern aktiv.
Erkennung lädt …
Kamera wird gestartet …
@@ -91,89 +125,34 @@
-
Grabablauf nur simuliert. Hand, Atemmotor, Licht und Ton bleiben simuliert. Kameraservos können im separaten Farbtest wirklich bewegt werden. Die automatische Erkennung kann ausschließlich diesen simulierten Ablauf auslösen.
+
Grabablauf nur simuliert. Hand, Atemmotor, Licht und Ton bleiben simuliert. Kameraservos lassen sich auf der Seite „Kamera / Finger“ bedienen. Die automatische Erkennung kann ausschließlich diesen simulierten Ablauf auslösen.
Die folgenden Knöpfe sind für manuelle Tests. Die Automatik gibt sich selbst frei.
-
-
+
+
-
-
Kamera folgt einem Farbpunkt · Testmodus
-
Halte einen kräftig roten oder grünen Aufkleber vor die Kamera. Ein Punkt am Finger geht auch. Kein Training und kein Internet nötig. Zuerst die automatische Auslösung ausschalten und speichern.
-
-
-
-
-
Eigene Farbe: Beispielfoto aufnehmen und dort auf die Mitte des Farbpunkts tippen. Das Foto wird nicht gespeichert.
-
-
Farbtest lädt …
-
- Servogrenzen und Richtung einstellen
-
Einheit µs = Pulsbreite, keine Winkel. Der Start setzt beide Servos auf 1500 µs; dabei kann die Halterung springen. Beim ersten Test Servohörner/Gestänge lösen, Mittelstellung prüfen und danach spannungsfrei wieder montieren. Grenzen nur schrittweise erweitern.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Endpunkte einrichten · manuell
-
Langsam annähern, vor dem Anschlag loslassen und die Grenze speichern. Auch links/rechts nicht gegen den Anschlag drücken. Keine automatische Endpunkterkennung.
-
-
Noch keine Grenze in dieser Sitzung gespeichert.
-
-
Sollposition: PAN 1500 / TILT 1500 µs
-
-
-
-
-
-
-
-
Taste gedrückt halten: langsame Bewegung. Loslassen: Zielposition bleibt stehen. Bei fehlendem Tastensignal endet die Bewegung spätestens nach 0,35 s, sofern Pi und Treiber reagieren.
-
-
-
-
-
-
-
Speichert den angeforderten Pulswert mit 10 µs Abstand nach innen, keine gemessene Position. Beim Einrichten gelten 1100–1900 µs, auch außerhalb der bisherigen Farbtest-Grenzen. Falls das nicht reicht: stoppen, nichts erzwingen. Zum Beenden „Kameraservos stoppen“ drücken. Neue Sitzung startet wieder bei 1500 µs.
-
-
Nur Kameraservos CH0/CH1. Kein automatischer Start nach Neustart. Im Farbtest: Bei Punktverlust keine Suchbewegung; nach einer Sekunde wird die Servositzung beendet. Bei verlorener Bedienverbindung oder Kamerafehler werden die PWM-Signale abgeschaltet, soweit die Verbindung zum Treiber funktioniert. Die Stromversorgung bleibt eingeschaltet. Bei einem Hardwarefehler Servoversorgung abziehen.
-
-
Gespeicherte Ereignisse
+ Gespeicherte Ereignisse
Erfolgreiche manuelle und automatische Auslösungen bleiben nach einem Neustart erhalten. Eine Profilzuordnung kann hier zum Testen oder Korrigieren gesetzt werden.
Lädt …
+
-
Personenprofile
+ Personenprofile
Die automatische Wiedererkennung per Kamera ist noch nicht aktiv. Diese Profile sind derzeit nur manuell angelegte Kennungen; Bilder oder Gesichtsmerkmale werden nicht gespeichert.