ShineBridge v1.12.0 — Feature: Prognosebasierter Zwangslade-Timeout (Open-Meteo)

Neuer Standort-Bereich im Einstellungen-Tab (Breitengrad/Längengrad, optional,
site-weit statt pro Gerät). Neues Modul weather_forecast.py fragt Open-Meteos
kostenlose stündliche Solarstrahlungs-Prognose (shortwave_radiation, 48h
voraus, timezone=auto) ab, gecacht alle 3h.

adjusted_timeout_h() passt den Zwangslade-Timeout adaptiv an:
- bald nennenswerte Sonne zu erwarten (naechste 3h) -> Timeout verlaengern
  (x1.75, gedeckelt auf Basiswert+4h)
- auch im 12h-Fenster keine nennenswerte Sonne zu erwarten -> Timeout
  verkuerzen (x0.5, min. 0.5h) statt sinnlos den vollen Timeout abzuwarten
- ohne Standort (lat/lon leer): unveraendertes Verhalten, Feature ist Opt-in

EmsController.update() bekommt einen neuen optionalen timeout_override_h-
Parameter statt selbst Standortdaten zu kennen — Wetter-Logik bleibt im
Poll-Loop, EmsController fokussiert auf die eigentliche Lade-Steuerung.

Auf Sonnenstand-Berechnung bewusst verzichtet — die stuendliche Strahlungs-
prognose bildet Tag/Nacht-Verlauf implizit schon ab, eigene Ephemeriden-
Berechnung waere doppelte Arbeit ohne Mehrwert.

Teil 2 von 2 des 'Prognosebasiertes Laden'-Roadmap-Punkts, damit komplett.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-08-26 15:57:04 +02:00
co-authored by Claude Sonnet 5
parent 202759cc84
commit 8f5106e3a0
7 changed files with 144 additions and 8 deletions
+26
View File
@@ -233,6 +233,18 @@
<button class="btn btn-primary" onclick="saveMqtt()">Speichern & Neu starten</button>
</div>
<div class="settings-section">
<h3>Standort</h3>
<p style="color:var(--text-dim);font-size:.85rem;margin:0 0 .75rem">Für die prognosebasierte Anpassung des Zwangslade-Timeouts (Solarstrahlungs-Vorhersage via Open-Meteo). Optional — leer lassen deaktiviert die Anpassung, Zwangsladen nutzt dann weiter den festen Timeout.</p>
<div style="display:flex;gap:10px;flex-wrap:wrap">
<div class="field" style="max-width:180px"><label>Breitengrad</label>
<input type="number" id="cfg-site-lat" step="0.0001" placeholder="z.B. 52.5200"></div>
<div class="field" style="max-width:180px"><label>Längengrad</label>
<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>
<div class="settings-section">
<h3>Stromtarif</h3>
<div class="field">
@@ -1238,6 +1250,20 @@ async function loadSettings() {
document.getElementById("cfg-monthly-rate").value = cfg.monthly_rate_eur ?? 0;
document.getElementById("cfg-grundpreis").value = cfg.grundpreis_eur_per_month ?? 0;
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 ?? "";
}
async function saveLocation() {
const lat = document.getElementById("cfg-site-lat").value;
const lon = document.getElementById("cfg-site-lon").value;
try {
await fetchJSON(api("api/config"), {
method: "POST", headers: {"Content-Type": "application/json"},
body: JSON.stringify({ site_lat: lat === "" ? null : lat, site_lon: lon === "" ? null : lon }),
});
showToast("Gespeichert!", "ok");
} catch(e) { showToast("Fehler beim Speichern", "err"); }
}
async function savePrices() {