Am verzweifeln mit Nodred und OpenDTU

Hallo Zusammen.
versuch meine HM600 und 800 über Nodered zu drosseln was immer nicht geht
dazu habe ich einen test Flow aufgebaut und es passiert nichts im OpenDTU
OpenDTU ist aktuell v26.3.30
Venus GX hat die V3.75
henne49 dbus opendtu auch die neueste version

woran klemmt es

Gruß Markus

[
    {
        "id": "0531efbe3bba3bc5",
        "type": "function",
        "z": "e50003d3fab7fd50",
        "name": "function 1",
        "func": "// 1. Wert vom Slider holen und als strikte Ganzzahl definieren\nlet percentLimit = parseInt(msg.payload);\n\n// Sicherheitsnetz (Da du manuell 10% getestet hast, erlauben wir das hier auch)\nif (percentLimit < 10) percentLimit = 10;\nif (percentLimit > 100) percentLimit = 100;\n\n// 2. Das exakte MQTT-Topic mit deinem Basis-Topic \"solar/\"\nmsg.topic = \"solar/114181XXXXXX/cmd/limit_nonpersistent_relative\";\n\n// 3. WICHTIG: Die Payload MUSS eine reine, nackte Zahl sein (KEIN Text, KEIN .toString())\nmsg.payload = percentLimit;\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 400,
        "y": 180,
        "wires": [
            [
                "8b6334eda206ae50"
            ]
        ]
    }
]

läuft ohne Probleme.

ich bin von irgendwelchen Dbus Erweiterungen abgekommen

Gruß Dirk

Ich wuerde das zuerst komplett ausserhalb von dbus-opendtu testen, damit du siehst, ob OpenDTU den MQTT-Befehl ueberhaupt annimmt.

Pruefe mit MQTT Explorer oder mosquitto_pub direkt gegen denselben Broker:

solar/114181XXXXXX/cmd/limit_nonpersistent_relative

Payload z.B. nur:

50

Wichtig laut OpenDTU-Doku: bei limit_nonpersistent_relative muss die MQTT-Nachricht non-retained gesendet werden. Wenn Retain gesetzt ist, wird sie ignoriert. Danach sollte sich solar/114181XXXXXX/status/limit_relative bzw. die Weboberflaeche ziemlich direkt aendern.

Wenn das direkte Publishen klappt, liegt es im Node-RED-Flow/MQTT-Out-Node. Dann dort speziell pruefen:

  • MQTT-Out wirklich auf denselben Broker wie OpenDTU?
  • Retain im MQTT-Out-Node aus?
  • Topic wirklich exakt mit der Wechselrichter-Seriennummer, nicht der DTU-ID?
  • Kommt aus dem Slider wirklich eine Zahl an? Zum Test vorher einen Debug-Node direkt vor den MQTT-Out haengen.

Deine Function sieht vom Prinzip her nicht falsch aus. Der typische Haken ist hier eher Retain, falscher Basis-Topic/Seriennummer oder dass der MQTT-Out gar nicht auf dem Broker landet, auf dem OpenDTU hoert.

Doku zum Abgleich: MQTT Topics - OpenDTU Documentation

Hallo Zusammen,
@ DirkW
das hatte ich aus erst getestet mit einem meiner Hoymiles was aber nicht ging SN und IP hatte ich angepasst.
Hatte dann was anderes versucht was auch nicht ging ,ist unten angehängt daher der kleine versuch ob überhaupt sich was regeln lässt.
warum ich diesen Test geschrieben habe war mein ursprünglicher Plan mit meinem EM24 die Drosselung der beiden Wechselrichter vorzunehmen.

