Node-Red: One button, multiple switches/lights

Today I wanted to share a Node-RED flow that turns on/off a combination of entities with just one button.

A trigger can be a button push - I use an Aqara Wireless Button, but it can be anything really.

Demo

Code in the “Sequence” function node:

const entities = [
    'switch.plug2',
    'switch.plug3',
    'switch.plug4',
].reverse();

const linear = entities.map((e, i) => Math.pow(2, i))

const ha = global.get('homeassistant').homeAssistant;

const binary = entities.map(entity => {
    const { state } = ha.states && ha.states[entity]
    return state === 'on' ? '1' : '0'
})

let counter = flow.get("counter") || parseInt(binary.join(''), 2) || 0;
this.label = counter;

if (counter < Math.pow(2, entities.length) - 1) {
    counter = counter + 1;
} else {
    counter = 0;
}

flow.set("counter", counter);

const states = (counter).toString(2).padStart(entities.length, 0)

const collection = entities.map((entity, i) => ({
    name: entity,
    turn_on: Boolean(Number(states[i]))
}))

const turn_on = collection.length
    ? collection
        .filter(item => item.turn_on)
        .map(entity => entity.name)
    : []

const turn_off = collection.length
    ? collection
        .filter(item => !item.turn_on)
        .map(entity => entity.name)
    : []

return {
    payload: {
        turn_on,
        turn_off
    }
};

What it does

Code mentioned above makes use of Node-RED’s flow context and Binary-coded decimals.
We can represent every combination of two states (on / off) as a single integer.

How to use it

  1. Copy and import the following flow to your Node-RED:
