ESS not working correctly in V3.70 when peakshaving

ESS Settings:

  • Mode: Optimized without battery life
  • Self-consumtion from battery: all system loads
  • Grid setpoint: 50W
  • AC-Coupled PC - feed in excess: on
  • Peak shaving: Always
    • Import: 13A (Node red logic in place to limit this to 5A if sum of all phases is >3000W)
    • Export: 25A

DESS Settings:

  • Mode: Green Mode
  • Allow feed in: true
  • Limit battery discharge to grid: false
1 Like

In other words, a pretty common ESS system. I looked at your data, and those grid exports were “commanded” by the control loop. This usually means there is a charging limit imposed somewhere, so to prevent the PV inverter power on the output from going into the battery, it has to send it to the grid.

The only issue, for me, is that nothing changed in Venus 3.70 related to how this works. We only made peak shaving fixes, and only when Self Consumption from battery is set to “output loads only”. There was one other fix, related to peak shaving while actively charging (eg, Keep Batteries Charged), but that is also not what your system is doing at this point.

So the only explanation I can see, is that something limited charge current at the time. This is unfortunately not logged to VRM, and you don’t have remote support on, so I was limited in what I could do.

Has it been tested with nodered disabled?

Hi Izak,

Yes it is indeed inspired by that chip :slight_smile:

Thank you for taking the time to look into it. I am positive that the charge voltage was 56.5V at the time of the screenshots and nothing else was preventing charge. I repeated the test on 27/2 as you can see in the next post. First screenshot 10:24 is on V3.70, rolled back to V3.67 at 10:25 and the second screenshot at 10:26 is on V3.67. For that test nothing besides the update/rollback was adjusted.

As for why the user defined voltage is nessecary (but please ignore this paragraph because it is not relevant to the issue and has been deactivated during testing): I am indeed setting a charge voltage of 54.8V from nodered once the battery is full (which it was not during testing). I deliberately omitted those details so as not to obscure the story (and because, of course, that would be the first thing people would point to). I disabled the necessary nodes for the test, and even turned off DVCC completely, but all with the same result. The reason for this user defined voltage is to allow transfer of DC PV power over the DC bus when the battery is full (and i am using that excess to heat a boiler on the AC side). My battery’s BMS is not lowering CVL when full, it only sets CCL to 0. As the MP uses this CVL voltage (56.5V) as ‘DC bus transfer voltage’, the battery is pushed into a OVP situation which we ofc do not want. So if the BMS decides the battery is full (which it determines based on pack/cell voltage), it recalibrates the SoC to 100 and sets the CCL to 0. I am setting the CVL tot 54.8V (which is a nice ‘absorption’ voltage for my battery) in nodered when CCL drops to 0. Aditionally i limit the charge current above 90% SoC to 40A so the battery is not pushed too hard in the final charge section. Excess power goes to a boiler thru an external PID loop. This workaround works perfect, but it would still be nice to have it embedded in fw for situations where the battery does not dynamically adjust CVL when full (eg. provide a ‘DC transfer voltage’ parameter which is used by the MP to transfer power at over the DC bus once the battery is full).

Today we will have excess solar, i have disabled all output nodes in nodered and updated to V3.70 at 9:33. At the moment the system behaves as expected (only thing i see is that the Grid power is fluctuating way more than before, but it does stay around the set 0W). When the solar input rises later today the issue should pop up. I will report back with the exact time of rollback later today. That way you will have more (and more relevant) data to look at.

Please let me know if there is anything else i can test on this setup.

  • Optimized without BL
  • Self cons > All system loads
  • Grid setpoint: 0W
  • Grid feed-in: All enabled, limited at 3500W
  • Peak shaving: Always - 10A
  • DVCC: Enabled, no manual limits set (Battery parameters: CVL: 56.50V, CCL: 200A)

The system is already exporting power to the grid for no obvious reason. I have also turned off the circuit breaker to the boiler as that would muddy the data (PID setpoint for the boiler is -100W so he would be taking the power)

Yes, I saw that, but I also noticed that this was not the reason for the limit, so that was not what I was referring to. A current limit of some sort would be what I would expect, for example, the DVCC maximum charge current setting. I see nothing like that coming from the battery. You seem fairly technical, so your input here is greatly appreciated.

I don’t think the peak shaving limits would be to blame. Those would not cause less charge to go into the battery, rather, it would cause more charge to go into the battery in some cases, in order to keep within the limits.

OK great, please turn on remote support, and I will take a look.

I have turned remote support on

Update from my side:

When I deactivate peakshaving, ESS is working fine. Also when I go back to V3.67, ESS works fine.

As lower the peakshaving import current, as higher the excess, see pictures below. From 20A and higher ESS is working fine.

1 Like

I can confirm, peakshaving deactivated (good, 0W):

Peakshaving 40A (good, 0W):

Peakshaving 10A (not good, exporting. I really need it to be working at this level because we have capacity based rates):

Sorry for changing these paramaters during remote support :slight_smile: I have only: Disabled PS > set 40A limit > back to 10A as it was.

