Experimental DESS algorithm using linear programing

I have no idea whether the inverse is possible as well: get dbus changes pushed over MQTT to your application. If that is possible, my logic still holds, if not, not. Anyway, it took so much effort to make Victron’s DESS behave the way it is advertised, that the step to reverse-engineer their scheduler and be done with, is al but a mere final plunge into the realm of HA (or a sort like automation platform). Which I guess is not coincidentally the path you are on.

Following up on my previous post: after thinking about this for a while, it seems that we must convert the exact numbers we have as output from the LP to an “intent” and have to set the values based on that.

To get a better feel for how things currently work, I closely observed the relation between the schedule graphs on the VRM page and the actual schedule. I have learned the following:

Allow Grid Feed In is always enabled, except if the export prices are negative. This one is thus easy to set.

Strategy is used in a way I didn’t entirely expect. I have observed the following scenarios yesterday evening (when there was no PV)

  • If the plan only included “battery to load” flows, the strategy was set to self-consume. In this mode, the target SoC isn’t relevant. All load will be taken from the battery (because we want to self-consume).
  • If the plan only included “grid to load” (so no battery usage), the strategy was set to pro battery and the target SoC was equal to the current SoC. In pro battery, all excess load will be taken from the grid, so if both SoC values are equal, everything is excess load and thus the grid will be used for everything.
  • If the plan included “grid to battery”, the strategy was set to pro battery and the target SoC was set higher than the current SoC. This also seems to make sense.

I haven’t observed target soc and pro grid yet, and also plan to do some observations during PV production.

Restrictions puzzle me a bit. There are 4 possible values: “No restrictions between battery and the grid”, “Grid to battery energy flow is restricted”, “Battery to grid energy flow is restricted”, “No energy flow between battery and grid”. To me it seems that this means no restrictions, restriction in one direction, restriction in the other direction and restriction in both directions. However, I think “grid to battery restricted” was active on my system while it was actively charging.

I think these limitations are primarily due to German law. We have some rather silly restrictions that, under certain circumstances, force you to choose whether to draw power from the grid into the battery or vice versa. Or even prohibit any energy flow between the battery and the grid altogether. I don’t think Victron would have received approval for the German market without these options/restrictions.

1 Like

Nice work @bartm , I read all your stuff and I hope I can contribute again next week. Ironically, I have to study now for my LP course, I neglected it, and I made this instead :sweat_smile: .

What @Sarowe1990 says makes sense. The ev charging station also has a grid restriction pin to comply with §14a EnWG in the German market. The pin is there to enable the grid operator to reduce the charging power to help maintain grid stability.

1 Like

These regulations are incredibly stupid, but we Germans like to protect our energy companies from too much competition. (Caution: May contain traces of sarcasm)

I am experimenting with this at the moment.

I have extended our flow to provide change signals every time SoC, Strategy or Restrictions change for the d-bus schedule (the local 12 hours ahead forward schedule slots). Combined with a 15 minute timer this provides a timely trigger to re-evaluate and adjust the d-bus schedule (for the current timeslot only), generally speaking 8 times an hour.

At every trigger I retrieve an updated vrm schedule object and read the vrm_soc_plan, vrm_to_battery_fc and vrm_to_grid_fc values for the current timeslot. High vrm_to_battery signals ‘Buy’, high vrm_to_grid signals ‘Sell’ and neither signals ‘Idle’.

During ‘Buy’ I update the d-bus INT SoC with an up-rounded value of the float vrmSoC, During ‘Sell’ I do the same but with a down-rounded SoC.

When ‘Idle’ (not buy or sell) I set the d-bus schedule Restriction (for current time-slot) to 3: “No energy flow between battery and grid”

And every 15 minutes and with every in between d-bus schedule update, this process repeats.

The result: No more ‘throttling’ during buy and sell timeslots, no more charge / discharge ‘overshoots’ right after buy and sell timeslots. AND as far as I can see at the moment this still leaves the reactive strategy logic intact. The inverter either goes into ‘Idle’ (ACin = ACout) OR it goes into ‘selfconsumption’ (ACin = Grid setpoint) depending on the actual battery SoC in relation to the scheduled SoC.

This is the best behaving DESS (Trade only, no PV) modification I have tried yet. And because this modification has minimal interference with the VRM scheduler, it might proof to work well for DESS with PV and irregular large loads as well. All I wish for now is the ability to send the vrm scheduler an independent future SoC target (that I have been arguing for ad infinitum ).

For ‘Buy’ or ‘Sell’ timeslots it is enough to set the target SoC (INT) for the current timeslot above or below the expected/required battery SoC at the end of that timeslot, and set the strategy to pro_battery (Buy) or pro_grid (Sell) accordingly. What to do with the timeslots in between depends on your use case and energy contract details. In NL the grid acts a free lossless AC battery (until saldering ends) which logically makes ‘self consumption’ useless unless you have a surplus of PV energy. That’s why we set the bi-directional restriction 3: “No energy flow between battery and grid” unless we buy or sell. With PV / EVCS involved you could try directional restrictions to suit your needs.

If you don’t care about VRM or Node-RED dashboard graphs, and have no need to insure your local Cerbo GX DESS scheduler keeps working (for up to 12 hours) when the VRM scheduler fails to push updates, you can simply set all 48 d-bus schedule entries to the same scheduled SoC, Strategy and Restrictions every couple of minutes (don’t forget to shift the start/end times every hour though)

As far as I can see, the DESS execution process only looks at target SoC, Strategy and Restrictions for the current timeslot . The only complication is that you cannot set those directly (well you probably can in ESS mode) and need to set the forward looking d-bus schedule slots instead. The VenusOS DESS process then copies them to the active current timeslot d-bus entries. You might want to look at the source code to see if there are ways around that. We only want to modify the d-bus schedules as little as needed away from the VRM schedules to make the DESS system as a whole behave more rational and predictable. For that it suffices to focus on the first eight (8) timeslots only, as it hardly ever happens that the scheduler is over an hour late with updates.

PS most charging done by a secondary HF boost charger (fixed DC current) wired to ACout (red line in middle graph), The MP-II only adds a few charging amps until the ACin current limit is reached (blue line in middel graph). Allmost all AC loads run from a secondary HF DC-to-AC inverter, mostly a small electric heater, to ensure the maximum inverter power is available to feed-in to the grid during ‘Sell’ timeslots.

Almost perfect, the ‘missed’ timeslots are related to the dayly schedule update and work on the node-red flow. Battery costs are deliberately set very low for testing.

I observed some more strategies and wrote it down in pseudocode to have a better idea what we are still missing. I have made no progress in how the restrictions are set.

    if (hasG2B) {
      // There's a grid charging flow which probably means electricity is cheap.
      // This means we'll want to use the grid as much as possible and store PV in the battery.
      // So we set pro-battery (and a target SoC that's higher than current SoC)
      strategy = Strategy.proBattery;
    } else if (hasB2G) {
      // There's an active discharge to grid which probably means electricity is expensive.
      // This means we'll want to use the battery for our own load as much as possible and export excess PV to the grid.
      // I haven't observed this case yet, but it's presumably pro-grid (and a target SoC lower than current SoC)
      // TODO: strategy remains unknown
    } else {
      if (deficitOrNoPv) {
        // We have a PV deficit to cover our planned loads.
        // Based on how this deficit is covered according to the plan, we can use the same handling for unexpected loads.
        // TODO: technically, if we have an unexpected PV surplus, we might also want to inject that into the grid. We don't handle that yet. If we do, we must also adapt the restrictions.
        if (hasB2L && !hasG2L) {
          // The battery is used to cover the deficit, so we'll do the same for unexpected loads.
          strategy = Strategy.selfConsumption;
        } else if (hasG2L && !hasB2L) {
          // The grid is used to cover the deficit, so we'll do the same for unexpected loads.
          // Target SoC should be close to current SoC or the reactive strategy will ignore the grid restrictions
          strategy = Strategy.proBattery;
        } else if (!hasB2L && !hasG2L) {
          // Predicted PV is exactly equal to predicted load, so there's no deficit handling in the plan.
          // We have thus no indication of how to handle unexpected loads.
          // TODO: simulate to determine if pro-battery or self-consumption is better? Or use a price indicator?
          strategy = Strategy.selfConsumption;
        } else {
          // PV deficit is served by both battery and grid.
          // We have thus no clear indication of how to handle unexpected loads.
          // I haven't observed this case yet, but I think it makes sense to use the grid here because the mix probably indicates that the battery is low.
          // TODO: strategy remains unknown
        }
      } else if (pvCoversLoad) {
        // In this case, PV is expected to cover all load and we have excess PV.
        // Based on how this excess PV is used according to the plan, we can use the same handling for unexpected excess PV.
        // It is however less clear how unexpected loads should be covered in this case.
        if (hasPV2B) {
          // If we see PV2B -> use self-consumption to cover the unexpected loads by battery or pro battery to cover by grid.
          // TODO: simulate to determine if pro-battery or self-consumption is better? Or use a price indicator?
          strategy = Strategy.selfConsumption;
        } else {
          // In this case, we see PV2G, but I haven't observed this yet.
          // TODO: strategy remains unknown
        }
      } else {
        // I don't think we can reach this branch?
      }
    }

where?

This should give you all a real good head start. It’s not complete, you’ll have to investigate how to fill in the blanks. And do get the boolean logic ultimate nodes first:

