diff --git a/haos-addon/src/main.py b/haos-addon/src/main.py index 6b2c9a8..fdf1cba 100644 --- a/haos-addon/src/main.py +++ b/haos-addon/src/main.py @@ -355,6 +355,59 @@ def api_get_models(): def api_new_id(): return jsonify({"id": uuid.uuid4().hex[:8]}) + +@app.get("/api/export-config") +def api_export_config(): + with State.lock: + data = { + "shinebridge_export": True, + "version": 1, + "exported_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "mqtt": { + "broker": State.mqtt_cfg.get("mqtt_broker", ""), + "port": State.mqtt_cfg.get("mqtt_port", 1883), + "user": State.mqtt_cfg.get("mqtt_user", ""), + }, + "inverters": State.inverters_cfg, + } + from flask import Response + filename = f"shinebridge-config-{time.strftime('%Y%m%d-%H%M%S')}.json" + return Response( + json.dumps(data, indent=2, ensure_ascii=False), + mimetype="application/json", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + + +@app.post("/api/import-config") +def api_import_config(): + data = request.get_json(force=True) or {} + if not data.get("shinebridge_export"): + return jsonify({"error": "Keine gültige ShineBridge-Export-Datei"}), 400 + + inverters = data.get("inverters", []) + if not isinstance(inverters, list): + return jsonify({"error": "inverters ungültig"}), 400 + for inv in inverters: + if not inv.get("modbus_ip"): + return jsonify({"error": f"modbus_ip fehlt in Gerät {inv.get('name', '?')}"}), 400 + if inv.get("inverter_model") not in INVERTERS: + return jsonify({"error": f"Unbekanntes Modell: {inv.get('inverter_model')}"}), 400 + + with State.lock: + if mqtt := data.get("mqtt"): + if mqtt.get("broker"): + State.mqtt_cfg["mqtt_broker"] = mqtt["broker"] + if mqtt.get("port"): + State.mqtt_cfg["mqtt_port"] = int(mqtt["port"]) + if mqtt.get("user"): + State.mqtt_cfg["mqtt_user"] = mqtt["user"] + State.inverters_cfg = inverters + save_config() + + threading.Thread(target=_restart_all, daemon=True).start() + return jsonify({"ok": True, "inverters": len(inverters)}) + @app.get("/") def index(): return send_from_directory(WEB_DIR, "index.html") diff --git a/haos-addon/src/web/index.html b/haos-addon/src/web/index.html index 9b268a0..5587262 100644 --- a/haos-addon/src/web/index.html +++ b/haos-addon/src/web/index.html @@ -91,6 +91,8 @@ .btn-danger:hover { background: rgba(248,81,73,.1); } .btn-primary { background: var(--accent); color: #000; border-color: var(--accent); font-weight: 700; } .btn-primary:hover { opacity: .85; } + .btn-secondary { color: var(--blue); border-color: var(--blue); } + .btn-secondary:hover { background: rgba(88,166,255,.1); } .inv-card-meta { font-size: 12px; color: var(--text-dim); line-height: 1.7; } .add-btn { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 20px; background: var(--surface); border: 2px dashed var(--border); @@ -189,6 +191,19 @@ + +
+

Konfiguration sichern

+

Alle Geräte und MQTT-Einstellungen als JSON exportieren und bei einer Neuinstallation wieder einlesen.

+
+ + +
+
+
@@ -529,6 +544,37 @@ async function saveMqtt() { } } +// ── Export / Import ─────────────────────────────────────────── + +function exportConfig() { + window.location.href = api("api/export-config"); +} + +async function importConfig(input) { + const file = input.files[0]; + if (!file) return; + const resultEl = document.getElementById("import-result"); + resultEl.textContent = "Wird geladen…"; + resultEl.style.color = "var(--text-dim)"; + try { + const text = await file.text(); + const data = JSON.parse(text); + const res = await fetchJSON(api("api/import-config"), { + method: "POST", headers: {"Content-Type": "application/json"}, + body: JSON.stringify(data), + }); + resultEl.textContent = `✓ ${res.inverters} Gerät(e) importiert. Neustart läuft…`; + resultEl.style.color = "var(--green)"; + showToast(`${res.inverters} Gerät(e) importiert`, "ok"); + setTimeout(() => { loadSettings(); loadInverters(); }, 3000); + } catch(e) { + resultEl.textContent = `Fehler: ${e.message}`; + resultEl.style.color = "var(--red)"; + showToast("Import fehlgeschlagen", "err"); + } + input.value = ""; +} + // ── Init ────────────────────────────────────────────────────── (async () => {