Hi @555 and also @Pigmaster ,

I found the line of code causing the issue. I took the liberty of installing a version with this line reverted on both your systems, and you should see that it is now working correctly. This has however also unfixed a bug that prevents you from overriding peak shaving with a very large setpoint, so just don’t do that for now.

Thanks for reporting, I know where to look now.

6 Likes

Looks good indeed. Thanks for the quick response!

Thank you very much appreciated. System is working as intended now.

Hi!

FYI, when ‘Keep batteries charged’ is selected, the system is still exporting energy to the grid (instead of taking extra energy from the grid to charge the battery, limited by the peak shaving current limit as it did before).

Optimised without BL (ok):

Keep batteries charged (less energy goes to the battery, energy is exported instead of imported):

I have a similar problem. Just installed a Multiplus 48V 8000VA on L3 and have 3x25A mains, so i wanted to limit the peak shaving to something like 20A or 23A, but it has no effect at all. Screenshot is showing that battery is charging with 3796W (I limited the charge current to 70A) while the load on L3 is 4749 and the peak shaving is set to 10A, so it should charge with 2.400W less and keep L3 around 2.300W. I just tested it with the beta version, but no respons to whatever I set the peak shaving. Or is this only working when the Multiplus is installed on L1?

I think I can solve it by calculating the maximum charge current in node red based on either the grid usage on L3 or the solar yield on L3 + some value so it never exceeds 25A on the main fuse. By smoothing the measurements and rounding output to multiples of 5 the filter only writes changed values to the MaxChargeCurrent. So hopefully not to often. I will find out tomorrow if it works. I just installed my ESS a couple of days ago and I am new to Victron and Node Red so if there is a better way i would like to hear that.

Did something change? Fix undone?

As it the max charge is again linked to the peak shave.

Install the 3.71 beta

I can confirm the issue is fixed in V3.71~3. Also when ‘Keep batteries charged’ is selected everything is working fine now in beta.

Oké installing.

Confirmed

Before:

After:

I installed a custom peak shave logic that limits the sum of the 3 phases using a new virtual switch. Idea is to set a desired peak shave wattage, and the logic will convert this to amps and set this. By default it will set this for each phase. But if you surpass the set wattage it will lower the peak shave to spread the allowed wattage over all phases