[
    {
        "id": "d2daef7783d73008",
        "type": "tab",
        "label": "Flow 4",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "9b4734b57b0d4fe0",
        "type": "group",
        "z": "d2daef7783d73008",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "21146a51d68e7ac2",
            "2426508ca6103175",
            "0f35bc357d718c0f",
            "98fc72a6d252fcc0",
            "33b197f61d63ddec",
            "76143a5719eced1d",
            "1b9203d0261407b9",
            "6d161434e4304177",
            "76371f16ae3b39d4",
            "13f460360d208ae8",
            "c659466dba0e2802",
            "4cb3bda9c634db18",
            "45a5d7d104686dcb",
            "b1da7133e0afead6",
            "b7f8bec713885648",
            "c350f95bb0b9e648",
            "8012f29aac8991a8",
            "11bdc130c94b5823",
            "0114af8f9f09b6ad",
            "3095ada6a29f5075",
            "9e5c33a06f025b8f",
            "4708f14a020c5b50",
            "3a8c39ad5406a570",
            "8bb3bcb092a19ff2",
            "5c96e3edf07ee2d8",
            "f0dcc84ae0a7b43e",
            "0325ec6d659904d0",
            "67b586be0cf0947d",
            "e43caee1ed2c087f",
            "1b96e5c956e85094",
            "6202b9c78eceb859",
            "3228378909ab771d",
            "a495510c28b03ed6",
            "aba3cef3f2bec934",
            "bcb6a979c3a94e78",
            "321fb2222b317ca3"
        ],
        "x": 14,
        "y": 639,
        "w": 942,
        "h": 442
    },
    {
        "id": "f3aa2d1483c11581",
        "type": "group",
        "z": "d2daef7783d73008",
        "name": "",
        "style": {
            "label": true
        },
        "nodes": [
            "548f6727918a2761",
            "f20757f56f95abe5",
            "4329aa3693076932",
            "7e02268826eb0146",
            "87d62f2776d3bb1e",
            "52c74eefe7fee5be",
            "f9b46bb155e0c18b",
            "2c1b4ffae9067429",
            "8cf37ba88b85629b"
        ],
        "x": 14,
        "y": 19,
        "w": 652,
        "h": 142
    },
    {
        "id": "4e1953eb360527b6",
        "type": "group",
        "z": "d2daef7783d73008",
        "name": "UpCyCle Hybrid DESS Trade : D-Bus Schedule Changes",
        "style": {
            "label": true
        },
        "nodes": [
            "4df6fc5ebcfb2498",
            "32fc7b201124c79e",
            "f37ffb9a61eacc09",
            "120d4e58834c531e",
            "6ea4b2373729a3a6",
            "a43827c2b82e54fb",
            "e26e2fad195d738f",
            "a491cb7cae163f33",
            "b49a03088a1c8592",
            "073b133aef3416cd",
            "0c2349ed3249e44a",
            "f282a09fde585c8a",
            "7c17013c52082449",
            "d3c518925e0d027d",
            "5fa79f3888eb4a72",
            "f3bb240cd60e7dce",
            "626d6c6235df3d55",
            "0ee760bc32ea5cf7",
            "98e7880d84f56ded",
            "c80b8742c481ac67",
            "74819b1176cafabf",
            "fb01a4b5df4ffb8a",
            "9006b3e95a545db3",
            "72a50dd7aaa74a3b",
            "512f72544cbb719e",
            "0b02c86ff6d06ceb",
            "3867ba816bffc6a8",
            "534dcd1a1b0830b0",
            "74377146b0d1d993",
            "85a4c0c0e5025857",
            "852f7aa865431df3",
            "052af5e8ebd4494a",
            "d20ed3b3db83845f",
            "c65fc3929abfa970",
            "d71c9cc984428c8c",
            "1d66401622730718",
            "94525a3ba1024834",
            "d5bc79b24877def4",
            "0bac72e596c044ae",
            "1c53999fa2e8984c",
            "7351595384039d53",
            "42bbf784205aa9f0",
            "5de572c3b0794564",
            "7bf2f4fdf95cc8c2",
            "e82c9446da1f5d1d"
        ],
        "x": 14,
        "y": 179,
        "w": 1172,
        "h": 442
    },
    {
        "id": "f03ad8631d0b0ab1",
        "type": "group",
        "z": "d2daef7783d73008",
        "name": "",
        "style": {
            "label": true
        },
        "nodes": [
            "49d9c26967d3c8c9",
            "b1866da960da0403",
            "c15b3220e6a18eb5",
            "1deca90bbfdbb765",
            "56558612c95df175",
            "f35e5141b73bdf4a",
            "3d6db8032eb7bffe",
            "bdac3b033f220c9e",
            "fb2cd14e35eeefe8",
            "1359a49b4e32fd1b",
            "a94146cf9c947c26",
            "da10e8cc5a220365",
            "fdddee2f88b2ec43",
            "acc8dcc81275989a",
            "2615693ca9624c6d",
            "4b2d20c23e249e7a",
            "65f65eba972bb536",
            "fb504d3c97035b15",
            "cd0d2d66f58ac339",
            "c5e76fbce9acdd24"
        ],
        "x": 1214,
        "y": 16.5,
        "w": 672,
        "h": 367
    },
    {
        "id": "e09cb27d1a558ff1",
        "type": "group",
        "z": "d2daef7783d73008",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "b7c3dffb337591b0",
            "a748125841083c1b",
            "a8bc276a8b9f4051",
            "87e86d220c2f8c21",
            "f3b726b2617068d8",
            "00fe85160eb42286",
            "7ab40feec890547a",
            "4fa6391efc896b30",
            "4d8fcb496ce551f1",
            "7cc306c3999d6244",
            "bad471a163983538",
            "d27de0bbcc7d7c2e",
            "b7c68ffb33ebbb47",
            "cecf6a4225731793",
            "70b76892e0ea7bae",
            "1afd53b8c84e54cb",
            "779226fe3f372b69"
        ],
        "x": 1214,
        "y": 396.5,
        "w": 672,
        "h": 367
    },
    {
        "id": "d24783f8b2c72351",
        "type": "group",
        "z": "d2daef7783d73008",
        "name": "",
        "style": {
            "label": true
        },
        "nodes": [
            "d0c97df1d407dfd7",
            "9faad491d9066dbb",
            "40cff0a35e1cc29e",
            "2716f9b1616c5904",
            "225bfcf97f866c6c",
            "6426f31bb2c47549",
            "91e06fe5cc3485e0",
            "a51403d4fd72067c",
            "5e296b9521abb282",
            "00ad025eec903d2a",
            "57db3033fecf3541",
            "d201c952fe3513a6",
            "a1e6c36357acc069",
            "e2d409885fdf2fb2",
            "74897ad024d65a53",
            "653e60e62e3d4460",
            "b599d62234ca95ef",
            "1d55681ad7165b0e",
            "cf5d4451ff2bbc64",
            "dedd529b90b8e317"
        ],
        "x": 1214,
        "y": 776.5,
        "w": 672,
        "h": 367
    },
    {
        "id": "21146a51d68e7ac2",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "timeslot_vrm_to_grid_fc",
        "func": "//\nlet timeslot = msg.timeslot\nlet schedules_key = 'vrm_to_grid_fc'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 0\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + 'Wh')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 450,
        "y": 920,
        "wires": [
            [
                "33b197f61d63ddec"
            ]
        ]
    },
    {
        "id": "2426508ca6103175",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link in 73",
        "links": [
            "626d6c6235df3d55",
            "72a50dd7aaa74a3b",
            "f9b46bb155e0c18b",
            "1c53999fa2e8984c"
        ],
        "x": 55,
        "y": 680,
        "wires": [
            [
                "45a5d7d104686dcb"
            ]
        ]
    },
    {
        "id": "0f35bc357d718c0f",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "timeslot_vrm_to_battery_fc",
        "func": "//\nlet timeslot = msg.timeslot\nlet schedules_key = 'vrm_to_battery_fc'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 0\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + 'Wh')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 460,
        "y": 800,
        "wires": [
            [
                "4708f14a020c5b50",
                "6d161434e4304177"
            ]
        ]
    },
    {
        "id": "98fc72a6d252fcc0",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "timeslot_deGb",
        "func": "//\nlet timeslot = msg.timeslot\nlet schedules_key = 'deGb'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\nlet get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 100\n    let digits = 3\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + 'ct')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 120,
        "y": 920,
        "wires": [
            [
                "f0dcc84ae0a7b43e"
            ]
        ]
    },
    {
        "id": "33b197f61d63ddec",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": ">=800",
        "func": "if (msg.payload >= 800)\n{\n    msg.payload = true\n} else {\n    msg.payload = false\n}\nreturn msg",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 920,
        "wires": [
            [
                "1b9203d0261407b9"
            ]
        ]
    },
    {
        "id": "76143a5719eced1d",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 86",
        "mode": "link",
        "links": [
            "6f1e157dc56351ca",
            "b61b54d0bcbfa1da"
        ],
        "x": 915,
        "y": 920,
        "wires": []
    },
    {
        "id": "1b9203d0261407b9",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "Status",
        "property": "payload",
        "x": 830,
        "y": 920,
        "wires": [
            [
                "76143a5719eced1d"
            ]
        ]
    },
    {
        "id": "6d161434e4304177",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "-800 > x < 1600",
        "func": "let bool = false\nif ( (msg.payload > -800 ) && (msg.payload < 1600) )\n{\n    bool = true\n}\nmsg.payload = bool\nreturn msg",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 680,
        "y": 860,
        "wires": [
            [
                "13f460360d208ae8"
            ]
        ]
    },
    {
        "id": "76371f16ae3b39d4",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 87",
        "mode": "link",
        "links": [],
        "x": 915,
        "y": 860,
        "wires": []
    },
    {
        "id": "13f460360d208ae8",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "Status",
        "property": "payload",
        "x": 830,
        "y": 860,
        "wires": [
            [
                "76371f16ae3b39d4"
            ]
        ]
    },
    {
        "id": "c659466dba0e2802",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 88",
        "mode": "link",
        "links": [
            "ee0df1fd2c8fb89c",
            "4e2c898b8a534dc3"
        ],
        "x": 915,
        "y": 800,
        "wires": []
    },
    {
        "id": "4cb3bda9c634db18",
        "type": "delay",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "1/15s",
        "pauseType": "rate",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "15",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 710,
        "y": 680,
        "wires": [
            [
                "b1da7133e0afead6"
            ]
        ]
    },
    {
        "id": "45a5d7d104686dcb",
        "type": "change",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "timestamp",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "timestamp",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "",
                "tot": "date"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 570,
        "y": 680,
        "wires": [
            [
                "4cb3bda9c634db18"
            ]
        ]
    },
    {
        "id": "b1da7133e0afead6",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "Status",
        "property": "payload",
        "x": 830,
        "y": 680,
        "wires": [
            [
                "0325ec6d659904d0",
                "aba3cef3f2bec934"
            ]
        ]
    },
    {
        "id": "b7f8bec713885648",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "SoC_0",
        "func": "//\nlet index = 0\nlet timeslot = msg.timeslot\nlet hourf = timeslot / 4\nlet hour = hourf - ( hourf %1 )\ntimeslot = 4 * hour + index\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 6\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + '%')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 90,
        "y": 1040,
        "wires": [
            []
        ]
    },
    {
        "id": "c350f95bb0b9e648",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "SoC_1",
        "func": "//\nlet index = 1\nlet timeslot = msg.timeslot\nlet hourf = timeslot / 4\nlet hour = hourf - ( hourf %1 )\ntimeslot = 4 * hour + index\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 6\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + '%')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 270,
        "y": 1040,
        "wires": [
            []
        ]
    },
    {
        "id": "8012f29aac8991a8",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "SoC_2",
        "func": "//\nlet index = 2\nlet timeslot = msg.timeslot\nlet hourf = timeslot / 4\nlet hour = hourf - ( hourf %1 )\ntimeslot = 4 * hour + index\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 6\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + '%')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 450,
        "y": 1040,
        "wires": [
            []
        ]
    },
    {
        "id": "11bdc130c94b5823",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "SoC_3",
        "func": "//\nlet index = 3\nlet timeslot = msg.timeslot\nlet hourf = timeslot / 4\nlet hour = hourf - ( hourf %1 )\ntimeslot = 4 * hour + index\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 6\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + '%')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 1040,
        "wires": [
            []
        ]
    },
    {
        "id": "0114af8f9f09b6ad",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "SoC_4",
        "func": "//\nlet index = 4\nlet timeslot = msg.timeslot\nlet hourf = timeslot / 4\nlet hour = hourf - ( hourf %1 )\ntimeslot = 4 * hour + index\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n\nif ( rounded )\n{\n    let factor = 1\n    let digits = 6\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( schedules_key + ' at Q' + timeslot + ' : ' + rounded + '%')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = topic\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 810,
        "y": 1040,
        "wires": [
            []
        ]
    },
    {
        "id": "3095ada6a29f5075",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "vrmSoC",
        "func": "//\n//let index = 0\nlet timeslot = msg.timeslot\n\n/*\n//let hourf = timeslot / 4\n//let hour = hourf - ( hourf %1 )\n//timeslot = timeslot + index\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\n\nlet global_get = 'installations.stats';\n\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\n\nlet get_records = null\n//let get_totals = 0\n//let get_price = 0\nlet records_length = 0\nvar is_records = false\n\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n//    get_totals = get_schedules.totals[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[timeslot][1] : false )\n}\n\nlet rounded = get_value\n*/\nlet rounded = msg.payload.curValue;\nif ( rounded )\n{\n    let factor = 1\n    let digits = 6\n    let multiplier = 10 ** digits\n    let multiplied = rounded * factor * multiplier\n    rounded = ( multiplied - multiplied % 1 ) / multiplier    \n}\n\nlet fill = ( (rounded !== false)  ? 'green' : 'red')\nlet shape = 'dot'\nlet text = ( 'vrmSoC(' + timeslot + '): ' + rounded + '%')\nnode.status({fill, shape, text})\n\nmsg.payload = rounded\nmsg.text = text\nmsg.topic = 'vrmSoC'\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 980,
        "wires": [
            [
                "321fb2222b317ca3"
            ]
        ]
    },
    {
        "id": "9e5c33a06f025b8f",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "Status",
        "property": "payload",
        "x": 830,
        "y": 800,
        "wires": [
            [
                "c659466dba0e2802"
            ]
        ]
    },
    {
        "id": "4708f14a020c5b50",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": ">=1600",
        "func": "if (msg.payload >= 1600)\n{\n    msg.payload = true\n} else {\n    msg.payload = false\n}\nreturn msg",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 800,
        "wires": [
            [
                "9e5c33a06f025b8f"
            ]
        ]
    },
    {
        "id": "3a8c39ad5406a570",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 89",
        "mode": "link",
        "links": [
            "43f4eff631cea5e3"
        ],
        "x": 555,
        "y": 860,
        "wires": []
    },
    {
        "id": "8bb3bcb092a19ff2",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "metrics vrm_soc_plan",
        "func": "/**\n * Processes schedule object to compute length, start/end times, min/max with indices, and averages.\n * Outputs single msg.payload with all metrics; stores in flow context.\n * @param {Object} msg Node-RED message with payload.schedule (array of [timestamp, value] pairs)\n * @returns {Object} msg with payload {length, startTime, endTime, minIndex, minTime, minValue, maxIndex, maxTime, maxValue, currentIndex, currentTime, currentValue, fullAverage, forwardAverage}\n */\n\nlet payload = msg.payload;   //usually timestamp\n\nlet schedules_key = 'vrm_soc_plan';\nlet topic = 'vrmdess_' + schedules_key;\n\nlet global_get = 'installations.stats';\nlet get_schedules = global.get( global_get );\nlet is_schedules = !( get_schedules == undefined || get_schedules == null );\n\nlet get_records = null;\nif ( is_schedules ) {\n    get_records = get_schedules.records[schedules_key]\n}\nlet schedule = get_records;\n\n//\nconst length = schedule.length; //TODO Fix for venusos update, length unknown\n \nif (!length || !Array.isArray(schedule)) {\n    node.warn(\"Invalid or empty schedule array\");\n    msg.payload = { error: \"Invalid or empty schedule array\" };\n    return msg;\n}\n\nconst curTimeSec = Math.floor(Date.now() );  // / 1000);\nlet startTime = null;\nlet endTime = null;\nlet minIndex = null;\nlet minTime = null;\nlet minValue = null;\nlet maxIndex = null;\nlet maxTime = null;\nlet maxValue = null;\nlet minfwdIndex = null;\nlet minfwdTime = null;\nlet minfwdValue = null;\nlet maxfwdIndex = null;\nlet maxfwdTime = null;\nlet maxfwdValue = null;\nlet curIndex = null;\nlet curTime = null;\nlet curValue = null;\nlet fullSum = 0;\nlet fullCount = 0;\nlet fwdSum = 0;\nlet fwdCount = 0;\nlet lastValue = 0;\nlet fwdupSum = 0;\nlet fwddwnSum = 0;\n\nfor (const [idx, [timestamp, value]] of schedule.entries()) {\n    // Validate data pair\n    if (!Array.isArray([timestamp, value]) || typeof timestamp !== \"number\" || !Number.isInteger(timestamp) || typeof value !== \"number\" || isNaN(value)) {\n        node.warn(`Invalid data at schedule[${idx}]`);\n        continue;\n    }\n    // Update start/end times\n    if (startTime === null || timestamp < startTime) startTime = timestamp;\n    if (endTime === null || timestamp > endTime) endTime = timestamp;\n    // Update min/max\n    if (minValue === null || value < minValue) {\n        minIndex = idx;\n        minTime = timestamp;\n        minValue = value;\n    }\n    if (maxValue === null || value > maxValue) {\n        maxIndex = idx;\n        maxTime = timestamp;\n        maxValue = value;\n    }\n    // Update averages\n    fullSum += value;\n    fullCount++;\n    //Update forward averages\n    if ( (timestamp + 900*1000) >= curTimeSec) {\n        if (curIndex === null) {\n            curIndex = idx;\n            curTime = timestamp;\n            curValue = value;\n        }\n        // Update minfwd/maxfwd\n        if (minfwdValue === null || value < minfwdValue) {\n            minfwdIndex = idx;\n            minfwdTime = timestamp;\n            minfwdValue = value;\n        }\n        if (maxfwdValue === null || value > maxfwdValue) {\n            maxfwdIndex = idx;\n            maxfwdTime = timestamp;\n            maxfwdValue = value;\n        }\n        fwdSum += value;\n        if (lastValue > 0) {\n            if (value > lastValue) fwdupSum += (value - lastValue);\n            if (value < lastValue) fwddwnSum += (value - lastValue); \n            fwdCount++;\n        }\n    }\n    // set lastvalue before forwards calculations\n    lastValue = value;\n}\n\n// Compute metrics\nlet fullAverage = fullCount > 0 ? fullSum / fullCount : 0;\nlet fwdAverage = fwdCount > 0 ? fwdSum / fwdCount : 0;\nlet fullMiddle = (maxValue + minValue) / 2\nlet fwdMiddle = (maxfwdValue + minfwdValue) / 2\n\n// Validate results\nif (length === 0 || startTime === null || minValue === null) {\n    node.warn(\"No valid data found\");\n    msg.payload = { error: \"No valid data found\" };\n    return msg;\n}\n/*\n// Set output\nconst result = {\n    length,\n    startTime,\n    endTime,\n    minIndex,\n    minTime,\n    minValue,\n    maxIndex,\n    maxTime,\n    maxValue,\n    minfwdIndex,\n    minfwdTime,\n    minfwdValue,\n    maxfwdIndex,\n    maxfwdTime,\n    maxfwdValue,\n    curIndex,\n    curTime,\n    curValue,\n    fullAverage,\n    fwdAverage,\n    fullMiddle,\n    fwdMiddle\n};\n*/\nlet digits = 1000\ncurValue = Math.round(digits*curValue)/digits;\nfwdAverage = Math.round(digits*fwdAverage)/digits;\nfwdupSum = Math.round(digits*fwdupSum)/digits;\nfwddwnSum = Math.round(digits*fwddwnSum)/digits;\nminfwdValue = Math.round(digits*minfwdValue)/digits;\nfwdMiddle = Math.round(digits*fwdMiddle)/digits;\nmaxfwdValue = Math.round(digits*maxfwdValue)/digits;\nfullAverage = Math.round(digits*fullAverage)/digits;\nminValue = Math.round(digits*minValue)/digits;\nfullMiddle = Math.round(digits*fullMiddle)/digits;\nmaxValue = Math.round(digits*fullMiddle)/digits;\nlength,\ncurIndex,\nminIndex,\nminfwdIndex,\nmaxfwdIndex,\nmaxIndex,\nstartTime,\ncurTime,\nminTime,\nminfwdTime,\nmaxfwdTime,\nmaxTime,\nendTime\n\nconst result = {\n    curValue,\n    fwdAverage,\n    fwdupSum,\n    fwddwnSum,\n    minfwdValue,\n    fwdMiddle,\n    maxfwdValue,\n    fullAverage,\n    minValue,\n    fullMiddle,\n    maxValue,\n    length,\n    curIndex,\n    minIndex,\n    minfwdIndex,\n    maxfwdIndex,\n    maxIndex,\n    startTime,\n    curTime,\n    minTime,\n    minfwdTime,\n    maxfwdTime,\n    maxTime,\n    endTime\n};\n\n// Store in flow context\nflow.set(\"vrmMETRICS\", result);\n\n// Store in global context\n// global.set(\"vrm_soc_plan\", result);\n\nmsg.timestamp = payload;\nmsg.timeslot = curIndex;\nmsg.payload = result;\nmsg.topic = topic;\n\nreturn msg;\n//",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 140,
        "y": 860,
        "wires": [
            [
                "21146a51d68e7ac2",
                "0f35bc357d718c0f",
                "98fc72a6d252fcc0",
                "1b96e5c956e85094",
                "3a8c39ad5406a570",
                "5c96e3edf07ee2d8"
            ]
        ]
    },
    {
        "id": "5c96e3edf07ee2d8",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "vrmMETRICS",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload.curValue",
        "statusType": "msg",
        "x": 420,
        "y": 860,
        "wires": []
    },
    {
        "id": "f0dcc84ae0a7b43e",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 90",
        "mode": "link",
        "links": [
            "88540bff3f5b326c"
        ],
        "x": 235,
        "y": 920,
        "wires": []
    },
    {
        "id": "0325ec6d659904d0",
        "type": "vrm-api",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "vrm": "9e7340b4a2802944",
        "name": "STATS DESS",
        "api_type": "installations",
        "idUser": "",
        "idSite": "{{flow.siteId}}",
        "installations": "stats",
        "attribute": "dynamic_ess",
        "stats_interval": "15mins",
        "show_instance": false,
        "stats_start": "bod",
        "stats_end": "eot",
        "use_utc": false,
        "gps_start": "",
        "gps_end": "",
        "widgets": "PVInverterStatus",
        "instance": "",
        "store_in_global_context": true,
        "verbose": false,
        "transform_price_schedule": true,
        "outputs": 2,
        "x": 120,
        "y": 740,
        "wires": [
            [
                "e43caee1ed2c087f",
                "67b586be0cf0947d"
            ],
            [
                "67b586be0cf0947d"
            ]
        ]
    },
    {
        "id": "67b586be0cf0947d",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "STATS DESS",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 420,
        "y": 740,
        "wires": []
    },
    {
        "id": "e43caee1ed2c087f",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "Status",
        "property": "payload",
        "x": 90,
        "y": 800,
        "wires": [
            [
                "8bb3bcb092a19ff2"
            ]
        ]
    },
    {
        "id": "1b96e5c956e85094",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "mux",
        "func": "return msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 390,
        "y": 980,
        "wires": [
            [
                "b7f8bec713885648",
                "c350f95bb0b9e648",
                "8012f29aac8991a8",
                "11bdc130c94b5823",
                "0114af8f9f09b6ad",
                "3095ada6a29f5075"
            ]
        ]
    },
    {
        "id": "6202b9c78eceb859",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "timeslot",
        "func": "//\nlet timestamp = msg.payload\n\nlet schedules_key = 'vrm_soc_plan'\nlet topic = 'vrmdess_' + schedules_key\nlet global_get = 'installations.fetch-dynamic-ess-schedules'\nlet get_schedules = global.get( global_get )\nlet is_schedules = !( get_schedules == undefined || get_schedules == null )\nlet get_records = null\nlet records_length = 0\nvar is_records = false\nif ( is_schedules )\n{\n    get_records = get_schedules.records[schedules_key]\n    records_length = get_records.length\n    is_records = ( (records_length > 0) ? true : false )\n    get_value = ( is_records ? get_records[0][0] : false )\n}\nlet schedulestart = get_value\nlet secondsfromstart = ( timestamp - schedulestart ) / 1000\n\nlet quarterfromstartf = ( secondsfromstart / ( 60 * 15  ) )\nlet quarterfromstart = quarterfromstartf - ( quarterfromstartf % 1 )\n\ntimeslot =quarterfromstart // test wintertijd\n\nlet fill = ( 'green' )\nlet shape = 'dot'\nlet text = ( 'timeslot : ' + timeslot )\n\nnode.status({fill, shape, text})\n//msg.payload = timeslot\nmsg.timeslot = timeslot\nmsg.topic = 'timeslot'\nreturn msg;\n\n\n\n\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 100,
        "y": 980,
        "wires": [
            []
        ]
    },
    {
        "id": "3228378909ab771d",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "query",
        "func": "const getStartOfDay = (date) => {\n    const start = new Date(date)\n    start.setHours(0, 0, 0, 0)\n    return Math.floor(start.getTime() / 1000)\n}\n\nconst now = new Date()\n\nconst start = getStartOfDay(now)\n  \nconst endOfTomorrow = new Date(now)\nendOfTomorrow.setDate(endOfTomorrow.getDate() + 1)\nendOfTomorrow.setHours(23, 59, 59, 999)\nconst end = Math.floor(endOfTomorrow.getTime() / 1000)\n\nconst siteId = flow.get('siteId')\n\nmsg.method = 'get'\nmsg.url = 'https://vrmapi.victronenergy.com/v2'\nmsg.query = `installations/${siteId}/stats?type=dynamic_ess&interval=15mins&start=${start}&end=${end}`\nmsg.topic = 'installations fetch-dynamic-ess-schedules'\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 650,
        "y": 740,
        "wires": [
            []
        ]
    },
    {
        "id": "a495510c28b03ed6",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 91",
        "mode": "link",
        "links": [
            "91b5076b60b12915"
        ],
        "x": 915,
        "y": 980,
        "wires": []
    },
    {
        "id": "aba3cef3f2bec934",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "link out 92",
        "mode": "link",
        "links": [],
        "x": 915,
        "y": 680,
        "wires": []
    },
    {
        "id": "bcb6a979c3a94e78",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "1 || 2 || 7 || 9 -> true",
        "func": "//\nif (  msg.payload == 1 || msg.payload == 2 || msg.payload == 7 || msg.payload == 9 )\n{\n    msg.payload = true\n    return msg\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 830,
        "y": 740,
        "wires": [
            []
        ]
    },
    {
        "id": "321fb2222b317ca3",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "9b4734b57b0d4fe0",
        "name": "Status",
        "property": "payload",
        "x": 830,
        "y": 980,
        "wires": [
            [
                "a495510c28b03ed6"
            ]
        ]
    },
    {
        "id": "548f6727918a2761",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Start",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Start",
            "name": "/Settings/DynamicEss/Schedule/0/Start",
            "type": "number",
            "value": 1762452000
        },
        "name": "dbsSTART",
        "onlyChanges": true,
        "x": 380,
        "y": 60,
        "wires": [
            [
                "f20757f56f95abe5",
                "87d62f2776d3bb1e"
            ]
        ]
    },
    {
        "id": "f20757f56f95abe5",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "dbsSTART",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 550,
        "y": 60,
        "wires": []
    },
    {
        "id": "4329aa3693076932",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "time",
        "func": "msg.timestamp = msg.payload;\nmsg.topic = 'time';\nmsg.payload = Math.round(msg.payload/1000);\nlet fill = ( 'green' );\nlet shape = 'dot';\nlet text = ( msg.topic +': ' + msg.payload );\nnode.status({fill, shape, text});\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 230,
        "y": 60,
        "wires": [
            [
                "87d62f2776d3bb1e"
            ]
        ]
    },
    {
        "id": "7e02268826eb0146",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "dbsindex",
        "func": "let deltatime = msg.payload;\nlet deltaminutes =  deltatime / 60;\nlet dbsindexf = ( deltaminutes / 15 ) ;\nlet dbsindex = dbsindexf - ( dbsindexf % 1 );\nlet fill = ( 'green' );\nlet shape = 'dot';\nlet text = ( 'dbsindex : ' + dbsindex );\nnode.status({fill, shape, text});\nmsg.payload = dbsindex;\nmsg.dbsindex = dbsindex;\nmsg.topic = 'dbsindex';\nif (msg.measurements == 2) {\n    msg.average = null;\n    msg.measurements = null;\n    flow.set('dbsindex', dbsindex);\n    return msg;\n}",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 120,
        "wires": [
            [
                "2c1b4ffae9067429"
            ]
        ]
    },
    {
        "id": "87d62f2776d3bb1e",
        "type": "SumUltimate",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "deltatime",
        "property": "payload",
        "math": "subtract",
        "subtractstartfrom": "time",
        "x": 100,
        "y": 120,
        "wires": [
            [
                "7e02268826eb0146"
            ]
        ]
    },
    {
        "id": "52c74eefe7fee5be",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "dbsIndex",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 540,
        "y": 120,
        "wires": []
    },
    {
        "id": "f9b46bb155e0c18b",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "link out 93",
        "mode": "link",
        "links": [
            "b1866da960da0403",
            "a8bc276a8b9f4051",
            "2426508ca6103175",
            "9faad491d9066dbb"
        ],
        "x": 625,
        "y": 120,
        "wires": []
    },
    {
        "id": "2c1b4ffae9067429",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "Status",
        "property": "payload",
        "x": 410,
        "y": 120,
        "wires": [
            [
                "52c74eefe7fee5be",
                "f9b46bb155e0c18b"
            ]
        ]
    },
    {
        "id": "8cf37ba88b85629b",
        "type": "inject",
        "z": "d2daef7783d73008",
        "g": "f3aa2d1483c11581",
        "name": "15m",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "*/15 0-23 * * *",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 110,
        "y": 60,
        "wires": [
            [
                "4329aa3693076932"
            ]
        ]
    },
    {
        "id": "4df6fc5ebcfb2498",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Soc",
            "name": "/Settings/DynamicEss/Schedule/0/Soc",
            "type": "number",
            "value": 64
        },
        "name": "dbsSoC0",
        "onlyChanges": true,
        "x": 100,
        "y": 220,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "32fc7b201124c79e",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/1/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/1/Soc",
            "name": "/Settings/DynamicEss/Schedule/1/Soc",
            "type": "number",
            "value": 62
        },
        "name": "dbsSoC1",
        "onlyChanges": true,
        "x": 140,
        "y": 260,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "f37ffb9a61eacc09",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/2/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/2/Soc",
            "name": "/Settings/DynamicEss/Schedule/2/Soc",
            "type": "number",
            "value": 62
        },
        "name": "dbsSoC2",
        "onlyChanges": true,
        "x": 180,
        "y": 300,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "120d4e58834c531e",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/3/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/3/Soc",
            "name": "/Settings/DynamicEss/Schedule/3/Soc",
            "type": "number",
            "value": 62
        },
        "name": "dbsSoC3",
        "onlyChanges": true,
        "x": 220,
        "y": 340,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "6ea4b2373729a3a6",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/4/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/4/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/4/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes4",
        "onlyChanges": true,
        "x": 860,
        "y": 460,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "a43827c2b82e54fb",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/5/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/5/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/5/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes5",
        "onlyChanges": true,
        "x": 900,
        "y": 500,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "e26e2fad195d738f",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/6/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/6/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/6/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes6",
        "onlyChanges": true,
        "x": 940,
        "y": 540,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "a491cb7cae163f33",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/7/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/7/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/7/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes7",
        "onlyChanges": true,
        "x": 980,
        "y": 580,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "b49a03088a1c8592",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/4/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/4/Soc",
            "name": "/Settings/DynamicEss/Schedule/4/Soc",
            "type": "number",
            "value": 61
        },
        "name": "dbsSoC4",
        "onlyChanges": true,
        "x": 100,
        "y": 460,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "073b133aef3416cd",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/5/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/5/Soc",
            "name": "/Settings/DynamicEss/Schedule/5/Soc",
            "type": "number",
            "value": 61
        },
        "name": "dbsSoC5",
        "onlyChanges": true,
        "x": 140,
        "y": 500,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "0c2349ed3249e44a",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/6/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/6/Soc",
            "name": "/Settings/DynamicEss/Schedule/6/Soc",
            "type": "number",
            "value": 61
        },
        "name": "dbsSoC6",
        "onlyChanges": true,
        "x": 180,
        "y": 540,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "f282a09fde585c8a",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/7/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/7/Soc",
            "name": "/Settings/DynamicEss/Schedule/7/Soc",
            "type": "number",
            "value": 61
        },
        "name": "dbsSoC7",
        "onlyChanges": true,
        "x": 220,
        "y": 580,
        "wires": [
            [
                "98e7880d84f56ded"
            ]
        ]
    },
    {
        "id": "7c17013c52082449",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/0/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes0",
        "onlyChanges": true,
        "x": 860,
        "y": 220,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "d3c518925e0d027d",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/1/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/1/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/1/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes1",
        "onlyChanges": true,
        "x": 900,
        "y": 260,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "5fa79f3888eb4a72",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/2/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/2/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/2/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes2",
        "onlyChanges": true,
        "x": 940,
        "y": 300,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "f3bb240cd60e7dce",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/3/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/3/Restrictions",
            "name": "/Settings/DynamicEss/Schedule/3/Restrictions",
            "type": "number",
            "value": 0
        },
        "name": "dbsRes3",
        "onlyChanges": true,
        "x": 980,
        "y": 340,
        "wires": [
            [
                "c80b8742c481ac67"
            ]
        ]
    },
    {
        "id": "626d6c6235df3d55",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "link out 94",
        "mode": "link",
        "links": [
            "2426508ca6103175"
        ],
        "x": 365,
        "y": 400,
        "wires": []
    },
    {
        "id": "0ee760bc32ea5cf7",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "dbsSoCX",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 320,
        "y": 440,
        "wires": []
    },
    {
        "id": "98e7880d84f56ded",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "Status",
        "property": "payload",
        "x": 130,
        "y": 400,
        "wires": [
            [
                "534dcd1a1b0830b0",
                "0ee760bc32ea5cf7"
            ]
        ]
    },
    {
        "id": "c80b8742c481ac67",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "Status",
        "property": "payload",
        "x": 890,
        "y": 400,
        "wires": [
            [
                "3867ba816bffc6a8",
                "74819b1176cafabf"
            ]
        ]
    },
    {
        "id": "74819b1176cafabf",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "dbsResX",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 1080,
        "y": 440,
        "wires": []
    },
    {
        "id": "fb01a4b5df4ffb8a",
        "type": "victron-input-dess",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.system/0",
        "path": "/DynamicEss/TargetSoc",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system"
        },
        "pathObj": {
            "path": "/DynamicEss/TargetSoc",
            "type": "float",
            "name": "The set target SOC for this time slot (%)"
        },
        "name": "dbsSoCT",
        "onlyChanges": true,
        "x": 320,
        "y": 520,
        "wires": [
            [
                "0b02c86ff6d06ceb",
                "e82c9446da1f5d1d"
            ]
        ]
    },
    {
        "id": "9006b3e95a545db3",
        "type": "victron-input-dess",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.system/0",
        "path": "/DynamicEss/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system"
        },
        "pathObj": {
            "path": "/DynamicEss/Restrictions",
            "type": "enum",
            "name": "Active restrictions",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            }
        },
        "name": "dbsResT",
        "onlyChanges": true,
        "x": 1080,
        "y": 520,
        "wires": [
            [
                "512f72544cbb719e",
                "5de572c3b0794564"
            ]
        ]
    },
    {
        "id": "72a50dd7aaa74a3b",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "link out 95",
        "mode": "link",
        "links": [
            "2426508ca6103175"
        ],
        "x": 1125,
        "y": 400,
        "wires": []
    },
    {
        "id": "512f72544cbb719e",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "link out 96",
        "mode": "link",
        "links": [],
        "x": 1125,
        "y": 580,
        "wires": []
    },
    {
        "id": "0b02c86ff6d06ceb",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "link out 97",
        "mode": "link",
        "links": [],
        "x": 365,
        "y": 580,
        "wires": []
    },
    {
        "id": "3867ba816bffc6a8",
        "type": "delay",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "1/1s",
        "pauseType": "rate",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 1050,
        "y": 400,
        "wires": [
            [
                "72a50dd7aaa74a3b"
            ]
        ]
    },
    {
        "id": "534dcd1a1b0830b0",
        "type": "delay",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "1/1s",
        "pauseType": "rate",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 290,
        "y": 400,
        "wires": [
            [
                "626d6c6235df3d55"
            ]
        ]
    },
    {
        "id": "74377146b0d1d993",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/4/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/4/Strategy",
            "name": "/Settings/DynamicEss/Schedule/4/Strategy",
            "type": "number",
            "value": 3
        },
        "name": "dbsStr4",
        "onlyChanges": true,
        "x": 470,
        "y": 460,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "85a4c0c0e5025857",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/5/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/5/Strategy",
            "name": "/Settings/DynamicEss/Schedule/5/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbsStr5",
        "onlyChanges": true,
        "x": 510,
        "y": 500,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "852f7aa865431df3",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/6/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/6/Strategy",
            "name": "/Settings/DynamicEss/Schedule/6/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbsStr6",
        "onlyChanges": true,
        "x": 550,
        "y": 540,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "052af5e8ebd4494a",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/7/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/7/Strategy",
            "name": "/Settings/DynamicEss/Schedule/7/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbsStr7",
        "onlyChanges": true,
        "x": 590,
        "y": 580,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "d20ed3b3db83845f",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Strategy",
            "name": "/Settings/DynamicEss/Schedule/0/Strategy",
            "type": "number",
            "value": 3
        },
        "name": "dbsStr0",
        "onlyChanges": true,
        "x": 470,
        "y": 220,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "c65fc3929abfa970",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/1/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/1/Strategy",
            "name": "/Settings/DynamicEss/Schedule/1/Strategy",
            "type": "number",
            "value": 3
        },
        "name": "dbsStr1",
        "onlyChanges": true,
        "x": 510,
        "y": 260,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "d71c9cc984428c8c",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/2/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/2/Strategy",
            "name": "/Settings/DynamicEss/Schedule/2/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbsStr2",
        "onlyChanges": true,
        "x": 550,
        "y": 300,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "1d66401622730718",
        "type": "victron-input-custom",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/3/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/3/Strategy",
            "name": "/Settings/DynamicEss/Schedule/3/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbsStr3",
        "onlyChanges": true,
        "x": 590,
        "y": 340,
        "wires": [
            [
                "94525a3ba1024834"
            ]
        ]
    },
    {
        "id": "94525a3ba1024834",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "Status",
        "property": "payload",
        "x": 510,
        "y": 400,
        "wires": [
            [
                "d5bc79b24877def4",
                "42bbf784205aa9f0"
            ]
        ]
    },
    {
        "id": "d5bc79b24877def4",
        "type": "debug",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "dbsStrX",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 700,
        "y": 440,
        "wires": []
    },
    {
        "id": "0bac72e596c044ae",
        "type": "victron-input-dess",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "service": "com.victronenergy.system/0",
        "path": "/DynamicEss/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system"
        },
        "pathObj": {
            "path": "/DynamicEss/Strategy",
            "type": "enum",
            "name": "Used strategy for current time slot",
            "enum": {
                "0": "Target SOC",
                "1": "Self-consumption",
                "2": "Pro battery",
                "3": "Pro grid"
            }
        },
        "name": "dbsStrT",
        "onlyChanges": true,
        "x": 710,
        "y": 520,
        "wires": [
            [
                "7351595384039d53",
                "7bf2f4fdf95cc8c2"
            ]
        ]
    },
    {
        "id": "1c53999fa2e8984c",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "link out 98",
        "mode": "link",
        "links": [
            "2426508ca6103175"
        ],
        "x": 745,
        "y": 400,
        "wires": []
    },
    {
        "id": "7351595384039d53",
        "type": "link out",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "link out 99",
        "mode": "link",
        "links": [],
        "x": 745,
        "y": 580,
        "wires": []
    },
    {
        "id": "42bbf784205aa9f0",
        "type": "delay",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "1/1s",
        "pauseType": "rate",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 670,
        "y": 400,
        "wires": [
            [
                "1c53999fa2e8984c"
            ]
        ]
    },
    {
        "id": "5de572c3b0794564",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "Status",
        "property": "payload",
        "x": 1090,
        "y": 480,
        "wires": [
            [
                "74819b1176cafabf"
            ]
        ]
    },
    {
        "id": "7bf2f4fdf95cc8c2",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "Status",
        "property": "payload",
        "x": 710,
        "y": 480,
        "wires": [
            [
                "d5bc79b24877def4"
            ]
        ]
    },
    {
        "id": "e82c9446da1f5d1d",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "4e1953eb360527b6",
        "name": "Status",
        "property": "payload",
        "x": 330,
        "y": 480,
        "wires": [
            [
                "0ee760bc32ea5cf7"
            ]
        ]
    },
    {
        "id": "49d9c26967d3c8c9",
        "type": "RailwaySwitchUltimate",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "DBSIndex",
        "triggertopic": "dbsindex",
        "initializewith": "4",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1480,
        "y": 80,
        "wires": [
            [
                "da10e8cc5a220365"
            ],
            [
                "fdddee2f88b2ec43"
            ],
            [
                "acc8dcc81275989a"
            ],
            [
                "2615693ca9624c6d"
            ],
            []
        ]
    },
    {
        "id": "b1866da960da0403",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "link in 74",
        "links": [
            "f9b46bb155e0c18b"
        ],
        "x": 1425,
        "y": 200,
        "wires": [
            [
                "56558612c95df175"
            ]
        ]
    },
    {
        "id": "c15b3220e6a18eb5",
        "type": "RailwaySwitchUltimate",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "DBSIndex",
        "triggertopic": "dbsindex",
        "initializewith": "4",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1480,
        "y": 320,
        "wires": [
            [
                "4b2d20c23e249e7a"
            ],
            [
                "65f65eba972bb536"
            ],
            [
                "fb504d3c97035b15"
            ],
            [
                "a94146cf9c947c26"
            ],
            []
        ]
    },
    {
        "id": "1deca90bbfdbb765",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "dbsindex0",
        "func": "if ( msg.topic == 'dbsindex' ) \n{\n    if ( msg.payload >= 4 )\n    {\n        msg.payload = 4;\n    }\n}\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1490,
        "y": 160,
        "wires": [
            [
                "49d9c26967d3c8c9"
            ]
        ]
    },
    {
        "id": "56558612c95df175",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "Status",
        "property": "payload",
        "x": 1510,
        "y": 200,
        "wires": [
            [
                "1deca90bbfdbb765",
                "f35e5141b73bdf4a"
            ]
        ]
    },
    {
        "id": "f35e5141b73bdf4a",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "dbsindex1",
        "func": "if ( msg.topic == 'dbsindex' ) \n{\n    if ( msg.payload < 4 )\n    {\n        msg.payload = 4;\n    } else if ( msg.payload < 8 )\n    {\n        msg.payload = msg.payload - 4;\n    }\n}\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1490,
        "y": 260,
        "wires": [
            [
                "c15b3220e6a18eb5"
            ]
        ]
    },
    {
        "id": "3d6db8032eb7bffe",
        "type": "InterruptFlowUltimate",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "blk",
        "triggertopic": "go",
        "initializewith": "0",
        "autoToggle": "0",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1300,
        "y": 260,
        "wires": [
            [
                "49d9c26967d3c8c9",
                "c15b3220e6a18eb5"
            ]
        ]
    },
    {
        "id": "bdac3b033f220c9e",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "link in 75",
        "links": [
            "c5b3f8bf2f23feb9"
        ],
        "x": 1255,
        "y": 340,
        "wires": [
            [
                "3d6db8032eb7bffe"
            ]
        ]
    },
    {
        "id": "fb2cd14e35eeefe8",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "Status",
        "property": "payload",
        "x": 1290,
        "y": 200,
        "wires": [
            [
                "3d6db8032eb7bffe"
            ]
        ]
    },
    {
        "id": "1359a49b4e32fd1b",
        "type": "change",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "Res:3",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "3",
                "tot": "num"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "dbsRes",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1290,
        "y": 160,
        "wires": [
            [
                "fb2cd14e35eeefe8"
            ]
        ]
    },
    {
        "id": "a94146cf9c947c26",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/7/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/7/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 7",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes7",
        "onlyChanges": false,
        "x": 1800,
        "y": 340,
        "wires": []
    },
    {
        "id": "da10e8cc5a220365",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 0",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes0",
        "onlyChanges": false,
        "x": 1680,
        "y": 60,
        "wires": []
    },
    {
        "id": "fdddee2f88b2ec43",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/1/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/1/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 1",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes1",
        "onlyChanges": false,
        "x": 1720,
        "y": 100,
        "wires": []
    },
    {
        "id": "acc8dcc81275989a",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/2/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/2/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 2",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes2",
        "onlyChanges": false,
        "x": 1760,
        "y": 140,
        "wires": []
    },
    {
        "id": "2615693ca9624c6d",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/3/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/3/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 3",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes3",
        "onlyChanges": false,
        "x": 1800,
        "y": 180,
        "wires": []
    },
    {
        "id": "4b2d20c23e249e7a",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/4/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/4/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 4",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes4",
        "onlyChanges": false,
        "x": 1680,
        "y": 220,
        "wires": []
    },
    {
        "id": "65f65eba972bb536",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/5/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/5/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 5",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes5",
        "onlyChanges": false,
        "x": 1720,
        "y": 260,
        "wires": []
    },
    {
        "id": "fb504d3c97035b15",
        "type": "victron-output-dess",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/6/Restrictions",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/6/Restrictions",
            "type": "enum",
            "name": "Active restrictions for schedule 6",
            "enum": {
                "0": "No restrictions between battery and the grid",
                "1": "Grid to battery energy flow is restricted",
                "2": "Battery to grid energy flow is restricted",
                "3": "No energy flow between battery and grid"
            },
            "mode": "both"
        },
        "name": "dbsRes6",
        "onlyChanges": false,
        "x": 1760,
        "y": 300,
        "wires": []
    },
    {
        "id": "cd0d2d66f58ac339",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "link in 76",
        "links": [
            "c5b3f8bf2f23feb9"
        ],
        "x": 1255,
        "y": 60,
        "wires": [
            [
                "c5e76fbce9acdd24"
            ]
        ]
    },
    {
        "id": "c5e76fbce9acdd24",
        "type": "delay",
        "z": "d2daef7783d73008",
        "g": "f03ad8631d0b0ab1",
        "name": "25ms",
        "pauseType": "delay",
        "timeout": "255",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "15",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 1290,
        "y": 120,
        "wires": [
            [
                "1359a49b4e32fd1b"
            ]
        ]
    },
    {
        "id": "b7c3dffb337591b0",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "link in 77",
        "links": [
            "9eb58497807ed1b6"
        ],
        "x": 1255,
        "y": 440,
        "wires": [
            [
                "779226fe3f372b69"
            ]
        ]
    },
    {
        "id": "a748125841083c1b",
        "type": "RailwaySwitchUltimate",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "DBSIndex",
        "triggertopic": "dbsindex",
        "initializewith": "4",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1480,
        "y": 460,
        "wires": [
            [
                "bad471a163983538"
            ],
            [
                "d27de0bbcc7d7c2e"
            ],
            [
                "b7c68ffb33ebbb47"
            ],
            [
                "cecf6a4225731793"
            ],
            []
        ]
    },
    {
        "id": "a8bc276a8b9f4051",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "link in 78",
        "links": [
            "f9b46bb155e0c18b"
        ],
        "x": 1425,
        "y": 580,
        "wires": [
            [
                "1afd53b8c84e54cb"
            ]
        ]
    },
    {
        "id": "87e86d220c2f8c21",
        "type": "RailwaySwitchUltimate",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "DBSIndex",
        "triggertopic": "dbsindex",
        "initializewith": "4",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1480,
        "y": 700,
        "wires": [
            [
                "7ab40feec890547a"
            ],
            [
                "4fa6391efc896b30"
            ],
            [
                "4d8fcb496ce551f1"
            ],
            [
                "7cc306c3999d6244"
            ],
            []
        ]
    },
    {
        "id": "f3b726b2617068d8",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "dbsindex0",
        "func": "if ( msg.topic == 'dbsindex' ) \n{\n    if ( msg.payload >= 4 )\n    {\n        msg.payload = 4;\n    }\n}\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1490,
        "y": 540,
        "wires": [
            [
                "a748125841083c1b"
            ]
        ]
    },
    {
        "id": "00fe85160eb42286",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "dbsindex1",
        "func": "if ( msg.topic == 'dbsindex' ) \n{\n    if ( msg.payload < 4 )\n    {\n        msg.payload = 4;\n    } else if ( msg.payload < 8 )\n    {\n        msg.payload = msg.payload - 4;\n    }\n}\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1490,
        "y": 640,
        "wires": [
            [
                "87e86d220c2f8c21"
            ]
        ]
    },
    {
        "id": "7ab40feec890547a",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/4/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/4/Soc",
            "name": "/Settings/DynamicEss/Schedule/4/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs4SoC",
        "onlyChanges": false,
        "x": 1680,
        "y": 600,
        "wires": []
    },
    {
        "id": "4fa6391efc896b30",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/5/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/5/Soc",
            "name": "/Settings/DynamicEss/Schedule/5/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs5SoC",
        "onlyChanges": false,
        "x": 1720,
        "y": 640,
        "wires": []
    },
    {
        "id": "4d8fcb496ce551f1",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/6/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/6/Soc",
            "name": "/Settings/DynamicEss/Schedule/6/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs6SoC",
        "onlyChanges": false,
        "x": 1760,
        "y": 680,
        "wires": []
    },
    {
        "id": "7cc306c3999d6244",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/7/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/7/Soc",
            "name": "/Settings/DynamicEss/Schedule/7/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs7SoC",
        "onlyChanges": false,
        "x": 1800,
        "y": 720,
        "wires": []
    },
    {
        "id": "bad471a163983538",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Soc",
            "name": "/Settings/DynamicEss/Schedule/0/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs0SoC",
        "onlyChanges": false,
        "x": 1680,
        "y": 440,
        "wires": []
    },
    {
        "id": "d27de0bbcc7d7c2e",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/1/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/1/Soc",
            "name": "/Settings/DynamicEss/Schedule/1/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs1SoC",
        "onlyChanges": false,
        "x": 1720,
        "y": 480,
        "wires": []
    },
    {
        "id": "b7c68ffb33ebbb47",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/2/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/2/Soc",
            "name": "/Settings/DynamicEss/Schedule/2/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs2SoC",
        "onlyChanges": false,
        "x": 1750,
        "y": 520,
        "wires": []
    },
    {
        "id": "cecf6a4225731793",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/3/Soc",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/3/Soc",
            "name": "/Settings/DynamicEss/Schedule/3/Soc",
            "type": "number",
            "value": 30
        },
        "name": "dbs3SoC",
        "onlyChanges": false,
        "x": 1800,
        "y": 560,
        "wires": []
    },
    {
        "id": "70b76892e0ea7bae",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "Status",
        "property": "payload",
        "x": 1290,
        "y": 580,
        "wires": [
            [
                "a748125841083c1b",
                "87e86d220c2f8c21"
            ]
        ]
    },
    {
        "id": "1afd53b8c84e54cb",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "Status",
        "property": "payload",
        "x": 1510,
        "y": 580,
        "wires": [
            [
                "f3b726b2617068d8",
                "00fe85160eb42286"
            ]
        ]
    },
    {
        "id": "779226fe3f372b69",
        "type": "change",
        "z": "d2daef7783d73008",
        "g": "e09cb27d1a558ff1",
        "name": "SoC:",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "dbsSoC",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1290,
        "y": 540,
        "wires": [
            [
                "70b76892e0ea7bae"
            ]
        ]
    },
    {
        "id": "d0c97df1d407dfd7",
        "type": "RailwaySwitchUltimate",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "DBSIndex",
        "triggertopic": "dbsindex",
        "initializewith": "4",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1480,
        "y": 840,
        "wires": [
            [
                "57db3033fecf3541"
            ],
            [
                "d201c952fe3513a6"
            ],
            [
                "a1e6c36357acc069"
            ],
            [
                "e2d409885fdf2fb2"
            ],
            []
        ]
    },
    {
        "id": "9faad491d9066dbb",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "link in 79",
        "links": [
            "f9b46bb155e0c18b"
        ],
        "x": 1425,
        "y": 960,
        "wires": [
            [
                "225bfcf97f866c6c"
            ]
        ]
    },
    {
        "id": "40cff0a35e1cc29e",
        "type": "RailwaySwitchUltimate",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "DBSIndex",
        "triggertopic": "dbsindex",
        "initializewith": "4",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1480,
        "y": 1080,
        "wires": [
            [
                "91e06fe5cc3485e0"
            ],
            [
                "a51403d4fd72067c"
            ],
            [
                "5e296b9521abb282"
            ],
            [
                "00ad025eec903d2a"
            ],
            []
        ]
    },
    {
        "id": "2716f9b1616c5904",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "dbsindex0",
        "func": "if ( msg.topic == 'dbsindex' ) \n{\n    if ( msg.payload >= 4 )\n    {\n        msg.payload = 4;\n    }\n}\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1490,
        "y": 920,
        "wires": [
            [
                "d0c97df1d407dfd7"
            ]
        ]
    },
    {
        "id": "225bfcf97f866c6c",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "Status",
        "property": "payload",
        "x": 1510,
        "y": 960,
        "wires": [
            [
                "2716f9b1616c5904",
                "6426f31bb2c47549"
            ]
        ]
    },
    {
        "id": "6426f31bb2c47549",
        "type": "function",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "dbsindex1",
        "func": "if ( msg.topic == 'dbsindex' ) \n{\n    if ( msg.payload < 4 )\n    {\n        msg.payload = 4;\n    } else if ( msg.payload < 8 )\n    {\n        msg.payload = msg.payload - 4;\n    }\n}\nreturn msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1490,
        "y": 1020,
        "wires": [
            [
                "40cff0a35e1cc29e"
            ]
        ]
    },
    {
        "id": "91e06fe5cc3485e0",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/4/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/4/Strategy",
            "name": "/Settings/DynamicEss/Schedule/4/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbs4Str",
        "onlyChanges": false,
        "x": 1680,
        "y": 980,
        "wires": []
    },
    {
        "id": "a51403d4fd72067c",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/5/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/5/Strategy",
            "name": "/Settings/DynamicEss/Schedule/5/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbs5Str",
        "onlyChanges": false,
        "x": 1720,
        "y": 1020,
        "wires": []
    },
    {
        "id": "5e296b9521abb282",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/6/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/6/Strategy",
            "name": "/Settings/DynamicEss/Schedule/6/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbs6Str",
        "onlyChanges": false,
        "x": 1760,
        "y": 1060,
        "wires": []
    },
    {
        "id": "00ad025eec903d2a",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/7/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/7/Strategy",
            "name": "/Settings/DynamicEss/Schedule/7/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbs7Str",
        "onlyChanges": false,
        "x": 1800,
        "y": 1100,
        "wires": []
    },
    {
        "id": "57db3033fecf3541",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/0/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/0/Strategy",
            "name": "/Settings/DynamicEss/Schedule/0/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbs0Str",
        "onlyChanges": false,
        "x": 1680,
        "y": 820,
        "wires": []
    },
    {
        "id": "d201c952fe3513a6",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/1/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/1/Strategy",
            "name": "/Settings/DynamicEss/Schedule/1/Strategy",
            "type": "number",
            "value": 2
        },
        "name": "dbs1Str",
        "onlyChanges": false,
        "x": 1720,
        "y": 860,
        "wires": []
    },
    {
        "id": "a1e6c36357acc069",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/2/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/2/Strategy",
            "name": "/Settings/DynamicEss/Schedule/2/Strategy",
            "type": "number",
            "value": 1
        },
        "name": "dbs2Str",
        "onlyChanges": false,
        "x": 1760,
        "y": 900,
        "wires": []
    },
    {
        "id": "e2d409885fdf2fb2",
        "type": "victron-output-custom",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Schedule/3/Strategy",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Schedule/3/Strategy",
            "name": "/Settings/DynamicEss/Schedule/3/Strategy",
            "type": "number",
            "value": 1
        },
        "name": "dbs3Str",
        "onlyChanges": false,
        "x": 1800,
        "y": 940,
        "wires": []
    },
    {
        "id": "74897ad024d65a53",
        "type": "change",
        "z": "d2daef7783d73008",
        "d": true,
        "g": "d24783f8b2c72351",
        "name": "Str:1",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "dbsStr",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1290,
        "y": 920,
        "wires": [
            [
                "1d55681ad7165b0e"
            ]
        ]
    },
    {
        "id": "653e60e62e3d4460",
        "type": "InterruptFlowUltimate",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "blk",
        "triggertopic": "go",
        "initializewith": "0",
        "autoToggle": "0",
        "payloadPropName": "payload",
        "translatorConfig": "",
        "x": 1300,
        "y": 1020,
        "wires": [
            [
                "d0c97df1d407dfd7",
                "40cff0a35e1cc29e"
            ]
        ]
    },
    {
        "id": "b599d62234ca95ef",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "link in 80",
        "links": [
            "c5b3f8bf2f23feb9"
        ],
        "x": 1255,
        "y": 1100,
        "wires": [
            [
                "653e60e62e3d4460"
            ]
        ]
    },
    {
        "id": "1d55681ad7165b0e",
        "type": "StatusUltimate",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "Status",
        "property": "payload",
        "x": 1290,
        "y": 960,
        "wires": [
            [
                "653e60e62e3d4460"
            ]
        ]
    },
    {
        "id": "cf5d4451ff2bbc64",
        "type": "link in",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "link in 81",
        "links": [
            "c5b3f8bf2f23feb9"
        ],
        "x": 1255,
        "y": 820,
        "wires": [
            [
                "dedd529b90b8e317"
            ]
        ]
    },
    {
        "id": "dedd529b90b8e317",
        "type": "delay",
        "z": "d2daef7783d73008",
        "g": "d24783f8b2c72351",
        "name": "25ms",
        "pauseType": "delay",
        "timeout": "255",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "15",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 1290,
        "y": 880,
        "wires": [
            [
                "74897ad024d65a53"
            ]
        ]
    },
    {
        "id": "9e7340b4a2802944",
        "type": "config-vrm-api",
        "name": "VRM API"
    },
    {
        "id": "2923c1e5f8117d1f",
        "type": "global-config",
        "env": [],
        "modules": {
            "node-red-contrib-boolean-logic-ultimate": "1.2.2",
            "victron-vrm-api": "0.3.7",
            "@victronenergy/node-red-contrib-victron": "1.6.52"
        }
    }
]

