ESS winter SoC service

Hi everyone,

during the winter months, rooftop PV often does not generate enough energy to keep an ESS battery at a healthy state of charge. If the battery remains at a low SoC for extended periods, this can be detrimental to battery longevity.

A practical solution is to raise the ESS minimum SoC before the dark season and lower it again in spring. I have automated this with a specialised, thoroughly tested Venus OS service that works directly via D-Bus:

https://github.com/martinthebrain/venus-ess-winter-soc-service

The service runs entirely on the GX device on which it is installed. Its purpose is to provide automatic winter SoC protection for Victron ESS systems by seasonally adjusting the ESS minimum SoC and using controlled charge windows when needed.

For installation, the following terminal commands are sufficient:

mkdir -p /data/venus-ess-winter-soc-service
cd /data/venus-ess-winter-soc-service
wget -O install.sh https://raw.githubusercontent.com/martinthebrain/venus-ess-winter-soc-service/main/install.sh
chmod +x install.sh
./install.sh

svc -d /service/venus-ess-winter-soc-service

The project includes unit tests, strict type checking, D-Bus simulation tests, and has been running on my own 30 kWh battery system for about a year. I am sharing it here in case it is useful for other Victron ESS users, or simply interesting for those who want to look into how it works.

Hi why not use batterlife in settings? That works the same right?

You can do this via Nodered without the need to install external scripts:

[
    {
        "id": "f2a2c02c8c22031a",
        "type": "inject",
        "z": "1459d4d7551ca112",
        "name": "execute-oncedaily",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "11 00 * * *",
        "once": true,
        "onceDelay": "0.6",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 210,
        "y": 480,
        "wires": [
            [
                "07f9999728e60555"
            ]
        ]
    },
    {
        "id": "07f9999728e60555",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "minSOC_saison",
        "func": "// Aktuelles Datum abrufen\n// const d = new Date();\n// let cmonth = d.getMonth(); // 0-11\n// Monat um 1 erhöhen, um 1 bis 12 zu erhalten:\n// cmonth++;\n// let currentdate = flow.get('cdatearray');\n// let cmonth = currentdate[0];\n// let cmonth = flow.get('cmonth');\n\nlet d = new Date();\nlet cmonth = 1 + d.getMonth();\nlet cday = d.getDate();\n\n// Berechnung minSOC nach Jahreszeit (Nordhalbkugel)\n// Winter 45%: November (11), Dezember (12), Januar (1), Februar (2)\n// Frühling, Herbst 30%: März (3), April (4), September (9), Oktober (10)\n// Sommer 20%: Mai (5), Juni (6), Juli (6), August (8)\n\nswitch (cmonth) {\n  case 1:\n      minSOC = 40;\n      break;\n  case 2:\n      minSOC = 40;\n      break;\n  case 3:\n      minSOC = 15;\n      break;\n  case 4:\n      minSOC = 15;\n      break;\n  case 5:\n      minSOC = 15;\n      break;\n  case 6:\n      minSOC = 15;\n      break;\n  case 7:\n      minSOC = 15;\n      break;\n  case 8:\n      minSOC = 15;\n      break;\n  case 9:\n      minSOC = 15;\n      break;\n  case 10:\n      minSOC = 15;\n      break;\n  case 11:\n      minSOC = 40;\n      break;\n  case 12:\n      minSOC = 40;\n      break;\n  default:\n      minSOC = 40;\n}\n\n// als flow-variablen setzen, um sie weiterhin nutzen zu können\nflow.set ('minSOC' , minSOC);\n\n// Nachricht setzen\nmsg.payload = minSOC;\n// Optional: minSOC auch als Payload für Schalter senden\n// msg.minSOC = minSOC; \n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 480,
        "wires": [
            [
                "84be15a11f9b4aca"
            ]
        ]
    },
    {
        "id": "9b375ebef613ee7c",
        "type": "victron-output-ess",
        "z": "1459d4d7551ca112",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
            "type": "integer",
            "name": "Minimum Discharge SOC (%)",
            "mode": "both"
        },
        "initial": 20,
        "name": "set-MinSOC",
        "onlyChanges": false,
        "outputs": 0,
        "x": 750,
        "y": 480,
        "wires": []
    },
    {
        "id": "84be15a11f9b4aca",
        "type": "rbe",
        "z": "1459d4d7551ca112",
        "name": "",
        "func": "deadband",
        "gap": "1",
        "start": "",
        "inout": "out",
        "septopics": false,
        "property": "payload",
        "topi": "topic",
        "x": 600,
        "y": 480,
        "wires": [
            [
                "9b375ebef613ee7c"
            ]
        ]
    },
    {
        "id": "ea5508647d813604",
        "type": "global-config",
        "env": [],
        "modules": {
            "@victronenergy/node-red-contrib-victron": "1.6.63"
        }
    }
]

