Zonneplan Solar Bonus

Zonneplan in the Netherlands uses a Solar bonus between sunrise and sunset of 10 percent.

The flow I created in nodered adjusts the dess scheduled sell formula according tus sunrise and sunset every day at 1:00.

[
    {
        "id": "d7b06853644b688b",
        "type": "group",
        "z": "28b7874e5f70d147",
        "name": "Set Victron Dess Sell Price Schedule based on sunrise sunset - for Zonneplan Zonnebonus",
        "style": {
            "label": true
        },
        "nodes": [
            "426f5b74668d1a6b",
            "be370ca5931e2729",
            "37c9a6df5ed98310",
            "e0b6c70677569798",
            "1d4ac16e0acc5e6a"
        ],
        "x": 94,
        "y": 99,
        "w": 952,
        "h": 282
    },
    {
        "id": "426f5b74668d1a6b",
        "type": "inject",
        "z": "28b7874e5f70d147",
        "g": "d7b06853644b688b",
        "name": "Daily at 1:00 Your Timezone",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "00 01 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 260,
        "y": 140,
        "wires": [
            [
                "37c9a6df5ed98310"
            ]
        ]
    },
    {
        "id": "be370ca5931e2729",
        "type": "debug",
        "z": "28b7874e5f70d147",
        "g": "d7b06853644b688b",
        "name": "All object debug",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 920,
        "y": 340,
        "wires": []
    },
    {
        "id": "37c9a6df5ed98310",
        "type": "suncron",
        "z": "28b7874e5f70d147",
        "g": "d7b06853644b688b",
        "name": "Center of The Netherlands",
        "lat": "52.1007",
        "lon": "5.1775",
        "replay": false,
        "ejectScheduleOnUpdate": false,
        "sunrisePayload": "1",
        "sunrisePayloadType": "num",
        "sunriseTopic": "sunrise",
        "sunriseOffsetType": 1,
        "sunriseOffsetHours": 0,
        "sunriseOffsetMinutes": 0,
        "sunriseOffset": 0,
        "sunriseEndPayload": "",
        "sunriseEndPayloadType": "str",
        "sunriseEndTopic": "",
        "sunriseEndOffsetType": 1,
        "sunriseEndOffsetHours": 0,
        "sunriseEndOffsetMinutes": 0,
        "sunriseEndOffset": 0,
        "goldenHourEndPayload": "",
        "goldenHourEndPayloadType": "str",
        "goldenHourEndTopic": "",
        "goldenHourEndOffsetType": 1,
        "goldenHourEndOffsetHours": 0,
        "goldenHourEndOffsetMinutes": 0,
        "goldenHourEndOffset": 0,
        "solarNoonPayload": "",
        "solarNoonPayloadType": "str",
        "solarNoonTopic": "",
        "solarNoonOffsetType": 1,
        "solarNoonOffsetHours": 0,
        "solarNoonOffsetMinutes": 0,
        "solarNoonOffset": 0,
        "goldenHourPayload": "",
        "goldenHourPayloadType": "str",
        "goldenHourTopic": "",
        "goldenHourOffsetType": 1,
        "goldenHourOffsetHours": 0,
        "goldenHourOffsetMinutes": 0,
        "goldenHourOffset": 0,
        "sunsetStartPayload": "",
        "sunsetStartPayloadType": "str",
        "sunsetStartTopic": "",
        "sunsetStartOffsetType": 1,
        "sunsetStartOffsetHours": 0,
        "sunsetStartOffsetMinutes": 0,
        "sunsetStartOffset": 0,
        "sunsetPayload": "1",
        "sunsetPayloadType": "num",
        "sunsetTopic": "sunset",
        "sunsetOffsetType": 1,
        "sunsetOffsetHours": 0,
        "sunsetOffsetMinutes": 0,
        "sunsetOffset": 0,
        "duskPayload": "",
        "duskPayloadType": "str",
        "duskTopic": "",
        "duskOffsetType": 1,
        "duskOffsetHours": 0,
        "duskOffsetMinutes": 0,
        "duskOffset": 0,
        "nauticalDuskPayload": "",
        "nauticalDuskPayloadType": "str",
        "nauticalDuskTopic": "",
        "nauticalDuskOffsetType": 1,
        "nauticalDuskOffsetHours": 0,
        "nauticalDuskOffsetMinutes": 0,
        "nauticalDuskOffset": 0,
        "nightPayload": "",
        "nightPayloadType": "str",
        "nightTopic": "",
        "nightOffsetType": 1,
        "nightOffsetHours": 0,
        "nightOffsetMinutes": 0,
        "nightOffset": 0,
        "nadirPayload": "",
        "nadirPayloadType": "str",
        "nadirTopic": "",
        "nadirOffsetType": 1,
        "nadirOffsetHours": 0,
        "nadirOffsetMinutes": 0,
        "nadirOffset": 0,
        "nightEndPayload": "",
        "nightEndPayloadType": "str",
        "nightEndTopic": "",
        "nightEndOffsetType": 1,
        "nightEndOffsetHours": 0,
        "nightEndOffsetMinutes": 0,
        "nightEndOffset": 0,
        "nauticalDawnPayload": "",
        "nauticalDawnPayloadType": "str",
        "nauticalDawnTopic": "",
        "nauticalDawnOffsetType": 1,
        "nauticalDawnOffsetHours": 0,
        "nauticalDawnOffsetMinutes": 0,
        "nauticalDawnOffset": 0,
        "dawnPayload": "",
        "dawnPayloadType": "str",
        "dawnTopic": "",
        "dawnOffsetType": 1,
        "dawnOffsetHours": 0,
        "dawnOffsetMinutes": 0,
        "dawnOffset": 0,
        "x": 280,
        "y": 240,
        "wires": [
            [
                "e0b6c70677569798"
            ]
        ]
    },
    {
        "id": "e0b6c70677569798",
        "type": "function",
        "z": "28b7874e5f70d147",
        "g": "d7b06853644b688b",
        "name": "Set sellPriceFormulaSchedule based on sunrise sunset",
        "func": "const suncronData = msg;\nlet returnmsg = {};\n\nif (suncronData && suncronData.schedule && suncronData.schedule.sunrise && suncronData.schedule.sunset) {\n  const sunriseTime = new Date(suncronData.schedule.sunrise.sunEventTime);\n  const sunsetTime = new Date(suncronData.schedule.sunset.sunEventTime);\n\n  // Helper function to round a date to the nearest 15-minute interval\n  const floorToQuarter = (date) => {\n    const minutes = date.getMinutes();\n    const floorMinutes = Math.round(minutes / 15) * 15;\n\n    let floorDate = new Date(date);\n    floorDate.setMinutes(floorMinutes);\n    floorDate.setSeconds(0);\n    floorDate.setMilliseconds(0);\n    \n    return floorDate;\n  };\n  \n  const ceilToQuarter = (date) => {\n    let ceilDate = floorToQuarter(date);\n    ceilDate.setTime(ceilDate.getTime() + (15 * 60 * 1000));\n\n    return ceilDate;\n  }\n\n  // Helper for START times (Exactly on the quarter hour: e.g., 06:45)\n  const formatStartTime = (date) => {\n    return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;\n  };\n\n  // Generate the formatted start and end times\n  // Acccording to https://jeroen.nl/dynamische-energie/aanbieders/zonneplan\n  // the start of sunbonus is at the beginning of the quarter when the sun is partly visible\n  // and the end of sunbonus is at the end of the quarter when the sun disappears completely\n  const sunrise = formatStartTime(floorToQuarter(sunriseTime)); // e.g., \"06:45\"\n  const sunset  = formatStartTime(ceilToQuarter(sunsetTime));  // e.g., \"20:15\"\n\n  const schedule = [\n    {\n      to: sunrise,\n      from: \"00:00\",\n      formula: \"p+0.01652\"\n    },\n    {\n      to: sunset,\n      from: sunrise,\n      formula: \"p*1.1+0.01652\"\n    },\n    {\n      to: \"00:00\",\n      from: sunset,\n      formula: \"p+0.01652\"\n    }\n  ];\n\n  const completeSchedule = [\n    {\n      days: [0, 1, 2, 3, 4, 5, 6],\n      schedule: schedule\n    }\n  ];\n\n  // Map the stringified schedules to the required VRM API parameters\n  returnmsg.payload = {\n        \"sellPriceFormulaSchedule\" : JSON.stringify(completeSchedule)\n  };\n\n  return returnmsg;\n} else {\n  node.warn(\"Invalid or unavailable suncron data, check 'schedule', 'sunrise', and 'sunset'.\");\n  return null;\n}\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 340,
        "wires": [
            [
                "be370ca5931e2729",
                "1d4ac16e0acc5e6a"
            ]
        ]
    },
    {
        "id": "1d4ac16e0acc5e6a",
        "type": "vrm-api",
        "z": "28b7874e5f70d147",
        "g": "d7b06853644b688b",
        "vrm": "930860d071785de1",
        "name": "Update Dynamic ESS configuration",
        "api_type": "installations",
        "idUser": "",
        "idSite": "000000",
        "installations": "patch-dynamic-ess-settings",
        "attribute": "",
        "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": 700,
        "y": 240,
        "wires": [
            [
                "be370ca5931e2729"
            ]
        ]
    },
    {
        "id": "930860d071785de1",
        "type": "config-vrm-api",
        "name": ""
    },
    {
        "id": "2b5bd863cffce7c7",
        "type": "global-config",
        "env": [],
        "modules": {
            "node-red-contrib-suncron": "1.6.9",
            "victron-vrm-api": "0.4.3"
        }
    }
]

