Webcam: Buttons fuer Kamera-Aufloesung (VGA/SVGA)

Schaltet die ESP32-CAM-Aufloesung per Button um (VGA 640x480 = mehr FPS,
SVGA 800x600 = mehr Detail). Server proxyt den /control-Aufruf an die
Kamera (Host automatisch aus CAMERA_URL, Port 80).

- /api/cam_framesize (Whitelist 10=VGA, 11=SVGA)
- zwei Buttons + Status-Feedback im Frontend

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 17:09:46 +02:00
parent b50a2d3316
commit 07c0e44cd8
2 changed files with 57 additions and 0 deletions

33
app.py
View File

@@ -6,6 +6,7 @@ import threading
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from threading import Event, Lock, Condition from threading import Event, Lock, Condition
from urllib.parse import urlparse
import cv2 import cv2
import numpy as np import numpy as np
@@ -839,6 +840,38 @@ def grabber_toggle():
return jsonify({"paused": paused}) return jsonify({"paused": paused})
# Erlaubte framesize-Werte der ESP32-CAM (OV2640): Wert -> Label.
FRAMESIZES = {10: "VGA 640x480", 11: "SVGA 800x600"}
@app.route("/api/cam_framesize", methods=["POST"])
def cam_framesize():
"""Setzt die Aufloesung der ESP32-CAM ueber deren /control-Endpoint.
Der Server proxyt den Aufruf (Browser braucht keinen Direktzugriff)."""
data = request.get_json(silent=True) or {}
try:
val = int(data.get("val"))
except (TypeError, ValueError):
abort(400, description="val (int) erforderlich")
if val not in FRAMESIZES:
abort(400, description="nur 10 (VGA) oder 11 (SVGA)")
host = urlparse(CAMERA_URL).hostname
if not host:
abort(500, description="CAMERA_URL ohne gueltigen Host")
# Steuer-Endpoint liegt auf Port 80 (Stream laeuft separat auf :81).
ctrl_url = f"http://{host}/control?var=framesize&val={val}"
try:
r = requests.get(ctrl_url, timeout=5)
r.raise_for_status()
except requests.RequestException as exc:
print(f"[cam] framesize set failed: {exc}", flush=True)
return jsonify({"ok": False, "error": str(exc)}), 502
print(f"[cam] framesize -> {FRAMESIZES[val]}", flush=True)
return jsonify({"ok": True, "val": val, "label": FRAMESIZES[val]})
@app.route("/api/reset_count", methods=["POST"]) @app.route("/api/reset_count", methods=["POST"])
def reset_count(): def reset_count():
data = request.get_json(silent=True) or {} data = request.get_json(silent=True) or {}

View File

@@ -153,6 +153,8 @@
<button id="setLineBtn">Zähllinie setzen</button> <button id="setLineBtn">Zähllinie setzen</button>
<button id="resetCountBtn" class="danger">Zähler zurücksetzen</button> <button id="resetCountBtn" class="danger">Zähler zurücksetzen</button>
<button id="grabberBtn">Kamera freigeben</button> <button id="grabberBtn">Kamera freigeben</button>
<button id="resVgaBtn" title="640×480 mehr FPS">VGA</button>
<button id="resSvgaBtn" title="800×600 mehr Detail">SVGA</button>
</div> </div>
<div class="info" id="infoText">Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.</div> <div class="info" id="infoText">Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.</div>
<a href="/">Back to Home</a> <a href="/">Back to Home</a>
@@ -334,6 +336,28 @@
} }
} }
async function setFramesize(val, label) {
infoText.textContent = `Setze Auflösung auf ${label}`;
try {
const r = await fetch('/api/cam_framesize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ val })
});
const d = await r.json();
infoText.textContent = d.ok
? `Auflösung auf ${label} gesetzt`
: `Fehler: ${d.error || 'Kamera nicht erreichbar'}`;
} catch (e) {
infoText.textContent = 'Fehler beim Setzen der Auflösung';
}
setTimeout(() => {
infoText.textContent = 'Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.';
}, 2500);
}
document.getElementById('resVgaBtn').addEventListener('click', () => setFramesize(10, 'VGA 640×480'));
document.getElementById('resSvgaBtn').addEventListener('click', () => setFramesize(11, 'SVGA 800×600'));
grabberBtn.addEventListener('click', async () => { grabberBtn.addEventListener('click', async () => {
grabberBtn.disabled = true; grabberBtn.disabled = true;
try { try {