The result of both my and @nortciv’s linear programs (and probably also Victron’s) is an optimal solution consisting of the different flows (b2l, b2g, pv2b, pv2l,…) and soc for each slot. Victron’s result is visualised on the VRM page in the Energy graph (the one with the different colored bars, although it’s grouped by hour instead of by quarter). I can configure my own algorithm to give me a result that is identical to Victron’s flows.

Victron converts these flows to a schedule consisting of strategy/restrictions/soc data which is used to control the system. As far as I know, this conversion is done by them server side and isn’t public. Since we need to convert our own flows to a schedule that can be used, it makes sense to mimic what Victron does. I am thus reverse engineering their conversion by monitoring my own system, the expected flows and the schedule that is set. The code I posted above is the result of that effort.

Once this is completed, the next step would be to set up a docker container (potentially as a Home Assistant addon) that effectively writes these strategies to DBUS/MQTT. At that point, we have reached feature parity with VRM DESS, but with full control over the linear program that calculates the schedules. As a next step, we can customize the algorithm by adding a value to terminal Soc, taking risk into account, include optimal EV planning, running it every 15 minutes instead of every hour, replace load predictions by something custom, …

This is very interesting. Can you modify that to allow an additional SoC setpoint in the (near, with known price schedule timeframe) future? I have been begging for a port of that function from Node-RED DESS to VRM DESS for ages. Where do start if I’d want to port your code to Node-RED?

