diff --git a/haos-addon/config.yaml b/haos-addon/config.yaml index e2f5530..02a1080 100644 --- a/haos-addon/config.yaml +++ b/haos-addon/config.yaml @@ -1,5 +1,5 @@ name: ShineBridge -version: "1.10.2" +version: "1.10.3" 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,7 +13,6 @@ host_network: true ingress: true ingress_port: 8099 panel_icon: mdi:solar-power -homeassistant_api: true options: mqtt_broker: "core-mosquitto" mqtt_port: 1883 diff --git a/haos-addon/src/ha_entity_client.py b/haos-addon/src/ha_entity_client.py index 8d2dbac..919c0bc 100644 --- a/haos-addon/src/ha_entity_client.py +++ b/haos-addon/src/ha_entity_client.py @@ -1,6 +1,5 @@ import json import logging -import os import urllib.error import urllib.request from typing import Dict, Optional @@ -29,25 +28,24 @@ class HAEntityReader: (z.B. ein per ESPHome direkt in HA eingebundener ShineWifi-X-Stick) statt sie selbst per Modbus/UDP zu pollen. - Braucht 'homeassistant_api: true' in config.yaml — HAOS injiziert dann - SUPERVISOR_TOKEN und die interne API ist unter http://supervisor/core/api - erreichbar (kein manuell erstelltes Long-Lived Token nötig).""" + Nutzt bewusst ein normales Long-Lived Access Token gegen die HA-REST-API statt + den Supervisor-Proxy (http://supervisor/core/api): ShineBridge läuft mit + host_network: true, dadurch ist der Container NICHT im internen Supervisor- + Netzwerk und bekommt keinen SUPERVISOR_TOKEN injiziert. Der direkte API-Weg + funktioniert trotzdem, weil HA Core dank host_network ganz normal unter + localhost erreichbar ist.""" - BASE_URL = "http://supervisor/core/api" - - def __init__(self, entity_prefix: str): + def __init__(self, entity_prefix: str, base_url: str, token: str): self.entity_prefix = entity_prefix - self.token = os.environ.get("SUPERVISOR_TOKEN", "") - if not self.token: - related = [k for k in os.environ if "TOKEN" in k.upper() or "SUPERVISOR" in k.upper() or "HASSIO" in k.upper()] - log.warning("[HA-Entity] SUPERVISOR_TOKEN nicht gesetzt. Verwandte Env-Vars gefunden: %s", related or "keine") + self.base_url = (base_url or "http://localhost:8123").rstrip("/") + self.token = token def read(self, inverter=None) -> Optional[Dict[str, float]]: if not self.entity_prefix: log.error("[HA-Entity] Kein Entity-Prefix konfiguriert") return None if not self.token: - log.error("[HA-Entity %s] Kein SUPERVISOR_TOKEN — 'homeassistant_api: true' in config.yaml gesetzt und Add-on neu installiert?", self.entity_prefix) + log.error("[HA-Entity %s] Kein Long-Lived Access Token konfiguriert", self.entity_prefix) return None result: Dict[str, float] = {} @@ -61,7 +59,7 @@ class HAEntityReader: def _get_state(self, entity_id: str) -> Optional[float]: req = urllib.request.Request( - f"{self.BASE_URL}/states/{entity_id}", + f"{self.base_url}/api/states/{entity_id}", headers={"Authorization": f"Bearer {self.token}"}, ) try: diff --git a/haos-addon/src/main.py b/haos-addon/src/main.py index 84e2796..67458e3 100644 --- a/haos-addon/src/main.py +++ b/haos-addon/src/main.py @@ -280,7 +280,11 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event): log.info("[%s] Poll-Loop: %s @ %s (Goodwe UDP/8899) alle %ds", inv_id, inverter.name, host, interval) elif inverter.protocol == "ha_entity": - reader = HAEntityReader(entity_prefix=inv_cfg.get("ha_entity_prefix", "")) + reader = HAEntityReader( + entity_prefix=inv_cfg.get("ha_entity_prefix", ""), + base_url=inv_cfg.get("ha_base_url", "http://localhost:8123"), + token=inv_cfg.get("ha_token", ""), + ) log.info("[%s] Poll-Loop: %s (HA-Entity Prefix: %s) alle %ds", inv_id, inverter.name, inv_cfg.get("ha_entity_prefix", ""), interval) elif inverter.protocol == "kathrein": diff --git a/haos-addon/src/web/index.html b/haos-addon/src/web/index.html index 2dce7d0..0538a31 100644 --- a/haos-addon/src/web/index.html +++ b/haos-addon/src/web/index.html @@ -346,7 +346,13 @@ +
+ +
Meist unverändert lassen — dank host_network erreicht ShineBridge HA Core direkt über localhost.
+
+ +
Kein Supervisor-Token nutzbar (host_network) — braucht ein manuell in HA erstelltes Token.
@@ -1081,6 +1087,8 @@ async function openModal(invId) { document.getElementById("modal-port").value = inv.modbus_port || 502; document.getElementById("modal-addr").value = inv.modbus_address || 1; document.getElementById("modal-ha-prefix").value = inv.ha_entity_prefix || ""; + document.getElementById("modal-ha-url").value = inv.ha_base_url || "http://localhost:8123"; + document.getElementById("modal-ha-token").value = inv.ha_token || ""; document.getElementById("modal-prefix").value = inv.mqtt_topic_prefix || ""; document.getElementById("modal-interval").value = inv.update_interval || 30; document.getElementById("ems-min-pv").value = inv.ems_min_pv ?? 1400; @@ -1097,6 +1105,8 @@ async function openModal(invId) { document.getElementById("modal-port").value = "502"; document.getElementById("modal-addr").value = "1"; document.getElementById("modal-ha-prefix").value = ""; + document.getElementById("modal-ha-url").value = "http://localhost:8123"; + document.getElementById("modal-ha-token").value = ""; document.getElementById("modal-prefix").value = "growatt/wechselrichter" + (invertersList.length + 1); document.getElementById("modal-interval").value = "30"; document.getElementById("ems-min-pv").value = "1400"; @@ -1135,6 +1145,8 @@ async function saveInverter() { }; if (model === "GROWATT_WIFI_X") { inv.ha_entity_prefix = document.getElementById("modal-ha-prefix").value.trim(); + inv.ha_base_url = document.getElementById("modal-ha-url").value.trim() || "http://localhost:8123"; + inv.ha_token = document.getElementById("modal-ha-token").value.trim(); } if (model === "KATHREIN_WALLBOX") { inv.ems_min_pv = parseInt(document.getElementById("ems-min-pv").value);