From 7cfc958cf06c76695d29e3bc98e59ed4970791a4 Mon Sep 17 00:00:00 2001 From: Julian Vollmer Date: Mon, 18 May 2026 15:54:53 +0200 Subject: [PATCH] Add manual AP mode override via web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - State Machine prüft /tmp/babycam-ap-override: 'on'/'off' = manuell, fehlt = Automatik - Neue API-Endpunkte: POST /ap/on, /ap/off, /ap/auto - Access-Point-Karte zeigt jetzt 3 Buttons (An / Aus / Auto) + aktuellen Modus - Manuell überschreibt die Automatik, Auto gibt Kontrolle zurück an die State Machine Co-Authored-By: Claude Sonnet 4.6 --- app/main.py | 30 +++++++++++++++++ app/templates/index.html | 16 ++++++++- scripts/network_state.py | 70 +++++++++++++++++++++++++++++----------- 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/app/main.py b/app/main.py index e45e02b..b61429c 100644 --- a/app/main.py +++ b/app/main.py @@ -10,6 +10,7 @@ import subprocess import json import re import time +import os from flask import Flask, Response, render_template, request, jsonify app = Flask(__name__) @@ -113,6 +114,7 @@ def get_system_status(): "ips": ips, "wifi_ssid": wifi_ssid, "ap_active": ap_active, + "ap_override": get_ap_override(), "cam_available": cam_available, "uptime": _get_uptime(), } @@ -159,6 +161,34 @@ def wifi_scan(): return jsonify(get_wifi_networks()) +@app.route("/ap/", methods=["POST"]) +def ap_control(action): + override_file = "/tmp/babycam-ap-override" + if action == "on": + with open(override_file, "w") as f: + f.write("on") + return jsonify({"success": True, "mode": "on"}) + elif action == "off": + with open(override_file, "w") as f: + f.write("off") + return jsonify({"success": True, "mode": "off"}) + elif action == "auto": + try: + os.remove(override_file) + except FileNotFoundError: + pass + return jsonify({"success": True, "mode": "auto"}) + return jsonify({"success": False, "error": "Unbekannte Aktion"}), 400 + + +def get_ap_override(): + try: + with open("/tmp/babycam-ap-override") as f: + return f.read().strip().lower() + except FileNotFoundError: + return "auto" + + @app.route("/wifi/connect", methods=["POST"]) def wifi_connect(): data = request.get_json() diff --git a/app/templates/index.html b/app/templates/index.html index 7be8545..72cfeab 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -219,10 +219,18 @@
-
+
{{ "aktiv" if status.ap_active else "aus" }} + + {{ {"on":"manuell an","off":"manuell aus","auto":"automatisch"}[status.ap_override] }} + +
+
+ + +
@@ -281,6 +289,12 @@ document.getElementById("wifi-password").focus(); } + async function setAP(action) { + const res = await fetch(`/ap/${action}`, { method: "POST" }); + const data = await res.json(); + if (data.success) setTimeout(() => location.reload(), 800); + } + async function connectWifi() { if (!selectedSSID) return; const password = document.getElementById("wifi-password").value; diff --git a/scripts/network_state.py b/scripts/network_state.py index 7018874..8386a37 100644 --- a/scripts/network_state.py +++ b/scripts/network_state.py @@ -2,15 +2,21 @@ """ Babycam Network State Machine Verwaltet die drei Betriebsmodi: - A – Heimnetz (wlan0 verbunden) - B – Kein WLAN (AP auf wlan1) + A – Heimnetz (wlan0 verbunden, AP aus) + B – Kein WLAN (AP auf wlan1 automatisch) C – Setup-Modus (AP läuft, User konfiguriert WLAN) + +Override-Datei: /tmp/babycam-ap-override + "on" → AP immer an (manuell) + "off" → AP immer aus (manuell) + fehlt → Automatik """ import subprocess import time import logging import sys +import os logging.basicConfig( level=logging.INFO, @@ -23,21 +29,30 @@ AP_INTERFACE = "wlan1" CLIENT_INTERFACE = "wlan0" AP_IP = "192.168.50.1" CHECK_INTERVAL = 30 # Sekunden +OVERRIDE_FILE = "/tmp/babycam-ap-override" def run(cmd, check=False): - result = subprocess.run( - cmd, shell=True, capture_output=True, text=True - ) + result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if check and result.returncode != 0: log.error(f"Fehler bei: {cmd}\n{result.stderr}") return result +def get_override(): + """Liest die manuelle Override-Einstellung. Gibt 'on', 'off' oder None zurück.""" + try: + with open(OVERRIDE_FILE) as f: + val = f.read().strip().lower() + if val in ("on", "off"): + return val + except FileNotFoundError: + pass + return None + + def is_wlan0_connected(): - result = run( - f"nmcli -t -f GENERAL.STATE device show {CLIENT_INTERFACE}" - ) + result = run(f"nmcli -t -f GENERAL.STATE device show {CLIENT_INTERFACE}") return "100 (connected)" in result.stdout @@ -68,21 +83,40 @@ def main(): current_mode = None while True: + override = get_override() connected = is_wlan0_connected() ap_running = is_ap_running() - if connected: - if current_mode != "A": - log.info("Modus A: Heimnetz aktiv.") - if ap_running: - stop_ap() - current_mode = "A" - else: - if current_mode != "B": - log.info("Modus B: Kein WLAN – starte Access Point.") + if override == "on": + # Manuell: AP immer an + if current_mode != "MANUAL_ON": + log.info("Modus: Manuell – Access Point erzwungen AN.") if not ap_running: start_ap() - current_mode = "B" + current_mode = "MANUAL_ON" + + elif override == "off": + # Manuell: AP immer aus + if current_mode != "MANUAL_OFF": + log.info("Modus: Manuell – Access Point erzwungen AUS.") + if ap_running: + stop_ap() + current_mode = "MANUAL_OFF" + + else: + # Automatik + if connected: + if current_mode != "A": + log.info("Modus A: Heimnetz aktiv – AP aus.") + if ap_running: + stop_ap() + current_mode = "A" + else: + if current_mode != "B": + log.info("Modus B: Kein WLAN – starte Access Point.") + if not ap_running: + start_ap() + current_mode = "B" time.sleep(CHECK_INTERVAL)