PS: weekend bonus flow (Automatic Battery Capacity detecting High Precision SoC% flow for BMV Smartshunts)

[
    {
        "id": "c164cb7636efd2df",
        "type": "group",
        "z": "aca9a41baba8e498",
        "name": "UpCycle Electric :  Battery Capacity and Precision SoC%",
        "style": {
            "label": true
        },
        "nodes": [
            "082878b0754a90a8",
            "f81e692fc62703d7",
            "2866f23c9718c311",
            "80a55421dc061bb4",
            "6d2f544603bebb6e",
            "064a69f1ecebd72d"
        ],
        "x": 34,
        "y": 39,
        "w": 462,
        "h": 142
    },
    {
        "id": "082878b0754a90a8",
        "type": "victron-input-battery",
        "z": "aca9a41baba8e498",
        "g": "c164cb7636efd2df",
        "service": "com.victronenergy.battery/288",
        "path": "/ConsumedAmphours",
        "serviceObj": {
            "service": "com.victronenergy.battery/288",
            "name": "BMV Battery Monitor"
        },
        "pathObj": {
            "path": "/ConsumedAmphours",
            "type": "float",
            "name": "Consumed Amphours (Ah)"
        },
        "name": "consumedah",
        "onlyChanges": false,
        "x": 130,
        "y": 80,
        "wires": [
            [
                "2866f23c9718c311"
            ]
        ]
    },
    {
        "id": "f81e692fc62703d7",
        "type": "victron-input-battery",
        "z": "aca9a41baba8e498",
        "g": "c164cb7636efd2df",
        "service": "com.victronenergy.battery/288",
        "path": "/Soc",
        "serviceObj": {
            "service": "com.victronenergy.battery/288",
            "name": "BMV Battery Monitor"
        },
        "pathObj": {
            "path": "/Soc",
            "type": "float",
            "name": "State of charge (%)"
        },
        "name": "soc",
        "onlyChanges": false,
        "x": 110,
        "y": 140,
        "wires": [
            [
                "2866f23c9718c311"
            ]
        ]
    },
    {
        "id": "2866f23c9718c311",
        "type": "function",
        "z": "aca9a41baba8e498",
        "g": "c164cb7636efd2df",
        "name": "SoC% @ batteryAh",
        "func": "// Battery Capacity Estimator: Async SoC and ConsumedAh to Bound Intersection\n// Receives msg.topic 'soc' (payload float 0.0-100.0 in 0.1 increments) or 'consumedah' (payload float <=0.0 in 0.1 increments)\n// Computes capacity bounds for each pair, intersects to converge on actual integer capacity\n\nvar store = flow.get('storeahsoc') || {\n    ahsoc: undefined,\n    capacity: undefined,\n    latestSoc: undefined,\n    latestConsumed: undefined,\n    minCapacity: 100,  // Initial wide range min\n    maxCapacity: 3000, // Initial wide range max\n    capacityStats: {}\n};\n\n// Optional reset on topic='reset'\nif (msg.topic === 'reset') {\n    store.minCapacity = 100;\n    store.maxCapacity = 3000;\n    flow.set('storeahsoc', store);\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    return null;\n}\n\n// Update based on topic\nif (msg.topic === 'soc') {\n    var newSoc = Math.round(10 * msg.payload) / 10;\n    if ( newSoc === store.latestSoc) {\n        return null; // Ignore duplicate value\n    }\n    store.latestSoc = newSoc;\n    flow.set('storeahsoc', store); // Persist immediately\n} else if (msg.topic === 'consumedah') {\n    var newConsumed =  Math.round(10 * msg.payload) / 10;\n    if ( newConsumed === store.latestConsumed) {\n        return null; // Ignore duplicate value\n    }\n    store.latestConsumed = newConsumed;\n    flow.set('storeahsoc', store); // Persist immediately\n} else {\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    return null; // Ignore invalid topic\n}\n\n// Compute only if both present\nif (store.latestSoc === undefined || store.latestConsumed === undefined) {\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    return null;\n}\nif (store.latestSoc <= 0 || store.latestSoc >= 100) {\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    return null; // Skip div-zero or invalid\n}\n\n// Compute bounds for this pair\nvar denom = 1 - (store.latestSoc / 100);\nif (denom <= 0) {\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    return null; // Redundant div-zero guard\n}\n\n// Linear search for consistent capacities\nvar Match = false;\nvar pairMin = store.maxCapacity;\nvar pairMax = store.minCapacity;\nfor (var c = store.minCapacity; c <= store.maxCapacity; c++) {\n    var expectedSoC = Math.round(10 * 100 * (1 + store.latestConsumed / c)) / 10;\n    if (expectedSoC === store.latestSoc) {\n        Match = true;\n        pairMin = Math.min(pairMin, c);\n        pairMax = Math.max(pairMax, c);\n    }\n}\n\n// No match found on a single pair\nif ( !Match && (pairMin == pairMax)) {\n//    node.warn('No Match single pair, widen range');\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    pairMin = pairMin - 1;\n    pairMax = pairMax + 1;\n}\n\n// Reset on inversion\nif (pairMin > pairMax) {\n//    node.warn('pairMin > pairMax, Skip invalid pair');\n    node.status({fill: 'red', shape: 'dot', text: 'wait'});\n    return null; // Skip invalid pair\n}\n\n// Intersect with global range\nstore.minCapacity = Math.max(store.minCapacity, pairMin);\nstore.maxCapacity = Math.min(store.maxCapacity, pairMax);\n\n// Handle inversion as inconsistency (reset)\nif (store.minCapacity > store.maxCapacity) {\n    store.minCapacity = 100;\n    store.maxCapacity = 3000;\n    node.warn('Inversion detected, resetting bounds');\n}\n\n// Determine output: capacity if converged, else midpoint\nif (store.minCapacity === store.maxCapacity) {\n    msg.payload = store.minCapacity;\n} else {\n    msg.payload = Math.round((store.minCapacity + store.maxCapacity) / 2);\n}\n\n// High precision ahsoc integration with div-zero guard\nstore.capacity = msg.payload;\nif (store.capacity !== 0) {\n    store.ahsoc = Math.round( 1000 * 100 * ( 1 + ( store.latestConsumed / store.capacity ) ) ) / 1000;\n} else {\n    store.ahsoc = null;\n}\n\n// Compute and store stats KPI under store.capacityStats\nstore.capacityStats = {\n    uniques: store.minCapacity <= store.maxCapacity ? store.maxCapacity - store.minCapacity + 1 : 0,\n    usingFallback: store.minCapacity !== store.maxCapacity\n};\n\n// Persist all state including stats KPI\nflow.set('storeahsoc', store);\nmsg.ahsoc = store.ahsoc;\nmsg.capacity = store.capacity;\nmsg.accurate = !store.capacityStats.usingFallback;\nmsg.payload = msg.ahsoc;\n\n// node status\nlet text = ( (msg.ahsoc !== undefined) ? ( msg.ahsoc + '% SoC @ '+ msg.capacity + 'Ah' ) : 'wait')\nlet fill = ( ( msg.accurate === true) ? 'green' : 'red')\nlet shape = 'dot'\nnode.status({fill, shape, text});\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 330,
        "y": 80,
        "wires": [
            [
                "80a55421dc061bb4"
            ]
        ]
    },
    {
        "id": "80a55421dc061bb4",
        "type": "link out",
        "z": "aca9a41baba8e498",
        "g": "c164cb7636efd2df",
        "name": "link out 86",
        "mode": "link",
        "links": [
            "6d2f544603bebb6e",
            "c1b98e927af57511"
        ],
        "x": 455,
        "y": 80,
        "wires": []
    },
    {
        "id": "6d2f544603bebb6e",
        "type": "link in",
        "z": "aca9a41baba8e498",
        "g": "c164cb7636efd2df",
        "name": "link in 102",
        "links": [
            "80a55421dc061bb4"
        ],
        "x": 255,
        "y": 140,
        "wires": [
            [
                "064a69f1ecebd72d"
            ]
        ]
    },
    {
        "id": "064a69f1ecebd72d",
        "type": "debug",
        "z": "aca9a41baba8e498",
        "g": "c164cb7636efd2df",
        "name": "AhSoC%",
        "active": true,
        "tosidebar": false,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "msg",
        "x": 360,
        "y": 140,
        "wires": []
    },
    {
        "id": "580227e4bbf43e36",
        "type": "global-config",
        "env": [],
        "modules": {
            "@victronenergy/node-red-contrib-victron": "1.6.52"
        }
    }
]

