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:
+19
-2
@@ -15,6 +15,7 @@ from modbus_client import ModbusReader
|
||||
from goodwe_client import GoodweReader
|
||||
from wallbox_client import WallboxReader
|
||||
from ha_entity_client import HAEntityReader
|
||||
import weather_forecast
|
||||
from ems_controller import EmsController
|
||||
from mqtt_publisher import MqttPublisher
|
||||
from surplus_devices import SurplusDeviceController
|
||||
@@ -125,6 +126,8 @@ def _defaults() -> Dict[str, Any]:
|
||||
"surplus_devices": [],
|
||||
"z2m_base": "zigbee2mqtt",
|
||||
"config_backup_retention_days": 14,
|
||||
"site_lat": None,
|
||||
"site_lon": None,
|
||||
}
|
||||
|
||||
def _load_json_safe(path: str) -> Optional[Dict]:
|
||||
@@ -176,6 +179,8 @@ def save_config():
|
||||
"surplus_devices": State.surplus_devices_cfg,
|
||||
"z2m_base": State.z2m_base,
|
||||
"config_backup_retention_days": State.mqtt_cfg.get("config_backup_retention_days", 14),
|
||||
"site_lat": State.mqtt_cfg.get("site_lat"),
|
||||
"site_lon": State.mqtt_cfg.get("site_lon"),
|
||||
}
|
||||
# Backup der letzten guten Config anlegen
|
||||
if os.path.exists(CONFIG_PATH):
|
||||
@@ -366,7 +371,10 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
||||
# charging_state aus 0x0060 (solo-Read); fehlt bei kein Fahrzeug → 0 (Idle)
|
||||
charging_state = int(values.get("charging_state", 0))
|
||||
wallbox_power = values.get("total_power", 0.0)
|
||||
ems_status = ems.update(reader, pv_surplus, charging_state, wallbox_power)
|
||||
site_lat = State.mqtt_cfg.get("site_lat")
|
||||
site_lon = State.mqtt_cfg.get("site_lon")
|
||||
timeout_override = weather_forecast.adjusted_timeout_h(ems.pv_timeout_h, site_lat, site_lon)
|
||||
ems_status = ems.update(reader, pv_surplus, charging_state, wallbox_power, timeout_override)
|
||||
values["ems_status_code"] = float(charging_state)
|
||||
log.info("[%s] EMS: %s | PV-Überschuss: %.0fW", inv_id, ems_status, pv_surplus)
|
||||
|
||||
@@ -527,6 +535,15 @@ def api_save_config():
|
||||
State.mqtt_cfg[k] = float(data[k])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
for k in ("site_lat", "site_lon"):
|
||||
if k in data:
|
||||
if data[k] in (None, ""):
|
||||
State.mqtt_cfg[k] = None
|
||||
else:
|
||||
try:
|
||||
State.mqtt_cfg[k] = float(data[k])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
for k in ("billing_day", "billing_month", "config_backup_retention_days"):
|
||||
if k in data and data[k] is not None:
|
||||
try:
|
||||
@@ -1049,7 +1066,7 @@ if __name__ == "__main__":
|
||||
"price_import", "price_export", "billing_day", "billing_month",
|
||||
"tariff_type", "spot_country", "spot_markup", "spot_chart",
|
||||
"billing_tracker_enabled", "monthly_rate_eur", "grundpreis_eur_per_month",
|
||||
"config_backup_retention_days",
|
||||
"config_backup_retention_days", "site_lat", "site_lon",
|
||||
) if k in cfg}
|
||||
State.inverters_cfg = cfg.get("inverters", [])
|
||||
State.surplus_devices_cfg = cfg.get("surplus_devices", [])
|
||||
|
||||
Reference in New Issue
Block a user