Sure
I have seen and supported your request.
I have also invested some time into creating a Sunspec frontend for OpenDTU using ESPhome on a ESP32 using a custom component. But during testing I found no advantages of this approach over the Node-Red/MQTT implementation.
[
{
"id": "557f588d95b816c4",
"type": "tab",
"label": "PV Management",
"disabled": false,
"info": "",
"env": []
},
{
"id": "c710b34276d325d5",
"type": "group",
"z": "557f588d95b816c4",
"name": "Initialization",
"style": {
"label": true,
"color": "#3f93cf"
},
"nodes": [
"d44d53ae9fe4b4b4",
"6e3b657bdb27649e"
],
"x": 54,
"y": 39,
"w": 492,
"h": 82
},
{
"id": "bf63a55262758cce",
"type": "group",
"z": "557f588d95b816c4",
"name": "Update inverters",
"style": {
"label": true,
"color": "#3f93cf"
},
"nodes": [
"37d6c2d60d69d0e0",
"50360f4917f2628e",
"b438fb7573988ceb",
"7f35a933b01bae63",
"a5daccf52b0628a7",
"1ec9bca917fa239c",
"a066a3120ef5935b",
"55f26d14e5030d7a",
"5399832a2857211b",
"60a3c28e9bca8fa2",
"7b8f2a8e1f2b07fb",
"c6a24122af813ece",
"966dc271ff30cf64"
],
"x": 54,
"y": 139,
"w": 1992,
"h": 182
},
{
"id": "d44d53ae9fe4b4b4",
"type": "inject",
"z": "557f588d95b816c4",
"g": "c710b34276d325d5",
"name": "Wait 5s then once",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "5",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 190,
"y": 80,
"wires": [
[
"6e3b657bdb27649e"
]
]
},
{
"id": "6e3b657bdb27649e",
"type": "change",
"z": "557f588d95b816c4",
"g": "c710b34276d325d5",
"name": "Initialize flow variables",
"rules": [
{
"t": "set",
"p": "CurrentLimit",
"pt": "flow",
"to": "0",
"tot": "num"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 420,
"y": 80,
"wires": [
[]
]
},
{
"id": "37d6c2d60d69d0e0",
"type": "change",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Save",
"rules": [
{
"t": "set",
"p": "CurrentLimit",
"pt": "flow",
"to": "payload",
"tot": "msg",
"dc": true
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1110,
"y": 240,
"wires": [
[
"55f26d14e5030d7a"
]
]
},
{
"id": "50360f4917f2628e",
"type": "delay",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Up: Once per 30s",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "30",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 710,
"y": 220,
"wires": [
[
"60a3c28e9bca8fa2"
]
]
},
{
"id": "b438fb7573988ceb",
"type": "delay",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Down: Once per 5s",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 710,
"y": 260,
"wires": [
[
"60a3c28e9bca8fa2"
]
]
},
{
"id": "7f35a933b01bae63",
"type": "split",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Split inverter array",
"splt": "\\n",
"spltType": "str",
"arraySplt": "1",
"arraySpltType": "len",
"stream": false,
"addname": "",
"x": 1530,
"y": 240,
"wires": [
[
"a066a3120ef5935b"
]
]
},
{
"id": "a5daccf52b0628a7",
"type": "debug",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "mqtt publish",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "'mqtt publish: topic=' & msg.topic & ', value=' & msg.payload",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1930,
"y": 260,
"wires": []
},
{
"id": "1ec9bca917fa239c",
"type": "mqtt out",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "HA_MQTT",
"topic": "",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "b9494de4dc4a6fee",
"x": 1930,
"y": 220,
"wires": []
},
{
"id": "a066a3120ef5935b",
"type": "change",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Build mqtt msg",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "'solar/' & $string(msg.payload) & '/cmd/limit_nonpersistent_absolute'",
"tot": "jsonata"
},
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "$ceil($flowContext(\"CurrentLimit\") / msg.invertercount)",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1740,
"y": 240,
"wires": [
[
"1ec9bca917fa239c",
"a5daccf52b0628a7"
]
]
},
{
"id": "55f26d14e5030d7a",
"type": "change",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Load array of inverters",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "[\"111111111111\",\"222222222222\",\"333333333333\",\"444444444444\",\"555555555555\",\"666666666666\",\"777777777777\",\"888888888888\",\"999999999999\"]",
"tot": "json",
"dc": true
},
{
"t": "set",
"p": "invertercount",
"pt": "msg",
"to": "payload.length",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1300,
"y": 240,
"wires": [
[
"7f35a933b01bae63"
]
]
},
{
"id": "5399832a2857211b",
"type": "comment",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Limit PV inverter update rate",
"info": "",
"x": 800,
"y": 180,
"wires": []
},
{
"id": "60a3c28e9bca8fa2",
"type": "delay",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Overall once per 5s",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 930,
"y": 240,
"wires": [
[
"37d6c2d60d69d0e0",
"966dc271ff30cf64"
]
]
},
{
"id": "7b8f2a8e1f2b07fb",
"type": "inject",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Wait 10s then every 5s",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "5",
"crontab": "",
"once": true,
"onceDelay": "10",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 210,
"y": 240,
"wires": [
[
"c6a24122af813ece"
]
]
},
{
"id": "c6a24122af813ece",
"type": "function",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "Calculate PV max power",
"func": "if(global.get(\"pv.producing\") == false) {\n // No PV\n flow.set(\"CurrentLimit\", 0);\n node.status({fill:\"grey\", shape:\"dot\", text:\"No PV\"});\n return( [null, null] ); // No need to continue\n}\n\nif(global.get(\"grid.present\")) {\n // On-grid, We must regulate PV\n if(global.get(\"battery.soc\") < 85.0) {\n // Battery can absorb all PV\n msg.payload = global.get(\"pv.powermax\");\n } else {\n msg.payload = 0.0;\n msg.payload += global.get(\"grid.maxfeedin\");\n msg.payload += global.get(\"acloads.power_avg\");\n msg.payload += Math.abs(global.get(\"battery.power_avg\"));\n \n msg.payload *= 1.02; // Compensate for losses (empirical determined)\n }\n} else {\n // Off-grid, Victron regulates using frequency shifting\n msg.payload = global.get(\"pv.powermax\");\n}\n\n// Boundary checks\nif(msg.payload < global.get(\"pv.powermin\")) { msg.payload = global.get(\"pv.powermin\"); }\nif(msg.payload > (global.get(\"pv.powermax\") - 100)) { msg.payload = global.get(\"pv.powermax\"); }\n\nmsg.payload = Math.round(msg.payload);\n\n// Change large enough? Larger than 200W?\nif(Math.abs(msg.payload - flow.get(\"CurrentLimit\")) < 200) {\n // No, don't bother\n return( [null, null] );\n}\n\nnode.status({fill:\"grey\", shape:\"dot\", text:msg.payload});\n\n// Up takes exit #1. down takes #2\nif(msg.payload > flow.get(\"CurrentLimit\")) {\n return( [msg, null] ); // Up\n} else {\n return( [null, msg] ); // Down (or equal)\n}\n\nreturn( [null, null] ); /* NOTREACHED */",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 470,
"y": 240,
"wires": [
[
"50360f4917f2628e"
],
[
"b438fb7573988ceb"
]
],
"outputLabels": [
"Up",
"Down"
]
},
{
"id": "966dc271ff30cf64",
"type": "debug",
"z": "557f588d95b816c4",
"g": "bf63a55262758cce",
"name": "CurrentLimit",
"active": true,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"statusVal": "payload",
"statusType": "auto",
"x": 1130,
"y": 280,
"wires": []
},
{
"id": "b9494de4dc4a6fee",
"type": "mqtt-broker",
"name": "",
"broker": "homeassistant",
"port": "8883",
"tls": "",
"clientid": "Victron",
"autoConnect": true,
"usetls": true,
"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": ""
}
]