version 1.0, for code see also: How to fetch the battery capacity as defined in smartshunt-settings? - #19 by UpCycleElectric

excelent

Thank you @bartm, @Sarowe1990, and @UpCycleElectric for researching and sharing information about the (VRM) DESS implementation. Putting DESS in mode 4 / Node-RED is something I initially wanted to avoid due to the deprecation warning. Utilizing this mode is still an option; for now, I investigated Victron’s own dynamicess.py to determine whether it’s worthwhile to build my own or if there are reasons not to. Here’s what I’ve learned:

  1. VRM creates SOC levels and strategies (pro-battery, self-consume, pro-grid). 24/48-hour schedules are stored locally.
  2. dynamicess.py takes in the current strategy and target SOC level—both for the current and next hour. Based on the time already spent in the current hour and the current system state, it decides on a reactive strategy. In an elaborate and well-documented decision tree in _determine_reactive_strategy, the function selects the most suitable reactive strategy. Restrictions are also processed here.
  3. The reactive strategy is published at /DynamicEss/ReactiveStrategy and is linked to specific system behavior. There are 26 reactive strategies, as far as I could count. Some examples:
    • SCHEDULED_SELFCONSUME
    • SCHEDULED_CHARGE_ALLOW_GRID
    • SCHEDULED_CHARGE_ENHANCED
    • SELFCONSUME_ACCEPT_CHARGE
    • IDLE_SCHEDULED_FEEDIN
    • SCHEDULED_DISCHARGE
  4. These reactive states all map to one of the following four functions:
    • charge(rate, restrictions, allow_feedin)
    • discharge(rate, restrictions, allow_feedin)
    • self_consume(restrictions, allow_feedin)
    • idle(allow_feedin)