Hi Marc, good question. BatteryLife is definitely the closest built-in Victron feature, and for many systems it may be the better/simple choice.

My service is not intended to replace BatteryLife for users who are happy with it. The difference is that BatteryLife dynamically adjusts the active SoC limit with the goal of regularly getting the battery back into a high SoC range. My service instead applies a deterministic seasonal MinSoC policy: normal low MinSoC in summer, transition targets when PV production drops, and a configurable winter reserve target.

The main differences are:

  • fixed/configurable seasonal targets instead of BatteryLife’s dynamic active SoC limit
  • transition logic based on recent PV production
  • staged reserve building rather than immediately jumping to the winter target
  • adaptive low-load night charging windows
  • temporary DVCC charge-current limiting while raising SoC
  • no Venus OS Large, Node-RED, or Signal K required

So yes: if BatteryLife gives the desired behaviour, use BatteryLife. This script is mainly for users who want predictable winter reserve handling while staying in normal ESS operation without BatteryLife, or who want more control over when and how the reserve is built.

I will add a “BatteryLife vs this service” section to the README, because that comparison should be clear.

Good point, Node-RED works well for this if you already have Venus OS Large running. My approach targets users who don’t want that dependency, and it also covers a few additional things (PV-production-based transitions, charge windows, DVCC limiting) that would make the flow a bit more involved. But for the basic use case, your flow looks clean and simple!

For all other cases you can install other Nodered flows. I am using a flow to integrate a PV inverter with a shelly meter, and I have a flow to control my battery discharge—based on the season, the current SOC, and the solar forecast—via the GridSetPoint, ensuring that the battery reaches 100% SOC only in the late afternoon. This allows me to feed the entire surplus from my 5.4 kWp solar system back into the grid, even with a small inverter (in my case, an MP2-3000).

That sounds nice. Would you mind sharing your Solution for that use case? I am very curious about that.

Here it is. Please note that you also need to set a global variable global.vrmSiteId with you VRM access data.

