Can't use VRM API node after disabling DESS battery balancing

I think I’m experiencing an API error, which I only noticed it after installing the beta. I’m not sure whether it’s being caused by Venus beta itself, the beta Node Red node, or something else.

I use the API node to patch DESS settings (e.g,. the buyPriceSchedule) via Node Red. After disabling the DESS battery balancing function, I started receiving this error from the VRM API for all patch requests:

{"success":false,"error_code":"validation_error",
"errors":
{"periodicFullChargeInterval":"periodicFullChargeInterval should be null"}}

When fetching the DESS configuration, I see the following after disabling balancing:

isPeriodicFullChargeOn: false
periodicFullChargeDuration: 2
periodicFullChargeInterval: 15
...
fullChargeDuration: 2
fullChargeInterval: 15

I tried directly setting the objects the API was complaining about with the following settings

{"isPeriodicFullChargeOn": false, 
"periodicFullChargeInterval": null, 
"periodicFullChargeDuration": null}

The VRM response initially acknowledges these settings:

isPeriodicFullChargeOn: false
periodicFullChargeDuration: null
periodicFullChargeInterval: null
...
fullChargeDuration: 2
fullChargeInterval: 15

But if I check the same settings again via another API call, they revert to 2 and 15. So I can’t figure out how to fix this… for now I just set the interval to a large value.

EDIT: I should clarify, I’m getting this error when configuring buyPriceSchedule

I ran into this quite some time ago as well. There was some ambiguity or documentation issue about setting null versus zero versus false but I forgot what precisely, I do remember the duration/interval numbers didn’t matter as long as periodicfullcharge was switched off. It took some trial and error testing to be able to switch it on/off on demand, unless there have been undocumented API changes in between.

@UpCycleElectric Interesting… so you’re on the latest beta, have DESS balancing disabled, and the API node lets you patch other DESS settings?

My config looks the same. Initial guess: try set to false, 2, 14 instead of false, null, null

I do not need the API to patch config currently. If you give me a (harmless) json to inject I can test for you.

Back then I was testing isScheduleSocOn, scheduledSocGoal, scheduledSocTime that is a) not implemented and b) does require 0, null, null respectively. You know, the infamous b_goal_SoC and b_goal_hour port from Node-RED DESS that I am catching so much heat on for requesting it for over a year already, and what we now ‘emulate’ by setting virtual forecast adjustments.:wink:

EDIT: found the old flow in backup.

[
    {
        "id": "fb0f0fcc232bb2e2",
        "type": "inject",
        "z": "77e8033910d87d1a",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "false",
        "payloadType": "bool",
        "x": 150,
        "y": 200,
        "wires": [
            [
                "b52372cf24b3801d"
            ]
        ]
    },
    {
        "id": "4f000daf47ccd578",
        "type": "function",
        "z": "77e8033910d87d1a",
        "name": "mux",
        "func": "return msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 140,
        "wires": [
            [
                "7b62b75e1a9eec6c"
            ]
        ]
    },
    {
        "id": "7b62b75e1a9eec6c",
        "type": "vrm-api",
        "z": "77e8033910d87d1a",
        "vrm": "cb2c784a33130f48",
        "name": "",
        "api_type": "installations",
        "idUser": "",
        "idSite": "{{flow.siteId}}",
        "installations": "patch-dynamic-ess-settings",
        "attribute": "dynamic_ess",
        "stats_interval": "",
        "show_instance": false,
        "stats_start": "",
        "stats_end": "",
        "use_utc": false,
        "gps_start": "",
        "gps_end": "",
        "widgets": "PVInverterStatus",
        "instance": "",
        "store_in_global_context": false,
        "verbose": true,
        "outputs": 1,
        "x": 740,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "cb1b34c53f6bde49",
        "type": "change",
        "z": "77e8033910d87d1a",
        "name": "pfco on",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"isPeriodicFullChargeOn\":true,\"periodicFullChargeDuration\":0,\"periodicFullChargeInterval\":1}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "url",
                "pt": "msg",
                "to": "https://vrmapi.victronenergy.com/v2",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "pfco",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 460,
        "y": 80,
        "wires": [
            [
                "4f000daf47ccd578"
            ]
        ]
    },
    {
        "id": "b92397935d861649",
        "type": "change",
        "z": "77e8033910d87d1a",
        "name": "pfco off",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"isPeriodicFullChargeOn\":false,\"periodicFullChargeDuration\":null,\"periodicFullChargeInterval\":null}",
                "tot": "json"
            },
            {
                "t": "set",
                "p": "url",
                "pt": "msg",
                "to": "https://vrmapi.victronenergy.com/v2",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "pfco",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 460,
        "y": 200,
        "wires": [
            [
                "4f000daf47ccd578"
            ]
        ]
    },
    {
        "id": "401770634aebaf82",
        "type": "switch",
        "z": "77e8033910d87d1a",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "true"
            },
            {
                "t": "false"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 270,
        "y": 140,
        "wires": [
            [
                "cb1b34c53f6bde49"
            ],
            [
                "b92397935d861649"
            ]
        ]
    },
    {
        "id": "b52372cf24b3801d",
        "type": "change",
        "z": "77e8033910d87d1a",
        "name": "pfco",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "pfco",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 150,
        "y": 140,
        "wires": [
            [
                "401770634aebaf82"
            ]
        ]
    },
    {
        "id": "d94c89a091d800fe",
        "type": "inject",
        "z": "77e8033910d87d1a",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 150,
        "y": 80,
        "wires": [
            [
                "b52372cf24b3801d"
            ]
        ]
    },
    {
        "id": "53f8fc21dc0336f2",
        "type": "global-config",
        "env": [],
        "modules": {
            "victron-vrm-api": "0.4.1"
        }
    }
]

