Files
SolarManager/mqttClient.py
T
adminandClaude Opus 5 0a37d8d763 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>
2026-09-15 16:16:07 +02:00

93 lines
3.7 KiB
Python

import sys
import dataclasses
import logging
import json
sys.path.append("./")
import paho.mqtt.client as mqtt
logging.config.fileConfig('./logging.ini')
_LOGGER = logging.getLogger()
class EnhancedJSONEncoder(json.JSONEncoder):
def default(self, o):
if dataclasses.is_dataclass(o):
return dataclasses.asdict(o)
return super().default(o)
unacked_publish = set()
mqttClient=mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
#mqttClient.on_publish = on_publish
mqttClient.user_data_set(unacked_publish)
logging.basicConfig(
format="%(asctime)s %(message)s",
level=logging.WARN,
)
def publish(dict,topic="solarManager"):
if dataclasses.is_dataclass(dict):
for field in dataclasses.fields(dict):
key = field.name
value = getattr(dict, field.name)
if hasattr(value, '__len__') and (not isinstance(value, str)):
if dataclasses.is_dataclass(value): #make subtopics of dataclasses
publish(value,topic+"/"+key)
else:
#try: #make subtopics of dataclass arrays
if len(value):
if dataclasses.is_dataclass(value[0]):
i=0
for d in value:
publish(d,topic+"/"+key+str(i))
i=i+1
#except:
else:
mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder))
else: #output empty array anyway to keep data consistent
mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder))
else:
try:
flt = float(value)
mqttClient.publish(topic+"/"+key,round(flt,2))
except ValueError:
mqttClient.publish(topic+"/"+key,value)
else:
for key, value in dict.items():
# 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
publish(value,topic+"/"+key)
else:
#try: #make subtopics of dataclass arrays
if len(value):
if dataclasses.is_dataclass(value[0]):
i=0
for d in value:
publish(d,topic+"/"+key+str(i))
i=i+1
#except:
else:
mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder))
else: #output empty array anyway to keep data consistent
mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder))
else:
try:
flt = float(value)
mqttClient.publish(topic+"/"+key,round(flt,2))
except ValueError:
mqttClient.publish(topic+"/"+key,value)
except:
_LOGGER.error(f"value is no number")
#print(message)
def startMqttClient():
mqttClient.connect("localhost", 1883, 60)
mqttClient.loop_start()
#await asyncio.Future() # run forever
def closeMqttClient():
mqttClient.disconnect()