Add manual AP mode override via web UI
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
d9514e23d7
commit
7cfc958cf0
30
app/main.py
30
app/main.py
|
|
@ -10,6 +10,7 @@ import subprocess
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import os
|
||||||
from flask import Flask, Response, render_template, request, jsonify
|
from flask import Flask, Response, render_template, request, jsonify
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
@ -113,6 +114,7 @@ def get_system_status():
|
||||||
"ips": ips,
|
"ips": ips,
|
||||||
"wifi_ssid": wifi_ssid,
|
"wifi_ssid": wifi_ssid,
|
||||||
"ap_active": ap_active,
|
"ap_active": ap_active,
|
||||||
|
"ap_override": get_ap_override(),
|
||||||
"cam_available": cam_available,
|
"cam_available": cam_available,
|
||||||
"uptime": _get_uptime(),
|
"uptime": _get_uptime(),
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +161,34 @@ def wifi_scan():
|
||||||
return jsonify(get_wifi_networks())
|
return jsonify(get_wifi_networks())
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/ap/<action>", 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"])
|
@app.route("/wifi/connect", methods=["POST"])
|
||||||
def wifi_connect():
|
def wifi_connect():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
|
||||||
|
|
@ -219,10 +219,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<label>Access Point</label>
|
<label>Access Point</label>
|
||||||
<div class="value">
|
<div class="value" style="display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;">
|
||||||
<span class="badge {{ 'on' if status.ap_active else 'off' }}">
|
<span class="badge {{ 'on' if status.ap_active else 'off' }}">
|
||||||
{{ "aktiv" if status.ap_active else "aus" }}
|
{{ "aktiv" if status.ap_active else "aus" }}
|
||||||
</span>
|
</span>
|
||||||
|
<span style="font-size:.75rem;color:#666;">
|
||||||
|
{{ {"on":"manuell an","off":"manuell aus","auto":"automatisch"}[status.ap_override] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:.4rem;margin-top:.6rem;flex-wrap:wrap;">
|
||||||
|
<button onclick="setAP('on')" class="{{ 'primary' if status.ap_override == 'on' else '' }}" style="font-size:.75rem;padding:.3rem .7rem;">An</button>
|
||||||
|
<button onclick="setAP('off')" class="{{ 'primary' if status.ap_override == 'off' else '' }}" style="font-size:.75rem;padding:.3rem .7rem;">Aus</button>
|
||||||
|
<button onclick="setAP('auto')"class="{{ 'primary' if status.ap_override == 'auto' else '' }}" style="font-size:.75rem;padding:.3rem .7rem;">Auto</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
@ -281,6 +289,12 @@
|
||||||
document.getElementById("wifi-password").focus();
|
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() {
|
async function connectWifi() {
|
||||||
if (!selectedSSID) return;
|
if (!selectedSSID) return;
|
||||||
const password = document.getElementById("wifi-password").value;
|
const password = document.getElementById("wifi-password").value;
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,21 @@
|
||||||
"""
|
"""
|
||||||
Babycam Network State Machine
|
Babycam Network State Machine
|
||||||
Verwaltet die drei Betriebsmodi:
|
Verwaltet die drei Betriebsmodi:
|
||||||
A – Heimnetz (wlan0 verbunden)
|
A – Heimnetz (wlan0 verbunden, AP aus)
|
||||||
B – Kein WLAN (AP auf wlan1)
|
B – Kein WLAN (AP auf wlan1 automatisch)
|
||||||
C – Setup-Modus (AP läuft, User konfiguriert WLAN)
|
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 subprocess
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
|
|
@ -23,21 +29,30 @@ AP_INTERFACE = "wlan1"
|
||||||
CLIENT_INTERFACE = "wlan0"
|
CLIENT_INTERFACE = "wlan0"
|
||||||
AP_IP = "192.168.50.1"
|
AP_IP = "192.168.50.1"
|
||||||
CHECK_INTERVAL = 30 # Sekunden
|
CHECK_INTERVAL = 30 # Sekunden
|
||||||
|
OVERRIDE_FILE = "/tmp/babycam-ap-override"
|
||||||
|
|
||||||
|
|
||||||
def run(cmd, check=False):
|
def run(cmd, check=False):
|
||||||
result = subprocess.run(
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||||
cmd, shell=True, capture_output=True, text=True
|
|
||||||
)
|
|
||||||
if check and result.returncode != 0:
|
if check and result.returncode != 0:
|
||||||
log.error(f"Fehler bei: {cmd}\n{result.stderr}")
|
log.error(f"Fehler bei: {cmd}\n{result.stderr}")
|
||||||
return result
|
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():
|
def is_wlan0_connected():
|
||||||
result = run(
|
result = run(f"nmcli -t -f GENERAL.STATE device show {CLIENT_INTERFACE}")
|
||||||
f"nmcli -t -f GENERAL.STATE device show {CLIENT_INTERFACE}"
|
|
||||||
)
|
|
||||||
return "100 (connected)" in result.stdout
|
return "100 (connected)" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -68,12 +83,31 @@ def main():
|
||||||
current_mode = None
|
current_mode = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
override = get_override()
|
||||||
connected = is_wlan0_connected()
|
connected = is_wlan0_connected()
|
||||||
ap_running = is_ap_running()
|
ap_running = is_ap_running()
|
||||||
|
|
||||||
|
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 = "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 connected:
|
||||||
if current_mode != "A":
|
if current_mode != "A":
|
||||||
log.info("Modus A: Heimnetz aktiv.")
|
log.info("Modus A: Heimnetz aktiv – AP aus.")
|
||||||
if ap_running:
|
if ap_running:
|
||||||
stop_ap()
|
stop_ap()
|
||||||
current_mode = "A"
|
current_mode = "A"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue