Using Node-RED to steer opportunistic loads based on temperature-thresholds

The new Opportunity Loads feature allows to run any kind of consumer based on available solar surplus and hot water heating is one of the most common use cases to burn “unusable” surplus.

With a quite simple Node-RED script, control of these consumers can be enabled/disabled based on conditions, i.e. a reached target-temperature or eventually weekday specific goals.

Example1: 3x1kW heating rod for general hot-water production.

We have a regular gas-heating for hot-water purpose, at current gas-prices / feed in prices it is still reasonable to use surplus power to make hot water over feeding in. Yet, heating the reservoir up above 50° is undesired, it causes increasing heat-losses, thermal stress, higher ambient temperature in the basement and is no “real saving”, because 50° is where we limit the gas-based-heating, and hence there are no more “alternative heating costs” that could be considered as saving beyond that point.

The node reads a already known temperature sensor from the system and uses three different pairs of thresholds (44/45, 46/47, 49/50) to change the control behaviour on individual heating rods. I’ve used a delta of 1° (i.e. OFF@45°, AUTO@44°) to avoid flickering on edge cases:

The Node does:

  • Whenever a threshold is reached: Set the /Auto Path of the corresponding shelly to 0 and send a 5 second delayed 0 to /State as well - as that shelly now just switched to manual mode, and should be ensured to be “off”. Opportunity Loads will now see a NO_CTRL state and not assign any energy to these devices.

  • Whenever the activation threshold is reached, it simply sets /Auto to 1 again, making the shelly offer a controllable state to Opportunity Loads again - and Opportunity Loads will resume to orchestrate it along with any other existing consumers.

JSON of the Node

