Compare commits

..
1 Commits
Author SHA1 Message Date
retr0andClaude Sonnet 5 1ce98df596 ShineBridge v1.12.1 — Feature: Wetterprognose-Vorschau im Einstellungen-Tab
v1.12.0 hat die Prognose nur intern verrechnet, nirgends sichtbar gemacht.
Neuer Endpunkt GET /api/weather-forecast (weather_forecast.summary()) liefert
aktuelle Solarstrahlung (naechste 3h/12h Maximum) + ein Beispiel, wie sich der
Zwangslade-Timeout dadurch gerade veraendern wuerde (Basis: erster konfigurierter
Kathrein-Timeout, sonst 2h-Default). Wird direkt unter den Standort-Feldern
angezeigt, aktualisiert sich nach dem Speichern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 16:28:34 +02:00
4 changed files with 73 additions and 1 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: ShineBridge
version: "1.12.0"
version: "1.12.1"
slug: shinebridge
description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte
url: https://gitea.bitfire.work/retr0/shinebridge
+13
View File
@@ -511,6 +511,19 @@ def _restart_all():
# ── REST API ──────────────────────────────────────────────────
@app.get("/api/weather-forecast")
def api_weather_forecast():
with State.lock:
lat = State.mqtt_cfg.get("site_lat")
lon = State.mqtt_cfg.get("site_lon")
# Beispiel-Basiswert: erstes konfigurierte Kathrein-Timeout, sonst 2h-Default
base = next(
(inv.get("ems_timeout") for inv in State.inverters_cfg if inv.get("ems_timeout")),
2.0,
)
return jsonify(weather_forecast.summary(lat, lon, float(base)))
@app.get("/api/config")
def api_get_config():
with State.lock:
+36
View File
@@ -84,3 +84,39 @@ def adjusted_timeout_h(base_timeout_h: float, lat: Optional[float], lon: Optiona
# nicht sinnlos den vollen Timeout abwarten, aber nie unter 0.5h
return max(base_timeout_h * 0.5, 0.5)
return base_timeout_h
def summary(lat: Optional[float], lon: Optional[float], base_timeout_h: float = 2.0) -> dict:
"""Für die UI: aktueller Prognose-Stand + Beispiel, wie sich ein Timeout
dadurch veraendern wuerde. Nicht fuers eigentliche EMS-Update genutzt
(das ruft adjusted_timeout_h direkt auf) — nur zur Anzeige/Diagnose."""
if lat is None or lon is None:
return {"configured": False}
hourly = get_hourly_ghi(lat, lon)
if not hourly:
return {"configured": True, "error": "Prognose aktuell nicht abrufbar"}
now = time.time()
near = [ghi for ts, ghi in hourly if now <= ts < now + 3 * 3600]
horizon = [ghi for ts, ghi in hourly if now <= ts < now + 12 * 3600]
next_3h_max = round(max(near), 0) if near else 0
horizon_12h_max = round(max(horizon), 0) if horizon else 0
adjusted = adjusted_timeout_h(base_timeout_h, lat, lon)
if adjusted > base_timeout_h:
verdict = "verlängert"
elif adjusted < base_timeout_h:
verdict = "verkürzt"
else:
verdict = "unverändert"
return {
"configured": True,
"next_3h_max_ghi": next_3h_max,
"horizon_12h_max_ghi": horizon_12h_max,
"sun_threshold": SUN_THRESHOLD,
"example_base_h": base_timeout_h,
"example_adjusted_h": round(adjusted, 2),
"verdict": verdict,
}
+23
View File
@@ -243,6 +243,7 @@
<input type="number" id="cfg-site-lon" step="0.0001" placeholder="z.B. 13.4050"></div>
</div>
<button class="btn btn-primary" onclick="saveLocation()">Speichern</button>
<div id="weather-preview" style="margin-top:.75rem;font-size:.85rem"></div>
</div>
<div class="settings-section">
@@ -1252,6 +1253,7 @@ async function loadSettings() {
document.getElementById("cfg-backup-retention").value = cfg.config_backup_retention_days ?? 14;
document.getElementById("cfg-site-lat").value = cfg.site_lat ?? "";
document.getElementById("cfg-site-lon").value = cfg.site_lon ?? "";
loadWeatherPreview();
}
async function saveLocation() {
@@ -1263,9 +1265,30 @@ async function saveLocation() {
body: JSON.stringify({ site_lat: lat === "" ? null : lat, site_lon: lon === "" ? null : lon }),
});
showToast("Gespeichert!", "ok");
setTimeout(loadWeatherPreview, 500);
} catch(e) { showToast("Fehler beim Speichern", "err"); }
}
async function loadWeatherPreview() {
const el = document.getElementById("weather-preview");
let data;
try { data = await fetchJSON(api("api/weather-forecast")); }
catch(e) { el.innerHTML = ""; return; }
if (!data.configured) {
el.innerHTML = `<span style="color:var(--text-dim)">Kein Standort gesetzt — Zwangslade-Timeout bleibt fest.</span>`;
return;
}
if (data.error) {
el.innerHTML = `<span style="color:var(--red)">${esc(data.error)}</span>`;
return;
}
const verdictColor = data.verdict === "verlängert" ? "var(--green)" : data.verdict === "verkürzt" ? "var(--orange, #ffa657)" : "var(--text-dim)";
el.innerHTML = `
<div style="color:var(--text-dim)">Solarstrahlung nächste 3h: <strong style="color:var(--text)">${data.next_3h_max_ghi} W/m²</strong> · nächste 12h: <strong style="color:var(--text)">${data.horizon_12h_max_ghi} W/m²</strong></div>
<div style="margin-top:4px;color:${verdictColor}">Beispiel bei ${data.example_base_h}h Basis-Timeout: <strong>${data.example_adjusted_h}h</strong> (${data.verdict})</div>`;
}
async function savePrices() {
const isSpot = document.getElementById("tariff-spot").checked;
const body = {