Depending on whether the system uses VE.Bus (I think a GX device, but I’m not sure) or a MultiRS device, different registers are altered. I haven’t figured out which registers apply in my case, but here’s what I found:

VE.Bus

  • /Overrides/FeedInExcess0 = follow setup, 1 = restrict, 2 = allow
  • /Overrides/SetpointNone = normal ESS, 0 = no export, >0 = export limit
  • /Overrides/ForceCharge0 = disable, 1 = enable
  • /Overrides/MaxDischargePower-1 = no limit, >0 = discharge cap

Only /Overrides/Setpoint is documented in the ModbusTCP registers.
/Settings/Cgwacs/MaxDischargePower, /Settings/Cgwacs/PreventFeedback, and /Settings/Cgwacs/AcPowerSetPoint could potentially be used to replace the others.

MultiRS

  • /Ess/DisableFeedIn0 = allow feed-in, 1 = disable feed-in
  • /Ess/UseInverterPowerSetpoint0 = use AC setpoint, 1 = use inverter setpoint
  • /Ess/InverterPowerSetpoint>0 = charge battery, <0 = discharge/export, 0 = idle
  • /Ess/AcPowerSetpoint0 = default for self-consumption and deactivation

One more thing—I noticed this and wanted to ask you all: I thought calculating one-way efficiency meant taking the square root of the total round-trip efficiency, not halving it like in the function below. For values close to 1, the difference is small, so it’s not a big deal. I just want to make sure my math is correct.

