Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
789e25967a | ||
|
|
1c787349e3 |
@@ -1,5 +1,5 @@
|
|||||||
name: ShineBridge
|
name: ShineBridge
|
||||||
version: "1.10.1"
|
version: "1.10.3"
|
||||||
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
|
||||||
@@ -13,7 +13,6 @@ host_network: true
|
|||||||
ingress: true
|
ingress: true
|
||||||
ingress_port: 8099
|
ingress_port: 8099
|
||||||
panel_icon: mdi:solar-power
|
panel_icon: mdi:solar-power
|
||||||
homeassistant_api: true
|
|
||||||
options:
|
options:
|
||||||
mqtt_broker: "core-mosquitto"
|
mqtt_broker: "core-mosquitto"
|
||||||
mqtt_port: 1883
|
mqtt_port: 1883
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
@@ -29,22 +28,24 @@ class HAEntityReader:
|
|||||||
(z.B. ein per ESPHome direkt in HA eingebundener ShineWifi-X-Stick) statt sie
|
(z.B. ein per ESPHome direkt in HA eingebundener ShineWifi-X-Stick) statt sie
|
||||||
selbst per Modbus/UDP zu pollen.
|
selbst per Modbus/UDP zu pollen.
|
||||||
|
|
||||||
Braucht 'homeassistant_api: true' in config.yaml — HAOS injiziert dann
|
Nutzt bewusst ein normales Long-Lived Access Token gegen die HA-REST-API statt
|
||||||
SUPERVISOR_TOKEN und die interne API ist unter http://supervisor/core/api
|
den Supervisor-Proxy (http://supervisor/core/api): ShineBridge läuft mit
|
||||||
erreichbar (kein manuell erstelltes Long-Lived Token nötig)."""
|
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, base_url: str, token: str):
|
||||||
|
|
||||||
def __init__(self, entity_prefix: str):
|
|
||||||
self.entity_prefix = entity_prefix
|
self.entity_prefix = entity_prefix
|
||||||
self.token = os.environ.get("SUPERVISOR_TOKEN", "")
|
self.base_url = (base_url or "http://localhost:8123").rstrip("/")
|
||||||
|
self.token = token
|
||||||
|
|
||||||
def read(self, inverter=None) -> Optional[Dict[str, float]]:
|
def read(self, inverter=None) -> Optional[Dict[str, float]]:
|
||||||
if not self.entity_prefix:
|
if not self.entity_prefix:
|
||||||
log.error("[HA-Entity] Kein Entity-Prefix konfiguriert")
|
log.error("[HA-Entity] Kein Entity-Prefix konfiguriert")
|
||||||
return None
|
return None
|
||||||
if not self.token:
|
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
|
return None
|
||||||
|
|
||||||
result: Dict[str, float] = {}
|
result: Dict[str, float] = {}
|
||||||
@@ -58,7 +59,7 @@ class HAEntityReader:
|
|||||||
|
|
||||||
def _get_state(self, entity_id: str) -> Optional[float]:
|
def _get_state(self, entity_id: str) -> Optional[float]:
|
||||||
req = urllib.request.Request(
|
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}"},
|
headers={"Authorization": f"Bearer {self.token}"},
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -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",
|
log.info("[%s] Poll-Loop: %s @ %s (Goodwe UDP/8899) alle %ds",
|
||||||
inv_id, inverter.name, host, interval)
|
inv_id, inverter.name, host, interval)
|
||||||
elif inverter.protocol == "ha_entity":
|
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",
|
log.info("[%s] Poll-Loop: %s (HA-Entity Prefix: %s) alle %ds",
|
||||||
inv_id, inverter.name, inv_cfg.get("ha_entity_prefix", ""), interval)
|
inv_id, inverter.name, inv_cfg.get("ha_entity_prefix", ""), interval)
|
||||||
elif inverter.protocol == "kathrein":
|
elif inverter.protocol == "kathrein":
|
||||||
|
|||||||
@@ -346,7 +346,13 @@
|
|||||||
<div id="ha-entity-fields" style="display:none">
|
<div id="ha-entity-fields" style="display:none">
|
||||||
<div class="field"><label>HA Entity-Prefix</label>
|
<div class="field"><label>HA Entity-Prefix</label>
|
||||||
<input type="text" id="modal-ha-prefix" placeholder="growatt_wifi_x_1">
|
<input type="text" id="modal-ha-prefix" placeholder="growatt_wifi_x_1">
|
||||||
<div style="font-size:11px;color:var(--muted);margin-top:3px">Ohne "sensor."-Präfix — z.B. bei <code>sensor.growatt_wifi_x_1_dcpower</code> ist der Wert <code>growatt_wifi_x_1</code>. Braucht ein bereits per ESPHome in HA eingebundenes Gerät, keinen direkten Netzwerkzugriff.</div></div>
|
<div style="font-size:11px;color:var(--muted);margin-top:3px">Ohne "sensor."-Präfix — z.B. bei <code>sensor.growatt_wifi_x_1_dcpower</code> ist der Wert <code>growatt_wifi_x_1</code>. Braucht ein bereits per ESPHome in HA eingebundenes Gerät.</div></div>
|
||||||
|
<div class="field"><label>Home Assistant URL</label>
|
||||||
|
<input type="text" id="modal-ha-url" placeholder="http://localhost:8123">
|
||||||
|
<div style="font-size:11px;color:var(--muted);margin-top:3px">Meist unverändert lassen — dank host_network erreicht ShineBridge HA Core direkt über localhost.</div></div>
|
||||||
|
<div class="field"><label>Long-Lived Access Token</label>
|
||||||
|
<input type="password" id="modal-ha-token" placeholder="Einstellungen → Profil → Sicherheit → Langlebige Zugriffs-Token">
|
||||||
|
<div style="font-size:11px;color:var(--muted);margin-top:3px">Kein Supervisor-Token nutzbar (host_network) — braucht ein manuell in HA erstelltes Token.</div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field"><label>MQTT Topic-Präfix</label>
|
<div class="field"><label>MQTT Topic-Präfix</label>
|
||||||
<input type="text" id="modal-prefix" placeholder="growatt/wechselrichter1"></div>
|
<input type="text" id="modal-prefix" placeholder="growatt/wechselrichter1"></div>
|
||||||
@@ -1081,6 +1087,8 @@ async function openModal(invId) {
|
|||||||
document.getElementById("modal-port").value = inv.modbus_port || 502;
|
document.getElementById("modal-port").value = inv.modbus_port || 502;
|
||||||
document.getElementById("modal-addr").value = inv.modbus_address || 1;
|
document.getElementById("modal-addr").value = inv.modbus_address || 1;
|
||||||
document.getElementById("modal-ha-prefix").value = inv.ha_entity_prefix || "";
|
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-prefix").value = inv.mqtt_topic_prefix || "";
|
||||||
document.getElementById("modal-interval").value = inv.update_interval || 30;
|
document.getElementById("modal-interval").value = inv.update_interval || 30;
|
||||||
document.getElementById("ems-min-pv").value = inv.ems_min_pv ?? 1400;
|
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-port").value = "502";
|
||||||
document.getElementById("modal-addr").value = "1";
|
document.getElementById("modal-addr").value = "1";
|
||||||
document.getElementById("modal-ha-prefix").value = "";
|
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-prefix").value = "growatt/wechselrichter" + (invertersList.length + 1);
|
||||||
document.getElementById("modal-interval").value = "30";
|
document.getElementById("modal-interval").value = "30";
|
||||||
document.getElementById("ems-min-pv").value = "1400";
|
document.getElementById("ems-min-pv").value = "1400";
|
||||||
@@ -1135,6 +1145,8 @@ async function saveInverter() {
|
|||||||
};
|
};
|
||||||
if (model === "GROWATT_WIFI_X") {
|
if (model === "GROWATT_WIFI_X") {
|
||||||
inv.ha_entity_prefix = document.getElementById("modal-ha-prefix").value.trim();
|
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") {
|
if (model === "KATHREIN_WALLBOX") {
|
||||||
inv.ems_min_pv = parseInt(document.getElementById("ems-min-pv").value);
|
inv.ems_min_pv = parseInt(document.getElementById("ems-min-pv").value);
|
||||||
|
|||||||
Reference in New Issue
Block a user