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