Blockzitat
[
{
“id”: “fcabfa2270aca509”,
“type”: “tab”,
“label”: “Flow 1”,
“disabled”: false,
“info”: “”,
“env”:
},
{
“id”: “503d0a58ee96d68c”,
“type”: “victron-input-gridmeter”,
“z”: “fcabfa2270aca509”,
“service”: “com.victronenergy.grid/30”,
“path”: “/Ac/Power”,
“serviceObj”: {
“service”: “com.victronenergy.grid/30”,
“name”: “Grid meter”
},
“pathObj”: {
“path”: “/Ac/Power”,
“type”: “float”,
“name”: “Power (W)”
},
“name”: “Netz-Leistung (Direkt EM24)”,
“onlyChanges”: false,
“roundValues”: “2”,
“rateLimit”: 0,
“outputs”: 1,
“x”: 160,
“y”: 160,
“wires”: [
[
“e1d9f41c3b276828”,
“e900dcaff77313a3”
]
]
},
{
“id”: “cb48f540f6620c61”,
“type”: “function”,
“z”: “fcabfa2270aca509”,
“name”: " Kopplung",
“func”: “const MAX_HM600 = 600;\nconst MAX_HM800 = 800;\nconst MAX_TOTAL = MAX_HM600 + MAX_HM800; // 1400W\nconst TARGET_GRID = 20; \n\n// 1. Das aktuelle Limit aus dem internen Gedächtnis holen (oder mit Maximum starten)\nlet currentLimitWatt = context.get(‘currentLimitWatt’);\nif (currentLimitWatt === undefined || currentLimitWatt === null || isNaN(currentLimitWatt)) {\n currentLimitWatt = MAX_TOTAL;\n}\n\n// Zeitstempel für die 5-Sekunden-Bremse abfragen\nlet lastSendTime = context.get(‘lastSendTime’) || 0;\nlet currentTime = Date.now();\n\n// 2. Den Netzwert DIREKT aus dem Kabel lesen\nlet gridPower = Number(msg.payload);\n\n// Sicherheitsnetz: Falls kein gültiges Signal im Kabel liegt, die Berechnung stoppen\nif (isNaN(gridPower)) {\n return null; \n}\n\n// 3. Regelabweichung berechnen\nlet error = gridPower - TARGET_GRID;\n\nif (gridPower < 0) {\n // A: EINSPEISUNG (z.B. -400W) → PV-Leistung SOFORT und aggressiv reduzieren\n currentLimitWatt += (error * 0.95);\n} else if (gridPower > 40) {\n // B: NETZBEZUG (z.B. 140W) → PV-Leistung schrittweise erhöhen\n currentLimitWatt += (error * 0.70);\n}\n\n// 4. Grenzen knallhart absichern\nif (currentLimitWatt > MAX_TOTAL) currentLimitWatt = MAX_TOTAL;\nif (currentLimitWatt < 140) currentLimitWatt = 140; // 10% Mindestleistung\n\n// Wert für das nächste Kabel-Signal im Gedächtnis speichern\ncontext.set(‘currentLimitWatt’, currentLimitWatt);\n\n// 5. Zeitprüfung: Sind bereits 5 Sekunden (5000 Millisekunden) vergangen?\nif (currentTime - lastSendTime >= 5000) {\n \n // Proportionale Verteilung berechnen\n let shareHM600 = (currentLimitWatt / MAX_TOTAL) * MAX_HM600;\n let shareHM800 = (currentLimitWatt / MAX_TOTAL) * MAX_HM800;\n\n let pctHM600 = Math.round((shareHM600 / MAX_HM600) * 100);\n let pctHM800 = Math.round((shareHM800 / MAX_HM800) * 100);\n\n if (pctHM600 < 10) pctHM600 = 10;\n if (pctHM600 > 100) pctHM600 = 100;\n if (pctHM800 < 10) pctHM800 = 10;\n if (pctHM800 > 100) pctHM800 = 100;\n\n // Aktuellen Zeitstempel für den nächsten Durchlauf merken\n context.set(‘lastSendTime’, currentTime);\n\n // Ausgaben an die MQTT-Nodes und Dashboard-Texte senden\n return [ { payload: pctHM600 }, { payload: pctHM800 } ];\n}\n\n// Falls keine 5 Sekunden vergangen sind, wird absolut nichts weitergeleitet\nreturn null;\n”,
“outputs”: 2,
“timeout”: “”,
“noerr”: 0,
“initialize”: “”,
“finalize”: “”,
“libs”: ,
“x”: 680,
“y”: 160,
“wires”: [
[
“2019e8a7051ce831”,
“e646aa8b46bac0c1”
],
[
“14fd5b5e970bb703”
]
]
},
{
“id”: “e1d9f41c3b276828”,
“type”: “ui_chart”,
“z”: “fcabfa2270aca509”,
“name”: “Netzverlauf”,
“group”: “battery_ctrl_group”,
“order”: 8,
“width”: 0,
“height”: 0,
“label”: “Netzverlauf (letzte Stunde)”,
“chartType”: “line”,
“legend”: “false”,
“xformat”: “HH:mm:ss”,
“interpolate”: “linear”,
“removeOlder”: 1,
“removeOlderUnit”: “3600”,
“colors”: [
#1f77b4”,
#aec7e8”,
#ff7f0e”,
#ffbb78”,
#2ca02c”,
#98df8a”,
#d62728”,
#ff9896”,
#9467bd
],
“outputs”: 1,
“x”: 370,
“y”: 100,
“wires”: [

]
},
{
“id”: “e900dcaff77313a3”,
“type”: “delay”,
“z”: “fcabfa2270aca509”,
“name”: “Zeitverzögerung 1Sec”,
“pauseType”: “rate”,
“timeout”: “5”,
“timeoutUnits”: “seconds”,
“rate”: “1”,
“nbRateUnits”: “5”,
“rateUnits”: “second”,
“randomFirst”: “1”,
“randomLast”: “5”,
“randomUnits”: “s”,
“drop”: true,
“allowrate”: false,
“outputs”: 1,
“x”: 460,
“y”: 160,
“wires”: [
[
“cb48f540f6620c61”
]
]
},
{
“id”: “e646aa8b46bac0c1”,
“type”: “mqtt out”,
“z”: “fcabfa2270aca509”,
“name”: “MQTT HM600 Limit”,
“topic”: “solar/1141810xxxxxx/cmd/limit_nonpersistent_relative”,
“qos”: “”,
“retain”: “”,
“respTopic”: “”,
“contentType”: “”,
“userProps”: “”,
“correl”: “”,
“expiry”: “”,
“broker”: “e6f6ef6a378da38e”,
“x”: 890,
“y”: 120,
“wires”:
},
{
“id”: “14fd5b5e970bb703”,
“type”: “delay”,
“z”: “fcabfa2270aca509”,
“name”: “1s Versatz”,
“pauseType”: “delay”,
“timeout”: “1”,
“timeoutUnits”: “seconds”,
“rate”: “1”,
“rateUnits”: “second”,
“randomFirst”: “1”,
“randomLast”: “5”,
“randomUnits”: “seconds”,
“drop”: false,
“allowrate”: false,
“outputs”: 1,
“x”: 870,
“y”: 200,
“wires”: [
[
“d944192787ca9945”,
“4566995916c5688e”
]
]
},
{
“id”: “d944192787ca9945”,
“type”: “mqtt out”,
“z”: “fcabfa2270aca509”,
“name”: “MQTT HM800 Limit”,
“topic”: “solar/114190xxxxxx/cmd/limit_nonpersistent_relative”,
“qos”: “”,
“retain”: “”,
“respTopic”: “”,
“contentType”: “”,
“userProps”: “”,
“correl”: “”,
“expiry”: “”,
“broker”: “e6f6ef6a378da38e”,
“x”: 1070,
“y”: 200,
“wires”:
},
{
“id”: “2019e8a7051ce831”,
“type”: “ui_text”,
“z”: “fcabfa2270aca509”,
“group”: “battery_ctrl_group”,
“order”: 10,
“width”: “3”,
“height”: “2”,
“name”: “Anzeige HM600”,
“label”: “Limit HM600:”,
“format”: “{{msg.payload}} %”,
“layout”: “row-spread”,
“x”: 880,
“y”: 80,
“wires”:
},
{
“id”: “4566995916c5688e”,
“type”: “ui_text”,
“z”: “fcabfa2270aca509”,
“group”: “battery_ctrl_group”,
“order”: 12,
“width”: “3”,
“height”: “2”,
“name”: “Anzeige HM800”,
“label”: “Limit HM800:”,
“format”: “{{msg.payload}} %”,
“layout”: “row-spread”,
“x”: 1060,
“y”: 260,
“wires”:
},
{
“id”: “battery_ctrl_group”,
“type”: “ui_group”,
“name”: “Batterieladung Regelung”,
“tab”: “battery_ctrl_tab”,
“order”: 1,
“disp”: true,
“width”: “16”,
“collapse”: false,
“className”: “”
},
{
“id”: “e6f6ef6a378da38e”,
“type”: “mqtt-broker”,
“name”: “”,
“broker”: “localhost”,
“port”: 1883,
“clientid”: “”,
“autoConnect”: true,
“usetls”: false,
“protocolVersion”: 4,
“keepalive”: 60,
“cleansession”: true,
“autoUnsubscribe”: true,
“birthTopic”: “”,
“birthQos”: “0”,
“birthRetain”: “false”,
“birthPayload”: “”,
“birthMsg”: {},
“closeTopic”: “”,
“closeQos”: “0”,
“closeRetain”: “false”,
“closePayload”: “”,
“closeMsg”: {},
“willTopic”: “”,
“willQos”: “0”,
“willRetain”: “false”,
“willPayload”: “”,
“willMsg”: {},
“userProps”: “”,
“sessionExpiry”: “”
},
{
“id”: “battery_ctrl_tab”,
“type”: “ui_tab”,
“name”: “Solar & Batterie”,
“icon”: “dashboard”,
“order”: 1,
“disabled”: false,
“hidden”: false
},
{
“id”: “db4befefa83a3fc9”,
“type”: “global-config”,
“env”: ,
“modules”: {
@victronenergy/node-red-contrib-victron”: “1.6.64”,
“node-red-dashboard”: “3.6.6”
}
}
]

