addet test set

This commit is contained in:
marcinator_2 committed 2026-10-14 17:40:31 +02:00
1 parent d370f992ce
commit b0f35bf3cc
21 files changed
+1796 -183

No files matched your search

+83 -13
View File
@@ -37,6 +37,7 @@
#events-list { margin: .5rem 0 0; padding-left: 1.4rem; }
#events-list li { margin: .35rem 0; }
#profiles-list li { margin: .35rem 0; }
.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; } }
</style>
@@ -53,17 +54,24 @@
<img id="live-image" class="camera" src="/stream.mjpg" alt="Livebild der Kamera">
<canvas id="detection-overlay" width="640" height="480" aria-label="Linie und Auslösebereich einstellen"></canvas>
</div>
<h2 style="margin-top:1rem">Personenerkennung</h2>
<h2 style="margin-top:1rem">Automatische Auslösung</h2>
<p>Endpunkte der orangefarbenen Linie und Ecken des grünen Bereichs ziehen. Der Pfeil zeigt zum Grab. Änderungen werden erst nach dem Speichern aktiv.</p>
<label for="detection-method">Erkennung wählen</label>
<select id="detection-method">
<option value="person_line">Person überquert Linie</option>
<option value="motion_test">Test: Bewegung in Pfeilrichtung</option>
</select>
<p id="detection-help" class="muted"></p>
<div class="row">
<label><input type="checkbox" id="detection-enabled"> Automatisch auslösen</label>
<button id="reverse-direction">Richtung umkehren</button>
<button id="save-detection">Linie und Bereich speichern</button>
</div>
<p id="detection-status" class="muted" role="status">Erkennung lädt …</p>
<p id="motion-details" class="muted" role="status" hidden></p>
<p class="muted" id="camera-status">Kamera wird gestartet …</p>
<a class="download" href="/snapshot.jpg" download="halloween-snapshot.jpg">Aktuelles Bild herunterladen</a>
<p class="notice">Aufnahmen bleiben deaktiviert. Den Auslösebereich vor dem Test auf den privaten Bereich begrenzen. Personenmarkierungen sind keine Besucherprofile.</p>
<p class="notice">Bei automatischer Auslösung wird der grüne Bereich als Bild lokal auf dem Pi gespeichert, bis du es löschst. Den Bereich vor dem Test auf den privaten Bereich begrenzen. Personenmarkierungen sind keine Besucherprofile.</p>
</section>
<section>
<h2>Betrieb</h2>
@@ -83,10 +91,11 @@
</select>
<button id="save-mode">Übernehmen</button>
</div>
<p class="notice"><strong>Aktoren nur simuliert.</strong> Kein Motor, Licht oder Ton ist mit dieser Version verbunden. Die Personenerkennung kann ausschließlich diesen simulierten Ablauf auslösen.</p>
<p class="notice"><strong>Aktoren nur simuliert.</strong> Kein Motor, Licht oder Ton ist mit dieser Version verbunden. Die automatische Erkennung kann ausschließlich diesen simulierten Ablauf auslösen.</p>
<p class="muted">Die folgenden Knöpfe sind für manuelle Tests. Die Automatik gibt sich selbst frei.</p>
<div class="row">
<button id="zone-clear">Bereich frei (Test)</button>
<button id="test-trigger">Testauslösung</button>
<button id="zone-clear">Bereich frei (manueller Test)</button>
<button id="test-trigger">Manuelle Testauslösung</button>
</div>
<div class="row" style="margin-top: 1rem">
<button class="danger" id="stop">Stopp</button>
@@ -97,6 +106,7 @@
<section class="events">
<h2>Gespeicherte Ereignisse</h2>
<p class="muted">Erfolgreiche manuelle und automatische Auslösungen bleiben nach einem Neustart erhalten. Eine Profilzuordnung kann hier zum Testen oder Korrigieren gesetzt werden.</p>
<button id="refresh-events">Ereignisse aktualisieren</button>
<ul id="events-list"><li>Lädt …</li></ul>
</section>
<section class="events">
@@ -129,7 +139,20 @@
const c = data.controller, cam = data.camera;
$('mode-label').textContent = names[c.mode] || c.mode;
if (document.activeElement !== $('mode')) $('mode').value = c.mode;
$('phase').textContent = c.phase;
const detection = data.detection;
let phase = c.phase;
// The manual zone-clear flag is not the automatic detection gate.
// Preserve running sequence, stop and cooldown labels from the controller.
if (detection.enabled && ['Bereit', 'Bereich frei abwarten'].includes(phase)) {
phase = !['sanft', 'voll'].includes(c.mode) ? 'Modus ohne Schreckauslösung'
: detection.reset_required ? 'Erkennung gesperrt; zurücksetzen'
: !detection.ready ? 'Auf Erkennung warten'
: detection.armed ? 'Bereit (Automatik)'
: detection.method === 'motion_test' ? 'Ruhiges Bild abwarten (Automatik)'
: 'Freien Bereich abwarten (Automatik)';
}
$('phase').textContent = phase;
$('zone-clear').title = 'Nur für manuelle Tests. Die Automatik prüft die Freigabe selbst.';
$('cooldown').textContent = c.cooldown_seconds ? c.cooldown_seconds + ' s' : '–';
$('last-event').textContent = c.last_event ? c.last_event.time + ' · ' + names[c.last_event.mode] : '–';
$('camera-status').textContent = cam.error || (cam.ready ? cam.source + ' · Bild aktiv' : cam.source + ' · wartet auf Bild');
@@ -191,7 +214,7 @@
for (const event of data.events) {
const item = document.createElement('li');
const summary = document.createElement('span');
summary.textContent = '#' + event.id + ' · ' + new Date(event.occurred_at).toLocaleString('de-DE') + ' · ' + (event.kind === 'person_line_crossing' ? 'Linienüberquerung' : 'Test') + ' · ' + (names[event.mode] || event.mode) + (event.profile_label ? ' · ' + event.profile_label : '');
summary.textContent = '#' + event.id + ' · ' + new Date(event.occurred_at).toLocaleString('de-DE') + ' · ' + ({person_line_crossing: 'Linienüberquerung', motion_direction_test: 'Bewegungs-Test', test_trigger: 'Manueller Test'}[event.kind] || event.kind) + ' · ' + (names[event.mode] || event.mode) + (event.profile_label ? ' · ' + event.profile_label : '');
const tools = document.createElement('div');
tools.className = 'row event-tools';
const select = document.createElement('select');
@@ -208,7 +231,31 @@
} catch (error) { $('message').textContent = error.message; }
};
tools.append(select, assign);
item.append(summary, tools);
item.append(summary);
if (event.image_url) {
const link = document.createElement('a');
link.href = event.image_url; link.target = '_blank'; link.rel = 'noopener';
link.textContent = 'Bild ansehen';
const preview = document.createElement('img');
preview.className = 'event-image'; preview.loading = 'lazy';
preview.src = event.image_url; preview.alt = 'Auslösebild zu Ereignis #' + event.id;
link.prepend(preview);
const removeImage = document.createElement('button');
removeImage.textContent = 'Bild löschen';
removeImage.onclick = async () => {
if (!confirm('Bild unwiderruflich löschen? Der Ereigniseintrag bleibt erhalten.')) return;
try {
await api(event.image_url, {}, 'DELETE');
await refreshEvents();
} catch (error) { $('message').textContent = error.message; }
};
item.append(link, removeImage);
} else {
const missing = document.createElement('span');
missing.className = 'muted'; missing.textContent = ' · Kein gespeichertes Bild';
item.append(missing);
}
item.append(tools);
list.append(item);
}
} catch (error) { $('events-list').textContent = error.message; }
@@ -222,6 +269,7 @@
$('events-list').textContent = error.message;
}
}
$('refresh-events').onclick = refreshEvents;
$('save-mode').onclick = () => action('/api/mode', { mode: $('mode').value });
$('zone-clear').onclick = () => action('/api/zone-clear');
$('test-trigger').onclick = () => action('/api/test-trigger');
@@ -292,9 +340,12 @@
if (!detectionConfig) return;
try {
detectionConfig.enabled = $('detection-enabled').checked;
detectionConfig.method = $('detection-method').value;
detectionConfig = await api('/api/detection/config', detectionConfig);
detectionEpoch++; people = []; drawDetection();
$('message').textContent = 'Gespeichert; freien Auslösebereich abwarten';
$('message').textContent = detectionConfig.method === 'motion_test'
? 'Testmodus gespeichert; Bild zwei Sekunden ruhig halten. Bei Stopp bewusst zurücksetzen.'
: 'Gespeichert; freien Auslösebereich abwarten';
} catch (error) { $('message').textContent = error.message; }
};
async function refreshDetection() {
@@ -304,9 +355,20 @@
if (!response.ok) throw new Error('Erkennung nicht erreichbar');
const data = await response.json(), status = data.status;
if (epoch !== detectionEpoch) { setTimeout(refreshDetection, 200); return; }
$('detection-status').textContent = status.error ||
(!status.enabled ? 'Automatische Auslösung aus' : status.armed ? 'Bereit für Überquerung zum Grab' : 'Freigabe / Sperrzeit abwarten') +
' · '+status.fps+' Auswertungen/s';
const motionTest = status.method === 'motion_test';
$('motion-details').hidden = !motionTest;
const motion = data.motion;
$('motion-details').textContent = motionTest && motion
? 'Bildänderung: '+(motion.changed_fraction*100).toLocaleString('de-DE', {maximumFractionDigits:2})+
' % · '+motion.note+' · Bestätigung '+motion.confirmed_frames+'/2'
: 'Warte auf Bewegungswerte';
const detail = motionTest && data.motion?.moving
? (data.motion.directed ? ' · Bewegung in Pfeilrichtung' : ' · Bewegung erkannt') : '';
$('detection-status').textContent = (motionTest ? 'Bewegungs-Test · ' : '') + (status.error ||
(!status.enabled ? 'Automatische Auslösung aus' : status.armed
? (motionTest ? 'Bereit für Bewegung in Pfeilrichtung' : 'Bereit für Überquerung zum Grab')
: (motionTest ? 'Ruhiges Bild / Sperrzeit abwarten' : 'Freigabe / Sperrzeit abwarten'))) +
detail+' · '+status.fps+' Auswertungen/s';
if (data.image_base64) {
const image = new Image();
image.src = 'data:image/jpeg;base64,'+data.image_base64;
@@ -330,8 +392,16 @@
}
setTimeout(refreshDetection, 200);
}
function updateDetectionHelp() {
$('detection-help').textContent = $('detection-method').value === 'motion_test'
? 'Nur Test: Hand, Kopf oder Gegenstand im grünen Bereich in Pfeilrichtung bewegen. Keine Linienüberquerung nötig. Zuerst zwei Sekunden ruhig halten; danach zehn Sekunden Sperrzeit und erneut Ruhe. Benötigt OpenCV, kein Personenmodell. Auch Kamerabewegung kann auslösen. Ein ruhiges Bild bedeutet nicht, dass der Bereich menschenfrei ist.'
: 'Eine erkannte Person muss die Linie in Pfeilrichtung überqueren. Zuerst den Bereich zwei Sekunden freihalten. Benötigt OpenCV und das installierte Personenmodell.';
}
$('detection-method').onchange = updateDetectionHelp;
updateDetectionHelp();
fetch('/api/detection/config').then(r => {if (!r.ok) throw new Error('Konfiguration nicht verfügbar'); return r.json();}).then(c => {
detectionConfig = c; $('detection-enabled').checked = c.enabled; drawDetection();
detectionConfig = c; $('detection-enabled').checked = c.enabled;
$('detection-method').value = c.method; updateDetectionHelp(); drawDetection();
}).catch(error => { $('detection-status').textContent = error.message; });
refreshDetection();
setInterval(drawDetection, 250);