A subflow useful for handling the outputs of (some) Virtual Switches

I thought there might be some interest in this subflow I made to help with handling the outputs of Virtual Switch nodes. Mostly they produce a first output which is a passthrough of any paths that were updated via the input and a second output representing the value set but that only gets a message if the value changed. So you end up with 3 possibilities - the input is being told to update the switch within VRM but it doesn’t change the value so no value is output, the input is being told to update and it also causes a value change, or the switch itself is generating a new value from the VRM interface with no input paths.

Often you would want a behaviour where switching from within VRM produces an effect in your flow which may then in turn feed back changes to VRM and you don’t want unintended loops if that happens to change the value. Similarly you may want to make sure the switch is initialised properly or otherwise set from your flow and again avoid unwanted side-effects feeding back to your flow.

The following subflow is intended to have both outputs of the virtual switch wired to its input and then emits only new values and only if the change was initiated within VRM thus protecting against loops.

[
    {
        "id": "12d0519962dc0e49",
        "type": "subflow",
        "name": "Virtual Switch Handler",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 50,
                "y": 30,
                "wires": [
                    {
                        "id": "4ee64b71e7013b8e"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 1060,
                "y": 40,
                "wires": [
                    {
                        "id": "5aa170062f73bb64",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [],
        "meta": {},
        "color": "#DDAA99"
    },
    {
        "id": "4ee64b71e7013b8e",
        "type": "join",
        "z": "12d0519962dc0e49",
        "name": "JoinManualArrayWithTimeout",
        "mode": "custom",
        "build": "array",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "useparts": false,
        "accumulate": false,
        "timeout": "0.1",
        "count": "",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "",
        "reduceFixup": "",
        "x": 240,
        "y": 40,
        "wires": [
            [
                "bb11b76511416beb"
            ]
        ]
    },
    {
        "id": "bb11b76511416beb",
        "type": "switch",
        "z": "12d0519962dc0e49",
        "name": "IfFirstElementIsNotObject",
        "property": "payload[0]",
        "propertyType": "msg",
        "rules": [
            {
                "t": "istype",
                "v": "object",
                "vt": "object"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 570,
        "y": 40,
        "wires": [
            [],
            [
                "5aa170062f73bb64"
            ]
        ]
    },
    {
        "id": "5aa170062f73bb64",
        "type": "change",
        "z": "12d0519962dc0e49",
        "name": "GetLastElementValue",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "payload[-1]",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 880,
        "y": 40,
        "wires": [
            []
        ]
    }
]

My example of using it relates to a temperature setpoint type virtual switch repurposed to temporarily override a hot water heater (that spends most of its time off) for up to 150 minutes - most of my flow is omitted but another subflow generates an override cleared message when the override has finished running or if the heater’s switched off in some other way, I also want the switch within VRM reinitialised when my flow is deployed/restarted and to set or update the override timing or cancel it altogether by sliding to 0 from the VRM interface. Excuse the messy layout as it’s a small excerpt from a much larger flow.

[
    {
        "id": "12d0519962dc0e49",
        "type": "subflow",
        "name": "Virtual Switch Handler",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 50,
                "y": 30,
                "wires": [
                    {
                        "id": "4ee64b71e7013b8e"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 1060,
                "y": 40,
                "wires": [
                    {
                        "id": "5aa170062f73bb64",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [],
        "meta": {},
        "color": "#DDAA99"
    },
    {
        "id": "4ee64b71e7013b8e",
        "type": "join",
        "z": "12d0519962dc0e49",
        "name": "JoinManualArrayWithTimeout",
        "mode": "custom",
        "build": "array",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "useparts": false,
        "accumulate": false,
        "timeout": "0.1",
        "count": "",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "",
        "reduceFixup": "",
        "x": 240,
        "y": 40,
        "wires": [
            [
                "bb11b76511416beb"
            ]
        ]
    },
    {
        "id": "bb11b76511416beb",
        "type": "switch",
        "z": "12d0519962dc0e49",
        "name": "IfFirstElementIsNotObject",
        "property": "payload[0]",
        "propertyType": "msg",
        "rules": [
            {
                "t": "istype",
                "v": "object",
                "vt": "object"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 570,
        "y": 40,
        "wires": [
            [],
            [
                "5aa170062f73bb64"
            ]
        ]
    },
    {
        "id": "5aa170062f73bb64",
        "type": "change",
        "z": "12d0519962dc0e49",
        "name": "GetLastElementValue",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "payload[-1]",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 880,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "3f08ba9acd3c8099",
        "type": "switch",
        "z": "aa940e18d5486a09",
        "name": "IfOverrideCleared",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "overrideCleared",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 990,
        "y": 520,
        "wires": [
            [
                "e02baf0e9a896008"
            ]
        ]
    },
    {
        "id": "4b8b572ccfc96abb",
        "type": "inject",
        "z": "aa940e18d5486a09",
        "name": "OnStart",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 1140,
        "y": 460,
        "wires": [
            [
                "e02baf0e9a896008"
            ]
        ]
    },
    {
        "id": "e02baf0e9a896008",
        "type": "change",
        "z": "aa940e18d5486a09",
        "name": "Reset",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"/SwitchableOutput/output_1/Dimming\": 0}",
                "tot": "json"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1230,
        "y": 520,
        "wires": [
            [
                "b6525cd9eed21452"
            ]
        ]
    },
    {
        "id": "b6525cd9eed21452",
        "type": "victron-virtual-switch",
        "z": "aa940e18d5486a09",
        "name": "Override",
        "outputs": 2,
        "switch_1_type": "3",
        "switch_1_min": "0",
        "switch_1_max": "150",
        "switch_1_initial": 0,
        "switch_1_label": "",
        "switch_1_step": 30,
        "switch_1_customname": "House Override",
        "switch_1_group": "Geysers",
        "switch_1_include_measurement": false,
        "switch_1_rgb_color_wheel": false,
        "switch_1_cct_wheel": false,
        "switch_1_rgb_white_dimmer": false,
        "x": 1380,
        "y": 520,
        "wires": [
            [
                "a45c7a12a7e3c943"
            ],
            [
                "a45c7a12a7e3c943"
            ]
        ]
    },
    {
        "id": "a45c7a12a7e3c943",
        "type": "subflow:12d0519962dc0e49",
        "z": "aa940e18d5486a09",
        "name": "Handler",
        "x": 1150,
        "y": 620,
        "wires": [
            [
                "48aee7780cac897e"
            ]
        ]
    },
    {
        "id": "48aee7780cac897e",
        "type": "change",
        "z": "aa940e18d5486a09",
        "name": "SetOverride",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"action\":\"override\",\t\"deviceid\":\"1002a03ecf\",\t\"override\":true,\t\"count\":(payload = 0 ? null : payload/30)}",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1310,
        "y": 620,
        "wires": [
            [
                "8554cc96e1506958"
            ]
        ]
    },
    {
        "id": "86d23ac72628577c",
        "type": "global-config",
        "env": [],
        "modules": {
            "@victronenergy/node-red-contrib-victron": "1.6.64"
        }
    }
]

This is the switch in VRM alongside a more normal toggle for the same water heater

1 Like