diff --git a/haos-addon/config.yaml b/haos-addon/config.yaml index 0eedbfc..813f71c 100644 --- a/haos-addon/config.yaml +++ b/haos-addon/config.yaml @@ -1,5 +1,5 @@ name: ShineBridge -version: "1.8.13" +version: "1.8.14" slug: shinebridge description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI url: https://gitea.bitfire.work/retr0/shinebridge diff --git a/haos-addon/src/main.py b/haos-addon/src/main.py index 207dd6e..2fb7afe 100644 --- a/haos-addon/src/main.py +++ b/haos-addon/src/main.py @@ -101,6 +101,9 @@ def _defaults() -> Dict[str, Any]: "spot_country": "de", "spot_markup": 0.0, "spot_chart": True, + "billing_tracker_enabled": False, + "monthly_rate_eur": 0.0, + "grundpreis_eur_per_month": 0.0, "inverters": [], "surplus_devices": [], "z2m_base": "zigbee2mqtt", @@ -139,6 +142,9 @@ def save_config(): "spot_country": State.mqtt_cfg.get("spot_country", "de"), "spot_markup": State.mqtt_cfg.get("spot_markup", 0.0), "spot_chart": State.mqtt_cfg.get("spot_chart", True), + "billing_tracker_enabled": State.mqtt_cfg.get("billing_tracker_enabled", False), + "monthly_rate_eur": State.mqtt_cfg.get("monthly_rate_eur", 0.0), + "grundpreis_eur_per_month": State.mqtt_cfg.get("grundpreis_eur_per_month", 0.0), "inverters": State.inverters_cfg, "surplus_devices": State.surplus_devices_cfg, "z2m_base": State.z2m_base, @@ -410,6 +416,11 @@ def api_save_config(): State.mqtt_cfg[k] = str(data[k]) if "spot_chart" in data: State.mqtt_cfg["spot_chart"] = bool(data["spot_chart"]) + if "billing_tracker_enabled" in data: + State.mqtt_cfg["billing_tracker_enabled"] = bool(data["billing_tracker_enabled"]) + for k in ("monthly_rate_eur", "grundpreis_eur_per_month"): + if k in data: + State.mqtt_cfg[k] = float(data[k]) save_config() threading.Thread(target=_restart_all, daemon=True).start() return jsonify({"ok": True}) @@ -493,6 +504,29 @@ def api_period_energy(): result[period_type] = entry + if State.mqtt_cfg.get("billing_tracker_enabled", False): + monthly_rate = float(State.mqtt_cfg.get("monthly_rate_eur", 0.0)) + grundpreis = float(State.mqtt_cfg.get("grundpreis_eur_per_month", 0.0)) + yr_key = history.period_key("yearly", billing_day, billing_month) + yr_start = datetime.date.fromisoformat(yr_key) + days_elapsed = (datetime.date.today() - yr_start).days + months_elapsed = round(days_elapsed / 30.4375, 4) + total_paid = round(months_elapsed * monthly_rate, 2) + grundpreis_total= round(months_elapsed * grundpreis, 2) + energy_cost = result.get("yearly", {}).get("import_cost", 0.0) + total_cost = round(energy_cost + grundpreis_total, 2) + nachzahlung = round(total_cost - total_paid, 2) + result["billing_tracker"] = { + "monthly_rate_eur": monthly_rate, + "grundpreis_eur_per_month": grundpreis, + "months_elapsed": round(months_elapsed, 1), + "total_paid_eur": total_paid, + "grundpreis_total_eur": grundpreis_total, + "energy_cost_eur": energy_cost, + "total_cost_eur": total_cost, + "nachzahlung_eur": nachzahlung, + } + return jsonify(result) @app.get("/api/z2m-devices") diff --git a/haos-addon/src/web/index.html b/haos-addon/src/web/index.html index 0fbb321..93e6017 100644 --- a/haos-addon/src/web/index.html +++ b/haos-addon/src/web/index.html @@ -254,6 +254,23 @@
z.B. 1 . 4 = 01. April
+
+
+ + +
+ +
@@ -648,9 +665,43 @@ function renderEnergy(inverters, aggregates, period, spotData) { const spotHtml = (period.spot_chart !== false) ? renderSpotChart(spotData) : ''; + const bt = period.billing_tracker; + const trackerHtml = bt ? (() => { + const nz = bt.nachzahlung_eur; + const isNachzahlung = nz > 0; + const nzColor = isNachzahlung ? '#e05c5c' : '#4caf82'; + const nzLabel = isNachzahlung ? 'Voraussichtliche Nachzahlung' : 'Voraussichtliches Guthaben'; + const nzSign = isNachzahlung ? '+' : '−'; + return `
+
Abschlags-Übersicht · ${bt.months_elapsed} Monate
+
+
+
${fEur(bt.total_paid_eur)}
+
Bereits bezahlt
${bt.months_elapsed} × ${fEur(bt.monthly_rate_eur)}/Monat
+
+
+
${fEur(bt.grundpreis_total_eur)}
+
Grundpreis anteilig
${fEur(bt.grundpreis_eur_per_month)}/Monat
+
+
+
${fEur(bt.energy_cost_eur)}
+
Energiekosten
Netzbezug
+
+
+
+
+
${nzLabel}
+
Gesamtkosten ${fEur(bt.total_cost_eur)} − Bezahlt ${fEur(bt.total_paid_eur)}
+
+
${nzSign}${fEur(Math.abs(nz))}
+
+
`; + })() : ''; + el.innerHTML = `
${svg}${spotHtml}
${cards ? `
${cards}
` : ''} + ${trackerHtml}
`; } @@ -894,6 +945,11 @@ function updateTariffUI() { document.getElementById("tariff-spot-fields").style.display = isSpot ? "" : "none"; } +function updateBillingTrackerUI() { + const enabled = document.getElementById("cfg-billing-tracker").checked; + document.getElementById("billing-tracker-fields").style.display = enabled ? "" : "none"; +} + async function loadSettings() { const cfg = await fetchJSON(api("api/config")); globalConfig = cfg; @@ -910,6 +966,11 @@ async function loadSettings() { document.getElementById("cfg-spot-chart").checked = cfg.spot_chart ?? true; document.getElementById((cfg.tariff_type ?? "fixed") === "spot" ? "tariff-spot" : "tariff-fixed").checked = true; updateTariffUI(); + const trackerEnabled = cfg.billing_tracker_enabled ?? false; + document.getElementById("cfg-billing-tracker").checked = trackerEnabled; + document.getElementById("billing-tracker-fields").style.display = trackerEnabled ? "" : "none"; + document.getElementById("cfg-monthly-rate").value = cfg.monthly_rate_eur ?? 0; + document.getElementById("cfg-grundpreis").value = cfg.grundpreis_eur_per_month ?? 0; } async function savePrices() { @@ -923,6 +984,9 @@ async function savePrices() { spot_chart: document.getElementById("cfg-spot-chart").checked, billing_day: parseInt(document.getElementById("cfg-billing-day").value), billing_month: parseInt(document.getElementById("cfg-billing-month").value), + billing_tracker_enabled: document.getElementById("cfg-billing-tracker").checked, + monthly_rate_eur: parseFloat(document.getElementById("cfg-monthly-rate").value || 0), + grundpreis_eur_per_month: parseFloat(document.getElementById("cfg-grundpreis").value || 0), }; try { await fetchJSON(api("api/config"), {