Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ce98df596 |
@@ -1,5 +1,5 @@
|
|||||||
name: ShineBridge
|
name: ShineBridge
|
||||||
version: "1.12.0"
|
version: "1.12.1"
|
||||||
slug: shinebridge
|
slug: shinebridge
|
||||||
description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte
|
description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte
|
||||||
url: https://gitea.bitfire.work/retr0/shinebridge
|
url: https://gitea.bitfire.work/retr0/shinebridge
|
||||||
|
|||||||
@@ -511,6 +511,19 @@ def _restart_all():
|
|||||||
|
|
||||||
# ── REST API ──────────────────────────────────────────────────
|
# ── 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")
|
@app.get("/api/config")
|
||||||
def api_get_config():
|
def api_get_config():
|
||||||
with State.lock:
|
with State.lock:
|
||||||
|
|||||||
@@ -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
|
# nicht sinnlos den vollen Timeout abwarten, aber nie unter 0.5h
|
||||||
return max(base_timeout_h * 0.5, 0.5)
|
return max(base_timeout_h * 0.5, 0.5)
|
||||||
return base_timeout_h
|
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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -243,6 +243,7 @@
|
|||||||
<input type="number" id="cfg-site-lon" step="0.0001" placeholder="z.B. 13.4050"></div>
|
<input type="number" id="cfg-site-lon" step="0.0001" placeholder="z.B. 13.4050"></div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" onclick="saveLocation()">Speichern</button>
|
<button class="btn btn-primary" onclick="saveLocation()">Speichern</button>
|
||||||
|
<div id="weather-preview" style="margin-top:.75rem;font-size:.85rem"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
<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-backup-retention").value = cfg.config_backup_retention_days ?? 14;
|
||||||
document.getElementById("cfg-site-lat").value = cfg.site_lat ?? "";
|
document.getElementById("cfg-site-lat").value = cfg.site_lat ?? "";
|
||||||
document.getElementById("cfg-site-lon").value = cfg.site_lon ?? "";
|
document.getElementById("cfg-site-lon").value = cfg.site_lon ?? "";
|
||||||
|
loadWeatherPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveLocation() {
|
async function saveLocation() {
|
||||||
@@ -1263,9 +1265,30 @@ async function saveLocation() {
|
|||||||
body: JSON.stringify({ site_lat: lat === "" ? null : lat, site_lon: lon === "" ? null : lon }),
|
body: JSON.stringify({ site_lat: lat === "" ? null : lat, site_lon: lon === "" ? null : lon }),
|
||||||
});
|
});
|
||||||
showToast("Gespeichert!", "ok");
|
showToast("Gespeichert!", "ok");
|
||||||
|
setTimeout(loadWeatherPreview, 500);
|
||||||
} catch(e) { showToast("Fehler beim Speichern", "err"); }
|
} 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() {
|
async function savePrices() {
|
||||||
const isSpot = document.getElementById("tariff-spot").checked;
|
const isSpot = document.getElementById("tariff-spot").checked;
|
||||||
const body = {
|
const body = {
|
||||||
|
|||||||
Reference in New Issue
Block a user