Output sequentially and randomly to 3 different nodes

Hi, I am looking for a few clues as to how to switch on randomly 3 relays, one by one depending on conditions and then switch them off either randomly or in the same order one by one.
The project is a 3-element heater which will work either with 1, 2 or 3 elements depending on available solar energy. The thing is I don't want the same element to always be on first as that will probably lead to failure of that particular element prematurely so I want to switch each element randomly.

Is there an easy way to do this?

The simplest way I've found to route messages to random output ports is to use the switch node that compares a JSONata expression against the 3 possible output values -- this post uses that technique to perform a "round-robin" effect...

Your case sounds a bit more complicated in that it seems like you will need to keep track of the previously sent messages. So if the last event turned on elements #2 and #1, and you wanted to leave only 1 of them on, then you would need to randomly turn off either #2 or #1 (since turning off #3 would effectively be a no-op, right?). Or you could design the flow to always turn every element "off" just before sending a message to randomly enable 1, 2 or 3 elements -- although I would imagine that the extra cycling would not be good for extending the element lifespan either.

Yes, an ideal solution would track the first one turned on for example and turn it off first but randomness would be ok and certainly better than always the same one on for the longest time.
I will check that round-robin out.
Excessive cycling is another thing I need to address in my flow.

Hi @usernamepasswordBS,
You could have a look at my message router node, which also supports random routing.
Bart

1 Like

@BartButenaers That's looks very good and it seems like it would be possible to turn on my relays one at a time and then off one at a time in the same order so they are used approximately equally or am I missing something? Which version would be most suitable for this?

Hmm, I have to be honest that I only read your question this morning very quickly. And I saw 'random routing' so I proposed my router node, because it supports that.

But as @shrickus already mentioned, your question needs some tracking of which outputs are already ON for the longest time. Therefore I think that my router node would also result in a rather complex flow, and it might be a better idea to create a custom router.

Have a look if the following flow can solve your problem:

image