@property
def oneway_efficency(self):
	''' When charging from AC, only half of the efficency-losses have to be considered
		So, with an overall system efficency of 0.8, the charging efficency would be 0.9 and so on.
    '''
	return min(1.0, ((1 - self._settings["dess_efficiency"] / 100.0) / -2.0) + 1.0)
1 Like

Have you been able to reverse engineer what kind of DESS system requirements this decision tree is meant to fulfill, if any? Or is all or most of it only low level correction logic around the higher level decision to make a SoC scheduler the master controller over an ESS?

Reason for me asking is that I wonder whether it wouldn’t make more sense from a systems architecture point of view to make energy flow (power) the primary DESS scheduling parameter and then correct for capacity limits at individual component levels (PV, Loads, Inverters, DC-batteries, AC-batteries (a.k.a. grid)

I cannot say for certain because the reactive strategy is published and maybe used somewhere else. From what I understand it is there for ease of development, nudge certain behavior and keep track of past reactive strategies that may influence the current reactive strategy.

As far as I know you cannot set the reactive strategy directly, which is an indication it aims to solve an internal issue within the DESS execution process and doesn’t serve any higher level requirements.

I’m trying to look at it in levels of abstraction. For DESS the top level is the energy services interface, one level down there is the executive energy planning system, one level below that we have energy flow machinery and at the lowest level energy resources.

The service level is provided with high level (owner/user/operator) instructions and directions on the goals and purpose of the system as a whole. These then need to be translated to rules and strategies to draw or deliver energy from/to the outside world and how (type ac/dc, power sink/source, time, duration, location, cost/profit), the executives purpose is to optimize those services within given bounds, both external (dynamic prices, pv/wind forecasts, interface limits) as well as internal (performance limits, material costs), the machinery is there to pump/transform (within power constraints) the energy and the resources represent internal sources, loads and storage. PV considered an internal source when under system control. Leaving out future developments where PV can also be seen as an external energy source. Indirectly through its, somewhat predictable, influence on grid energy prices or even directly in case of a local DC grid system (think: neighbours with PV but no or limited ESS capacity). And the always a couple of years away concept of large scale V2G integration.

In a perfectly layered system design, any level need only to fulfil requirements of the level directly above it. And visa versa needs only to take constraints from the level below it in consideration. Looking at it this way I find it hard to understand why there even exists such a thing as a ‘reactive strategy’ process at the energy flow machinery level (inverters/mppts,dc-dcs), any issues with the flow should have been proactively solved by the executive level based on constraints known from the machinery.

Translated back to our DESS system, I would think something need to be done to either provide better defined constraints to the executive level (the scheduler) to enable it to prevent reactive (corrective) actions on machinery level or, at least, to communicate exceptions (from normal operational constraints) back up to the executive level to enable re-evaluation of the forward looking schedule there where that schedule is created in the first place, before they become an issue for the machinery. This does not fit well with the mixed open source venusOS / closed source private cloud VRM system architecture currently implemented. The main reason being that it forces executive (planning) functionality onto the machinery level because (intermittent) availability issues of said cloud services prohibite the transfer of that functionality to the cloud.

Arguably better would be (from a systems engineering point of view) to port the scheduler itself from VRM to venusOS entirely and only have the cloud provide generic prediction data (solar, wind, pricing), long term performance data storage services (monitoring) and settings management (remote user interface).

It also implicates there might be something problematic with the system design decision to nominate a (low level) key storage parameter (battery State of Charge) as the primary control parameter throughout all those levels. I’d rather suspect it better to use State of Energy and it’s flow (power) for all levels except for the lowest storage level instead.

1 Like

Hi all, sorry for the radio silence! I have continued working on this and am happy to report that I’m currently running my own custom DESS algorithm, integrated as a Home Assistant add-on :partying_face:

The code consists of:

  • a linear programming algorithm
  • fetching PV and load forecasts and prices from VRM
  • fetching system settings from VRM and from the cerbo unit using MQTT
  • a mapper that converts the optimal flows to a strategy that Venus can work with
  • a module that pushes that strategy to the cerbo unit using MQTT
  • a frontend that both shows the planning as well as controls the algorithm settings
  • an express webserver that ties everything together

For now, there is no easy install option because I’m not sure if the choices are made are generic enough to be applicable to other people. I’ll let it run for a few days to see how it behaves, but I’m fairly confident it will work as expected.

5 Likes

I wish I had the time to dive right in but that will take a little longer. Nevertheless congratulations are in order, I can hardly wait to dig in and see how you tied it all together, very well done! :waving_hand::waving_hand::waving_hand:

A (not so) quick question about the scheduling algorithm: can you provide a high level functional requirement specification: a description of the primary goals that can be set and primary constraints taken into account for doing that? I am thinking about:

  • Key top level use case goals/strategies (Green/Trade in DESS terms but hopefully a bit more useful to a normal system operator/household/business owner)
  • Key time based constraints (reserve capacities, plannable power loads, semi-predictable power sources/solar)
    Never mind the (obvious) lower level implementation details such as technical capacity/energy/power constraints but also dynamic prices and consumption and solar forecasts.

That looks really nice Bart.

Is the code generic enough that it could also run on other systems than HA, maybe even run it on a cerbo itself…

Happy to help test (but will be away for the next few weeks.)