for on:

{
    "isPeriodicFullChargeOn": true,
    "periodicFullChargeDuration": 0,
    "periodicFullChargeInterval": 1
}

for off:

{
    "isPeriodicFullChargeOn": false,
    "periodicFullChargeDuration": null,
    "periodicFullChargeInterval": null
}

That worked worked fine to switch DESS into and out of ‘balancing’ mode on the hour for an hour.

But it would indeed default back to

isPeriodicFullChargeOn: false
periodicFullChargeDuration: 2
periodicFullChargeInterval: 14
...
isScheduledSocOn: 0
scheduledSocGoal: null
scheduledSocTime: null
...
fullChargeDuration: 2
fullChargeInterval: 14

in the return object.

So if this does not work anymore, you might be on to something.

How about something like this?
image

That’s a picture, can’t you just give the json :slight_smile:

Never mind, it’s readabie

Ha fair… here you go:

[
    {
        "id": "fbce31ce21483966",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "c7ec983c7d59fd9e",
        "type": "vrm-api",
        "z": "fbce31ce21483966",
        "vrm": "",
        "name": "",
        "api_type": "installations",
        "idUser": "",
        "idSite": "",
        "installations": "patch-dynamic-ess-settings",
        "attribute": "Bc",
        "stats_interval": "",
        "show_instance": false,
        "stats_start": "",
        "stats_end": "",
        "use_utc": false,
        "gps_start": "",
        "gps_end": "",
        "widgets": "",
        "instance": "",
        "store_in_global_context": false,
        "verbose": true,
        "transform_price_schedule": false,
        "outputs": 1,
        "x": 440,
        "y": 140,
        "wires": [
            [
                "9f0af6d618f323bd"
            ]
        ]
    },
    {
        "id": "a9ca4d9bbc5a12c8",
        "type": "inject",
        "z": "fbce31ce21483966",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "{\"batteryCapacity\": 60}",
        "payloadType": "str",
        "x": 140,
        "y": 140,
        "wires": [
            [
                "c7ec983c7d59fd9e"
            ]
        ]
    },
    {
        "id": "9f0af6d618f323bd",
        "type": "debug",
        "z": "fbce31ce21483966",
        "name": "debug 35",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 700,
        "y": 140,
        "wires": []
    },
    {
        "id": "475be8077f403d98",
        "type": "global-config",
        "env": [],
        "modules": {
            "victron-vrm-api": "0.4.1"
        }
    }
]

Thanks for checking. Seems there’s indeed a bug somewhere.

I guess for now I can just continue setting the interval to something like 100 days, and then when I want to balance set it to 0 days.

@dfaber just flagging for you.

This worked though (changed back battery capacity to my own):

{
    "batteryCapacity": 87.35,
    "isPeriodicFullChargeOn": false,
    "periodicFullChargeDuration": null,
    "periodicFullChargeInterval": null
}

Ah good point… so alternatively, for every API request just include those 3 parameters until fixed.

Coulda shoulda woulda have known you’d need a workaround

Or use a large enough rolling (EV) forecast adjustment to make DESS hit 100% often enough not having to manually balance anymore. Basically invert the current ‘Sell to MinimumSoC’ bias to a ‘Buy to Full’ bias. As I would argue is the better option for DESS Trade anyway. Added bonus is you can finetune the forecast adjustment not to miss neither sell nor buy opportunities, and let DESS optimize the rest.

PS: and indeed the return object defaults back to false, 2, 14

So apparently you’d need at least to pad your config change json with “periodicFullChargeInterval: null”

Check your inject node as well, it was set to ‘text’ instead of ‘json’ not sure whether that makes a difference but for my examples above I changed it to ‘json’.

Summary:

After disabling DESS battery balancing (isPeriodicFullChargeOn: false), any PATCH to dynamic-ess-settings via VRM API node (e.g. buyPriceSchedule) fails with:

{
  "success": false,
  "error_code": "validation_error",
  "errors": {
    "periodicFullChargeInterval": "periodicFullChargeInterval should be null"
  }
}

GET shows periodicFullChargeInterval: 15 (and duration 2) even after PATCH with null values; the nulls do not persist. Other fields like fullChargeInterval remain. The validation blocks all PATCHes once balancing is off. Workarounds discussed: always include {"isPeriodicFullChargeOn": false, "periodicFullChargeDuration": null, "periodicFullChargeInterval": null} in every payload, or set large interval values.

Victron GitHub analysis:
No public source for the VRM API backend validation logic (proprietary; vrmapi.victronenergy.com endpoints).

The bug is an overly critical (non-optional) validation check in the closed-source VRM API for partial PATCHes to dynamic-ess-settings. It enforces periodicFullChargeInterval: null strictly when balancing is disabled, breaking API usability for other DESS config changes. No fix visible in public repos.

This is a good find and seems to be a bug. Still need to figure out if that is on the node side or more on the API side itself. I’ll look into it.

Thanks as always @dfaber