[{"id":"2deb91ebdba3a961","type":"function","z":"950348bb2a0562c3","name":"Sequence","func":"const entities = [\n    'switch.plug2',\n    'switch.plug3',\n    'switch.plug4',\n].reverse();\n\nconst linear = entities.map((e, i) => Math.pow(2, i))\n\nconst ha = global.get('homeassistant').homeAssistant;\n\nconst binary = entities.map(entity => {\n    const { state } = ha.states && ha.states[entity]\n    return state === 'on' ? '1' : '0'\n})\n\nlet counter = flow.get(\"counter\") || parseInt(binary.join(''), 2) || 0;\nthis.label = counter;\n\nif (counter < Math.pow(2, entities.length) - 1) {\n    counter = counter + 1;\n} else {\n    counter = 0;\n}\n\nflow.set(\"counter\", counter);\n\nconst states = (counter).toString(2).padStart(entities.length, 0)\n\nconst collection = entities.map((entity, i) => ({\n    name: entity,\n    turn_on: Boolean(Number(states[i]))\n}))\n\nconst turn_on = collection.length\n    ? collection\n        .filter(item => item.turn_on)\n        .map(entity => entity.name)\n    : []\n\nconst turn_off = collection.length\n    ? collection\n        .filter(item => !item.turn_on)\n        .map(entity => entity.name)\n    : []\n\nreturn {\n    payload: {\n        turn_on,\n        turn_off\n    }\n};","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":1600,"wires":[["7534d0c6b1259806","93cc0f6b80bf7d3b"]]},{"id":"977d94a04c986b7b","type":"api-call-service","z":"950348bb2a0562c3","name":"Turn on","server":"e5d3a404.ac0078","version":3,"debugenabled":false,"service_domain":"homeassistant","service":"turn_on","entityId":"","data":"{ \"entity_id\": $join(payload.turn_on, \",\") }","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":760,"y":1600,"wires":[[]]},{"id":"7534d0c6b1259806","type":"switch","z":"950348bb2a0562c3","name":"Is there anything to turn on?","property":"payload.turn_on.length","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":540,"y":1600,"wires":[["977d94a04c986b7b"]]},{"id":"93cc0f6b80bf7d3b","type":"switch","z":"950348bb2a0562c3","name":"Is there anything to turn off?","property":"payload.turn_off.length","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":540,"y":1640,"wires":[["b52b844a3cf7214c"]]},{"id":"700ce69198720e2b","type":"inject","z":"950348bb2a0562c3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":160,"y":1600,"wires":[["2deb91ebdba3a961"]]},{"id":"b52b844a3cf7214c","type":"api-call-service","z":"950348bb2a0562c3","name":"Turn off","server":"e5d3a404.ac0078","version":3,"debugenabled":false,"service_domain":"homeassistant","service":"turn_off","entityId":"","data":"{ \"entity_id\": $join(payload.turn_off, \",\") }","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":760,"y":1660,"wires":[[]]},{"id":"e5d3a404.ac0078","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30}]
  1. In your “Sequence” node modify the entities array so that it contains a collection of switches (or other toggleable entities) you’d like to control:
const entities = [
    'switch.plug2',
    'switch.plug3',
    'switch.plug4',
].reverse();
  1. Add a triggering action right before the “Sequence” node. In my case it’s the Aqara wireless switch single click event:

Here’s the whole flow for your convenience:

[{"id":"2deb91ebdba3a961","type":"function","z":"950348bb2a0562c3","name":"Sequence","func":"const entities = [\n    'switch.plug2',\n    'switch.plug3',\n    'switch.plug4',\n].reverse();\n\nconst linear = entities.map((e, i) => Math.pow(2, i))\n\nconst ha = global.get('homeassistant').homeAssistant;\n\nconst binary = entities.map(entity => {\n    const { state } = ha.states && ha.states[entity]\n    return state === 'on' ? '1' : '0'\n})\n\nlet counter = flow.get(\"counter\") || parseInt(binary.join(''), 2) || 0;\nthis.label = counter;\n\nif (counter < Math.pow(2, entities.length) - 1) {\n    counter = counter + 1;\n} else {\n    counter = 0;\n}\n\nflow.set(\"counter\", counter);\n\nconst states = (counter).toString(2).padStart(entities.length, 0)\n\nconst collection = entities.map((entity, i) => ({\n    name: entity,\n    turn_on: Boolean(Number(states[i]))\n}))\n\nconst turn_on = collection.length\n    ? collection\n        .filter(item => item.turn_on)\n        .map(entity => entity.name)\n    : []\n\nconst turn_off = collection.length\n    ? collection\n        .filter(item => !item.turn_on)\n        .map(entity => entity.name)\n    : []\n\nreturn {\n    payload: {\n        turn_on,\n        turn_off\n    }\n};","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":1600,"wires":[["7534d0c6b1259806","93cc0f6b80bf7d3b"]]},{"id":"977d94a04c986b7b","type":"api-call-service","z":"950348bb2a0562c3","name":"Turn on","server":"e5d3a404.ac0078","version":3,"debugenabled":false,"service_domain":"homeassistant","service":"turn_on","entityId":"","data":"{ \"entity_id\": $join(payload.turn_on, \",\") }","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":760,"y":1600,"wires":[[]]},{"id":"7534d0c6b1259806","type":"switch","z":"950348bb2a0562c3","name":"Is there anything to turn on?","property":"payload.turn_on.length","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":540,"y":1600,"wires":[["977d94a04c986b7b"]]},{"id":"93cc0f6b80bf7d3b","type":"switch","z":"950348bb2a0562c3","name":"Is there anything to turn off?","property":"payload.turn_off.length","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":540,"y":1640,"wires":[["b52b844a3cf7214c"]]},{"id":"700ce69198720e2b","type":"inject","z":"950348bb2a0562c3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":160,"y":1600,"wires":[["2deb91ebdba3a961"]]},{"id":"b52b844a3cf7214c","type":"api-call-service","z":"950348bb2a0562c3","name":"Turn off","server":"e5d3a404.ac0078","version":3,"debugenabled":false,"service_domain":"homeassistant","service":"turn_off","entityId":"","data":"{ \"entity_id\": $join(payload.turn_off, \",\") }","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":760,"y":1660,"wires":[[]]},{"id":"1b3bf7f64ed16f86","type":"switch","z":"950348bb2a0562c3","name":"Click Type","property":"payload.event.click_type","propertyType":"msg","rules":[{"t":"eq","v":"single","vt":"str"},{"t":"eq","v":"double","vt":"str"},{"t":"eq","v":"long_click_press","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":610,"y":1800,"wires":[["2deb91ebdba3a961"],[],[]]},{"id":"8f052f4556246d7b","type":"switch","z":"950348bb2a0562c3","name":"Light Switches","property":"payload.entity_id","propertyType":"msg","rules":[{"t":"eq","v":"binary_sensor.switch_158d0001137XXX","vt":"str"},{"t":"eq","v":"binary_sensor.switch_158d00013f9YYY","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":400,"y":1800,"wires":[["1b3bf7f64ed16f86"],["1b3bf7f64ed16f86"]]},{"id":"f4773b8cd5756c13","type":"server-events","z":"950348bb2a0562c3","name":"","server":"e5d3a404.ac0078","version":1,"event_type":"xiaomi_aqara.click","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":190,"y":1800,"wires":[["8f052f4556246d7b"]]},{"id":"e5d3a404.ac0078","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30}]
  1. Deploy

Enjoy :smiley:

Looks confusing to use, I would probably use single, double and triple click instead.
But definitely a interesting way of doing it.