[
    {
        "id": "1459d4d7551ca112",
        "type": "tab",
        "label": "SolarVorhersage",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "cecfa760836ea314",
        "type": "inject",
        "z": "1459d4d7551ca112",
        "name": "execute_every_30min",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "*/30 6-20 * * *",
        "once": true,
        "onceDelay": "0.5",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 170,
        "y": 100,
        "wires": [
            [
                "83e1731df2cb4a4f",
                "4509958d36211e97",
                "58bf60b68439ec53"
            ]
        ]
    },
    {
        "id": "83e1731df2cb4a4f",
        "type": "vrm-api",
        "z": "1459d4d7551ca112",
        "vrm": "cca5d0a225796a2b",
        "name": "forecast_today",
        "api_type": "installations",
        "idUser": "",
        "idSite": "{{global.vrmSiteId}}",
        "installations": "stats",
        "attribute": "solar_yield_forecast",
        "stats_interval": "15mins",
        "show_instance": false,
        "stats_start": "bod",
        "stats_end": "eod",
        "use_utc": false,
        "gps_start": "",
        "gps_end": "",
        "widgets": "",
        "instance": "",
        "store_in_global_context": false,
        "verbose": false,
        "outputs": 1,
        "x": 400,
        "y": 40,
        "wires": [
            [
                "19383e3d7c1b8fcf"
            ]
        ]
    },
    {
        "id": "53ec77049a4d0973",
        "type": "victron-input-battery",
        "z": "1459d4d7551ca112",
        "service": "com.victronenergy.battery/512",
        "path": "/Soc",
        "serviceObj": {
            "service": "com.victronenergy.battery/512",
            "name": "Batterie"
        },
        "pathObj": {
            "path": "/Soc",
            "type": "float",
            "name": "State of charge (%)"
        },
        "name": "currentSOC",
        "onlyChanges": true,
        "roundValues": "0",
        "outputs": 1,
        "x": 190,
        "y": 340,
        "wires": [
            [
                "e2356f404424d7e5"
            ]
        ]
    },
    {
        "id": "f2a2c02c8c22031a",
        "type": "inject",
        "z": "1459d4d7551ca112",
        "name": "execute-oncedaily",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "11 00 * * *",
        "once": true,
        "onceDelay": "0.6",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 210,
        "y": 480,
        "wires": [
            [
                "07f9999728e60555"
            ]
        ]
    },
    {
        "id": "4509958d36211e97",
        "type": "vrm-api",
        "z": "1459d4d7551ca112",
        "vrm": "cca5d0a225796a2b",
        "name": "forecast_tomorrow",
        "api_type": "installations",
        "idUser": "",
        "idSite": "{{global.vrmSiteId}}",
        "installations": "stats",
        "attribute": "solar_yield_forecast",
        "stats_interval": "15mins",
        "show_instance": false,
        "stats_start": "bot",
        "stats_end": "eot",
        "use_utc": false,
        "gps_start": "",
        "gps_end": "",
        "widgets": "",
        "instance": "",
        "store_in_global_context": false,
        "verbose": false,
        "transform_price_schedule": false,
        "outputs": 1,
        "x": 410,
        "y": 160,
        "wires": [
            [
                "d85c58c4e9cc5238"
            ]
        ]
    },
    {
        "id": "07f9999728e60555",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "minSOC_saison",
        "func": "// Aktuelles Datum abrufen\n// const d = new Date();\n// let cmonth = d.getMonth(); // 0-11\n// Monat um 1 erhöhen, um 1 bis 12 zu erhalten:\n// cmonth++;\n// let currentdate = flow.get('cdatearray');\n// let cmonth = currentdate[0];\n// let cmonth = flow.get('cmonth');\n\nlet d = new Date();\nlet cmonth = 1 + d.getMonth();\nlet cday = d.getDate();\n\n// Berechnung minSOC nach Jahreszeit (Nordhalbkugel)\n// Winter 45%: November (11), Dezember (12), Januar (1), Februar (2)\n// Frühling, Herbst 30%: März (3), April (4), September (9), Oktober (10)\n// Sommer 20%: Mai (5), Juni (6), Juli (6), August (8)\n\nswitch (cmonth) {\n  case 1:\n      minSOC = 40;\n      break;\n  case 2:\n      minSOC = 40;\n      break;\n  case 3:\n      minSOC = 15;\n      break;\n  case 4:\n      minSOC = 15;\n      break;\n  case 5:\n      minSOC = 15;\n      break;\n  case 6:\n      minSOC = 15;\n      break;\n  case 7:\n      minSOC = 15;\n      break;\n  case 8:\n      minSOC = 15;\n      break;\n  case 9:\n      minSOC = 15;\n      break;\n  case 10:\n      minSOC = 15;\n      break;\n  case 11:\n      minSOC = 40;\n      break;\n  case 12:\n      minSOC = 40;\n      break;\n  default:\n      minSOC = 40;\n}\n\n// als flow-variablen setzen, um sie weiterhin nutzen zu können\nflow.set ('minSOC' , minSOC);\n\n// Nachricht setzen\nmsg.payload = minSOC;\n// Optional: minSOC auch als Payload für Schalter senden\n// msg.minSOC = minSOC; \n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 480,
        "wires": [
            [
                "84be15a11f9b4aca"
            ]
        ]
    },
    {
        "id": "ce10e89d55ef11a6",
        "type": "debug",
        "z": "1459d4d7551ca112",
        "name": "forecast_today",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 820,
        "y": 40,
        "wires": []
    },
    {
        "id": "bc15c2dd4db42c81",
        "type": "debug",
        "z": "1459d4d7551ca112",
        "name": "forecast_tomorrow",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 830,
        "y": 160,
        "wires": []
    },
    {
        "id": "9b375ebef613ee7c",
        "type": "victron-output-ess",
        "z": "1459d4d7551ca112",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
            "type": "integer",
            "name": "Minimum Discharge SOC (%)",
            "mode": "both"
        },
        "initial": 20,
        "name": "set-MinSOC",
        "onlyChanges": false,
        "outputs": 0,
        "x": 750,
        "y": 480,
        "wires": []
    },
    {
        "id": "58bf60b68439ec53",
        "type": "vrm-api",
        "z": "1459d4d7551ca112",
        "vrm": "cca5d0a225796a2b",
        "name": "forecast_restofday",
        "api_type": "installations",
        "idUser": "",
        "idSite": "{{global.vrmSiteId}}",
        "installations": "stats",
        "attribute": "solar_yield_forecast",
        "stats_interval": "15mins",
        "show_instance": false,
        "stats_start": "0",
        "stats_end": "eod",
        "use_utc": false,
        "gps_start": "",
        "gps_end": "",
        "widgets": "",
        "instance": "",
        "store_in_global_context": false,
        "verbose": false,
        "outputs": 1,
        "x": 410,
        "y": 100,
        "wires": [
            [
                "07b3c351977bce02"
            ]
        ]
    },
    {
        "id": "4d003d0b786480b7",
        "type": "debug",
        "z": "1459d4d7551ca112",
        "name": "forecast_restofday",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 830,
        "y": 100,
        "wires": []
    },
    {
        "id": "2cc09b109a7328c4",
        "type": "inject",
        "z": "1459d4d7551ca112",
        "name": "execute-every10min",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "*/10 6-20 * * *",
        "once": true,
        "onceDelay": "0.6",
        "topic": "",
        "payload": "20",
        "payloadType": "num",
        "x": 200,
        "y": 560,
        "wires": [
            [
                "5096be16a5dae81c"
            ]
        ]
    },
    {
        "id": "5096be16a5dae81c",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "calc_gridsetpoint",
        "func": "let forecast_today = flow.get ('forecast_today');\nlet forecast_tomorrow = flow.get ('forecast_tomorrow');\nlet fcrest = flow.get ('forecast_restofday');\nlet currentSOC = flow.get ('currentSOC');\nlet battvoltage = flow.get ('battvoltage');\nlet minSOC = flow.get ('minSOC') || 20;\nlet missingload = flow.get ('missingload') || 0;\nlet solarYield = flow.get ('solarYield') || 0;\n\nlet d = new Date();\nlet cmonth = 1 + d.getMonth();\nlet cday = d.getDate();\nlet chour = d.getHours();\nlet cminute = d.getMinutes();\nlet time = \"0.0\";\nchour = chour.toFixed(0);\nif (cminute < 10) {\n   cminute = '0' + cminute.toFixed(0);\n} else {\n   cminute = cminute.toFixed(0);\n}\ntime = parseFloat(chour + \".\" + cminute);\n\nlet gridsetpoint = 20;\n\n// SOC wird oft grob falsch angezeigt, deshalb Entladung nur bei \n// Mindest-Batteriespannung von ĂĽber 51,5 V\nif (51.0 < battvoltage) {\nif (cmonth === 5 || cmonth === 6 || cmonth === 7 || cmonth === 8) {\n    if (15.30 <= time) { // after 15:30 full charging and resting\n        gridsetpoint = -20;\n    } else if ((time <= 8.00) && (35 < currentSOC) && (14000 < forecast_today)) {\n        gridsetpoint = -500;\n    } else if ((8.00 < time) && (time < 13.00) && (25 < currentSOC) && (12000 < fcrest)) {\n        gridsetpoint = -2600;\n    } else if ((13.00 <= time) && (time < 15.30) && (60 < currentSOC) && (8000 < fcrest)) {\n        gridsetpoint = -2800;\n    } else {\n        gridsetpoint = -20;\n    }\n} else if (cmonth === 4 || cmonth === 9) {\n    if (15.00 <= time) { // after 15:30 full charging and resting\n        gridsetpoint = -20;\n    } else if ((time <= 8.00) && (40 < currentSOC) && (14000 < forecast_today)) {\n        gridsetpoint = -450;\n    } else if ((8.00 < time) && (time < 12.30) && (35 < currentSOC) && (14000 < fcrest)) {\n        gridsetpoint = -2400;\n    } else if ((12.30 <= time) && (time < 15.00) && (60 < currentSOC) && (8000 < fcrest)) {\n        gridsetpoint = -2400;\n    } else {\n        gridsetpoint = -20;\n    }\n} else if (cmonth === 3 && 20 < cday) {\n    if ((15.00 <= time) && (time < 19.0)) { // between 14:45 und 20 full charging\n        gridsetpoint = -20;\n    } else if ((19.0 <= time) && (80 < currentSOC) && (14000 < forecast_tomorrow)) {\n        gridsetpoint = -20;\n    } else if ((time <= 8.30) && (60 < currentSOC) && (14000 < forecast_today)) {\n        gridsetpoint = -250;\n    } else if ((8.30 < time) && (time < 10.0) && (50 < currentSOC) && (14000 < fcrest)) {\n        gridsetpoint = -2000;\n    } else if ((10.0 <= time) && (time < 13.00) && (60 < currentSOC) && (9000 < fcrest)) {\n        gridsetpoint = -2600;\n    } else if ((13.00 <= time) && (time < 15.00) && (80 < currentSOC) && (7000 < fcrest)) {\n        gridsetpoint = -2600;\n    } else {\n        gridsetpoint = -20;\n    }\n} else if ((cmonth === 3 && cday <= 20) || cmonth === 10) {\n    if ((14.30 <= time) && (time < 19.0)) { // between 14:45 und 20 full charging\n        gridsetpoint = -20;\n    } else if ((19.0 <= time) && (80 < currentSOC) && (14000 < forecast_tomorrow)) {\n        gridsetpoint = -20;\n    } else if ((time <= 8.45) && (70 < currentSOC) && (14000 < forecast_today)) {\n        gridsetpoint = -200;\n    } else if ((8.45 < time) && (time < 10.0) && (60 < currentSOC) && (14000 < fcrest)) {\n        gridsetpoint = -1200;\n    } else if ((10.0 <= time) && (time < 12.30) && (70 < currentSOC) && (9000 < fcrest)) {\n        gridsetpoint = -2200;\n    } else if ((12.30 <= time) && (time < 14.30) && (80 < currentSOC) && (7000 < fcrest)) {\n        gridsetpoint = -2200;\n    } else {\n        gridsetpoint = -20;\n    }\n} else if (cmonth === 2) {\n    if ((8.45 <= time) && (time < 13.00) && (60 < currentSOC)) {\n        if (7000 < fcrest) {\n            gridsetpoint = -1500;\n        } else if (5000 < fcrest) {\n            gridsetpoint = -600;\n        }  else {\n        gridsetpoint = 0;\n    }\n    } else {\n        gridsetpoint = 0;\n    }\n} else {\n    gridsetpoint = 20;\n}\n} else {\n    gridsetpoint = 20;\n}\n\nflow.set ('gridsetpoint' , gridsetpoint);\n\nmsg.payload = gridsetpoint;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 600,
        "wires": [
            [
                "1c1dfb11bc4ceb8a"
            ]
        ]
    },
    {
        "id": "62cff6b823712e53",
        "type": "victron-output-ess",
        "z": "1459d4d7551ca112",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/AcPowerSetPoint",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings",
            "communityTag": "ess"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/AcPowerSetPoint",
            "type": "integer",
            "name": "Grid set-point (W)",
            "mode": "both"
        },
        "initial": 20,
        "name": "set-GridSetpoint",
        "onlyChanges": false,
        "outputs": 0,
        "conditionalMode": false,
        "outputTrue": "",
        "outputFalse": "",
        "debounce": "",
        "x": 760,
        "y": 600,
        "wires": []
    },
    {
        "id": "e2356f404424d7e5",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "flow_currentSOC",
        "func": "let currentSOC = msg.payload;\nlet minSOC = flow.get ('minSOC') || 40;\nlet diffSOC = 100 - currentSOC;\nconst fullbattery = 14000;\nlet missingload = diffSOC * fullbattery / 100;\n\nflow.set ('missingload', missingload);\nflow.set ('currentSOC', currentSOC);\n\nmsg.payload = currentSOC;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 550,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "2f5522e00a078a17",
        "type": "comment",
        "z": "1459d4d7551ca112",
        "name": "Description",
        "info": "Global variable:\n\nvrmSiteId = the individual VRM site id\n            I set this global variable in an extra flow\n            Never publish your VRM site id!\n\nFlow variables:\n\nUpdated every hour:\nforecast_today = VRM forecast for whole today\nforecast_tomorrow = VRM forecast for whole day tomorrow\nforecast_restofday = VRM forecast from now to end of day\n\nPermanently updated:\ncurrentSOC = the current SOC \nmissingload = load (in Wh) necessary for 100% SOC\n\nUpdated once daily:\nminSOC = minimal SOC for ESS, different for each month of the year\n\nUpdated every hour:\ngridsetpoint = Grid Setpoint, to enable grid-feed-in when more solar yield is expected than inverter power\n\nThe gridsetpoint is modified according to month (seasonal difference), hour (discharging full battery over night etc), current SOC and forecast_restofday\n\nThe calc_gridsetpoint_demo can be used to evaluate different settings according to a different system. The variables cmonth, cday, forecast_today, forecast_tomorrow, forecast_restofday, currentSOC, missingload can be set to the values to be tested. Just click on the inject node.",
        "x": 140,
        "y": 40,
        "wires": []
    },
    {
        "id": "07b3c351977bce02",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "adjustment",
        "func": "let forecast_restofday = 0.95 * msg.payload.totals.solar_yield_forecast;\nflow.set ('forecast_restofday', forecast_restofday);\nmsg.payload = forecast_restofday;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 610,
        "y": 100,
        "wires": [
            [
                "4d003d0b786480b7"
            ]
        ]
    },
    {
        "id": "19383e3d7c1b8fcf",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "adjustment",
        "func": "let forecast_today = 0.95 * msg.payload.totals.solar_yield_forecast;\nflow.set ('forecast_today', forecast_today);\nmsg.payload = forecast_today;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 610,
        "y": 40,
        "wires": [
            [
                "ce10e89d55ef11a6"
            ]
        ]
    },
    {
        "id": "d85c58c4e9cc5238",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "adjustment",
        "func": "let forecast_tomorrow = 0.95 * msg.payload.totals.solar_yield_forecast;\nflow.set ('forecast_tomorrow', forecast_tomorrow);\nmsg.payload = forecast_tomorrow;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 610,
        "y": 160,
        "wires": [
            [
                "bc15c2dd4db42c81"
            ]
        ]
    },
    {
        "id": "1c1dfb11bc4ceb8a",
        "type": "rbe",
        "z": "1459d4d7551ca112",
        "name": "",
        "func": "deadband",
        "gap": "1",
        "start": "",
        "inout": "out",
        "septopics": false,
        "property": "payload",
        "topi": "topic",
        "x": 600,
        "y": 600,
        "wires": [
            [
                "62cff6b823712e53"
            ]
        ]
    },
    {
        "id": "84be15a11f9b4aca",
        "type": "rbe",
        "z": "1459d4d7551ca112",
        "name": "",
        "func": "deadband",
        "gap": "1",
        "start": "",
        "inout": "out",
        "septopics": false,
        "property": "payload",
        "topi": "topic",
        "x": 600,
        "y": 480,
        "wires": [
            [
                "9b375ebef613ee7c"
            ]
        ]
    },
    {
        "id": "909dfa353bd46276",
        "type": "inject",
        "z": "1459d4d7551ca112",
        "name": "execute-nightly",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "0 0-5 * * *",
        "once": false,
        "onceDelay": "0.6",
        "topic": "",
        "payload": "20",
        "payloadType": "num",
        "x": 220,
        "y": 640,
        "wires": [
            [
                "5096be16a5dae81c"
            ]
        ]
    },
    {
        "id": "a483b3a4cf9ab25e",
        "type": "victron-input-system",
        "z": "1459d4d7551ca112",
        "d": true,
        "service": "com.victronenergy.system/0",
        "path": "/Dc/Pv/Power",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system",
            "communityTag": "system"
        },
        "pathObj": {
            "path": "/Dc/Pv/Power",
            "type": "float",
            "name": "MPPTs - power (W)"
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "outputs": 1,
        "conditionalMode": false,
        "outputTrue": "true",
        "outputFalse": "false",
        "debounce": "2000",
        "x": 260,
        "y": 280,
        "wires": [
            [
                "2e196c6361b12c20"
            ]
        ]
    },
    {
        "id": "2e196c6361b12c20",
        "type": "function",
        "z": "1459d4d7551ca112",
        "d": true,
        "name": "flow_solarYield",
        "func": "let pv_inverter = global.get ('pv_inverter') || 0.0;\nlet solarYield = msg.payload + pv_inverter;\n\nflow.set ('solarYield', solarYield);\n\nmsg.payload = solarYield;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 540,
        "y": 280,
        "wires": [
            [
                "53e40b28dc0246f9"
            ]
        ]
    },
    {
        "id": "53e40b28dc0246f9",
        "type": "debug",
        "z": "1459d4d7551ca112",
        "d": true,
        "name": "solarYield",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 760,
        "y": 280,
        "wires": []
    },
    {
        "id": "db140c33c65a4bd3",
        "type": "victron-input-battery",
        "z": "1459d4d7551ca112",
        "service": "com.victronenergy.battery/512",
        "path": "/Dc/0/Voltage",
        "serviceObj": {
            "service": "com.victronenergy.battery/512",
            "name": "Batterie",
            "communityTag": "battery"
        },
        "pathObj": {
            "path": "/Dc/0/Voltage",
            "type": "float",
            "name": "Battery voltage (V)"
        },
        "name": "battvoltage",
        "onlyChanges": true,
        "roundValues": "2",
        "outputs": 1,
        "conditionalMode": false,
        "outputTrue": "",
        "outputFalse": "",
        "debounce": "",
        "x": 180,
        "y": 400,
        "wires": [
            [
                "1a3e63a34925729a"
            ]
        ]
    },
    {
        "id": "1a3e63a34925729a",
        "type": "function",
        "z": "1459d4d7551ca112",
        "name": "flow_currentSOC",
        "func": "let battvoltage = msg.payload;\n\nflow.set ('battvoltage', battvoltage);\n\nmsg.payload = battvoltage;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 550,
        "y": 400,
        "wires": [
            []
        ]
    },
    {
        "id": "cca5d0a225796a2b",
        "type": "config-vrm-api",
        "name": "SolarVorhersage"
    },
    {
        "id": "97ad0d7a85c50c9e",
        "type": "global-config",
        "env": [],
        "modules": {
            "victron-vrm-api": "0.3.11",
            "@victronenergy/node-red-contrib-victron": "1.6.63"
        }
    }
]