Der Zugang zur Viessmann API ist nicht ganz trivial. Die Beschreibung auf den Viessmann Seiten lässt auch zu wünschen übrig. Trotzdem schadet es nicht, sich das alles dort einmal durchzulesen. Ich beschreibe hier den Ablauf analog dazu aber eben auf Node-Red angepasst. Ist der Flow erst einmal fertiggestellt, ist alles Weitere ein Klacks.
Mit jeder Erkenntnis wird dieser Beitrag aktualisiert und dadurch leider auch länger und etwas unübersichtlich. Wichtige Änderungen in meiner Anleitung sind entsprechend hervorgehoben und zusätzlich haben meine Beiträge jetzt auch ein Inhaltsverzeichnis.
Inhalt
Schritt für Schritt
Vorbemerkung: diese Anleitung geht davon aus, dass Node-Red native auf einem Linux Rechner, das heißt einem Raspberry Pi installiert wird also ohne ein Hausautomatisierungsframework darum herum. Für Spezialrechner auf Linux Basis (z.B. CCU3) oder Windows PCs, Synology Disk Stations etc. gelten unter Umständen andere Voraussetzungen – zum Beispiel spezielle Client Firewall Regeln und Port-Einstellungen, die hier nicht behandelt werden. Dasselbe gilt für ein embedded Node-Red im Homeassistant oder IO Broker. Auch Docker macht immer wieder Probleme. Ich kann hier auch nur eingeschränkten Support leisten. Schaut euch auch bitte die untenstehenden "Hinweise zur Fehlersuche" an, wo ich Lösungsmöglichkeiten dank der Hinweise von einigen Lesern zusammengetragen habe.
A propos Firewall: Der Router braucht keine Portfreischaltung im Firewall. Hütet euch ferner davor, den Node-Red Port 1880 im Router freizuschalten. Damit hättet ihr ein riesiges Sicherheitsproblem.
Voraussetzungen – Vorarbeiten
Grundvoraussetzung ist, die ViCare App zum Laufen zu bringen. Mit der App wird ein Viessmann Account angelegt und die Anlage registriert. Damit kennen die Viessmann-Server deine Installation. Es ist dann auch sichergestellt, dass die internetseitige Verbindung funktioniert. Darauf achten, dass das Vitoconnect Kästchen – sofern verwendet – eine ausreichende Empfangsstärke hat. 1 Balken ist definitiv zu wenig. Siehe hier (Abschnitt Anleitung).
Anschließend, bzw. nach Erhalt und Quittierung des Bestätigungsmails solltest du dich bei developer.viessmann.com einloggen. Dabei sind dieselben Zugangsdaten zu verwenden, die bei der Registrierung der ViCare App verwendet wurden. Das Ganze ist weitestgehend selbsterklärend.
Nach dem Login hat man Zugriff auf die Dokumentation und das Viessmann Developer Dahboard. Dort muss zuerst einmal die
Client ID
angelegt werden. Mit Klick auf My Dashboard wird dieses angezeigt. Auf der Kachel "Your Clients" klicken wir das Stiftsymbol an. Es kann nur ein Client angelegt werden. Normalerweise braucht du nur eine URI. Zwei URIs sind ggf. vonnöten, wenn Node-Red zusammen mit dem Viessmann API Adapter auf einem IO Broker laufen soll.
Bei Name trägst du denselben Namen ein, den du deiner Anlage in der App verpasst hast – ich bin mir aber nicht sicher, ob das wirklich so sein muss. Die Client ID wird automatisch erzeugt, wenn du am Ende "Save Client" anklickst.
Zuvor muss aber noch das Google reCAPTCHA ausgeschaltet und eine Redirect URI eingegeben werden.
Die Redirect URI ist die Aufrufadresse (Callback Adresse) deiner Applikation, an die bei Aufruf ein AuthCode übermittelt wird. In der generischen Viessmann Anleitung steht http://localhost:4200 . Wir hingegen verwenden http://localhost:1880/authcode – das ist die Adresse bzw. der Port, auf dem Node-Red auf Input lauscht. Das /authcode ist ein beliebiger Pfad, der unbedingt erforderlich ist, sonst ruft Viessmann den Node-Red Editor anstatt des Flows auf. Merke dir diesen Pfad, du brauchst ihn in den nächsten Schritten.
Die Client ID ist statisch und bleibt so lange gültig, bis du sie im Dashboard löschst.
Merke: Die ClientID wird im Folgenden insgesamt dreimal in einen http Request Node eingetragen. Das wird gerne übersehen, wenn man die ClientID später einmal geändert hat.
Authcode abrufen
Das Erzeugen der Client ID war der letzte manuelle Schritt und muss auch nur einmalig durchgeführt werden. Der Rest ist künftig automatisiert:
Wir ziehen uns nun in Node-Red drei Nodes auf das Editiertableau und "verkabeln" diese.
Timestamp zum Triggern, http request zum Abfragen und Debug für das Ergebnis.
ACHTUNG: Der Debug Node (hier debug 5) wird erst dann ein sinnvolles Ergebnis liefern, wenn ihr auch den weiter unten stehenden Schritt mit dem http in Node gegangen seid. Erst der http in Node erzeugt das virtuelle Verzeichnis, in das der Viessmann Server hineinschreibt.
In die URL Zeile des http request kommt folgender Inhalt:
1 |
https://iam.viessmann.com/idp/v3/authorize?client_id=DeineClientIDkommtHierhin&redirect_uri=http://localhost:1880/authcode&response_type=code&code_challenge=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&scope=IoT%20User%20offline_access |
Bisherige Nutzer der API aufgepasst: seit Februar 2023 gibt es eine neue Version des API Authentifizierungsprozesses: Anstatt https://iam.viessmann.com/idp/v2/authorize… heißt es jetzt https://iam.viessmann.com/idp/v3/authorize… sonst ändert sich nichts. Der Vorteil liegt in konkreteren Fehlermeldungen beim Login. Siehe Viessmanm API Changelog Abschnitt Februar 2023. Übernimmt man die Änderung, sollte weiter unten d.h. bei https://iam.viessmann.com/idp/v3/token… ebenfalls v3 anstatt v2 verwendet werden.
Deine Client ID an der entsprechenden Stelle eintragen, den gesamten anderen Kram könnt ihr so stehen lassen. In der Viessmann Anleitung ist etwas von Oauth die Rede für Code Challenge und -Response. Ich habe nie verstanden, wie das funktioniert. Es reicht, hier dieselbe Challenge/Response wie in der Viessmann Dokumentation zu nehmen.
Die Redirect URI 1:1 aus dem ClientID Prozess auf den Viessmann Seiten übernehmen.
scope= IoT User (bzw. scope=IoT%20User ) ist vorgegeben, offline_access veranlasst die API noch ein Refresh Token mitzugeben, was die spätere Nutzung deutlich vereinfacht.
Den Rest des http requests wie folgt ausfüllen:
Methode ist GET, Nutzdaten ignorieren, Basis Authentifizierung verwenden – hier die Viessmann Account Daten (Username/eMail und Passwort aus der App – nicht den Namen aus dem Viessmann „Edit Client“ Fenster) eintragen – und schließlich bei Rückgabe eine UTF-8 Zeichenfolge auswählen.
So lange du die Client ID nicht änderst, brauchst du diese Blöcke nicht mehr anfassen.
Mit dem http Request Node haben wir die Anfrage losgeschickt, mit einem http In Node empfangen wir die Antwort. Im Prinzip bauen wir hier einen winzigen Webserver auf.
Der http In Node wird wie folgt konfiguriert:
Das URL Feld muss dem Pfad entsprechen, der beim Anlegen der Client ID verwendet wurde. Allerdings ohne Localhost und Portnummer.
Der Response Node wird lediglich auf Status 200 gesetzt. Der Response Node ist wichtig, um die Anfrage des Viessmann Servers korrekt abzuschließen!
Bitte beachte ferner, dass der Sende- und der Empfangsteil sind nicht miteinander verdrahtet sind. Hier zaubert das Internet.
Jetzt – erst jetzt – könnt ihr testen: Wenn alles funktioniert, sollte der Debug Node am Empfangsende den Authorization Code anzeigen wenn der Trigger auf der Sendeseite ausgelöst wurde. Der Code steckt jetzt in msg.payload.code .
Den kompletten JSON Flow findest du am Ende des Artikels.
Hinweise zur Fehlersuche
Cannot get /authcode
Wie man manchen Kommentaren entnehmen kann, kommt es gelegentlich zu Fehlern der Art "Cannot GET /authcode".
Der Fehler tritt immer dann auf, wenn der Viessmann Server unter der angegebenen Adresse nichts findet. Das kann an einer falsch konfigurierten URL liegen, einer falschen Portnummer oder daran, dass der in NR eingebaute Webserver nicht angesprochen werden kann. Hauptursache ist aber wahrscheinlich, dass der zum ersten http Request passende http in Node noch nicht eingebaut wurde. Siehe mit Achtung markierten Absatz weiter oben. Erst der http in Node, oben mit
[get] /authcode bezeichnet, erzeugt das vom Viessmann Server benötigte virtuelle Verzeichnis.
connect ECONNREFUSED
Unter bestimmten Umständen, z.B. wegen einer nicht ganz standardmäßgen Konfiguration auf eurer Seite, durchläuft Node Red eine Identitätskrise und weiß auf einmal nicht mehr, dass es selbst durch localhost aufgerufen wird. Demzufolge wird der Viessmann-Callback abgelehnt und es gibt eine Fehlermeldung der Art:
RequestError: connect ECONNREFUSED ::1:1880
Test: probiert mal Folgendes:
1 |
[{"id":"e008437fd10a7172","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"19375c41d180b1f7","type":"http in","z":"e008437fd10a7172","name":"\"http in\" Node","url":"test","method":"get","upload":false,"swaggerDoc":"","x":990,"y":160,"wires":[["62d56fd0194be715"]]},{"id":"cb7ae7e2d1498dd5","type":"http response","z":"e008437fd10a7172","name":"","statusCode":"200","headers":{"content-type":"text/plain"},"x":1460,"y":160,"wires":[]},{"id":"62d56fd0194be715","type":"template","z":"e008437fd10a7172","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"This is the payload!","output":"str","x":1240,"y":160,"wires":[["cb7ae7e2d1498dd5"]]},{"id":"59871b13fa1668f2","type":"http request","z":"e008437fd10a7172","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"localhost:1880/test/?helloword","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1190,"y":80,"wires":[["e961ab6d8dca2a1f"]]},{"id":"53be7c837e4a97d8","type":"inject","z":"e008437fd10a7172","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"trigger","payloadType":"str","x":1010,"y":80,"wires":[["59871b13fa1668f2"]]},{"id":"e961ab6d8dca2a1f","type":"debug","z":"e008437fd10a7172","name":"debug 145","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1410,"y":80,"wires":[]}] |
Mit dem Trigger wird ein http request angestupst, der an die Adresse localhost:1880/test eine belanglose Nachricht schickt. Der http in Node empfängt diese – vorausgesetzt localhost ist nicht blockiert – und gibt an den http request eine Nachricht zurück, die ihr im Debugfenster lesen könnt. Funktioniert das nicht, erscheint eine Fehlermeldung "Cannot GET /test".
Um zu sehen, ob Node-Red überhaupt antwortet, könnt ihr den gerade erzeugten Microwebserver über einen Browser auf einem anderen Gerät im lokalen Netz aufrufen: http://IPdesNodeRedServers:1880/test . Wenn das nicht funktioniert, hat NR ein generelles Problem oder ihr habt euch irgendwo verschrieben.
Dem ECONNREFUSED Problem kann man oft abhelfen, indem man durchgehend 127.0.0.1 anstatt localhost verwendet. Logisch betrachtet ist beides dasselbe aber manchmal hilft es, anstatt des lokalen Namens, die IP Adresse zu verwenden .
Dafür die Viessmann Client ID löschen und dort die redirect URI neu mit
http://127.0.0.1:1880/authcode
anlegen. Dann noch die neu generierte Client ID und die neue URI in den obersten Request Node übertragen.
Dank an Günter fürs geduldige Ausprobieren und die Rückmeldung!
Home Assistant:
Bei einem "embedded Node Red" ist die Adressierung des NR internen Servers anders.
Als virtuelles NR Verzeichnis für den Autorisierungsvorgang haben wir weiter oben /authcode verwendet. Der Verzeichname ist beim native NR wahlfrei. Bei HA allerdings nicht! Das Verzeichnis muss /endpoint/... lauten. Also z.B. /endpoint/authcode . Und zwar überall:
-
- Auf der Viessmann Seite beim Anlegen der Redirect Client URI,
- beim ersten http Request – Generieren des Authorization Codes
- beim http in "[get]/authcode/
- und bei den beiden "set payload & headers" Nodes zu erstmaligen Erzeugen des Access tokens und zum Refresh desselben.
Herzlichen Dank an Alex P. fürs Aufzeigen der Lösung!
IO Broker:
Ich habe mir mal die Mühe gemacht, und den IO Broker nebst integriertem Node-Red Adapter auf einem Raspi installiert. Abgesehen davon, dass ich den IO Broker für ein hoffnungslos überladenes Klickibunti halte, hat alles astrein 1:1 funktioniert. Bei der Instanziierung darauf achen, dass die NR Portnummer 1880 gesetzt wird. Läuft auch noch der Viessmann Adapter auf dem Broker, müssen (wahrscheinlich) zwei URIs bei der Edit Client Seite bei Viessmann eingetragen werden. Ist also der Viessmann Adapter und der Node-Red Adapter gleichzeitig installiert, muss dann noch gemäß Vorschrift im Broker eine 2. URI (http://localhost:4200) angelegt werden (Angabe ohne Gewähr).
Docker:
Habt ihr NR in einem Docker Container laufen, dann wird das so ohne weiteres nicht funktionieren. Docker sperrt – so habe ich es verstanden – den Zugriff auf localhost. Das müsste erst einmal freigeschaltet werden. Wie? Keine Ahnung! Ich habe für mich keinen Vorteil von Docker erkannt und nutze es deshalb nicht. Zum Basteln habe ich einen isolierten Raspberry Pi und laufe so wenig Gefahr, etwas zu zuerstören.
Noch eine Möglichkeit (mangels Docker Installation blind geraten): Wenn Docker localhost sperrt, dann klappt es vielleicht mit 127.0.0.1 anstatt localhost. Siehe connect ECONNREFUSED weiter oben. Mir bitte unbedingt Bescheid geben, was daraus geworden ist.
Oder dieses Link hier: Docker Container unter Hostname nicht erreichbar
Zitat:
"Haben Docker Container nicht eine eigene "virtuelle" Netzwerkinfrastruktur, die man erst nach außen routen muss?
Also sprich sowas wie (Beispiel nginx Server):
docker run --name mynginx -d -p 8080:80 nginx Das macht nginx nach außen (localhost) auf Port 8080 erreichbar."
Zitat Ende.
Aktuelle Node Red Version
Noch was: seht zu, dass ihr die aktuellste Version von Node Red auf eurer Kiste laufen habt. Stand November 2023 ist das Version 3.1.0
Feedback, bitte:
Ich gebe mir alle Mühe, meinen Lesern bei der Fehlersuche weiterzuhelfen.
Daher eine Bitte: gebt genau an, welcher Node den Fehler ausgibt, ferner die komplett ausgeklappte Fehlermeldung im Debugfenster rechts. Idealerweise auch euren – anonymisierten – Flow per Mail schicken. (Adresse steht im Impressum). Gut zu wissen wäre ebenfalls, ob ihr Node-Red direkt auf eurem Rechner betreibt oder gekapselt in Docker, IO Broker, Home Assistant etc.
Und noch eine Bitte: Immer wieder zermartere ich mein Hirn und gebe ich Hinweise, wie ein Problem zu lösen sein könnte. Vom Adressaten bekomme ich meist… nichts! Kein Feedback, Funkstille. Damit stochere ich mit 'ner Stange im Nebel, da ich nicht weiß, ob mein Tipp geholfen hat. Also, bitte gebt mir Feedback und ich gebe mein Bestes, euch weiterzuhelfen. Auch wenn ihr eine Lösung für ein Problem gefunden habt, wäre ich für eine Mitteilung dankbar.
Access Token initial erzeugen
Wenn ihr jetzt den Authcode erzeugen konntet, geht's weiter:
Die drei Nodes oben links kennen bzw. haben wir schon.
Kommen wir zum Funktionsnode set payload & headers, der den darauf folgenden http Request Node mit Daten auflädt. Der im vorherigen Schritt gewonnene Code wird per Stringvariable hinten angehängt.
Auch hier kommt wieder die Client ID an die entsprechende Stelle des Aufrufstrings – wird gerne vergessen, wenn man mal die ClientID neu erzeugt hat.
1 2 3 4 5 |
msg.payload = 'grant_type=authorization_code&client_id=DeineclientIDKommtHierhin&redirect_uri=http://localhost:1880/authcode&code_verifier=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&code=' + msg.payload.code; msg.headers = {}; msg.headers['Content-Type'] = 'application/x-www-form-urlencoded'; return msg; |
Anders als der oben verwendete http Request ist der hier verwendete http Request Node ein POST Request, d.h. es werden Daten im http header anstatt in der URL transportiert. Er wird wie folgt konfiguriert. Darauf achten, dass die Rückgabe als "Ein parsed JSON Objekt" erfolgen soll.
Auch hier sollte wieder v3 anstatt v2 verwendet werden.
Wenn alles funktioniert, dann enthält das zurück geschickte JSON Objekt sowohl das Access Token als auch das Refresh Token. Die Token stecken in msg.payload.access_token bzw. msg.payload.refresh_token.
Mit dem Access Token könnten wir nun 3600 Sekunden oder eine Stunde lang arbeiten bevor es verfällt.
Sinnvollerweise packen wir die beiden Token in Flow Variable um sie weiter zu verwenden. Hast du die Viessmann Logik auf mehreren Node-Red Blätter verteilt, sollten sie in Global anstatt in Flow Variablen untergebracht werden.
Das geschieht am einfachsten mit einem Change Node:
Variable können auch innerhalb eines Function Nodes mit
flow.set("FlowVariablenname", meineVariable) gesetzt werden. Das macht vor allem innnerhalb komplexerer Funktionsblöcke Sinn.
Abgerufen werden die Flow Variablen mit einem Kommando der Art:
var meineVariable = flow.get("FlowVariablenname");
Sind wir wie oben beschrieben vorgegangen, können wir jetzt überall innerhalb des Flows (Tabellenblatt) auf die Tokens zugreifen.
Statische Parameter
Um mit der API zu arbeiten, benötigen wir noch drei statische Werte, welche am Gerät hängen und sich normalerweise nie ändern.
Wie man sie bekommt, ist in der Viessmann Doku beschrieben oder man nutzt diesen JSON Flow für Node Red:
1 |
[{"id":"8ebef3e8da13cc88","type":"http request","z":"640d4a161fb8fa28","name":"DeviceID","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.viessmann.com/iot/v1/equipment/installations/{{iid}}/gateways/{{gws}}/devices/ ","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":660,"y":560,"wires":[["46022befd51c1c67"]]},{"id":"798ae186e9020315","type":"change","z":"640d4a161fb8fa28","name":"","rules":[{"t":"set","p":"deviceID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":560,"wires":[[]]},{"id":"dc35dc89a8567d81","type":"function","z":"640d4a161fb8fa28","name":"extended auth header","func":"var atoken = flow.get('accessToken')\nmsg.headers = {\n Authorization: \"Bearer \"+ atoken\n}\nmsg.iid = flow.get('installationID');\nmsg.gws = flow.get('gatewaySerial');\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":560,"wires":[["8ebef3e8da13cc88"]]},{"id":"46022befd51c1c67","type":"function","z":"640d4a161fb8fa28","name":"locate deviceID","func":"const deviceArray=msg.payload.data;\nvar idx = deviceArray.findIndex( (element) => element.deviceType === 'heating');\nmsg.payload=msg.payload.data[idx].id;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":560,"wires":[["6e1ddceae4be021a","798ae186e9020315"]]}] |
Es bleibt dir überlassen, wo du diesen Teil einbaust. Im Prinzip kann man die drei Parameter Installation ID, Gateway Serial und Device ID ein einziges Mal abfragen und dann als fixe Werte, also "hard coded" weiterverarbeiten. Oder – so habe ich es gemacht – jedes Mal neu abfragen, wenn die Logik nach einem Neustart des Servers bzw. alle 180 Tage (siehe unten) initialisiert und die Token wieder neu generiert werden.
Erläuterung der drei statischen Parameter:
InstallationID identifiziert eure gesamte Anlage mit allen Komponenten, Therme, Wärmepumpe, Photovoltaik etc. Normalerweise gibt es pro Haus/Wohnung nur eine InstallationID.
GatewaySerial: Hat man nur einen Viessmann Wärme-/Energieerzeuger, dann gibt es in der Regel auch nur eine einzige GatewaySerial. Hat man deren mehrere, dann hat jeder modernere Wärmeerzeuger eine eigene WiFi Komponente (oder bei Legacy Geräten ein VitoConnect Kästchen).
Sind z.B. sowohl WP als auch PV installiert, musst du, um die gesamte Anlage über die API anzusprechen, beide GatewaySerials heranziehen und die Abfragen bzw. Befehle für die Geräte dem jeweiligen Gateway zuordnen. Auch bei einer späteren Nachrüstung (z.B. mit PV) muss die Programmierung entsprechend angepasst werden.
Jedes Gateway hat wiederum mehrere Device IDs. Relevant bzw. nutzbar für unsere Anwendungsfälle ist das Device Nummer "0" – der Wärme- oder Energieerzeuger.
Nr "1" ist das Gateway selbst, hier gibt es keine Features, die abgerufen werden können.
Nr. "2" ist virtuell und bei meiner Therme etwas, das sich "HeatDemandControl" nennt und wohl die Sensorik (?) darstellt. Ebenfalls keine Features.
Device ID "3" ist auch virtuell und die Einzelraumsteuerung welche in der Vitotronic wohl auch dann angelegt ist, wenn physisch es gar keine Einzelraumsteuerung gibt.
Update Anfang 2024: Leider stimmt diese Reihenfolge nicht mehr generell. Siehe untenstehendes Kapitel.
ACHTUNG NEU 2024
Nur relevant für bereits bestehende Nutzer dieser Anleitung: Warum auch immer hat Viessmann etwas an der Position der DeviceID geändert. Die Abfrage nach der Device ID in obigem Flow bzw. die Abfrage
https://api.viessmann.com/iot/v1/equipment/installations/{{iid}}/gateways/{{gws}}/devices/ generiert eine Payload bestehend aus einem Array mit vier Elementen — msg.payload.data[0] bis data[3]. Bisher stand die Device ID in msg.payload.data[0].id, seit ca. Anfang 2024 steckt die DeviceID bei mir in msg.payload.data[1].id
Leider ist das aber nicht bei jedem so. Bei Wärmepumpen kann es zum Beispiel sein, dass die deviceID im dritten Element .data[2] steckt. Auch wenn es etwas nassforsch ist, empfehle ich, generell einfach "0" als DeviceID in euren Flows zu verwenden anstatt das regelmäßig bei Viessmann abzufragen.
Für alle Puristen, die die Device ID trotzdem gerne bei der API abfragen, hier noch der Flow, der eine hoffentlich betriebssichere Abfrage ermöglicht. Gesucht wird nach dem Element in der Payload, das bei deviceType den Wert "heating" drinstehen hat. Einfach die hier abgebildeten Nodes ersetzen. Diese Methode ist auch in obigem Flow realisiert.
Bei Fotovoltaik, Lüftung oder Kühlung müsst ihr nachsehen, welcher deviceType verwendet wird und den "locate deviceID" Node entsprechend anpassen.
1 |
[{"id":"8ebef3e8da13cc88","type":"http request","z":"640d4a161fb8fa28","name":"DeviceID","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.viessmann.com/iot/v1/equipment/installations/{{iid}}/gateways/{{gws}}/devices/ ","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":660,"y":560,"wires":[["46022befd51c1c67"]]},{"id":"798ae186e9020315","type":"change","z":"640d4a161fb8fa28","name":"","rules":[{"t":"set","p":"deviceID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":560,"wires":[[]]},{"id":"dc35dc89a8567d81","type":"function","z":"640d4a161fb8fa28","name":"extended auth header","func":"var atoken = flow.get('accessToken')\nmsg.headers = {\n Authorization: \"Bearer \"+ atoken\n}\nmsg.iid = flow.get('installationID');\nmsg.gws = flow.get('gatewaySerial');\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":560,"wires":[["8ebef3e8da13cc88"]]},{"id":"46022befd51c1c67","type":"function","z":"640d4a161fb8fa28","name":"locate deviceID","func":"const deviceArray=msg.payload.data;\nvar idx = deviceArray.findIndex( (element) => element.deviceType === 'heating');\nmsg.payload=msg.payload.data[idx].id;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":560,"wires":[["6e1ddceae4be021a","798ae186e9020315"]]}] |
Refresh alle 3600 Sekunden
Jetzt endlich können wir mit der API arbeiten. Aber nach einer Stunde ist der Spaß vorbei, das Access Token ist abgelaufen. Um nun nicht wieder die gesamte obige Routine durchlaufen zu müssen, gibt es eine Abkürzung mit dem Refresh Token.
Hier wird der Trigger – auf "wiederhole automatisch alle 59 Minuten" eingestellt,
Der Funktionsnode hat folgenden Inhalt:
1 2 3 4 5 |
let refreshToken=flow.get('refreshToken'); msg.payload = 'grant_type=refresh_token&client_id=HierKommtDeine ClientIDHin&refresh_token=' + refreshToken; msg.headers = {}; msg.headers['Content-Type'] = 'application/x-www-form-urlencoded'; return msg; |
Wie du siehst, wird hier die Flow Variable "refreshToken" im Javascript ausgelesen. Deine Client ID muss ebenfalls wieder eingetragen werden und das Refresh Token wird als String hinten agehängt.
Es folgt ein http Request Node mit folgender Konfiguration:
Ferner noch ein Change Node, welcher die zurückgegebene Payload in die Access_Token Variable schreibt.
Und hier der zugehörige JSON Flow zum importieren:
1 |
[{"id":"794bf2047ed74376","type":"function","z":"8da86d39b73b390b","name":"set payload and headers","func":"let refreshToken=flow.get('refreshToken');\nmsg.payload = 'grant_type=refresh_token&client_id=DeineClientIDkommtHierhin&refresh_token=' + refreshToken;\nmsg.headers = {};\nmsg.headers['Content-Type'] = 'application/x-www-form-urlencoded';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":160,"wires":[["ed038a951c955501"]]},{"id":"38dd6ecae9f953db","type":"inject","z":"8da86d39b73b390b","name":"Refresh Access Token","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"3540","crontab":"","once":true,"onceDelay":"2","topic":"","payload":"","payloadType":"date","x":230,"y":160,"wires":[["794bf2047ed74376"]]},{"id":"ed038a951c955501","type":"http request","z":"8da86d39b73b390b","name":"refreshtoken","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://iam.viessmann.com/idp/v3/token","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"credentials":{"user":"","password":""},"x":710,"y":160,"wires":[["e33267b623c5f5d6"]]},{"id":"c2d4d5a3cc1023ad","type":"debug","z":"8da86d39b73b390b","name":"Tokens","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1140,"y":160,"wires":[]},{"id":"e33267b623c5f5d6","type":"change","z":"8da86d39b73b390b","name":"","rules":[{"t":"set","p":"accessToken","pt":"flow","to":"payload.access_token","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":930,"y":160,"wires":[["c2d4d5a3cc1023ad"]]}] |
Bisherige Nutzer achten wieder auf "v3" in der URL des Request Nodes.
Wird der Flow regelmäßig ausgeführt, hat man immer ein gültiges Access Token.
Refresh nach 180 Tagen erneuern
…zumindest für 180 Tage. Dann verfällt auch das Refresh Token und wir müssen tatsächlich wieder beim Kapitel Authcode abrufen einsteigen. Die Client ID muss dafür nicht neu erzeugt werden. Das Triggern der Schritte kann natürlich auch automatisch erfolgen, sodaß es keinerlei menschlichen Eingriffs mehr bedarf. Das ist z.B. möglich, indem man in der "Access Token Refresh" Routine eine Abfrage nach dem Wert des erneuerten Access Tokens einbaut. Hat das Access Token den Wert "null" (das ist etwas anderes als der Wert 0), wird auf die Initialroutine verzweigt. Die Abfrage mittels Switch-Node sieht so aus:
Siehe auch unten abgebildeten Flow mit Kommentar.
Was auch möglich wäre, aber nicht so betriebssicher, ist den Initialflow einfach alle 4319 Stunden (= 180 Tage minus 1 Stunde) per Trigger aufzurufen. Das wird aber nur funktionieren, wenn die ganze Viessmann Logik wirklich ununterbrochen durchläuft, damit der Trigger komplett abläuft. In 180 Tagen kann eine Menge passieren…
Im Prinzip spricht auch wenig dagegen, den "Refresh Token aktualisieren" Zyklus alle 24 Stunden oder so aufzurufen.
Gesamter Flow
JSON für dieses Kapitel
1 |
[{"id":"41f5e66ea1123715","type":"function","z":"edb7360203dfca8a","name":"set payload and headers","func":"let refreshToken=flow.get('refreshToken');\nmsg.payload = 'grant_type=refresh_token&client_id=deineClientID&refresh_token=' + refreshToken;\nmsg.headers = {};\nmsg.headers['Content-Type'] = 'application/x-www-form-urlencoded';\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":860,"wires":[["202623e583f50b9d"]]},{"id":"ccc0fba0ce9645c1","type":"inject","z":"edb7360203dfca8a","name":"Refresh Access Token","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"3540","crontab":"","once":false,"onceDelay":"2","topic":"","payload":"","payloadType":"date","x":430,"y":860,"wires":[["41f5e66ea1123715"]]},{"id":"202623e583f50b9d","type":"http request","z":"edb7360203dfca8a","name":"refreshtoken","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://iam.viessmann.com/idp/v3/token","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":true,"headers":[],"x":910,"y":860,"wires":[["e89fe2cc1c73775b","9ed4f0d7bf8b5660"]]},{"id":"706b68f457ed12c3","type":"debug","z":"edb7360203dfca8a","name":"complete msg object","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1240,"y":280,"wires":[]},{"id":"a7188662ba731da7","type":"http request","z":"edb7360203dfca8a","name":"1st time Token Request","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://iam.viessmann.com/idp/v3/token","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":true,"headers":[],"x":970,"y":400,"wires":[["706b68f457ed12c3","af369dbbde2acfbc","77feb868a756cde1","32a92f39827be27e"]]},{"id":"82ff5c6c7dda8279","type":"function","z":"edb7360203dfca8a","name":"set payload & headers","func":"msg.payload = 'grant_type=authorization_code&client_id=deineClientID&redirect_uri=http://localhost:1880/authcode&code_verifier=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&code=' + msg.payload.code;\nmsg.headers = {};\nmsg.headers['Content-Type'] = 'application/x-www-form-urlencoded';\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":400,"wires":[["a7188662ba731da7"]]},{"id":"acbf0c1a6db3e9cb","type":"comment","z":"edb7360203dfca8a","name":"Refresh Access Token in normal operation - expires after 3600 seconds","info":"","x":570,"y":800,"wires":[]},{"id":"e92010ef0dfe54cf","type":"inject","z":"edb7360203dfca8a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":520,"y":180,"wires":[["f941ac264dad0dfd"]]},{"id":"134c745731091253","type":"debug","z":"edb7360203dfca8a","name":"Request Result","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":900,"y":180,"wires":[]},{"id":"10ac545d4a7179d1","type":"http in","z":"edb7360203dfca8a","name":"","url":"/authcode/","method":"get","upload":false,"swaggerDoc":"","x":400,"y":300,"wires":[["cd0e4c633d826bf5"]]},{"id":"ec7dd7cefa63b846","type":"debug","z":"edb7360203dfca8a","name":"msg.payload.code","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.code","targetType":"msg","statusVal":"","statusType":"auto","x":710,"y":260,"wires":[]},{"id":"9e8956bf53f56c41","type":"comment","z":"edb7360203dfca8a","name":"Generate authorization code","info":"","x":560,"y":140,"wires":[]},{"id":"cc2bd5d85345dbb3","type":"comment","z":"edb7360203dfca8a","name":"Click here -->","info":"","x":350,"y":180,"wires":[]},{"id":"771e439c68b4bc4d","type":"comment","z":"edb7360203dfca8a","name":"after expiry","info":"","x":340,"y":140,"wires":[]},{"id":"af369dbbde2acfbc","type":"debug","z":"edb7360203dfca8a","name":"access_token","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1220,"y":360,"wires":[]},{"id":"32a92f39827be27e","type":"debug","z":"edb7360203dfca8a","name":"refresh_token","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1220,"y":320,"wires":[]},{"id":"77feb868a756cde1","type":"change","z":"edb7360203dfca8a","name":"set token flow variables","rules":[{"t":"set","p":"accessToken","pt":"flow","to":"payload.access_token","tot":"msg"},{"t":"set","p":"refreshToken","pt":"flow","to":"payload.refresh_token","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1250,"y":400,"wires":[["7177030fe9bd815a"]]},{"id":"221dabf581eb4756","type":"http request","z":"edb7360203dfca8a","name":"Installation ID","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.viessmann.com/iot/v1/equipment/installations","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":920,"y":540,"wires":[["29991e8f79994b07"]]},{"id":"7177030fe9bd815a","type":"function","z":"edb7360203dfca8a","name":"Auth Header","func":"var atoken = flow.get('accessToken')\nmsg.headers = {\n Authorization: \"Bearer \"+ atoken\n}\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":540,"wires":[["221dabf581eb4756"]]},{"id":"989ceef32cf23cdc","type":"http request","z":"edb7360203dfca8a","name":"Gateway Serial","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.viessmann.com/iot/v1/equipment/gateways","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":920,"y":580,"wires":[["4266736c7fd3130a"]]},{"id":"29991e8f79994b07","type":"change","z":"edb7360203dfca8a","name":"set flow.installationID","rules":[{"t":"set","p":"installationID","pt":"flow","to":"payload.data[0].id","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1120,"y":540,"wires":[["91e135ba6d79060e"]]},{"id":"4266736c7fd3130a","type":"change","z":"edb7360203dfca8a","name":"set flow.gatewaySerial","rules":[{"t":"set","p":"gatewaySerial","pt":"flow","to":"payload.data[0].serial","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1120,"y":580,"wires":[["74f4935316632db8"]]},{"id":"91e135ba6d79060e","type":"function","z":"edb7360203dfca8a","name":"Auth Header","func":"var atoken = flow.get('accessToken')\nmsg.headers = {\n Authorization: \"Bearer \"+ atoken\n}\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":580,"wires":[["989ceef32cf23cdc"]]},{"id":"dcbd89935b7dfcfb","type":"http request","z":"edb7360203dfca8a","name":"DeviceID","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.viessmann.com/iot/v1/equipment/installations/{{iid}}/gateways/{{gws}}/devices/ ","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":900,"y":620,"wires":[["e5d6363248f0ff0b"]]},{"id":"b6a236b47b09e0bf","type":"change","z":"edb7360203dfca8a","name":"","rules":[{"t":"set","p":"deviceID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1250,"y":620,"wires":[[]]},{"id":"74f4935316632db8","type":"function","z":"edb7360203dfca8a","name":"extended auth header","func":"var atoken = flow.get('accessToken')\nmsg.headers = {\n Authorization: \"Bearer \"+ atoken\n}\nmsg.iid = flow.get('installationID');\nmsg.gws = flow.get('gatewaySerial');\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":620,"wires":[["dcbd89935b7dfcfb"]]},{"id":"25a0350eb2855d86","type":"comment","z":"edb7360203dfca8a","name":"get tokens 1st time","info":"","x":530,"y":360,"wires":[]},{"id":"1c83d789d12a8d81","type":"comment","z":"edb7360203dfca8a","name":"get/set invariable parameters","info":"","x":720,"y":480,"wires":[]},{"id":"e89fe2cc1c73775b","type":"switch","z":"edb7360203dfca8a","name":"","property":"payload.access_token","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1070,"y":860,"wires":[["f941ac264dad0dfd"],["188685a4b5de73a8"]]},{"id":"9ed4f0d7bf8b5660","type":"debug","z":"edb7360203dfca8a","name":"RefreshToken","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1060,"y":780,"wires":[]},{"id":"f941ac264dad0dfd","type":"http request","z":"edb7360203dfca8a","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://iam.viessmann.com/idp/v3/authorize?client_id=deineClientID&redirect_uri=http://localhost:1880/authcode&response_type=code&code_challenge=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&scope=IoT%20User%20offline_access","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"basic","senderr":true,"headers":[],"x":690,"y":180,"wires":[["134c745731091253"]]},{"id":"b02cd61bbe1d4d9f","type":"http response","z":"edb7360203dfca8a","name":"Reponse","statusCode":"200","headers":{},"x":760,"y":300,"wires":[]},{"id":"9f9bc4ad9aa94822","type":"debug","z":"edb7360203dfca8a","name":"debug 155","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":720,"wires":[]},{"id":"e5d6363248f0ff0b","type":"function","z":"edb7360203dfca8a","name":"locate deviceID","func":"const deviceArray=msg.payload.data;\nvar idx = deviceArray.findIndex( (element) => element.deviceType === 'heating');\nmsg.payload=msg.payload.data[idx].id;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":620,"wires":[["9f9bc4ad9aa94822","b6a236b47b09e0bf"]]},{"id":"188685a4b5de73a8","type":"change","z":"edb7360203dfca8a","name":"","rules":[{"t":"set","p":"accessToken","pt":"flow","to":"payload.access_token","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1270,"y":900,"wires":[[]]},{"id":"cd0e4c633d826bf5","type":"junction","z":"edb7360203dfca8a","x":540,"y":300,"wires":[["ec7dd7cefa63b846","82ff5c6c7dda8279","b02cd61bbe1d4d9f"]]}] |
Weiter gehts mit dem nächsten Kapitel Daten abfragen und visualisieren.
Hallo Chris,
hoffe du kannst mir helfen/einen Hinweis geben meinen Vi Dev.Account zu reactivieren.
Warum auch immer mußte ich mein NR neu starten und dabei ist mir der Zugang zu iam.viessmann.com/idp/v3/authorize?client_id… verloren gegangen.
Wenn ich versuche den Dev.Account aufzurufen, erhalte ich die Nachricht:
You have no second factor available. Please contact support.
Einfach gesagt, aber ich finde keine URL auf der ich den Support (welchen? für Dev.Account) kontaktieren könnte.
Hast du eine Idee, am besten eine URL oder Mail Adr!
Danke für deine Mühe!
Gruß
Günter
Genau kann ich es dir auch nicht sagen. Probiere mal von ganz vorne anzufangen: Die Client ID auf der Viessmann Seite löschen und neu generieren. Evtl hilft das. Die neue ID dann gemäß Anleitung in den Flow einbauen.
Hilfe bekommst du von developer@viessmann.com
Oder über einen Hilferuf in der Viessmann Community
VG
Chris
Vielen Dank für den Tipp mit der geänderten Position der DeviceID in 2024.
Bis letzte Woche lief alles, dann gestern Abend der Ausfall.
Benutze Viessmann WP 200-S
Danke für deinen Hinweis ACHTUNG Neu 2024
meine Datenabfrage ging seit heute 11.2.2024 nicht mehr. Bei mir steckt die DeviceID jetzt in msg.payload.data[2].id
Ich habe mal deinen Node zur Suche nach deviceType === 'heating'
eingebaut. läuft wieder
VG
Florian
Freut mich, dass mein Blog doch zu was nutze ist… 🙃
Schöne Faschings/Fasnet/Karnevalzeit
Chris
Sehr umfangreich hier und bestimmt hilfreich … Danke dafür. Stehe noch ganz am Anfang, aber vielleicht kann ich einen Tip geben bzgl. Docker. Ich habe für meine Docker ( auf einer Synology) ein Macvlan Netzwerk eingerichtet. Das hat den Vorteil dass man dann IP's aus dem Hauptnetz, bzw. dem Netz auf dem der DockeHost läuft ,manuell vergeben kann. Damit holt man die Docker aus dem 172.er Dockernetz raus. Man braucht sich dann auch keine Sorgen mehr machen dass die Docker Ports untereinander Probleme machen. Für die Synology hat der Kollege Navigio recht gute Anleitungen auf YouTube
VG Jürgen
Vielen Dank für diesen Tipp!
VG Chris