[

    {
        "id": "9aecdc9c7c2fb5ae",
        "type": "tab",
        "label": "OL:Heater",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "28c61cd4e6c5ee07",
        "type": "victron-output-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.acload/54",
        "path": "/SwitchableOutput/1/Auto",
        "serviceObj": {
            "service": "com.victronenergy.acload/54",
            "name": "Heater1 (54)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/1/Auto",
            "name": "/SwitchableOutput/1/Auto",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 40,
        "wires": []
    },
    {
        "id": "1bbb71b0617cd912",
        "type": "victron-output-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.acload/55",
        "path": "/SwitchableOutput/0/Auto",
        "serviceObj": {
            "service": "com.victronenergy.acload/55",
            "name": "Heater2 (55)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/0/Auto",
            "name": "/SwitchableOutput/0/Auto",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 180,
        "wires": []
    },
    {
        "id": "af6af2c5ad9c10ae",
        "type": "victron-output-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.acload/56",
        "path": "/SwitchableOutput/1/Auto",
        "serviceObj": {
            "service": "com.victronenergy.acload/56",
            "name": "Heater3 (56)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/1/Auto",
            "name": "/SwitchableOutput/1/Auto",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 320,
        "wires": []
    },
    {
        "id": "ae6cf2a1cb695052",
        "type": "victron-output-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.acload/54",
        "path": "/SwitchableOutput/1/State",
        "serviceObj": {
            "service": "com.victronenergy.acload/54",
            "name": "Heater1 (54)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/1/State",
            "name": "/SwitchableOutput/1/State",
            "type": "number",
            "value": 0
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 100,
        "wires": []
    },
    {
        "id": "a72b225cbd996c27",
        "type": "victron-output-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.acload/55",
        "path": "/SwitchableOutput/0/State",
        "serviceObj": {
            "service": "com.victronenergy.acload/55",
            "name": "Heater2 (55)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/0/State",
            "name": "/SwitchableOutput/0/State",
            "type": "number",
            "value": 0
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 240,
        "wires": []
    },
    {
        "id": "0b720246c14402e5",
        "type": "victron-output-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.acload/56",
        "path": "/SwitchableOutput/1/State",
        "serviceObj": {
            "service": "com.victronenergy.acload/56",
            "name": "Heater3 (56)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/1/State",
            "name": "/SwitchableOutput/1/State",
            "type": "number",
            "value": 0
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 380,
        "wires": []
    },
    {
        "id": "185bf6af606be86b",
        "type": "delay",
        "z": "9aecdc9c7c2fb5ae",
        "name": "delay",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 790,
        "y": 380,
        "wires": [
            [
                "0b720246c14402e5"
            ]
        ]
    },
    {
        "id": "4923510d2121a762",
        "type": "switch",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Threshold 45°",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "44",
                "vt": "num"
            },
            {
                "t": "gte",
                "v": "45",
                "vt": "num"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 440,
        "y": 360,
        "wires": [
            [
                "93912373fe01f375"
            ],
            [
                "7eca412d9a9ec5e9"
            ]
        ]
    },
    {
        "id": "93912373fe01f375",
        "type": "change",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Set On",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 620,
        "y": 320,
        "wires": [
            [
                "af6af2c5ad9c10ae"
            ]
        ]
    },
    {
        "id": "7eca412d9a9ec5e9",
        "type": "change",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Set Off",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "0",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 610,
        "y": 380,
        "wires": [
            [
                "af6af2c5ad9c10ae",
                "185bf6af606be86b"
            ]
        ]
    },
    {
        "id": "69b36403075158b7",
        "type": "delay",
        "z": "9aecdc9c7c2fb5ae",
        "name": "delay",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 790,
        "y": 240,
        "wires": [
            [
                "a72b225cbd996c27"
            ]
        ]
    },
    {
        "id": "06259ede14142a93",
        "type": "switch",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Threshold 47",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "46",
                "vt": "num"
            },
            {
                "t": "gte",
                "v": "47",
                "vt": "num"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 430,
        "y": 220,
        "wires": [
            [
                "43db3fb3b0d96050"
            ],
            [
                "2004bb63ce190905"
            ]
        ]
    },
    {
        "id": "43db3fb3b0d96050",
        "type": "change",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Set On",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 620,
        "y": 180,
        "wires": [
            [
                "1bbb71b0617cd912"
            ]
        ]
    },
    {
        "id": "2004bb63ce190905",
        "type": "change",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Set Off",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "0",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 610,
        "y": 240,
        "wires": [
            [
                "69b36403075158b7",
                "1bbb71b0617cd912"
            ]
        ]
    },
    {
        "id": "960303af45c79566",
        "type": "delay",
        "z": "9aecdc9c7c2fb5ae",
        "name": "delay",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 790,
        "y": 100,
        "wires": [
            [
                "ae6cf2a1cb695052"
            ]
        ]
    },
    {
        "id": "2a0925e8aab5332a",
        "type": "switch",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Threshold 50°",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "49",
                "vt": "num"
            },
            {
                "t": "gte",
                "v": "50",
                "vt": "num"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 440,
        "y": 80,
        "wires": [
            [
                "2e67f28bcc803e65"
            ],
            [
                "6dea9d150da8ec76"
            ]
        ]
    },
    {
        "id": "2e67f28bcc803e65",
        "type": "change",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Set On",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 620,
        "y": 40,
        "wires": [
            [
                "28c61cd4e6c5ee07"
            ]
        ]
    },
    {
        "id": "6dea9d150da8ec76",
        "type": "change",
        "z": "9aecdc9c7c2fb5ae",
        "name": "Set Off",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "0",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 610,
        "y": 100,
        "wires": [
            [
                "960303af45c79566",
                "28c61cd4e6c5ee07"
            ]
        ]
    },
    {
        "id": "da4ba9ad6066d2d9",
        "type": "victron-input-custom",
        "z": "9aecdc9c7c2fb5ae",
        "service": "com.victronenergy.temperature/100",
        "path": "/Temperature",
        "serviceObj": {
            "service": "com.victronenergy.temperature/100",
            "name": "Reservoir (100)"
        },
        "pathObj": {
            "path": "/Temperature",
            "name": "/Temperature",
            "type": "number",
            "value": 54.3
        },
        "name": "",
        "onlyChanges": true,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "outputTrue": "true",
        "outputFalse": "false",
        "debounce": "2000",
        "x": 160,
        "y": 220,
        "wires": [
            [
                "06259ede14142a93",
                "4923510d2121a762",
                "2a0925e8aab5332a"
            ]
        ]
    },
    {
        "id": "4aad3a7092694ff6",
        "type": "global-config",
        "env": [],
        "modules": {
            "@victronenergy/node-red-contrib-victron": "1.7.13"
        }
    }
]

Example 2: 700W Heat pump + 2.7kW Heater for the Kids Pool
For the kids, we have a pool, and our feel-well temperature is somewhere around 28-30° Thus, Opportunity Loads is also orchestrating 2 shellies, one connected to a Heat pump (more efficient), one connected to a regular heating rod, used for “burst-heating”

The flow is literally the same as example 1, just a different reasoning as this is not about “saving and opportunistic costs”, but just about getting the pool temperature about right: Up to 28° it should run full-throttle, whenever there is solar power available. 28-30 it can omit the burst-heater and only rely on the more efficient heat pump to reach the final 30° target eventually.

(Yes, 3 days of cold weather, we are far away from that :smiley: )

JSON of the Node
[
    {
        "id": "a970ae66d296654b",
        "type": "tab",
        "label": "OL: Pool Heating",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "4b6e50f4505e23d0",
        "type": "victron-input-custom",
        "z": "a970ae66d296654b",
        "service": "com.victronenergy.temperature/101",
        "path": "/Temperature",
        "serviceObj": {
            "service": "com.victronenergy.temperature/101",
            "name": "Pool (101)"
        },
        "pathObj": {
            "path": "/Temperature",
            "name": "/Temperature",
            "type": "number",
            "value": 25.84
        },
        "name": "",
        "onlyChanges": true,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "outputTrue": "true",
        "outputFalse": "false",
        "debounce": "2000",
        "x": 170,
        "y": 180,
        "wires": [
            [
                "83fb8e2ae1be3c86",
                "e83d1380812b63d4"
            ]
        ]
    },
    {
        "id": "7e5ec9c8ff2d9227",
        "type": "victron-output-custom",
        "z": "a970ae66d296654b",
        "service": "com.victronenergy.acload/57",
        "path": "/SwitchableOutput/0/Auto",
        "serviceObj": {
            "service": "com.victronenergy.acload/57",
            "name": "Pool-Heatpump (57)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/0/Auto",
            "name": "/SwitchableOutput/0/Auto",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 60,
        "wires": []
    },
    {
        "id": "9282cc55b6e5d99f",
        "type": "victron-output-custom",
        "z": "a970ae66d296654b",
        "service": "com.victronenergy.acload/57",
        "path": "/SwitchableOutput/0/State",
        "serviceObj": {
            "service": "com.victronenergy.acload/57",
            "name": "Pool-Heatpump (57)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/0/State",
            "name": "/SwitchableOutput/0/State",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1020,
        "y": 120,
        "wires": []
    },
    {
        "id": "225a45b98256fe30",
        "type": "delay",
        "z": "a970ae66d296654b",
        "name": "delay",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 770,
        "y": 120,
        "wires": [
            [
                "9282cc55b6e5d99f"
            ]
        ]
    },
    {
        "id": "83fb8e2ae1be3c86",
        "type": "switch",
        "z": "a970ae66d296654b",
        "name": "Threshold 30°",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "29",
                "vt": "num"
            },
            {
                "t": "gte",
                "v": "30",
                "vt": "num"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 420,
        "y": 100,
        "wires": [
            [
                "38f2b6c0a599b525"
            ],
            [
                "792cf63227c22c8e"
            ]
        ]
    },
    {
        "id": "38f2b6c0a599b525",
        "type": "change",
        "z": "a970ae66d296654b",
        "name": "Set On",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 600,
        "y": 60,
        "wires": [
            [
                "7e5ec9c8ff2d9227"
            ]
        ]
    },
    {
        "id": "792cf63227c22c8e",
        "type": "change",
        "z": "a970ae66d296654b",
        "name": "Set Off",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "0",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 590,
        "y": 120,
        "wires": [
            [
                "225a45b98256fe30",
                "7e5ec9c8ff2d9227"
            ]
        ]
    },
    {
        "id": "d730adf8e69094ed",
        "type": "victron-output-custom",
        "z": "a970ae66d296654b",
        "service": "com.victronenergy.acload/58",
        "path": "/SwitchableOutput/1/Auto",
        "serviceObj": {
            "service": "com.victronenergy.acload/58",
            "name": "Pool-Heater (58)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/1/Auto",
            "name": "/SwitchableOutput/1/Auto",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1010,
        "y": 200,
        "wires": []
    },
    {
        "id": "46d2e8b13df2c229",
        "type": "victron-output-custom",
        "z": "a970ae66d296654b",
        "service": "com.victronenergy.acload/58",
        "path": "/SwitchableOutput/1/State",
        "serviceObj": {
            "service": "com.victronenergy.acload/58",
            "name": "Pool-Heater (58)"
        },
        "pathObj": {
            "path": "/SwitchableOutput/1/State",
            "name": "/SwitchableOutput/1/State",
            "type": "number",
            "value": 1
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "no",
        "rateLimit": 0,
        "conditionalMode": false,
        "condition1Operator": ">",
        "condition2Enabled": false,
        "condition2Service": "",
        "condition2Path": "",
        "condition2Operator": ">",
        "logicOperator": "AND",
        "outputTrue": "true",
        "outputFalse": "false",
        "outputOnChange": false,
        "debounce": 2000,
        "x": 1010,
        "y": 260,
        "wires": []
    },
    {
        "id": "8b781c67a06ecdbf",
        "type": "delay",
        "z": "a970ae66d296654b",
        "name": "delay",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 770,
        "y": 260,
        "wires": [
            [
                "46d2e8b13df2c229"
            ]
        ]
    },
    {
        "id": "e83d1380812b63d4",
        "type": "switch",
        "z": "a970ae66d296654b",
        "name": "Threshold 28°",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "27",
                "vt": "num"
            },
            {
                "t": "gte",
                "v": "28",
                "vt": "num"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 420,
        "y": 240,
        "wires": [
            [
                "2e4210e22c2a17c1"
            ],
            [
                "dbec5ef217b6f3b7"
            ]
        ]
    },
    {
        "id": "2e4210e22c2a17c1",
        "type": "change",
        "z": "a970ae66d296654b",
        "name": "Set On",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 600,
        "y": 200,
        "wires": [
            [
                "d730adf8e69094ed"
            ]
        ]
    },
    {
        "id": "dbec5ef217b6f3b7",
        "type": "change",
        "z": "a970ae66d296654b",
        "name": "Set Off",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "0",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 590,
        "y": 260,
        "wires": [
            [
                "8b781c67a06ecdbf",
                "d730adf8e69094ed"
            ]
        ]
    },
    {
        "id": "914dbe33d88143af",
        "type": "global-config",
        "env": [],
        "modules": {
            "@victronenergy/node-red-contrib-victron": "1.7.13"
        }
    }
]

The temperature values are - in my case - just a readout from my home-mqtt, Node-RED offers various ways to bring in some values into the virtual temperature sensors used here.

@BjoernK, responding to your question on this thread :slight_smile:

Your usecase would be perfectly covered with this node as well. I’d like to add some “best practice” on additional configuration on that:

Prioirities:
You should arrange the 3 shelly devices in a order so that the 2 kW has the highest priority, followed by 1kW, followed by 0.5 kW. This would make Opportunity Loads try to enable the “most expensive consumer” first, skipping to the lower priority rods, if it does not yet fit into the budget.

This would give you the best possible resolution to maximize self-consumption there, as you could heat with 0.5, 1, 1.5, 2, 2.5, 3 and 3.5 kW in a staggered way.

Minimum durations:
You generally can set them up as you prefer and feel comfortable, heating rods are not very sensitive to frequent on/off behaviour, but if arranging the consumers as suggested, you should not pick the same values for every device.

Reasoning: When a solar shortage occurs, consumers will be turned off in reverse-priority order. If all have the same rest-duration, that would lead to the consumer with the lowest priority finishing its rest-duration, first. If solar then is already restored, it may be turned on again, just to have a higher priority consumer becoming available 5 seconds later - and causing a switch of these 2 again.

Rather than that, I would - along with above priorities - go with something like: 30s for the 2kW, 45s for the 1 kW and 60s for the 0.5kW element. This would ensure, that if all 3 are turned off at the same time, the highest priority one is also available again, first.

(But that is just my experience, you may find other settings to work better based on your system / expectation)