Kleiner allgemeiner Hinweis, bzw. “Beobachtung”:

Habe da auch mal mit experimentiert, das throttling via OpenDTU ist aber ein furchtbar zerbrechliches Konstrukt: Wenn die OpenDTU in der Kommunikation zum WR nicht nachkommt, beginnt Sie ankommende “Werte” zu queuen, und wenn du mehr instruktionen generierst als die Kommunikation zwischen DTU und Micro hergibt, dann läuft das langfristig auf einen völlig unsinnigen setpoint raus, weil der Micro Werte bekommt die du vor 20, 30 Minuten und länger “gesendet” hast.

Mehr als 1 Wert pro 30 Sekunden konnte meine Open DTU bei 3 Micros nicht verlässlich durchbringen.

Hallo Markus

ich hab den Node Red Code auf 4 verschiedenen Hoymiles HM und HMS (OHNE W) laufen.

GGF musst du das Passwort im Code ändern

grafik

Viel Erfolg

Gruß
Dirk

Die Meßwerte (PV Ertrag & Co) werden von der OpenDTU wohl alle 20sec aktualisiert.

Ich nutze die OpenDTU um via MQTT die einzelnen Microwechselrichter als virtuelle PV-Inverter in Venus OS darzustellen.

Der Aktualisierungsintervall der openDTU lässt sich einstellen. Zu beachten ist, dass der Intervall eine Pausenzeit zwischen zwei Abfragen unabhängig der Wechselrichter ist. Soll heißen, hat man zB vier Wechselrichter und eine Zeit von zehn Sekunden eingestellt wird jeder Wechselrichter alle 40 Sekunden aktualisiert.

