From 07c0e44cd86565ac44822cbe512b52d61fe3e0c6 Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Tue, 2 Jun 2026 17:09:46 +0200 Subject: [PATCH] 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) --- app.py | 33 +++++++++++++++++++++++++++++++++ templates/webcam.html | 24 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/app.py b/app.py index 40b24e4..0180ad2 100644 --- a/app.py +++ b/app.py @@ -6,6 +6,7 @@ import threading from datetime import datetime from zoneinfo import ZoneInfo from threading import Event, Lock, Condition +from urllib.parse import urlparse import cv2 import numpy as np @@ -839,6 +840,38 @@ def grabber_toggle(): 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"]) def reset_count(): data = request.get_json(silent=True) or {} diff --git a/templates/webcam.html b/templates/webcam.html index 71d3f60..d999c91 100644 --- a/templates/webcam.html +++ b/templates/webcam.html @@ -153,6 +153,8 @@ + +
Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.
Back to Home @@ -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.disabled = true; try {