[
    {
        "id": "60c13cd86d711ebe",
        "type": "tab",
        "label": "Peak Shave Optimize",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "16b31c57d1131c9f",
        "type": "group",
        "z": "60c13cd86d711ebe",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "1c5d0768146dc6c6",
            "9bfafca68a2faa99",
            "023d8e0145686e5b",
            "81e78fc8734de095",
            "37a1d098e1535fbf",
            "9ff4c90b24da6bd7",
            "2d84a0450fcb5637",
            "337a3da64248bfe2",
            "a0472456618f6478"
        ],
        "x": 24,
        "y": 39,
        "w": 1242,
        "h": 202
    },
    {
        "id": "1c5d0768146dc6c6",
        "type": "victron-input-gridmeter",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "service": "com.victronenergy.grid/0",
        "path": "/Ac/Power",
        "serviceObj": {
            "service": "com.victronenergy.grid/0",
            "name": "Fluvius GRID",
            "communityTag": "gridmeter"
        },
        "pathObj": {
            "path": "/Ac/Power",
            "type": "float",
            "name": "Power (W)"
        },
        "name": "Grid Power",
        "onlyChanges": true,
        "roundValues": "0",
        "rateLimit": "2",
        "outputs": 1,
        "conditionalMode": false,
        "outputTrue": "",
        "outputFalse": "",
        "debounce": "",
        "x": 120,
        "y": 80,
        "wires": [
            [
                "9ff4c90b24da6bd7"
            ]
        ]
    },
    {
        "id": "9bfafca68a2faa99",
        "type": "change",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "notification",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 940,
        "y": 140,
        "wires": [
            [
                "2d84a0450fcb5637"
            ]
        ]
    },
    {
        "id": "023d8e0145686e5b",
        "type": "function",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "name": "PeakShave Controller",
        "func": "// --- CONFIGURATIE ---\nconst MAX_SAMPLES = 20;\nconst MIN_HIGH_TIME_MS = 1*60*1000;\nconst MAX_VALUE = 25\nconst MIN_VALUE = 5\n\n// --- INPUT VERWERKING ---\nlet currentLimit = Number(msg.payload[\"AC input current limit\"]);\nlet inputPower = parseFloat(msg.payload[\"Grid Power\"]);\nlet lastSwitchTime = context.get('lastSwitchTime') || 0;\nlet now = Date.now();\n\nlet highThreshold = Number(msg.payload[\"PeakShave/value\"]) || 3000;\nlet lowThreshold = highThreshold - 200;\nlet highValue = Math.round(Math.min(MAX_VALUE,highThreshold/230));\nlet lowValue = Math.round(Math.max(MIN_VALUE,highThreshold/230/3));\n\nif (isNaN(inputPower) || isNaN(currentLimit)) return null; // Stop als input geen getal is\n\n// --- GEEN VERTRAGING VIRTUAL SWITCH ---\nif (msg.topic === \"PeakShave/value\") {\n    lastSwitchTime = now - MIN_HIGH_TIME_MS;\n    context.set('lastSwitchTime', lastSwitchTime);\n}\n\n// --- BUFFER LOGICA ---\nlet buffer = context.get('buffer') || [];\nbuffer.push(inputPower);\nif (buffer.length > MAX_SAMPLES) buffer.shift(); \n//if (buffer.length < 3) return null;\n\n// --- GEMIDDELDE BEREKENEN ---\nlet sum = buffer.reduce((a, b) => a + b, 0);\nlet avg = sum / buffer.length;\ncontext.set('buffer', buffer);\n\n// Deadband (optioneel, zet hele kleine waardes op 0)\nif (Math.abs(avg) < 50) avg = 0;\n\n// --- SCHAKEL LOGICA ---\nlet newPayload = null; \nlet notifyText = \"\";\n\nif (avg >= highThreshold ) {\n    newPayload = lowValue;\n    notifyText = `High load: ${avg.toFixed(0)}W. Switched to ${newPayload}A.`;\n} else if (avg <= lowThreshold) {\n    let elapsed = now - lastSwitchTime;\n    if (elapsed > MIN_HIGH_TIME_MS) {\n        newPayload = highValue;\n        notifyText = `Current load: ${avg.toFixed(0)}W. Restoring to ${newPayload}A.`;\n    }\n}\n\n// --- OUTPUT BEPALEN ---\nif (newPayload === currentLimit || newPayload ===  null) return null;\n\n// Er is een actie nodig (schakelen naar 5A of 10A)\ncontext.set('lastSwitchTime', now);\nmsg.payload = newPayload;\nmsg.type = \"info\";\nmsg.notification = notifyText;\nreturn msg; \n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 80,
        "wires": [
            [
                "9bfafca68a2faa99",
                "81e78fc8734de095"
            ]
        ]
    },
    {
        "id": "81e78fc8734de095",
        "type": "victron-output-settings",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/AcInputLimit",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings",
            "communityTag": "settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/AcInputLimit",
            "type": "float",
            "name": "AC input current limit (A)",
            "mode": "both"
        },
        "name": "",
        "onlyChanges": false,
        "outputs": 0,
        "x": 1080,
        "y": 80,
        "wires": []
    },
    {
        "id": "37a1d098e1535fbf",
        "type": "victron-input-settings",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/AcInputLimit",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/AcInputLimit",
            "type": "float",
            "name": "AC input current limit (A)",
            "mode": "both"
        },
        "name": "AC input current limit",
        "onlyChanges": true,
        "roundValues": "0",
        "outputs": 1,
        "x": 150,
        "y": 140,
        "wires": [
            [
                "9ff4c90b24da6bd7"
            ]
        ]
    },
    {
        "id": "9ff4c90b24da6bd7",
        "type": "join",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "name": "Merge input",
        "mode": "custom",
        "build": "object",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "useparts": true,
        "accumulate": true,
        "timeout": "",
        "count": "3",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "num",
        "reduceFixup": "",
        "x": 480,
        "y": 80,
        "wires": [
            [
                "023d8e0145686e5b"
            ]
        ]
    },
    {
        "id": "2d84a0450fcb5637",
        "type": "victron-inject",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "name": "",
        "notificationType": 2,
        "notificationTitle": "PeakShave Controller",
        "x": 1150,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "337a3da64248bfe2",
        "type": "victron-virtual-switch",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "name": "PeakShave",
        "outputs": 2,
        "switch_1_type": 7,
        "switch_1_min": "2500",
        "switch_1_max": "9000",
        "switch_1_initial": 0,
        "switch_1_label": "",
        "switch_1_unit": "W",
        "switch_1_step": 250,
        "switch_1_customname": "Dynamic Peak Shave",
        "switch_1_group": "Logic",
        "switch_1_include_measurement": false,
        "switch_1_rgb_color_wheel": false,
        "switch_1_cct_wheel": false,
        "switch_1_rgb_white_dimmer": false,
        "x": 310,
        "y": 200,
        "wires": [
            [],
            [
                "9ff4c90b24da6bd7"
            ]
        ]
    },
    {
        "id": "a0472456618f6478",
        "type": "inject",
        "z": "60c13cd86d711ebe",
        "g": "16b31c57d1131c9f",
        "name": "Button init",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "0",
        "topic": "",
        "payload": "{\"/SwitchableOutput/output_1/Dimming\":3000}",
        "payloadType": "json",
        "x": 150,
        "y": 200,
        "wires": [
            [
                "337a3da64248bfe2"
            ]
        ]
    },
    {
        "id": "0fbd43d364b7ba76",
        "type": "global-config",
        "env": [],
        "modules": {
            "@victronenergy/node-red-contrib-victron": "1.6.63"
        }
    }
]

i also issues here, running V3.71 20260315163256
seems to discharge to minimum SOC, and only charge to 55% no further than that.