so heute nochmals versucht und komme nicht weiter mein letzter test.
kann sein das sich was in der V26.3.30 einiges geändert ahtt als bei den aleten versionen vor einem Jahr ?

[
    {
        "id": "6010b36fb76ac905",
        "type": "tab",
        "label": "OpenDTU Steuerung - Pure",
        "disabled": false,
        "info": ""
    },
    {
        "id": "delay_wr2",
        "type": "delay",
        "z": "6010b36fb76ac905",
        "name": "4s Gänsemarsch",
        "pauseType": "rate",
        "timeout": "4",
        "timeoutUnits": "seconds",
        "rate": "1",
        "rateUnits": "4",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 530,
        "y": 220,
        "wires": [
            [
                "http_out_fixed"
            ]
        ]
    },
    {
        "id": "http_out_fixed",
        "type": "http request",
        "z": "6010b36fb76ac905",
        "name": "HTTP POST an OpenDTU",
        "method": "POST",
        "ret": "bin",
        "paytoqs": "ignore",
        "url": "http://192.168.2.40",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "basic",
        "senderr": false,
        "headers": [
            {
                "keyType": "Content-Type",
                "keyValue": "",
                "valueType": "other",
                "valueValue": "application/json"
            }
        ],
        "x": 760,
        "y": 220,
        "wires": [
            [
                "8a1bee2c157edeb7"
            ]
        ]
    },
    {
        "id": "manual_slider",
        "type": "ui_slider",
        "z": "6010b36fb76ac905",
        "name": "Manuelles Limit",
        "label": "Festes WR Limit",
        "tooltip": "",
        "group": "pure_ctrl_group",
        "order": 1,
        "width": 6,
        "height": 1,
        "passthru": true,
        "outs": "end",
        "topic": "",
        "topicType": "str",
        "min": 10,
        "max": 100,
        "step": 5,
        "className": "",
        "x": 140,
        "y": 220,
        "wires": [
            [
                "slider_logic"
            ]
        ]
    },
    {
        "id": "slider_logic",
        "type": "function",
        "z": "6010b36fb76ac905",
        "name": "Slider Verarbeitung",
        "func": "// --- BITTE HIER DEINE ECHTEN SERIENNUMMERN EINTRAGEN ---\nconst SNR_600W = '114181xxxxxx';\nconst SNR_800W = '11419xxxxxx;\n\nlet val = Math.round(Number(msg.payload) / 5) * 5;\nif (val < 10) val = 10;\nif (val > 100) val = 100;\n\nflow.set('manual_mode', true);\n\n// REINES JSON FÜR OPENDTU V26 (Kein 'data=', Feld heißt 'limit')\nlet msg1 = { payload: { serial: SNR_600W, limit_type: 1, limit: val } };\nnode.send(msg1);\n\nsetTimeout(function() {\n    let msg2 = { payload: { serial: SNR_800W, limit_type: 1, limit: val } };\n    node.send(msg2);\n}, 4000);\n\nreturn null;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 330,
        "y": 220,
        "wires": [
            [
                "delay_wr2"
            ]
        ]
    },
    {
        "id": "8a1bee2c157edeb7",
        "type": "debug",
        "z": "6010b36fb76ac905",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 220,
        "wires": []
    },
    {
        "id": "pure_ctrl_group",
        "type": "ui_group",
        "name": "Manuelle Steuerung",
        "tab": "pure_ctrl_tab",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false,
        "className": ""
    },
    {
        "id": "pure_ctrl_tab",
        "type": "ui_tab",
        "name": "WR Manuell",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    },
    {
        "id": "9d276f226c2c432d",
        "type": "global-config",
        "env": [],
        "modules": {
            "node-red-dashboard": "3.6.6"
        }
    }
]

