Bruecke nimmt jetzt auch Kommandos entgegen
Der Schreibweg wattpilot/properties/<schluessel>/set war unbenutzbar. Die
Bibliothek reichte Zahlen als Zeichenkette an die Wallbox weiter
("value must be uint8_t"), griff bei unbekannten Namen und bei
Eigenschaften ohne rw-Angabe ungeprueft zu - ftt und fte haben keine, ein
Klick auf die Ladeplanung haette den paho-Callback getoetet - und meldete
nichts zurueck.
Jetzt bekommt der Wert den Typ aus der API-Beschreibung, so dass die
Wallbox 'lmo = 4' genauso nimmt wie 'lmo = Awattar'. Unbekanntes und
Schreibgeschuetztes wird abgelehnt statt zu werfen, und das Ergebnis geht
auf <schluessel>/result - genau wie es der go-e tut. Damit laesst sich das
Dashboard auf einen Weg fuer beide Wallboxen umstellen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,17 @@ 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.
|
||||
|
||||
Die Schreibrichtung
|
||||
-------------------
|
||||
Dieselbe Bruecke nimmt Kommandos auf wattpilot/properties/<schluessel>/set
|
||||
entgegen - das Gegenstueck zu api/set?<schluessel>=... beim go-e. Auch dieser
|
||||
Weg war unbenutzbar: die Bibliothek reicht Zahlen als Zeichenkette weiter
|
||||
("value must be uint8_t"), greift bei unbekannten Namen und bei
|
||||
Eigenschaften ohne rw-Angabe ungeprueft zu, und meldet nichts zurueck. Hier
|
||||
wird der Wert in den Typ gebracht, den die API-Beschreibung nennt, unsauberes
|
||||
verworfen statt den paho-Callback zu toeten, und das Ergebnis auf
|
||||
<schluessel>/result veroeffentlicht - genau wie es der go-e tut.
|
||||
|
||||
Warum hier und nicht in der Bibliothek
|
||||
--------------------------------------
|
||||
Die Bibliothek liegt unter ~/.local/lib/python3.10/site-packages und damit
|
||||
@@ -55,6 +66,7 @@ dass unsere Fassung greift.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from wattpilot import wattpilotshell as shell
|
||||
@@ -124,8 +136,136 @@ def mqtt_publish_message(event, message):
|
||||
shell.mqtt_publish_property(wp, shell.mqtt_client, beschreibung, wert)
|
||||
|
||||
|
||||
# --- Schreibrichtung: was aus MQTT hereinkommt ------------------------------
|
||||
|
||||
_original_dekodieren = shell.mqtt_get_decoded_property
|
||||
|
||||
# requestId -> Eigenschaftsname, damit die Antwort der Wallbox ihr Topic
|
||||
# wiederfindet. Bleibt eine Antwort aus, verfaellt der Eintrag beim Aufraeumen.
|
||||
_offene_anfragen = {}
|
||||
_antwort_haengt = [False]
|
||||
|
||||
|
||||
_KEIN_WERT = object()
|
||||
|
||||
|
||||
def _als_typ(text, typ):
|
||||
"""Bringt eine Zeichenkette in den Typ, den die API-Beschreibung nennt.
|
||||
|
||||
Gibt _KEIN_WERT zurueck, wenn das nicht geht - dann ist der Wert keine
|
||||
Zahlenform, sondern vermutlich ein Name aus der valueMap ("Awattar"), um
|
||||
den sich die Bibliothek selbst kuemmert.
|
||||
"""
|
||||
if text.lower() in ("null", "none"):
|
||||
return None
|
||||
try:
|
||||
if typ == "integer":
|
||||
return int(float(text))
|
||||
if typ == "number":
|
||||
return float(text)
|
||||
if typ == "boolean":
|
||||
if text.lower() in ("true", "1"):
|
||||
return True
|
||||
if text.lower() in ("false", "0"):
|
||||
return False
|
||||
except ValueError:
|
||||
pass
|
||||
return _KEIN_WERT
|
||||
|
||||
|
||||
def mqtt_get_decoded_property(pd, value):
|
||||
"""Ersatz fuer wattpilotshell.mqtt_get_decoded_property.
|
||||
|
||||
Die Bibliothek holt nur Listen und Objekte aus dem JSON; alles andere
|
||||
reicht sie als Zeichenkette an die Wallbox weiter, und die lehnt ab:
|
||||
|
||||
ERROR:wattpilot:Error Sending Request 1. Message: value must be uint8_t
|
||||
|
||||
Hier bekommt der Wert zuerst den Typ, den die API-Beschreibung nennt.
|
||||
Damit nimmt die Wallbox 'lmo = 4' genauso wie 'lmo = Awattar' - der go-e
|
||||
kennt nur die Zahlenform, und beide sollen gleich bedienbar sein. Nur was
|
||||
sich nicht als Zahl lesen laesst, geht den alten Weg ueber die valueMap.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
gewandelt = _als_typ(value.strip(), pd.get("jsonType"))
|
||||
if gewandelt is not _KEIN_WERT:
|
||||
return gewandelt
|
||||
return _original_dekodieren(pd, value)
|
||||
|
||||
|
||||
def _antwort_veroeffentlichen(event, nachricht):
|
||||
"""Meldet zurueck, ob die Wallbox den Wert genommen hat.
|
||||
|
||||
Der go-e antwortet auf <praefix><schluessel>/result mit 'success' oder
|
||||
einer Fehlermeldung. Die Bibliothek kennt das nicht, also bauen wir es
|
||||
nach - sonst bleibt ein abgelehnter Wert fuer die Weboberflaeche
|
||||
unsichtbar.
|
||||
"""
|
||||
if shell.mqtt_client is None:
|
||||
return
|
||||
# Die Wallbox schickt die Kennung als Zeichenkette zurueck ("1"),
|
||||
# gezaehlt wird sie als Zahl. Beide Seiten als Text vergleichen.
|
||||
name = _offene_anfragen.pop(str(getattr(nachricht, "requestId", "")), None)
|
||||
if name is None:
|
||||
return
|
||||
erfolg = bool(getattr(nachricht, "success", False))
|
||||
antwort = "success" if erfolg else str(getattr(nachricht, "message", "error"))
|
||||
thema = shell.mqtt_subst_topic("~/result", {
|
||||
"baseTopic": shell.MQTT_TOPIC_BASE,
|
||||
"serialNumber": event["wp"].serial,
|
||||
"propName": name,
|
||||
})
|
||||
shell.mqtt_client.publish(thema, antwort)
|
||||
|
||||
|
||||
def mqtt_set_value(client, userdata, message):
|
||||
"""Ersatz fuer wattpilotshell.mqtt_set_value.
|
||||
|
||||
Das Original greift zweimal ungeprueft zu: wpdef["properties"][name] wirft
|
||||
bei einem unbekannten Namen, pd['rw'] bei jeder Eigenschaft ohne
|
||||
Schreibangabe - ftt und fte zum Beispiel haben keine. Beides toetet den
|
||||
paho-Callback, und danach nimmt die Bruecke keine Kommandos mehr an.
|
||||
"""
|
||||
muster = shell.mqtt_subst_topic(shell.MQTT_TOPIC_PROPERTY_SET,
|
||||
{"propName": "([^/]+)"})
|
||||
treffer = re.fullmatch(muster, message.topic)
|
||||
if treffer is None:
|
||||
_LOG.warning("Unerwartetes Set-Topic %s - ignoriert", message.topic)
|
||||
return
|
||||
name = treffer.group(1)
|
||||
|
||||
beschreibung = shell.wpdef["properties"].get(name)
|
||||
if beschreibung is None:
|
||||
_LOG.warning("Schreibversuch auf unbekannte Eigenschaft '%s' - "
|
||||
"ignoriert (Topic %s)", name, message.topic)
|
||||
return
|
||||
if beschreibung.get("rw") == "R":
|
||||
_LOG.warning("Eigenschaft '%s' ist schreibgeschuetzt - ignoriert", name)
|
||||
return
|
||||
|
||||
wert = mqtt_get_decoded_property(beschreibung, message.payload.decode("utf-8"))
|
||||
|
||||
if not _antwort_haengt[0]:
|
||||
shell.wp.add_event_handler("wp_response", _antwort_veroeffentlichen)
|
||||
_antwort_haengt[0] = True
|
||||
if len(_offene_anfragen) > 50:
|
||||
_offene_anfragen.clear()
|
||||
|
||||
# Erst vormerken, dann senden. Die Wallbox antwortet binnen Millisekunden
|
||||
# und aus einem anderen Thread; wer die Zuordnung hinterher eintraegt,
|
||||
# findet die Antwort nicht mehr vor. send_update() zaehlt die Kennung
|
||||
# hoch und benutzt sie sofort, die naechste ist also vorhersagbar.
|
||||
naechste = getattr(shell.wp, "_Wattpilot__requestid", 0) + 1
|
||||
_offene_anfragen[str(naechste)] = name
|
||||
|
||||
shell.wp.send_update(name, wert)
|
||||
_LOG.info("Gesetzt: %s = %r (%s)", name, wert, type(wert).__name__)
|
||||
|
||||
|
||||
shell.mqtt_get_encoded_property = mqtt_get_encoded_property
|
||||
shell.mqtt_get_decoded_property = mqtt_get_decoded_property
|
||||
shell.mqtt_publish_message = mqtt_publish_message
|
||||
shell.mqtt_set_value = mqtt_set_value
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user