Also check out the github page for this where I will push future changes:

I updated the location (center of The Netherlands) and the start and end time of the zonnebonus.

The starttime is at the beginning of the quarter of sunrise and the endtime is at the end of the quarter of sunset.

Hi Jeroen,

Great! And which formulas for 2027 do you use? I’m also switching to Zonneplan from next month, so I’ll need to convert everything.

Hi Tristan,

This is for 2027:

Buy: (P*1.21)+0.02+0.10648

Sell:

Good evening, Jeroen,

I’m new to the forum and wanted to ask if you could tell me which formula is filled in by default in Dynamic ESS when you use it for the first time? (Perhaps you could share a screenshot.)

This is the buy and sell formula, so I can get the right values for Zonneplan.

As soon as I’ve entered the correct details there, I’ll also install the Node-RED flow you created.

Regards,
Jeroen

Buy: (P*1.21)+0.02+0.10648
Sell: P+0.01653

Good evening, Jeroen,

I don’t think that’s quite right.

Shouldn’t it be → (P+0,02+0,10648)*1.21 ?

Regards, Jeroen

Good evening

I’ve just looked into it a bit further, and according to him the formulas below are correct for 2027:

I may have got it wrong, but I’d be glad to hear if I have

Buy: (P+0,01653+0,10648)*1,21
Sell: (P+0,01653)*1.21

(The 0.01653 is the price of 0.02 minus VAT)

Regards, Jeroen

The energy tax is 0,10648 VAT included for 2027, in your calculation you add the VAT twice.

I had indeed a typo in the 2 cent without tax included, which is 0,01653.

That makes the formula for 2027:

Buy: (P*1.21)+0.02+0.10648

Or Buy: (P+0.01653+0.088)*1.21

Sell: P+0.01653

Source energy tax with VAT

Source energy tax without VAT

Government Announcement

Best regards!