Ich würde den letzten Test noch nicht als Beleg für eine Änderung in OpenDTU v26 werten. Der Flow hat so schon ein paar harte Stolperstellen:

  1. In der Function fehlt beim zweiten String das schließende Anführungszeichen:
const SNR_800W = '11419xxxxxx';

Mit const SNR_800W = '11419xxxxxx; läuft die Function nicht sauber.

  1. Beim HTTP-Test würde ich nicht gegen die Root-URL posten, sondern gegen die Limit-API:
http://192.168.2.40/api/limit/config

Laut aktueller OpenDTU-Doku ist das Beispiel für ein relatives, nicht persistentes Limit:

data={"serial":"11418180xxxx", "limit_type":1, "limit_value":50}

Also Feldname limit_value, nicht limit. Je nach Node-RED HTTP-Request-Node heißt das auch: eher als Formularwert data=... senden, nicht als reines JSON an /. Basic Auth mit dem OpenDTU-Admin-Passwort nicht vergessen.

  1. Direkt zum Eingrenzen vorher/nachher prüfen:
http://192.168.2.40/api/limit/status

Nach dem Setzen steht der Status ggf. erst auf Pending und nach ein paar Sekunden auf Ok.

Ich würde das einmal ganz ohne Node-RED mit curl/Postman testen. Wenn das klappt, liegt der Fehler im Flow/HTTP-Node. Wenn es dort schon nicht klappt, ist es OpenDTU/Auth/Seriennummer/WR-Erreichbarkeit.

@busbum
das war der entscheidende Hinweis
http://192.168.2.40/api/limit/config geändert und schon geht es, so jetzt kann ich den rest der Regelung zum laufen bringen.
Ladestrom Drosselung bei 55V und 56V da das paceBMS so bescheiden ist und die monatliche Vollladung mit Balancen des 9,6KWh Speichers automatisieren.

Gruß Markus