[{"id":"e2ad9c95.f967c8","type":"function","z":"17a2ce422a8730a6","name":"Custom router","func":"debugger\n\n// Initialize the FIFO queue if it doesn't exist in the context\nif (context.queue === undefined) {\n    context.queue = [];\n}\n\nswitch (msg.payload) {\n    case \"ON\":\n        if (context.queue.length >= node.outputCount) {\n            node.error(\"All outputs are ON already\", msg);\n            return;\n        }\n\n        let availableOutputs = getAvailableOutputs();\n\n        // Select a random output number (from the list of available output numbers)\n        let randomIndex = Math.floor(Math.random() * availableOutputs.length);\n        let randomOutputNumber = availableOutputs[randomIndex];\n\n        // Add the output number to the head of the queue\n        context.queue.push(randomOutputNumber);\n\n        // Send the message to the random output\n        sendOutput(randomOutputNumber, msg);\n        break;\n\n    case \"OFF\":\n        if (context.queue.length === 0) {\n            node.error(\"No output is ON yet\", msg);\n            return;\n        }\n\n        // Get the oldest output number from the queue (i.e. output that is ON already for the longest time)\n        let oldestOutputNumber = context.queue.shift();\n\n        // Send the message to the oldest output\n        sendOutput(oldestOutputNumber, msg);\n        break;\n\n    default:\n        node.error(\"Invalid payload.\");\n}\n\n// Get the available output numbers (i.e. not in the queue yet)\nfunction getAvailableOutputs() {\n    const availableOutputs = [];\n\n    // Loop through all output numbers and check if they are in the used outputs array\n    for (let i = 0; i < node.outputCount; i++) {\n        if (!context.queue.includes(i)) {\n            availableOutputs.push(i);\n        }\n    }\n\n    return availableOutputs;\n}\n\n// Send the message to the specified output number\nfunction sendOutput(outputNumber, msg) {\n    // Create an array with null for all outputs except the specified output number\n    let outputMsgs = Array(node.outputCount);\n    outputMsgs[outputNumber] = msg;\n\n    // Send the array of messages to the outputs\n    node.send(outputMsgs);\n}","outputs":4,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":1260,"wires":[["86f913f8.5d6508"],["6e0b8fe4.bec6e"],["a25e3091.d4b6d"],["ed5c5509.8e982"]]},{"id":"e8c5d47d.399c18","type":"inject","z":"17a2ce422a8730a6","name":"ON","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"ON","payloadType":"str","x":550,"y":1240,"wires":[["e2ad9c95.f967c8"]]},{"id":"e6f9d894.e5dca8","type":"inject","z":"17a2ce422a8730a6","name":"OFF","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"OFF","payloadType":"str","x":550,"y":1280,"wires":[["e2ad9c95.f967c8"]]},{"id":"86f913f8.5d6508","type":"debug","z":"17a2ce422a8730a6","name":"Output 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1200,"wires":[]},{"id":"6e0b8fe4.bec6e","type":"debug","z":"17a2ce422a8730a6","name":"Output 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1240,"wires":[]},{"id":"a25e3091.d4b6d","type":"debug","z":"17a2ce422a8730a6","name":"Output 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1280,"wires":[]},{"id":"ed5c5509.8e982","type":"debug","z":"17a2ce422a8730a6","name":"Output 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1320,"wires":[]}]

It works like this:

  • When a msg.payload = "ON" arrives:

    1. It gives an error if all outputs are already ON and stops.
    2. It selects a random output that is OFF
    3. It sends the msg to that output
    4. It stores the output number in a fifo queue
  • When a msg.payload = "OFF" arrives:

    1. It gives an error if all outputs are already OFF and stops.
    2. It selects the output that is already on for the longest time (i.e. from the fifo queue)
    3. It sends the msg to that output
    4. It removes the output number from the fifo queue

I "think" that is what you are asking for. It selects a random output to turn ON. And it turns OFF the device that is already ON for the longest time. That way your heaters will be turned ON randomly, and the time that they are ON will be minimized...

@usernamepasswordBS,

I see some other interesting use cases similar to yours.
Not sure whether selecting randomly outputs is the best way to implement this. Personally I think it is better to do this time based. Because you want all your heaters to be ON during a rather similar time interval, to make sure they have a bit of a similar load.

In the next example flow I calculate the total time interval that each output has been in the ON state.

  • When msg.payload = "ON" arrives, the message will be forwarded to the output with the lowest total ON time interval (and the time starts counting for this output).
  • When msg.payload = "OFF" arrives, the message will be forwarded to the output with the highest total ON time interval (and the time stops counting for this output)

image

[{"id":"61ac0e8357507f4b","type":"function","z":"17a2ce422a8730a6","name":"Time based router","func":"debugger\n\nswitch (msg.payload) {\n    case \"ON\":\n        // Find the output with the shortest total ON time\n        const onOutputIndex = getOutputWithShortestTotalTime();\n\n        // Check if any output is available to switch ON\n        if (onOutputIndex < 0) {\n            node.error(\"All outputs are already switched ON\");\n            return;\n        }\n\n        // Set the output state to \"ON\"\n        context.outputData[onOutputIndex].state = \"ON\";\n\n        // Start counting the time for the selected output\n        context.outputData[onOutputIndex].lastOnStartTime = Date.now();\n\n        // Send the message to the corresponding output\n        sendOutput(onOutputIndex, msg);\n        break;\n\n    case \"OFF\":\n        // Find the output that is currently ON and has the longest total time\n        const offOutputIndex = getOutputWithLongestTotalTime();\n\n        // Check if any output is currently ON\n        if (offOutputIndex < 0) {\n            node.error(\"All outputs are already switched OFF\");\n            return;\n        }\n\n        // Set the output state to \"OFF\"\n        context.outputData[offOutputIndex].state = \"OFF\";\n\n        // Calculate the ON time for the selected output\n        const onTime = Date.now() - context.outputData[offOutputIndex].lastOnStartTime;\n\n        // Update the total ON time for the selected output\n        context.outputData[offOutputIndex].totalOnTime += onTime;\n\n        // Send the message to the corresponding output\n        sendOutput(offOutputIndex, msg);\n        break;\n\n    default:\n        node.error(\"Invalid payload.\");\n        break;\n}\n\n// Get the output index with the shortest total ON time among those in the \"OFF\" state\nfunction getOutputWithShortestTotalTime() {\n    let shortestTime = Infinity;\n    let outputIndex = -1;\n\n    context.outputData.forEach((data, index) => {\n        if (data.state === \"OFF\" && data.totalOnTime < shortestTime) {\n            shortestTime = data.totalOnTime;\n            outputIndex = index;\n        }\n    });\n\n    return outputIndex;\n}\n\n// Get the output index with the longest total ON time among those in the \"ON\" state\nfunction getOutputWithLongestTotalTime() {\n    let longestTime = -Infinity;\n    let outputIndex = -1;\n\n    // Find the output index with the longest total ON time among those in the \"ON\" state\n    context.outputData.forEach((data, index) => {\n        if (data.state === \"ON\" && data.totalOnTime > longestTime) {\n            longestTime = data.totalOnTime;\n            outputIndex = index;\n        }\n    });\n\n    return outputIndex;\n}\n\n// Send the message to the specified output number\nfunction sendOutput(outputNumber, msg) {\n    // Create an array with null for all outputs except the specified output number\n    let outputMsgs = Array(node.outputCount);\n    outputMsgs[outputNumber] = msg;\n\n    // Send the array of messages to the outputs\n    node.send(outputMsgs);\n}","outputs":4,"noerr":0,"initialize":"if (context.outputData === undefined) {\n    context.outputData = [];\n    for (let i = 0; i < node.outputCount; i++) {\n        context.outputData.push({\n            state: \"OFF\",\n            lastOnStartTime: 0,\n            totalOnTime: 0\n        });\n    }\n}","finalize":"","libs":[],"x":730,"y":1000,"wires":[["60fe54115b3e8b68"],["b5f7a53c59c953b7"],["584b0954d69063b9"],["06b14b269ce67616"]]},{"id":"f3672ac1bf4396b1","type":"inject","z":"17a2ce422a8730a6","name":"ON","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"ON","payloadType":"str","x":530,"y":980,"wires":[["61ac0e8357507f4b"]]},{"id":"ca5344e68b2513bc","type":"inject","z":"17a2ce422a8730a6","name":"OFF","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"OFF","payloadType":"str","x":530,"y":1020,"wires":[["61ac0e8357507f4b"]]},{"id":"60fe54115b3e8b68","type":"debug","z":"17a2ce422a8730a6","name":"Output 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":940,"wires":[]},{"id":"b5f7a53c59c953b7","type":"debug","z":"17a2ce422a8730a6","name":"Output 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":980,"wires":[]},{"id":"584b0954d69063b9","type":"debug","z":"17a2ce422a8730a6","name":"Output 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1020,"wires":[]},{"id":"06b14b269ce67616","type":"debug","z":"17a2ce422a8730a6","name":"Output 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1060,"wires":[]}]

I did only some basic testing. And there is e.g. no possibility yet to reset the counters. But my time is up for today.

1 Like

Thank you for all your replies. I think that the time on may be less important than the number of cycles so I will try with your first example and see how it goes. It sound like exactly what I need!

1 Like

@usernamepasswordBS,
I quickly made a new version during my lunch break. It counts the number of ON messages per output. And it sends the ON/OFF messages to the output with the lowest count.

image

[{"id":"91e2744d9fa15bdc","type":"function","z":"17a2ce422a8730a6","name":"Count based router","func":"debugger\n\nswitch (msg.payload) {\n    case \"ON\":\n        // Find the output with the shortest total ON time\n        const onOutputIndex = getOutputWithLeastCalls(\"OFF\");\n\n        // Check if any output is available to switch ON\n        if (onOutputIndex < 0) {\n            node.error(\"All outputs are already switched ON\");\n            return;\n        }\n\n        // Set the output state to \"ON\"\n        context.outputData[onOutputIndex].state = \"ON\";\n\n        // Start counting the time for the selected output\n        context.outputData[onOutputIndex].totalCalls++;\n\n        // Send the message to the corresponding output\n        sendOutput(onOutputIndex, msg);\n        break;\n\n    case \"OFF\":\n        // Find the output that is currently ON and has the longest total time\n        const offOutputIndex = getOutputWithLeastCalls(\"ON\");\n\n        // Check if any output is currently ON\n        if (offOutputIndex < 0) {\n            node.error(\"All outputs are already switched OFF\");\n            return;\n        }\n\n        // Set the output state to \"OFF\"\n        context.outputData[offOutputIndex].state = \"OFF\";\n\n        // Send the message to the corresponding output\n        sendOutput(offOutputIndex, msg);\n        break;\n\n    default:\n        node.error(\"Invalid payload.\");\n        break;\n}\n\n// Get the output index with the shortest total ON time among those in the \"OFF\" state\nfunction getOutputWithLeastCalls(currentStatus) {\n    let leastCalls = Infinity;\n    let outputIndex = -1;\n\n    context.outputData.forEach((data, index) => {\n        if (data.state === currentStatus && data.totalCalls < leastCalls) {\n            leastCalls = data.totalCalls;\n            outputIndex = index;\n        }\n    });\n\n    return outputIndex;\n}\n\n// Send the message to the specified output number\nfunction sendOutput(outputNumber, msg) {\n    // Create an array with null for all outputs except the specified output number\n    let outputMsgs = Array(node.outputCount);\n    outputMsgs[outputNumber] = msg;\n\n    // Send the array of messages to the outputs\n    node.send(outputMsgs);\n}","outputs":4,"noerr":0,"initialize":"if (context.outputData === undefined) {\n    context.outputData = [];\n    for (let i = 0; i < node.outputCount; i++) {\n        context.outputData.push({\n            state: \"OFF\",\n            totalCalls: 0\n        });\n    }\n}","finalize":"","libs":[],"x":710,"y":820,"wires":[["b3fb2c01a4bc8df5"],["c6ea79a6abab4cb8"],["0a345585bde3976b"],["33d601c07088e7fa"]]},{"id":"b02e1b78ee1bbc0f","type":"inject","z":"17a2ce422a8730a6","name":"ON","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"ON","payloadType":"str","x":510,"y":800,"wires":[["91e2744d9fa15bdc"]]},{"id":"54e3996858d5caae","type":"inject","z":"17a2ce422a8730a6","name":"OFF","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"OFF","payloadType":"str","x":510,"y":840,"wires":[["91e2744d9fa15bdc"]]},{"id":"b3fb2c01a4bc8df5","type":"debug","z":"17a2ce422a8730a6","name":"Output 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":920,"y":760,"wires":[]},{"id":"c6ea79a6abab4cb8","type":"debug","z":"17a2ce422a8730a6","name":"Output 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":920,"y":800,"wires":[]},{"id":"0a345585bde3976b","type":"debug","z":"17a2ce422a8730a6","name":"Output 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":920,"y":840,"wires":[]},{"id":"33d601c07088e7fa","type":"debug","z":"17a2ce422a8730a6","name":"Output 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":920,"y":880,"wires":[]}]

I only had time to do some very basic testing!

I wonder whether an input payload of 0, 1, 2 or 3, indicating the number of elements to be on currently, would suit @usernamepasswordBS's requirement better.

Here is my simple example.

[{"id":"2dccd787ae6ed074","type":"function","z":"f2641222b72e0cd8","name":"Sequentially","func":"// On Start\nlet msg1 = {}\nlet msg2 = {}\nlet msg3 = {}\n\nlet aArray = context.get('aArray')\nlet aCount = context.get('aCount')\nlet pFront = context.get('pFront')\nlet pBack = context.get('pBack')\n\nconst invar = msg.payload\nnode.status(invar)\n\nswitch (invar) {\n    case 1:\n        if(aCount === 3) {\n            // \n        } else {\n            aCount += 1\n            pFront = (pFront += 1)% 3\n            aArray[pFront] = 1\n        }   \n        break\n    case 0:\n        if (aCount === 0) {\n            //\n        } else {\n            aCount -= 1\n            pBack = (pBack += 1)% 3\n            aArray[pBack] = 0\n        }\n        break\n}\n\ncontext.set('aArray', aArray)\ncontext.set('aCount', aCount)\ncontext.set('pFront', pFront)\ncontext.set('pBack', pBack)\nmsg1.payload = aArray[0]\nmsg2.payload = aArray[1]\nmsg3.payload = aArray[2]\n\nreturn [msg1, msg2, msg3]\n","outputs":3,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n\ncontext.set('aArray', [0,0,0])\ncontext.set('aCount', 0)\ncontext.set('pFront', -1)\ncontext.set('pBack', -1)\n\nlet msg1 = {payload: 0}\nlet msg2 = {payload: 0}\nlet msg3 = {payload: 0}\n\nnode.send([msg1, msg2, msg3])\n\n","finalize":"","libs":[],"x":450,"y":620,"wires":[["b2fb7d0b6d673ae4"],["ee98fa52b13f762a"],["3f81707ba7938056"]]},{"id":"fd2237af7b29a6b1","type":"inject","z":"f2641222b72e0cd8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":190,"y":580,"wires":[["6e99ce3f0e3ddd4a"]]},{"id":"b26f21866fc65e87","type":"inject","z":"f2641222b72e0cd8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":190,"y":660,"wires":[["6e99ce3f0e3ddd4a"]]},{"id":"b2fb7d0b6d673ae4","type":"debug","z":"f2641222b72e0cd8","name":"debug 267","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":690,"y":560,"wires":[]},{"id":"ee98fa52b13f762a","type":"debug","z":"f2641222b72e0cd8","name":"debug 268","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":690,"y":620,"wires":[]},{"id":"3f81707ba7938056","type":"debug","z":"f2641222b72e0cd8","name":"debug 269","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":690,"y":680,"wires":[]},{"id":"6e99ce3f0e3ddd4a","type":"junction","z":"f2641222b72e0cd8","x":320,"y":620,"wires":[["2dccd787ae6ed074"]]}]

This is working brilliantly.

[{"id":"2dccd787ae6ed074","type":"function","z":"427b38e8dd718872","name":"Sequentially","func":"// On Start\nlet msg1 = {}\nlet msg2 = {}\nlet msg3 = {}\n\nlet aArray = context.get('aArray')\nlet aCount = context.get('aCount')\nlet pFront = context.get('pFront')\nlet pBack = context.get('pBack')\n\nconst invar = msg.payload\nnode.status(invar)\n\nswitch (invar) {\n    case 1:\n        if(aCount === 3) {\n            // \n        } else {\n            aCount += 1\n            pFront = (pFront += 1)% 3\n            aArray[pFront] = 1\n        }   \n        break\n    case 0:\n        if (aCount === 0) {\n            //\n        } else {\n            aCount -= 1\n            pBack = (pBack += 1)% 3\n            aArray[pBack] = 0\n        }\n        break\n}\n\ncontext.set('aArray', aArray)\ncontext.set('aCount', aCount)\ncontext.set('pFront', pFront)\ncontext.set('pBack', pBack)\nmsg1.payload = aArray[0]\nmsg2.payload = aArray[1]\nmsg3.payload = aArray[2]\n\nreturn [msg1, msg2, msg3]\n","outputs":3,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n\ncontext.set('aArray', [0,0,0])\ncontext.set('aCount', 0)\ncontext.set('pFront', -1)\ncontext.set('pBack', -1)\n\nlet msg1 = {payload: 0}\nlet msg2 = {payload: 0}\nlet msg3 = {payload: 0}\n\nnode.send([msg1, msg2, msg3])\n\n","finalize":"","libs":[],"x":656.2653732299805,"y":229.27309226989746,"wires":[["b2fb7d0b6d673ae4"],["ee98fa52b13f762a"],["3f81707ba7938056"]]},{"id":"b2fb7d0b6d673ae4","type":"debug","z":"427b38e8dd718872","name":"debug 267","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":838.2653732299805,"y":169.27309036254883,"wires":[]},{"id":"ee98fa52b13f762a","type":"debug","z":"427b38e8dd718872","name":"debug 268","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":838.2653732299805,"y":229.27309036254883,"wires":[]},{"id":"3f81707ba7938056","type":"debug","z":"427b38e8dd718872","name":"debug 269","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":838.2653732299805,"y":289.2730903625488,"wires":[]},{"id":"397d01c92ab99c26","type":"switch","z":"427b38e8dd718872","name":"Charge Amps","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"20","vt":"num"},{"t":"lt","v":"2","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":251.39613342285156,"y":226.6653652191162,"wires":[["d6d5b8096fd7fdfd"],["23fe357e2cdb1793"]]},{"id":"d6d5b8096fd7fdfd","type":"change","z":"427b38e8dd718872","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":438.3961486816406,"y":196.5115261077881,"wires":[["6e99ce3f0e3ddd4a"]]},{"id":"23fe357e2cdb1793","type":"change","z":"427b38e8dd718872","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":437.4038391113281,"y":254.50382041931152,"wires":[["6e99ce3f0e3ddd4a"]]},{"id":"5c0a94fdd048349c","type":"inject","z":"427b38e8dd718872","name":"High Charge","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"21","payloadType":"num","x":101.39999389648438,"y":163.98078060150146,"wires":[["397d01c92ab99c26"]]},{"id":"e282ef3bb3514a85","type":"inject","z":"427b38e8dd718872","name":"Low Charge","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":101.39614868164062,"y":290.97691917419434,"wires":[["397d01c92ab99c26"]]},{"id":"6e99ce3f0e3ddd4a","type":"junction","z":"427b38e8dd718872","x":562.7551673054695,"y":230.21187245845795,"wires":[["2dccd787ae6ed074"]]}]

I need then to further control things with respect to state of battery charge: over 50% allow only one element, over 70% allow two elements and over 90% allow all three.
I've tried all sorts of AND gate arrangements but can't get it right.

The input payload of 0,1,2 or 3 sounds interesting. I need to read the number of relays "on" and compare to the number that are "allowed" depending on Battery SoC. Then I can send a message through the flow to switch on or off another relay, or do nothing.
@BartButenaers, how could I read that node to know how many relays are "on" ?

I think it might be better to adjust the function so that you tell it how many you want on.

From your feedback I assume you are using the flow from @Frida. Didn't have a chance yet to look at his code, so it might be better if he adds this feature to his flow.

Ah, I thought Frida's flow was based on your node.

Try this one.

[{"id":"2dccd787ae6ed074","type":"function","z":"f2641222b72e0cd8","name":"Sequentially","func":"// On Start\nlet msg1 = {}\nlet msg2 = {}\nlet msg3 = {}\nlet msg4 = {}\n\nlet aArray = context.get('aArray')\nlet aCount = context.get('aCount')\nlet pFront = context.get('pFront')\nlet pBack = context.get('pBack')\n\nconst invar = msg.payload\n\nswitch (invar) {\n    case 1:\n        if(aCount === 3) {\n            // \n        } else {\n            aCount += 1\n            pFront = (pFront += 1)% 3\n            aArray[pFront] = 1\n        }   \n        break\n    case 0:\n        if (aCount === 0) {\n            //\n        } else {\n            aCount -= 1\n            pBack = (pBack += 1)% 3\n            aArray[pBack] = 0\n        }\n        break\n}\n\nnode.status('Input ' + invar + ' Count ' + aCount)\n\ncontext.set('aArray', aArray)\ncontext.set('aCount', aCount)\ncontext.set('pFront', pFront)\ncontext.set('pBack', pBack)\nmsg1.payload = aArray[0]\nmsg2.payload = aArray[1]\nmsg3.payload = aArray[2]\nmsg4.payload = aCount\n\nreturn [msg1, msg2, msg3,msg4]\n","outputs":4,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n\ncontext.set('aArray', [0,0,0])\ncontext.set('aCount', 0)\ncontext.set('pFront', -1)\ncontext.set('pBack', -1)\n\nlet msg1 = {payload: 0}\nlet msg2 = {payload: 0}\nlet msg3 = {payload: 0}\nlet msg4 = {payload: 0}\nnode.send([msg1, msg2, msg3, msg4])\nnode.status('Input ' + 0 + ' Count ' + 0)\n\n","finalize":"","libs":[],"x":450,"y":620,"wires":[["b2fb7d0b6d673ae4"],["ee98fa52b13f762a"],["3f81707ba7938056"],["137fd7b270de88b2"]]},{"id":"fd2237af7b29a6b1","type":"inject","z":"f2641222b72e0cd8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":190,"y":580,"wires":[["6e99ce3f0e3ddd4a"]]},{"id":"b26f21866fc65e87","type":"inject","z":"f2641222b72e0cd8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":190,"y":660,"wires":[["6e99ce3f0e3ddd4a"]]},{"id":"b2fb7d0b6d673ae4","type":"debug","z":"f2641222b72e0cd8","name":"debug 267","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":690,"y":560,"wires":[]},{"id":"ee98fa52b13f762a","type":"debug","z":"f2641222b72e0cd8","name":"debug 268","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":690,"y":620,"wires":[]},{"id":"3f81707ba7938056","type":"debug","z":"f2641222b72e0cd8","name":"debug 269","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":690,"y":680,"wires":[]},{"id":"137fd7b270de88b2","type":"debug","z":"f2641222b72e0cd8","name":"Count","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":670,"y":740,"wires":[]},{"id":"6e99ce3f0e3ddd4a","type":"junction","z":"f2641222b72e0cd8","x":320,"y":620,"wires":[["2dccd787ae6ed074"]]}]
1 Like

That's exactly what I asked for, thank you.
I've now got the Charge current flow working as it should. It tries to turn on one relay or turn off one relay depending on charge current thresholds, which operates through the sequential node.
I've also got the SoC flow working so that a "set flow state" node counts the number of relays "allowed".
I need to get the "context based routing" node to then allow or block messages from the Charge Amps switch node depending on how the number of relays "on" compares to the number "allowed".

[{"id":"397d01c92ab99c26","type":"switch","z":"427b38e8dd718872","name":"Charge Amps","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"20","vt":"num"},{"t":"lt","v":"2","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":215.39614868164062,"y":504.66521072387695,"wires":[["d6d5b8096fd7fdfd"],["23fe357e2cdb1793"]]},{"id":"d6d5b8096fd7fdfd","type":"change","z":"427b38e8dd718872","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":414.3961486816406,"y":432.51140213012695,"wires":[["a4e09f3d94470dc7"]]},{"id":"23fe357e2cdb1793","type":"change","z":"427b38e8dd718872","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":414.40380859375,"y":579.5038328170776,"wires":[["b5344c3eb792e316"]]},{"id":"9b9a565329868d48","type":"function","z":"427b38e8dd718872","name":"Sequentially","func":"// On Start\nlet msg1 = {}\nlet msg2 = {}\nlet msg3 = {}\nlet msg4 = {}\n\nlet aArray = context.get('aArray')\nlet aCount = context.get('aCount')\nlet pFront = context.get('pFront')\nlet pBack = context.get('pBack')\n\nconst invar = msg.payload\n\nswitch (invar) {\n    case 1:\n        if(aCount === 3) {\n            // \n        } else {\n            aCount += 1\n            pFront = (pFront += 1)% 3\n            aArray[pFront] = 1\n        }   \n        break\n    case 0:\n        if (aCount === 0) {\n            //\n        } else {\n            aCount -= 1\n            pBack = (pBack += 1)% 3\n            aArray[pBack] = 0\n        }\n        break\n}\n\nnode.status('Input ' + invar + ' Count ' + aCount)\n\ncontext.set('aArray', aArray)\ncontext.set('aCount', aCount)\ncontext.set('pFront', pFront)\ncontext.set('pBack', pBack)\nmsg1.payload = aArray[0]\nmsg2.payload = aArray[1]\nmsg3.payload = aArray[2]\nmsg4.payload = aCount\n\nreturn [msg1, msg2, msg3,msg4]\n","outputs":4,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n\ncontext.set('aArray', [0,0,0])\ncontext.set('aCount', 0)\ncontext.set('pFront', -1)\ncontext.set('pBack', -1)\n\nlet msg1 = {payload: 0}\nlet msg2 = {payload: 0}\nlet msg3 = {payload: 0}\nlet msg4 = {payload: 0}\nnode.send([msg1, msg2, msg3, msg4])\nnode.status('Input ' + 0 + ' Count ' + 0)\n\n","finalize":"","libs":[],"x":654.2653160095215,"y":579.0000019073486,"wires":[["445c021019c3b8f3"],["4a4383b3bee043ee"],["2cdbea9e4a1f5cd0"],["531126a1be7b95e7"]]},{"id":"445c021019c3b8f3","type":"debug","z":"427b38e8dd718872","name":"debug 267","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":869.2653121948242,"y":508.99999618530273,"wires":[]},{"id":"4a4383b3bee043ee","type":"debug","z":"427b38e8dd718872","name":"debug 268","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":869.2653121948242,"y":570.9999961853027,"wires":[]},{"id":"2cdbea9e4a1f5cd0","type":"debug","z":"427b38e8dd718872","name":"debug 269","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":871.2653121948242,"y":629.9999656677246,"wires":[]},{"id":"531126a1be7b95e7","type":"debug","z":"427b38e8dd718872","name":"Count","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":863.2653121948242,"y":701.9999952316284,"wires":[]},{"id":"a4e09f3d94470dc7","type":"switch","z":"427b38e8dd718872","name":"Context based routing","property":"state","propertyType":"flow","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"}],"checkall":"true","repair":false,"outputs":3,"x":628.2653427124023,"y":433.1345558166504,"wires":[[],[],["b5344c3eb792e316"]]},{"id":"67ade33ae95ace29","type":"inject","z":"427b38e8dd718872","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"21","payloadType":"num","x":68,"y":454.9154243469238,"wires":[["397d01c92ab99c26"]]},{"id":"b1f41935c86bae0f","type":"inject","z":"427b38e8dd718872","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":68.40383911132812,"y":560.9153928756714,"wires":[["397d01c92ab99c26"]]},{"id":"b5344c3eb792e316","type":"junction","z":"427b38e8dd718872","x":552.3887345716357,"y":579.398544549942,"wires":[["9b9a565329868d48"]]}]

Or am I barking up the wrong tree?

Or am I barking up the wrong tree?

No. We just have to understand each other.
When you write that you want 1 relay, they must turn on 1,2,3...
When you write that you want 2 relays, they must turn on, 12, 23,31...
When you write that you want 3 relays, they must switch on 123, 123...

If, for example, relay 1 is on (and the others off) and another 1 is passed in, then should it not just leave relay 1 on?