BYD-Speicher direkt aus der BMU auslesen

gatherBYDData.py fragt die BMU der HVM unter 192.168.16.254:8080 ab
(BE-Connect-Protokoll, nach ioBroker.bydhvs): Ladestand und SOH laut BMU,
128 Zellspannungen, 64 Temperaturen, Spreizung, Ausgleich, Fehlerbits,
Gesamtzaehler. Das Netzwerkmodul startet alle ~102 s neu und bedient nur
die ersten Verbindungen danach - eigene Klopf-Schleife, ein Satz etwa alle
100 s. Werte unter solarManager/byd/#, Historie in byd und byd_zellen.

tbatt kommt jetzt aus der BMU statt fest 0. mqttClient.publish legt ein
einzelnes Dataclass-Objekt in rtData als Untertopics ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 16:16:07 +02:00
co-authored by Claude Opus 5
parent 46ef245f7c
commit 0a37d8d763
5 changed files with 344 additions and 7 deletions
+16 -4
View File
@@ -17,6 +17,7 @@ flowchart LR
WR["Wechselrichter<br/><small>GoodWe · OpenDTU · DTU-BI</small>"] WR["Wechselrichter<br/><small>GoodWe · OpenDTU · DTU-BI</small>"]
EM3["Stromzähler<br/><small>2× Shelly EM3</small>"] EM3["Stromzähler<br/><small>2× Shelly EM3</small>"]
HEIZ["Heizung"] HEIZ["Heizung"]
BYD["BYD-Speicher<br/><small>BMU</small>"]
GOE["go-eCharger"] GOE["go-eCharger"]
WPILOT["Wattpilot"] WPILOT["Wattpilot"]
SKODA["MySkoda-API"] SKODA["MySkoda-API"]
@@ -25,7 +26,7 @@ flowchart LR
VENTILE["Ventilsteuerungen<br/><small>2× ESP32</small>"] VENTILE["Ventilsteuerungen<br/><small>2× ESP32</small>"]
HAUSGER["Thermostate · Shellys · Schalter<br/><small>melden sich per<br/>Home-Assistant-Discovery</small>"] HAUSGER["Thermostate · Shellys · Schalter<br/><small>melden sich per<br/>Home-Assistant-Discovery</small>"]
MGR["<b>solarManager.py</b><br/><small>gatherModbusData · gatherOpenDTUData<br/>gatherDTUBIData · gatherShellyEM3Data EG/UG<br/>gatherHeaterData · gatherSkodaData<br/>gatherWaterData · charger_goE</small>"] MGR["<b>solarManager.py</b><br/><small>gatherModbusData · gatherOpenDTUData<br/>gatherDTUBIData · gatherShellyEM3Data EG/UG<br/>gatherHeaterData · gatherSkodaData<br/>gatherBYDData · gatherWaterData<br/>charger_goE</small>"]
WPB["<b>wattpilot_bruecke.py</b>"] WPB["<b>wattpilot_bruecke.py</b>"]
WSB["<b>wsMQTTbridge.py</b>"] WSB["<b>wsMQTTbridge.py</b>"]
RAIN["<b>gatherRainData.py</b>"] RAIN["<b>gatherRainData.py</b>"]
@@ -36,6 +37,7 @@ flowchart LR
WR -->|Modbus · HTTP| MGR WR -->|Modbus · HTTP| MGR
EM3 -->|HTTP| MGR EM3 -->|HTTP| MGR
HEIZ -->|HTTP| MGR HEIZ -->|HTTP| MGR
BYD -->|Modbus-RTU in TCP| MGR
GOE -->|HTTP| MGR GOE -->|HTTP| MGR
WPILOT -->|WebSocket| MGR WPILOT -->|WebSocket| MGR
SKODA -->|HTTPS| MGR SKODA -->|HTTPS| MGR
@@ -44,7 +46,7 @@ flowchart LR
METEO -->|HTTPS| RAIN METEO -->|HTTPS| RAIN
SOLARLOG -->|zisterne| MGR SOLARLOG -->|zisterne| MGR
MGR -->|"EnergyFlow · skoda<br/>skoda_ladepunkte"| SOLARLOG MGR -->|"EnergyFlow · skoda<br/>skoda_ladepunkte · byd<br/>byd_zellen"| SOLARLOG
MGR -->|"solarManager/#"| BROKER MGR -->|"solarManager/#"| BROKER
WPB -->|"wattpilot/#"| BROKER WPB -->|"wattpilot/#"| BROKER
@@ -57,7 +59,7 @@ flowchart LR
classDef quelle fill:#eef4fb,stroke:#7f9dc0 classDef quelle fill:#eef4fb,stroke:#7f9dc0
classDef skript fill:#fff6e5,stroke:#d0a548 classDef skript fill:#fff6e5,stroke:#d0a548
classDef speicher fill:#eaf5ee,stroke:#6fa981 classDef speicher fill:#eaf5ee,stroke:#6fa981
class WR,EM3,HEIZ,GOE,WPILOT,SKODA,STATION,METEO,VENTILE,HAUSGER quelle class WR,EM3,HEIZ,BYD,GOE,WPILOT,SKODA,STATION,METEO,VENTILE,HAUSGER quelle
class MGR,WPB,WSB,RAIN skript class MGR,WPB,WSB,RAIN skript
class BROKER,SOLARLOG speicher class BROKER,SOLARLOG speicher
``` ```
@@ -68,11 +70,20 @@ Home-Assistant-Discovery spricht, schreiben ohne Umweg auf den Broker. Für
deren Messwerte ist also **kein Skript** zuständig — wer sie sucht, sucht am deren Messwerte ist also **kein Skript** zuständig — wer sie sucht, sucht am
Gerät, nicht im Quelltext. Gerät, nicht im Quelltext.
`solarManager.py` ist der Sonderfall: ein Prozess, aber acht `solarManager.py` ist der Sonderfall: ein Prozess, aber neun
Sammler-Module, jedes für eine Anlage. Sie laufen nicht einzeln, sondern Sammler-Module, jedes für eine Anlage. Sie laufen nicht einzeln, sondern
werden importiert; ihr gemeinsames Ergebnis geht als ein Baum nach werden importiert; ihr gemeinsames Ergebnis geht als ein Baum nach
`solarManager/#`. `solarManager/#`.
`gatherBYDData` liest die BMU der Batterie direkt (192.168.16.254:8080) und
legt alles unter `solarManager/byd/…` ab: Ladestand und Gesundheit laut BMU,
Zellspannungen (`zellen`, 128 Werte in mV, 16 je Modul), Temperaturen
(`temperaturen`, 64 Werte, 8 je Modul), Spreizung, Ausgleich, Fehlerbits und
die Gesamtzähler. `ok` fällt auf 0, wenn zehn Minuten nichts kam. Das
Netzwerkmodul der BMU startet etwa alle 102 Sekunden neu und antwortet nur
kurz danach — der Sammler klopft deshalb jede Sekunde an und bekommt so rund
alle 100 Sekunden einen vollständigen Satz. Einzelheiten im Kopf des Moduls.
Die Zisterne fällt aus der Reihe — ihr Stand steht in `solarLog`, und Die Zisterne fällt aus der Reihe — ihr Stand steht in `solarLog`, und
`gatherWaterData` liest ihn von dort. Die Daten laufen also durch die `gatherWaterData` liest ihn von dort. Die Daten laufen also durch die
Datenbank hindurch von einem Prozess zum nächsten. Datenbank hindurch von einem Prozess zum nächsten.
@@ -147,6 +158,7 @@ von Hand, wenn sich am Bestand etwas geändert hat.
| `Raumtemp/#`, `Power_*/#`, `wasser/#` | die Geräte selbst | Browser, Runner | | `Raumtemp/#`, `Power_*/#`, `wasser/#` | die Geräte selbst | Browser, Runner |
| `homeassistant/#` | die Geräte selbst | `device_discovery.py` | | `homeassistant/#` | die Geräte selbst | `device_discovery.py` |
| **`solarLog`** | `solarManager.py` | `ajax/*.php`, `gatherWaterData` | | **`solarLog`** | `solarManager.py` | `ajax/*.php`, `gatherWaterData` |
| `byd`, `byd_zellen` (in `solarLog`) | `gatherBYDData.py`, alle 5 bzw. 15 Minuten | Web-Repo (`solarLog_byd.sql`) |
| **`homeMesh`** | `device_discovery.py`, Runner, Web-Editor | Runner, `ajax/*.php` | | **`homeMesh`** | `device_discovery.py`, Runner, Web-Editor | Runner, `ajax/*.php` |
| `skoda.conf` | Einstellungen → Fahrzeug (`restricted/skodaKeys.php`) oder von Hand | `gatherSkodaData.py`, `ajax/skodaCmd.php` | | `skoda.conf` | Einstellungen → Fahrzeug (`restricted/skodaKeys.php`) oder von Hand | `gatherSkodaData.py`, `ajax/skodaCmd.php` |
+7
View File
@@ -47,3 +47,10 @@ intervall_minuten = 30
; Messwerte geleert, und es wird nicht mehr bewaessert - eine retained "0,0 mm" ; Messwerte geleert, und es wird nicht mehr bewaessert - eine retained "0,0 mm"
; von gestern wuerde den Runner sonst giessen lassen. ; von gestern wuerde den Runner sonst giessen lassen.
max_alter_stunden = 3 max_alter_stunden = 3
; ===========================================================================
; BYD-Speicher (gatherBYDData.py)
; ===========================================================================
; Adresse der BMU. Ab Werk 192.168.16.254; der Abschnitt darf fehlen.
[byd]
host = 192.168.16.254
+308
View File
@@ -0,0 +1,308 @@
"""BYD Battery-Box HVM direkt an der BMU auslesen.
Der Gen24 liefert ueber Modbus nur, was er selbst von der Batterie wissen
muss: Ladestand, Spannung, Strom. Alles darunter - 128 Zellspannungen, 64
Temperaturfuehler, Gesundheit (SOH), Gesamtzaehler, Ausgleich - kennt nur die
BMU. Sie spricht auf Port 8080 dasselbe Protokoll wie die BE-Connect-App:
Modbus-RTU in TCP verpackt. Ablauf und Byte-Lagen stammen aus dem
ioBroker-Adapter bydhvs (MIT, github.com/christianh17/ioBroker.bydhvs).
Drei Eigenheiten bestimmen den Aufbau:
Alles in EINER Verbindung, Anfrage 0 zuerst. Eine neue Verbindung, die
gleich mit Anfrage 1 beginnt, schliesst die BMU wortlos.
Das Netzwerkmodul (Hi-Link HLK-RM08K) startet etwa alle 102 Sekunden neu,
unabhaengig davon, ob jemand fragt. Waehrend des Neustarts laufen
Verbindungen rund 15 Sekunden ins Leere; danach bedient es nur die ersten
ein, zwei Verbindungen, alle spaeteren schliesst es sofort mit 0 Bytes.
Das Fenster ist also nur wenige Sekunden breit (beobachtet am 15.09.2026).
Der Sammler klopft deshalb in einer eigenen Schleife jede _KLOPFEN
Sekunde mit kurzem Verbindungs-Timeout an - der 3-Sekunden-Takt des
Managers waere dafuer zu grob. Geht eine Verbindung durch, laeuft der
ganze Zyklus in ihr; die haelt das Modul offen, auch ueber die Messpause.
Nach einem Erfolg ruht die Schleife _RUHE Sekunden bis kurz vor dem
naechsten Neustart.
Die Lade-/Entladezaehler stehen bei dieser HVM wortvertauscht (low word
zuerst). Mit der Lesart des Adapters kaemen 31 Millionen kWh heraus.
Wie die anderen langsamen Sammler blockiert gatherData() nie: die Schleife
laeuft als Hintergrund-Task, zurueck kommt immer der letzte Stand.
Historie: alle 5 Minuten eine Zeile in byd, alle 15 Minuten die Zellen
in byd_zellen (siehe solarLog_byd.sql im Web-Repository).
"""
import asyncio
import datetime
import json
import logging
import struct
import time
from dataclasses import dataclass, field
from typing import List
import mysql.connector as mc
import konfig
_LOGGER = logging.getLogger(__name__)
_HOST = konfig.wert("byd", "host", "192.168.16.254")
_PORT = 8080
_KLOPFEN = 1 # s zwischen zwei Versuchen, solange das Fenster zu ist
_RUHE = 80 # s nach einem Erfolg - das naechste Fenster kommt erst nach ~102 s
_VERBINDEN = 1.5 # s Timeout fuers Verbinden; im Neustart antwortet niemand
_ZEITLIMIT = 6 # s je Antwort
_MESSPAUSE = 8 # s, die die BMU nach "Zellmessung starten" braucht
_VERALTET = 600 # s ohne Erfolg, danach gilt der Stand als nicht mehr aktuell
_DB_ZEILE = 300 # s zwischen zwei Zeilen in byd
_DB_ZELLEN = 900 # s zwischen zwei Zeilen in byd_zellen
_ANFRAGE = {
"stamm": bytes.fromhex("010300000066c5e0"),
"echt": bytes.fromhex("01030500001984cc"),
"typ": bytes.fromhex("010300100003040e"),
"messen": bytes.fromhex("0110055000020400018100f853"),
"status": bytes.fromhex("010305510001d517"),
"zellen": bytes.fromhex("01030558004104e5"),
}
# Bit j in Paket 1, Byte 29/30 - Wortlaut aus dem Adapter, uebersetzt.
_FEHLER = [
"Zu warm beim Laden", "Zu kalt beim Laden", "Ueberstrom beim Entladen",
"Ueberstrom beim Laden", "Hauptstromkreis gestoert", "Kurzschlussalarm",
"Zellen ungleich", "Stromsensor gestoert", "Batteriespannung zu hoch",
"Batteriespannung zu niedrig", "Zellspannung zu hoch",
"Zellspannung zu niedrig", "Spannungssensor gestoert",
"Temperatursensor gestoert", "Zu warm beim Entladen",
"Zu kalt beim Entladen",
]
@dataclass
class BYDData:
ok: int = 0 # 1 = Stand juenger als _VERALTET
stand: int = 0 # Unix-Zeit des letzten vollstaendigen Zyklus
seriennummer: str = ""
firmware: str = ""
module: int = 0
soc: float = 0.0 # % laut BMU
soh: float = 0.0 # %
spannung: float = 0.0 # V am Batterieausgang
strom: float = 0.0 # A, positiv = laden
leistung: float = 0.0 # W, positiv = laden
tMin: float = 0.0 # degC ueber alle Fuehler
tMax: float = 0.0
zelleMin: int = 0 # mV
zelleMax: int = 0
zelleMinNr: int = 0 # 1..128
zelleMaxNr: int = 0
spreizung: int = 0 # mV
ausgleich: int = 0 # Zahl der Zellen, die gerade ausgeglichen werden
ausgleichRoh: str = "" # "0x" + 32 Hex-Zeichen, Bitlage noch nicht bestaetigt;
# das Praefix haelt mqttClient davon ab, es als Zahl zu lesen
fehler: int = 0
fehlertext: str = ""
geladen: float = 0.0 # kWh seit Inbetriebnahme
entladen: float = 0.0
zellen: List[int] = field(default_factory=list) # mV, 16 je Modul
temperaturen: List[int] = field(default_factory=list) # degC, 8 je Modul
ret = BYDData()
_task = None
_letzteZeile = 0.0
_letzteZellen = 0.0
def _crc_ok(p):
crc = 0xFFFF
for b in p:
crc ^= b
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
return crc == 0
class _Abgewiesen(Exception):
"""Die BMU hat die Verbindung ohne Antwort geschlossen - Fenster zu."""
async def _frage(reader, writer, name, pause=0.2):
await asyncio.sleep(pause)
writer.write(_ANFRAGE[name])
await writer.drain()
buf = b""
while True:
teil = await asyncio.wait_for(reader.read(1024), _ZEITLIMIT)
if not teil:
raise _Abgewiesen("%s: geschlossen nach %d Bytes" % (name, len(buf)))
buf += teil
if len(buf) < 3:
continue
if buf[1] & 0x80:
n = 5
elif buf[1] == 0x10:
n = 8
else:
n = buf[2] + 5
if len(buf) >= n:
p = buf[:n]
if not _crc_ok(p):
raise IOError("%s: Pruefsumme falsch" % name)
if p[1] & 0x80:
raise IOError("%s: BMU meldet Ausnahme %d" % (name, p[2]))
return p
def _i16(b, o):
return struct.unpack_from(">h", b, o)[0]
def _u16(b, o):
return struct.unpack_from(">H", b, o)[0]
def _u32_vertauscht(b, o):
"""Zwei Worte, das niedrige zuerst."""
return (_u16(b, o + 2) << 16) | _u16(b, o)
async def _zyklus():
reader, writer = await asyncio.wait_for(
asyncio.open_connection(_HOST, _PORT), _VERBINDEN)
try:
p0 = await _frage(reader, writer, "stamm", 0)
p1 = await _frage(reader, writer, "echt")
p2 = await _frage(reader, writer, "typ")
module = p0[36] % 16
hvm = p2[5] == 1
nZellen = module * (16 if hvm else 32)
nTemps = module * (8 if hvm else 12)
await _frage(reader, writer, "messen")
await asyncio.sleep(_MESSPAUSE)
await _frage(reader, writer, "status", 0)
p5 = await _frage(reader, writer, "zellen")
p6 = await _frage(reader, writer, "zellen")
p7 = await _frage(reader, writer, "zellen")
p8 = await _frage(reader, writer, "zellen")
finally:
writer.close()
zellen = [_i16(p5, 101 + 2 * i) for i in range(16)]
zellen += [_i16(p6, 5 + 2 * i) for i in range(min(64, nZellen - 16))]
zellen += [_i16(p7, 5 + 2 * i) for i in range(min(48, nZellen - 80))]
temps = [p7[103 + i] for i in range(min(30, nTemps))]
temps += [p8[5 + i] for i in range(min(34, nTemps - 30))]
d = BYDData()
d.seriennummer = p0[3:22].decode("ascii", "replace")
d.firmware = "BMS V%d.%d" % (p0[31], p0[32])
d.module = module
d.soc = _i16(p1, 3)
d.soh = _i16(p1, 9)
d.strom = _i16(p1, 11) / 10
d.spannung = _u16(p1, 35) / 100
d.leistung = round(d.strom * d.spannung)
d.fehler = _u16(p1, 29)
d.fehlertext = "; ".join(t for j, t in enumerate(_FEHLER) if d.fehler & (1 << j))
d.geladen = _u32_vertauscht(p1, 37) / 10
d.entladen = _u32_vertauscht(p1, 41) / 10
d.zellen = zellen
d.temperaturen = temps
gueltig = [z for z in zellen if 2000 < z < 4000]
if gueltig:
d.zelleMin, d.zelleMax = min(gueltig), max(gueltig)
d.zelleMinNr = zellen.index(d.zelleMin) + 1
d.zelleMaxNr = zellen.index(d.zelleMax) + 1
d.spreizung = d.zelleMax - d.zelleMin
if temps:
d.tMin, d.tMax = min(temps), max(temps)
else:
d.tMin, d.tMax = _i16(p1, 17), _i16(p1, 15)
d.ausgleichRoh = "0x" + p5[17:33].hex()
d.ausgleich = bin(int(d.ausgleichRoh, 16)).count("1")
d.stand = int(time.time())
d.ok = 1
return d
def _speichern(d):
"""Historie schreiben, gedrosselt auf _DB_ZEILE / _DB_ZELLEN."""
global _letzteZeile, _letzteZellen
jetzt = time.time()
zeile = jetzt - _letzteZeile >= _DB_ZEILE
zellen = jetzt - _letzteZellen >= _DB_ZELLEN
if not (zeile or zellen):
return
zeit = datetime.datetime.now().replace(microsecond=0)
try:
with mc.connect(**konfig.datenbank()) as verbindung:
with verbindung.cursor() as cursor:
if zeile:
cursor.execute(
"INSERT INTO byd (datetime, soc, soh, spannung, strom, leistung, "
"t_min, t_max, zelle_min, zelle_max, zelle_min_nr, zelle_max_nr, "
"spreizung, ausgleich, fehler, geladen_kwh, entladen_kwh) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);",
(zeit, d.soc, d.soh, d.spannung, d.strom, d.leistung,
d.tMin, d.tMax, d.zelleMin, d.zelleMax, d.zelleMinNr,
d.zelleMaxNr, d.spreizung, d.ausgleich, d.fehler,
d.geladen, d.entladen))
if zellen:
cursor.execute(
"INSERT INTO byd_zellen (datetime, zellen, temperaturen, ausgleich) "
"VALUES (%s,%s,%s,%s);",
(zeit, json.dumps(d.zellen), json.dumps(d.temperaturen),
d.ausgleichRoh[2:]))
verbindung.commit()
if zeile:
_letzteZeile = jetzt
if zellen:
_letzteZellen = jetzt
except Exception as e:
_LOGGER.error("BYD: Historie nicht geschrieben: %s", e)
async def _schleife():
global ret
gewarnt = False
letzterErfolg = time.time()
while True:
try:
d = await _zyklus()
except (_Abgewiesen, ConnectionError, asyncio.TimeoutError, OSError) as e:
# Ein zugesperrtes Fenster ist der Normalfall und kein Log wert.
# Erst wenn gar nichts mehr durchkommt, soll es jemand sehen.
_LOGGER.debug("BYD: %s: %s", type(e).__name__, e)
if not gewarnt and time.time() - letzterErfolg > _VERALTET:
_LOGGER.warning("BYD: seit %d Minuten keine Daten von %s (%s)",
_VERALTET // 60, _HOST, e)
gewarnt = True
if ret.ok and time.time() - ret.stand > _VERALTET:
ret.ok = 0
await asyncio.sleep(_KLOPFEN)
except Exception:
_LOGGER.exception("BYD: Antwort nicht verstanden")
await asyncio.sleep(_RUHE)
else:
ret = d
letzterErfolg = time.time()
if gewarnt:
_LOGGER.warning("BYD: Daten kommen wieder")
gewarnt = False
await asyncio.get_running_loop().run_in_executor(None, _speichern, d)
await asyncio.sleep(_RUHE)
async def gatherData() -> BYDData:
"""Startet beim ersten Aufruf die Schleife und gibt den letzten Stand zurueck."""
global _task
if _task is None or _task.done():
if _task is not None and not _task.cancelled() and _task.exception():
_LOGGER.error("BYD: Schleife beendet: %r - starte neu", _task.exception())
_task = asyncio.ensure_future(_schleife())
return ret
+5 -1
View File
@@ -54,7 +54,11 @@ def publish(dict,topic="solarManager"):
mqttClient.publish(topic+"/"+key,value) mqttClient.publish(topic+"/"+key,value)
else: else:
for key, value in dict.items(): for key, value in dict.items():
if hasattr(value, '__len__') and (not isinstance(value, str)): # Ein einzelnes Dataclass-Objekt (z.B. rtData["byd"]) hat keine
# Laenge und fiel frueher in den Zahlenzweig - "value is no number".
if dataclasses.is_dataclass(value) and not isinstance(value, type):
publish(value,topic+"/"+key)
elif hasattr(value, '__len__') and (not isinstance(value, str)):
if dataclasses.is_dataclass(value): #make subtopics of dataclasses if dataclasses.is_dataclass(value): #make subtopics of dataclasses
publish(value,topic+"/"+key) publish(value,topic+"/"+key)
else: else:
+8 -2
View File
@@ -16,6 +16,7 @@ import gatherWaterData
import gatherHeaterData import gatherHeaterData
import gatherShellyEM3DataEG import gatherShellyEM3DataEG
import gatherShellyEM3DataUG import gatherShellyEM3DataUG
import gatherBYDData
import datetime import datetime
from dateutil import tz from dateutil import tz
from suntime import Sun, SunTimeException from suntime import Sun, SunTimeException
@@ -156,6 +157,7 @@ rtData = {"P_Load":0.0,
"waterHeight":0, "waterHeight":0,
"waterTemp": 0.0, "waterTemp": 0.0,
"inverters":[InvData(),InvData(),InvData(),InvData(),InvData(),InvData(),InvData(),InvData()], "inverters":[InvData(),InvData(),InvData(),InvData(),InvData(),InvData(),InvData(),InvData()],
"byd":gatherBYDData.BYDData(), # direkt aus der BMU, siehe gatherBYDData.py
"P_AC": 0.0 "P_AC": 0.0
} }
avgData = dict(rtData) avgData = dict(rtData)
@@ -268,7 +270,7 @@ async def repeat(interval):
rtData["inverters"][0].p_PV.append(0) rtData["inverters"][0].p_PV.append(0)
while True: while True:
inv,dtubi,dtu,heat,chrg,pEG,pUG,skoda,slp = await asyncio.gather( inv,dtubi,dtu,heat,chrg,pEG,pUG,skoda,byd,slp = await asyncio.gather(
gatherModbusData.get_runtime_data(estProduction, sunset), gatherModbusData.get_runtime_data(estProduction, sunset),
gatherDTUBIData.gatherData(), gatherDTUBIData.gatherData(),
gatherOpenDTUData.gatherData(), gatherOpenDTUData.gatherData(),
@@ -280,8 +282,10 @@ async def repeat(interval):
rtData["evPowerOG"], rtData["evPlugOG"], rtData["evPowerOG"], rtData["evPlugOG"],
rtData["P_PV"]/1000.0, rtData["P_Grid"]/1000.0, rtData["P_PV"]/1000.0, rtData["P_Grid"]/1000.0,
rtData["wbWh"], rtData["wbogWh"]), rtData["wbWh"], rtData["wbogWh"]),
gatherBYDData.gatherData(),
asyncio.sleep(interval), asyncio.sleep(interval),
) )
rtData["byd"] = byd
rtData["evSOC"] = skoda.soc rtData["evSOC"] = skoda.soc
rtData["evRange"] = skoda.range_km rtData["evRange"] = skoda.range_km
rtData["evRemChrgTime"] = skoda.chgRemMin rtData["evRemChrgTime"] = skoda.chgRemMin
@@ -400,7 +404,9 @@ async def repeat(interval):
rtData["P_WR"] = inv.p_wr rtData["P_WR"] = inv.p_wr
rtData["ibatt"] = inv.ibatt rtData["ibatt"] = inv.ibatt
rtData["ubatt"] = inv.ubatt rtData["ubatt"] = inv.ubatt
rtData["tbatt"] = 0 # Der Gen24 kennt keine Batterietemperatur; die BMU schon. Die
# Spalte battTemp in EnergyFlow stand bisher immer auf 0.
rtData["tbatt"] = byd.tMax if byd.ok else 0
rtData["crgMaxPct"] = inv.crgMaxPct rtData["crgMaxPct"] = inv.crgMaxPct
#rtData["pwrMaxPct"] = inv.pwrMaxPct #rtData["pwrMaxPct"] = inv.pwrMaxPct
if((rtData["P_PV"]+rtData["P_Akku"]) != 0): if((rtData["P_PV"]+rtData["P_Akku"]) != 0):