ATON-Heizstab der Technischen Alternative per nodeRED ansteuern
Verfasst: Mo Sep 19, 2022 4:05 pm
Nach elendig langem hin- und her habe ich es nun geschafft, einen modbus-Server aufzusetzen, mit dem ich über den C.M.I. der TA den Überschuss aus der openWB in das dortige Universum bekomme.
Ich fange gleich mal mit dem Flow an: (es braucht node-red-contrib-modbus)
Eigentlich sind es drei Flows:
Eine Anmerkung noch zum verwendeten Port: Seitens CMI kann der unter "Ethernet" eingestellt werden, gilt dann aber für alle modbus-Verbindungen. Default ist 502 und der ist bei nodeRED auf einer eigenen Raspi-Installation nicht erreichbar, denn der nodeRED-Prozess läuft als user "pi" und der darf erst ab Port 1024 ran. Hier muss man entweder sich auf einen anderen Port (z.B. 10502) einigen oder nodeRED als root laufen lassen. Bei mir läuft nodeRED als REDmatic auf der homematic-CCU und da gibt es das Problem nicht. Hier sind auch die http-Endpunkte ein bisschen anders als auf einer normalen nodeRED-Installation hier kommt ein "addons/red" davor, wohingegen bei einer Raspi-Installation auf Port 1880 statt 80 gelauscht wird. Die IP-Adresse 192.168.8.2 heisst an allen anderen Stellen ccu.fritz.box - das kann TA leider nicht.
Und hier noch das smarthome2.0-Gerät:
Aus historischen Gründen habe ich hier als Ein-Aus-Schalter noch einen Shelly 1 drin, der bei einem Regler einen Digital-Eingang zur Freigabe der Pufferheizung schaltet. Mit dieser 1/0 schalte ich dann die Funktion "Leistungsregler" in Zwangsbetrieb und die Vorgabe-Leistung kommt über den CMI als Analogwert. Eine Nutzung des Energiemanagers verbietet sich, da der erwartet, dass sich die zu verbratende Leistung um die Vorgabe reduziert wird. Das ist der richtige Weg, wenn man das TA-Zählermodul verwendet, mit einer Vorgabe aus der openWB wird das nur kompliziert. Hier der Leistungsregler - eigentlich stellt man da nur die min- und max-Leistung ein, der Rest ist Default.
Der Regler selbst ist ein RSM610, der Ausgang an dem der ATON angeschlossen ist, steht auf PWM: (Wichtig ist die 1000 bei Eingangswert 2 - mit den erwartet angenommenen 100 sind's um eine Größenordnung zu wenig Watts)
Zu guter Letzt noch den Flow für den einfachen Import:
Es läuft gerade mal so produktiv, Erfahrungswerte fehlen noch. Das hier sollte aber allen Interessierten schon einmal genug Einstiegshilfe bieten. Auch wenn's kein Heizstab von TA ist, sondern irgendein anderer mit Leistungsvorgabe von außen. Oder eine Wärmepumpe.
edit: Update des Flows und aktualisierte Screenshots einer neuen Version nach der Winterpause.
Ich fange gleich mal mit dem Flow an: (es braucht node-red-contrib-modbus)
Eigentlich sind es drei Flows:
- Ganz oben mit den Inject-Nodes ist der Modbus-Server (ohne flex, ich habe ja nur zwei Bytes) Für Tests schreibt die Inject-Node einen numerischen Wert in die erste Funktion, die aus dem Wert ein zwei-elementiges Array mit den beiden Bytes für einen 16-bittigen signed Int im little Endian Format macht. Dieses Array wird dann in der nächsten Funktion in den Server als holding-Register geschrieben.
- Ein bisschen drunter ist ein eigentlich überflüssiger Modbus-Client, der zeigt aber ganz schön, was da gerade so im Server für Werte stehen. Die beiden sind über den Port 502 auf localhost miteinander verbunden.
- Der letzte Flow ist der Endpunkt für die Leistungsabfrage das smarthome2.0-http-Geräts. Dem wird von der openWB die zu verbrauchende Leistung mitgegeben und diese wird in der ersten Funktion herausgelesen und in den Modbus-Server geschrieben. Im weiteren Verlauf muss noch die Leistungsaufnahme des Heizstabs ermittelt werden, das passiert per HTTP-Request auf den zur Leistungsmessung verbauten Shelly 1PM. Wenn man mit der Funktion "Energiemanager" arbeitet, dann könnte man sicherlich auch die verbrauchte Leistung aus der TA-Welt wieder aus dem CMI importieren, allerdings erfüllt die openWB bereits die Aufgabe des Energiemanagers. Die Vorgabewerte sind auch nur theoretischer Natur, die Leistungsaufnahme in der echten Welt sind um einiges geringer. (Das habe ich mit dem 1pm, einem 2em und einer homematic-Steckdose verifiziert: Aus 3000W Leistung werden gemessen nur so um die 2800W. Laut TA gehört das so wegen erlaubter Streuungen bei den Bauteilen).
Eine Anmerkung noch zum verwendeten Port: Seitens CMI kann der unter "Ethernet" eingestellt werden, gilt dann aber für alle modbus-Verbindungen. Default ist 502 und der ist bei nodeRED auf einer eigenen Raspi-Installation nicht erreichbar, denn der nodeRED-Prozess läuft als user "pi" und der darf erst ab Port 1024 ran. Hier muss man entweder sich auf einen anderen Port (z.B. 10502) einigen oder nodeRED als root laufen lassen. Bei mir läuft nodeRED als REDmatic auf der homematic-CCU und da gibt es das Problem nicht. Hier sind auch die http-Endpunkte ein bisschen anders als auf einer normalen nodeRED-Installation hier kommt ein "addons/red" davor, wohingegen bei einer Raspi-Installation auf Port 1880 statt 80 gelauscht wird. Die IP-Adresse 192.168.8.2 heisst an allen anderen Stellen ccu.fritz.box - das kann TA leider nicht.
Und hier noch das smarthome2.0-Gerät:
Aus historischen Gründen habe ich hier als Ein-Aus-Schalter noch einen Shelly 1 drin, der bei einem Regler einen Digital-Eingang zur Freigabe der Pufferheizung schaltet. Mit dieser 1/0 schalte ich dann die Funktion "Leistungsregler" in Zwangsbetrieb und die Vorgabe-Leistung kommt über den CMI als Analogwert. Eine Nutzung des Energiemanagers verbietet sich, da der erwartet, dass sich die zu verbratende Leistung um die Vorgabe reduziert wird. Das ist der richtige Weg, wenn man das TA-Zählermodul verwendet, mit einer Vorgabe aus der openWB wird das nur kompliziert. Hier der Leistungsregler - eigentlich stellt man da nur die min- und max-Leistung ein, der Rest ist Default.
Der Regler selbst ist ein RSM610, der Ausgang an dem der ATON angeschlossen ist, steht auf PWM: (Wichtig ist die 1000 bei Eingangswert 2 - mit den erwartet angenommenen 100 sind's um eine Größenordnung zu wenig Watts)
Zu guter Letzt noch den Flow für den einfachen Import:
Code: Alles auswählen
[{"id":"ba6bd7e6.cd6d48","type":"modbus-server","z":"b8ffe81b.38e0c8","name":"","logEnabled":false,"hostname":"0.0.0.0","serverPort":"502","responseDelay":100,"delayUnit":"ms","coilsBufferSize":"0","holdingBufferSize":"8","inputBufferSize":"0","discreteBufferSize":"0","showErrors":true,"x":820,"y":100,"wires":[["aface45f.0de24"],[],[],[],[]]},{"id":"7ef3011c.be2738","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1500","payloadType":"num","x":110,"y":180,"wires":[["bf859d30.7ba0a"]]},{"id":"5a99fd48.c44c04","type":"function","z":"b8ffe81b.38e0c8","name":"set_holding","func":"\nvar a = new Int16Array(2);\n\na = msg.payload;\n\nmsg.payload = {\n 'value': [ a[0], a[1] ],\n 'register': 'holding', \n 'address': 0 , \n 'disableMsgOutput' : 0 \n}; \nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":610,"y":120,"wires":[["c32d5414.fc92a","ba6bd7e6.cd6d48"]]},{"id":"841b64ab.cc7be8","type":"modbus-read","z":"b8ffe81b.38e0c8","d":true,"name":"","topic":"","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"unitid":"0","dataType":"HoldingRegister","adr":"0","quantity":"2","rate":"3","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"ee8487e.a76e878","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":550,"y":320,"wires":[[],["aa7bb0e0.105b78","bab5d2c7.0b9558"]]},{"id":"aa7bb0e0.105b78","type":"modbus-response","z":"b8ffe81b.38e0c8","name":"","registerShowMax":"2","x":830,"y":280,"wires":[]},{"id":"bf859d30.7ba0a","type":"function","z":"b8ffe81b.38e0c8","name":"calc_big_endian","func":"\n\nvar m1 = {};\nvar m2 = {};\n\nvar a = new Int16Array(2);\n\nvar n = msg.payload;\nvar neg = false;\n\nvar p = Math.pow(2,16) / 2;\n\n// Eingabe auf Minimum und Maximum kappen\n// positiv ist 0...32.767\nif (n > p - 1){\n n = p - 1;\n}\n\n// negativ ist -32.768...-1\nif ( n < 0 ) {\n neg = true;\n if (n <= -1 * p ) {\n n = p;\n }\n else {\n n = -n;\n }\n}\n\n// konvertieren nach hex\nvar s = n.toString(16);\n\n// vierstellig machen, damit stringweise schön nach \n// LSB und MSB getrennt werden kann\ns = \"000\" + s;\ns = s.slice(s.length - 4, s.length);\n\nvar b0 = s.slice(0, 2); // MSB\nvar b1 = s.slice(2, 4); // LSB \n\nif (neg){\n // 0,128 = -32.768; 255,255 = -1\n // bei negativen Zahlen wird von 255 heruntergezählt:\n a[0] = 255 - parseInt(b0,16) + 1;\n if ( a[0] == 256 ){ // overflow\n a[0] = 255;\n }\n a[1] = 255 - parseInt(b1,16); \n if ( a[1] == 256 ){ // overflow\n a[1] = 255;\n }\n}\nelse {\n // 255,127 = 32.767\n //positive Zahlen zählen wie sonst auch 0...255\n a[0] = parseInt(b0,16);\n a[1] = parseInt(b1,16);\n} \n\nm1.payload = a;\nm2.payload = s;\nreturn [m1, m2]","outputs":2,"noerr":0,"initialize":"","finalize":"","x":380,"y":100,"wires":[["5a99fd48.c44c04","de3cdc1a.b14838"],[]]},{"id":"b338ee58.2597c","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":110,"y":60,"wires":[["bf859d30.7ba0a"]]},{"id":"ff4ba88d.77abe","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"300","payloadType":"num","x":110,"y":100,"wires":[["bf859d30.7ba0a"]]},{"id":"2496d4f4.5ba07c","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1700","payloadType":"num","x":110,"y":220,"wires":[["bf859d30.7ba0a"]]},{"id":"44a2e09c.0f4148","type":"function","z":"b8ffe81b.38e0c8","name":"","func":"var m = msg;\n\nvar w;\n\nif (msg.req.query.watt === undefined ){\n w = \"0\";\n}\nelse{\n w = msg.req.query.watt;\n}\n\nm.payload = Number(w);\n\nreturn m;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":360,"y":480,"wires":[["33d4078a.9ff1a8","13aa1d0a.4da8cb","bf859d30.7ba0a"]]},{"id":"2c8d77bc.893b9","type":"http in","z":"b8ffe81b.38e0c8","name":"","url":"/heizstab","method":"get","upload":false,"swaggerDoc":"","x":160,"y":480,"wires":[["44a2e09c.0f4148"]]},{"id":"af8ddf23.6b6ba8","type":"function","z":"b8ffe81b.38e0c8","name":"","func":"var w = msg.payload.meters[0].power;\nif (w === undefined){\n msg.payload = 0;\n} \nelse{ \n msg.payload = Math.trunc(w);\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":800,"y":480,"wires":[["5dfb331f.2b7ddc","cfb321a7.bf72e"]]},{"id":"13aa1d0a.4da8cb","type":"http request","z":"b8ffe81b.38e0c8","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"192.168.8.24/status","tls":"","persist":false,"proxy":"","authType":"","x":590,"y":480,"wires":[["af8ddf23.6b6ba8"]]},{"id":"33d4078a.9ff1a8","type":"debug","z":"b8ffe81b.38e0c8","name":"heizstab_setW","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":600,"y":540,"wires":[]},{"id":"5dfb331f.2b7ddc","type":"debug","z":"b8ffe81b.38e0c8","name":"heizstab_getW","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1020,"y":540,"wires":[]},{"id":"aface45f.0de24","type":"debug","z":"b8ffe81b.38e0c8","name":"modbus_buffer","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1060,"y":180,"wires":[]},{"id":"1f0b2e4c.d2bd22","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"990","payloadType":"num","x":110,"y":140,"wires":[["bf859d30.7ba0a"]]},{"id":"e4c4e652.1e6e6","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"3000","payloadType":"num","x":110,"y":260,"wires":[["bf859d30.7ba0a"]]},{"id":"de3cdc1a.b14838","type":"debug","z":"b8ffe81b.38e0c8","name":"hex-out","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":600,"y":180,"wires":[]},{"id":"c32d5414.fc92a","type":"debug","z":"b8ffe81b.38e0c8","name":"holding_out","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":180,"wires":[]},{"id":"bab5d2c7.0b9558","type":"debug","z":"b8ffe81b.38e0c8","name":"modbus_read","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":820,"y":360,"wires":[]},{"id":"998b4d29.d6f7e8","type":"inject","z":"b8ffe81b.38e0c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"3600","payloadType":"num","x":110,"y":300,"wires":[["bf859d30.7ba0a"]]},{"id":"cfb321a7.bf72e","type":"http response","z":"b8ffe81b.38e0c8","name":"","statusCode":"","headers":{},"x":990,"y":480,"wires":[]},{"id":"ee8487e.a76e878","type":"modbus-client","name":"modbus-local","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"tcpHost":"127.0.0.1","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":1,"commandDelay":1,"clientTimeout":1000,"reconnectOnTimeout":true,"reconnectTimeout":2000,"parallelUnitIdsAllowed":true}]
edit: Update des Flows und aktualisierte Screenshots einer neuen Version nach der Winterpause.