'lot' steht in der API-Beschreibung als jsonType integer, die Firmware schickt aber ein Objekt. Die Bibliothek reicht es unveraendert an paho weiter, paho nimmt nur Skalare - und die Ausnahme riss dieselbe Schleife mit wie der KeyError auf unbekannte Felder. Im rotierten Log machte dieser zweite Fehler 1.264.882 von 2.985.071 Zeilen aus. mqtt_get_encoded_property wird jetzt ebenfalls ersetzt: was nach der Kodierung kein Skalar ist, geht als JSON hinaus, einmalig mit Warnung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
135 lines
5.0 KiB
Python
Executable File
135 lines
5.0 KiB
Python
Executable File
#!/usr/local/bin/python3.10
|
|
"""Startet die wattpilotshell, aber mit einer Reparatur an der MQTT-Bruecke.
|
|
|
|
Das Problem
|
|
-----------
|
|
Die Wallbox schickt in ihren Statusnachrichten Felder, die die
|
|
API-Beschreibung der Bibliothek (wattpilot 0.2, Stand 2022) nicht kennt -
|
|
gemessen 34 Stueck, darunter 'clea', 'cle', 'opad' und die ganze
|
|
'ocpp*'-Familie. Die Bibliothek schlaegt jedes Feld ungeprueft in ihrem
|
|
Verzeichnis nach:
|
|
|
|
for prop_name, value in msg_dict["status"].items():
|
|
pd = wpdef["properties"][prop_name] # KeyError bei 'clea'
|
|
mqtt_publish_property(wp, mqtt_client, pd, value)
|
|
|
|
Der KeyError reisst die restliche Schleife mit. Alles, was in derselben
|
|
Nachricht *hinter* dem unbekannten Feld steht, wird nie veroeffentlicht -
|
|
beim Verbindungsaufbau trifft das unter anderem 'amp', 'car', 'ust', 'err',
|
|
'cbl', 'pha' und 'wh'. Sichtbar wurde das nur als Fehlerpaar im Log, etwa
|
|
alle 16 Sekunden:
|
|
|
|
ERROR:wattpilot:Error received from WebSocketApp: 'clea'
|
|
ERROR:websocket:error from callback ... Wattpilot.__on_message ...: 'clea'
|
|
|
|
Der zweite Fehler
|
|
-----------------
|
|
Dieselbe Ursache aus der anderen Richtung: 'lot' steht in der
|
|
API-Beschreibung als jsonType 'integer', die Firmware schickt aber ein
|
|
Objekt. Die Bibliothek reicht es dann unveraendert an paho weiter, und das
|
|
lehnt ab:
|
|
|
|
ERROR:wattpilot:Error received from WebSocketApp:
|
|
payload must be a string, bytearray, int, float or None
|
|
|
|
Auch dieser Fehler riss die Schleife mit. Im alten Log machte er gut vier von
|
|
zehn Zeilen aus.
|
|
|
|
Die Reparatur
|
|
-------------
|
|
Unbekannte Felder werden uebersprungen statt zu werfen, jedes einmal mit
|
|
einer Warnung im Log. Werte, die trotz Kodierung kein Skalar sind, gehen als
|
|
JSON hinaus statt als Python-Objekt. Alle uebrigen Felder derselben Nachricht
|
|
kommen dadurch durch.
|
|
|
|
Warum hier und nicht in der Bibliothek
|
|
--------------------------------------
|
|
Die Bibliothek liegt unter ~/.local/lib/python3.10/site-packages und damit
|
|
ausserhalb dieses Repositorys; eine Neuinstallation wuerde eine Aenderung
|
|
dort spurlos zurueckdrehen. wattpilot 0.2 ist seit Mai 2022 die letzte
|
|
Veroeffentlichung, ein Update behebt es also nicht. Deshalb steht die
|
|
Reparatur hier, unter Versionskontrolle, und ersetzt die Funktion zur
|
|
Laufzeit. mqtt_setup() schlaegt den Namen erst beim Registrieren nach, so
|
|
dass unsere Fassung greift.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import sys
|
|
|
|
from wattpilot import wattpilotshell as shell
|
|
|
|
_LOG = logging.getLogger("wattpilot.bruecke")
|
|
|
|
# Jedes unbekannte Feld soll genau einmal auffallen, nicht bei jeder Nachricht.
|
|
_gemeldet = set()
|
|
|
|
|
|
_original_kodieren = shell.mqtt_get_encoded_property
|
|
|
|
|
|
def mqtt_get_encoded_property(pd, value):
|
|
"""Ersatz fuer wattpilotshell.mqtt_get_encoded_property.
|
|
|
|
Die Bibliothek verpackt nur dann in JSON, wenn die API-Beschreibung den
|
|
Wert als Objekt, Liste oder Wahrheitswert fuehrt. Stimmt die Beschreibung
|
|
nicht mit dem ueberein, was die Firmware wirklich schickt, kommt ein
|
|
dict oder eine list bei paho an - und paho nimmt nur Skalare.
|
|
"""
|
|
kodiert = _original_kodieren(pd, value)
|
|
if kodiert is None or isinstance(kodiert, (str, bytes, bytearray, int, float)):
|
|
return kodiert
|
|
|
|
name = pd.get("key", "?")
|
|
if name not in _gemeldet:
|
|
_gemeldet.add(name)
|
|
_LOG.warning(
|
|
"Feld '%s' ist als jsonType %r beschrieben, kommt aber als %s an "
|
|
"- wird als JSON veroeffentlicht", name, pd.get("jsonType"),
|
|
type(kodiert).__name__)
|
|
return json.dumps(kodiert, cls=shell.JSONNamespaceEncoder)
|
|
|
|
|
|
def mqtt_publish_message(event, message):
|
|
"""Ersatz fuer wattpilotshell.mqtt_publish_message - siehe Modulkopf."""
|
|
wp = event["wp"]
|
|
if shell.mqtt_client is None:
|
|
return
|
|
|
|
nachricht = json.loads(message)
|
|
typ = nachricht["type"]
|
|
|
|
if shell.MQTT_PUBLISH_MESSAGES == "true" and (
|
|
shell.MQTT_MESSAGES in ([], [""]) or typ in shell.MQTT_MESSAGES):
|
|
thema = shell.mqtt_subst_topic(shell.MQTT_TOPIC_MESSAGES, {
|
|
"baseTopic": shell.MQTT_TOPIC_BASE,
|
|
"serialNumber": wp.serial,
|
|
"messageType": typ,
|
|
})
|
|
shell.mqtt_client.publish(thema, message)
|
|
|
|
if shell.MQTT_PUBLISH_PROPERTIES != "true" or typ not in ("fullStatus", "deltaStatus"):
|
|
return
|
|
|
|
eigenschaften = shell.wpdef["properties"]
|
|
for name, wert in nachricht["status"].items():
|
|
beschreibung = eigenschaften.get(name)
|
|
if beschreibung is None:
|
|
if name not in _gemeldet:
|
|
_gemeldet.add(name)
|
|
_LOG.warning(
|
|
"Feld '%s' steht nicht in der API-Beschreibung der "
|
|
"Bibliothek - wird uebersprungen (Wert: %.80s)", name, repr(wert))
|
|
continue
|
|
shell.mqtt_publish_property(wp, shell.mqtt_client, beschreibung, wert)
|
|
|
|
|
|
shell.mqtt_get_encoded_property = mqtt_get_encoded_property
|
|
shell.mqtt_publish_message = mqtt_publish_message
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(shell.main())
|
|
except KeyboardInterrupt:
|
|
sys.exit(0)
|