How to prevent grid feed in from PV inverter when batteries are fully charged

My set up: Multiplus II 5KW-Cerbo-gx EM24 energy meter and 16 kW battery and PV Hoymiles micro inverters.

I don’t want to feed in to the grid, the Hoymiles cannot be controlled by the cerbo and will start feeding in when the batteries are fully charged. That is what happens at this moment. Would it be possible to switch off (disconnect) the PV inverters with a suitable relay, driven from the programmable relay outputs of the multiplus. (with relay assistant(s), setup for relay off at 99% SOC and relay on at 50% SOC or something??)

Rob K

If you use a DTU-Pro(-s), then you can implement zero-export functionality for your Hoymiles PV.

Monitoring - Hoymiles - Export Management - Technical Note EN.pdf (10,8 MB)

Monitoring - Hoymiles - Export Management - Datasheet EN.pdf (502,9 KB)

Alternatively, you can use OpenDTU or Ahoy (e.g. running on OpenDTU Fusion) as a replacement for the Hoymiles DTU.

I use OpenDTU Fusion with OpenDTU for my nine HMS-2000-4T inverters. Throttling the inverters is done from Node-Red on the Cerbo using MQTT which is one of the options of OpenDTU.

HTH

@Desperado

would you mind to share your NodeRed flow to throttle Hoymiles via OpenDTU?

I have a similar setup and requested SunSpec on OpenDTU to integrate with Venus OS natively but their is no progress.

Many thanks in advance

Björn

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": ""
    }
]
1 Like