Reusing the part of the code (or even the node) in many places - or is there any easier way to repeat similar tasks?

Hi,
So I heavily use NR for interaction with my IoT devices (of course).
In the particular case below I am controlling the sleeping device that uses ESPnow to talk to non-sleeping device (gateway) and then data goes to Home Assistant.
I built in the functionality to send the command to the sleeping device from the gateway. To do that I need first send the command over MQTT to the gateway, and it will then relay this message to the sensor device when the latter wakes up.

Gateway serves multiple end devices (20+). The way to relay the command to particular end device is to address it properly. So it looks like this:
{"mac":"2a:01:01:01:00:21","command":"1"}
where mac is the address of the end device and command is the particular command for end device to act up on.
The only problem I am facing now is: when I want to perform bulk operations, I need to create multiple nodes, i.e. like this:


alternatively, instead of putting the entire command in the inject node, I found out other way:

but both approach require to multiply the nodes
Is there any way to make it more "clever" instead of copy/paste nodes and changing their content?
what I was thinking is:
1- build the universal node with the full mac and command
2- provide the node that will only inject MAC and the COMMAND
3- the first node will then replace the command and mac with the proviced one
4- press and done

If someone did such, please share the idea how to - thx.

You can create the command payload and the topics in the function node, so input node > function > mqtt out, is all that is required. You can use node.send() within a loop in the function to send topic and command payloads to mqtt out node.

If you supply example commands topics and input payloads it would be easier to offer you an example.

The Node-RED Editor is not really meant to be used as a dashboard so you might be better off creating your own page using Dashboard. Then you can do things like have a button with an associated input field - maybe a drop-down to save having all those buttons and then you could add a 2nd button to trigger them all.

this I did here:

but if the function node could be common and do the replacement of the command (see on the left side: 6 commands already and 6 function nodes) and mac...
instead of creating 20+ such nodes

the topic(s) are 3 as there are 3 gateways and you never know which one will be used by sensor device when it wakes up so command has to go to all gateways:

esp32027/cmd/cmd_for_sender
esp32029/cmd/cmd_for_sender
esp32030/cmd/cmd_for_sender

the command(s):

{ "mac": "2a:01:01:01:00:35", "command": "4" }

where mac address changes (last 2 bytes) and command changes (1 byte)

Images text can not be copied, so if you want an example please supply what has been asked for.
[Edit] just seen other post, when i get home i will make an example. Still require incoming messages fro inject nodes

I agree with you! Function + different topics would solve this.

thank you @E1cid
there is NOTHING in incoming node
the idea is:
1- create message "{ "mac": "2a:01:01:01:00:35", "command": "4" }" on topic "esp32027/cmd/cmd_for_sender" (on 3 topics, as in the above post)
2- send it to all 3 gateways

what I am looking for is to somehow have less clicks - let my describe again:
1- create universal node (or group of nodes) that accept 2 input variables:

  • mac
  • command
    2- reuse such node whenever needed

I am thinking loudly now: will this be the same what I did today?
Still there has to be a choice of:
1- which command to send
2- which sender (so mac) to send to

I have programmed different command for different actions, i.e.:

  - ota:      {"mac":"xx:01:01:01:zz:yy","command":"1"} 
  - restart:  {"mac":"xx:01:01:01:zz:yy","command":"2"} 
  - captive:  {"mac":"xx:01:01:01:zz:yy","command":"3"} 
  - factory:  {"mac":"xx:01:01:01:zz:yy","command":"4"} 
  - Motion OFF:  {"mac":"xx:01:01:01:zz:yy","command":"10"} 
  - Motion ON:  {"mac":"xx:01:01:01:zz:yy","command":11"} 
  - LEDs OFF: {"mac":"xx:01:01:01:zz:yy","command":"200"}  - LEDs completely OFF 
  - LEDs AUTO:{"mac":"xx:01:01:01:zz:yy","command":"201"}  - LEDs will dimm, lux < 1->dc = 1, 1<x<10 = 10, > 10 = 255 

of course more commands can be programmed (command is of type int_8 so up to 256 commands can be programmed)
and that is what I am "afraid" of: my Node Red will have million pages with different commands considering 20+ sensors (soon it will come to 30)

Or maybe my approach is wrong?
But I am not able to talk to sensor device as it sleeps and it does NOT use wifi - only ESPnow with gateway device

btw the project is public and here:

I always think that way - if I have to populate anything quite similar - code, nodes, anything, then this should and must be replaced by code. It is not because it is extra handwork/hard to change but it can cause a numerous errors hard to debug. I suggest to go for @E1cid suggested way - organize by mqtt topics.
Btw you have an option to set rules in ESP code for incoming and outgoing mqqt and then later let the function to sort out. For instance you do not have need for addressing to particular ESP macadress, you can send mqtt to all ESP listening to the same topic and let the ESP to sort out whether it is a message for it. Json is also great way to standartize messaging across devices.

topic different? - the topics are constant: 3 topics as there are 3 gateways
messages are changing: parameter "mac" and parameter "command" depending who to send to (mac) and which command to invoke (command)

thank you @Vilnis_R
so thinking loudly again:
1- gateway is programmed to listen on the topic:
"gatewayname/cmd/cmd_for_sender"
so in case of gateway "esp32027" the topic it listens to is:
esp32027/cmd/cmd_for_sender
2- the content of the message includes:

  • address of the device it is supposed to
  • command to be executed by that device (the sleeping one)
    3- thanks to such approach, irrespectively how many sensor devices are in the network and how many gateways in the network - always the message is the same and depending only on the "end device" and the command it should perform
    4- so with that in place, I think the only way of manipulation is the Node Red or HA

or am I wrong?

no, the end devices are not using mqtt - only espnow, and only when they wake up

the gateway puts all received MQTT messages in the queue and when sensor device wakes up and connects to gateway, then gateway checks if there is anything in the queue for this device and it sends it there - otherwise it sends "0" just to confirm that message from sensor device was received

Well, at the beginning you can store all macadresses into the array and the same you can do for commands. For that you can create json array for instance that holds all macs as objects and use keys for commands. When you are triggering device you just send short two numbers that lets the function to pick up device & command and then the function pass it to all 3 gateways. Are 3 gateways are necessary? Mby there can be one instead of 3?
If you have some kind of triggering algorithm You can put it in the function too.

  1. good idea but I am "afraid" of doing ANYTHING manually, unless such array can be automatically created by MAC that is also visible on HA in one of the sensors - I am trying as strong as possible to have "manual work free" environment
  2. 3 gateways are for 2 reasons:
  • increasing the range of the network (few sensors are outside the house, house has few floors etc.)
  • since some of the devices have direct impact on my heating (and now is the winter) I need to have 100% reliable network - the sensor device will send to 1 of the 3 gateways only, but if there is no ACK received it will try next gateway and then next - of course if all 3 fail, then nothing works
  • also sensors control lights as each sensor has light measurement and motion sensor so high reliability is needed but still heating is the most important

Remark:
Sensor device has also wifi credentials stored (wifi_ok sensor) for the reason of OTA - but only OTA

Example flow

[{"id":"5e83fee07ef4ea01","type":"inject","z":"da8a6ef0b3c9a5c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"mac\":\"xx:01:01:01:zz:yy\",\"command\":\"1\"}","payloadType":"json","x":210,"y":4580,"wires":[["bf5fbbafd94a885f"]]},{"id":"bf5fbbafd94a885f","type":"function","z":"da8a6ef0b3c9a5c8","name":"function 20","func":"const topics =[\n    \"esp32027/cmd/cmd_for_sender\",\n    \"esp32029/cmd/cmd_for_sender\",\n    \"esp32030/cmd/cmd_for_sender\"\n];\ntopics.forEach(str => {\n    msg.topic = str;\n    node.send(msg);\n})\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":4600,"wires":[["eb74b20280fde506","948c50cb943cd974"]]},{"id":"d4accf76111fbe5b","type":"inject","z":"da8a6ef0b3c9a5c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"mac\":\"xx:01:01:01:zz:yy\",\"command\":\"2\"}","payloadType":"json","x":210,"y":4620,"wires":[["bf5fbbafd94a885f"]]},{"id":"eb74b20280fde506","type":"debug","z":"da8a6ef0b3c9a5c8","name":"debug 231","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":4660,"wires":[]},{"id":"948c50cb943cd974","type":"mqtt out","z":"da8a6ef0b3c9a5c8","name":"","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"e8ba3ef5.22f4a8","x":690,"y":4580,"wires":[]},{"id":"e8ba3ef5.22f4a8","type":"mqtt-broker","name":"testb","broker":"192.168.1.25","port":"1883","clientid":"node-red-test","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

Simple example of selecting mac and command frow ui_dashboard dropdowns and sending to all topics. This is a simple example and will require some validation and checks before sending payloads to topics.

[{"id":"40af0b19a53969e3","type":"ui_dropdown","z":"da8a6ef0b3c9a5c8","name":"","label":"Mac's","tooltip":"","place":"Select option","group":"2d4fe667.28f8ba","order":19,"width":0,"height":0,"passthru":false,"multiple":false,"options":[{"label":"xx:01:01:01:zz:yy","value":"xx:01:01:01:zz:yy","type":"str"},{"label":"xx:01:01:01:zz:xx","value":"xx:01:01:01:zz:xx","type":"str"},{"label":"xx:01:01:01:zz:zz","value":"xx:01:01:01:zz:zz","type":"str"}],"payload":"","topic":"mac","topicType":"str","className":"","x":310,"y":4660,"wires":[["b5abf9ac49ebd729"]]},{"id":"b5abf9ac49ebd729","type":"join","z":"da8a6ef0b3c9a5c8","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":490,"y":4660,"wires":[["bf5fbbafd94a885f"]]},{"id":"f993227bc94e2cd7","type":"ui_dropdown","z":"da8a6ef0b3c9a5c8","name":"","label":"","tooltip":"","place":"Select option","group":"2d4fe667.28f8ba","order":20,"width":0,"height":0,"passthru":true,"multiple":false,"options":[{"label":"ota","value":"1","type":"str"},{"label":"restart","value":"2","type":"str"},{"label":"captive","value":"3","type":"str"}],"payload":"","topic":"command","topicType":"str","className":"","x":340,"y":4720,"wires":[["b5abf9ac49ebd729"]]},{"id":"bf5fbbafd94a885f","type":"function","z":"da8a6ef0b3c9a5c8","name":"function 20","func":"const topics =[\n    \"esp32027/cmd/cmd_for_sender\",\n    \"esp32029/cmd/cmd_for_sender\",\n    \"esp32030/cmd/cmd_for_sender\"\n];\nmsg.payload = {\n    command: msg.payload.command, \n    mac: msg.payload.mac\n};\n    \ntopics.forEach(str => {\n    msg.topic = str;\n    node.send(msg);\n})\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":4660,"wires":[["eb74b20280fde506"]]},{"id":"eb74b20280fde506","type":"debug","z":"da8a6ef0b3c9a5c8","name":"debug 231","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":810,"y":4660,"wires":[]},{"id":"2d4fe667.28f8ba","type":"ui_group","name":"demo","tab":"1caa8458.b17814","order":2,"disp":true,"width":"12","collapse":false},{"id":"1caa8458.b17814","type":"ui_tab","name":"Demo","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Of course arrays can be created automatically upon the results of Your test batch. You can make automatically arrays for the test results, you can use them later for alarm triggering etc. However some kind of "initial settings" array also must be (in my opinion) where you fill macs & action pattern once and update when some device is added to your network.
If I look at this "I have programmed different command for different actions, i.e.:"
I think that better organization would be by MAC not the command approach. Macs are objects (lets say 30 macs), come commands will be common to all, some unique, so store them as keys - some can be boolean, some ints etc.
So instance by that way you can trigger battery status on one device, you can trigger all battery status on all macs using one command consisting of 2 ints (1 for mac, 2nd for command). You can loop through all macs and all commands at once to perform all actions. Etc.

ok, so let me show you the situation here:
from "my" approach:

[{"id":"2665949582a5bcde","type":"mqtt out","z":"83f2cc172699056d","name":"","topic":"esp32027/cmd/cmd_for_sender","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"dab2e4d677f055f8","x":2110,"y":180,"wires":[]},{"id":"d040127c5efd153c","type":"mqtt out","z":"83f2cc172699056d","name":"","topic":"esp32029/cmd/cmd_for_sender","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"dab2e4d677f055f8","x":2110,"y":240,"wires":[]},{"id":"1ea982f20780e9d7","type":"mqtt out","z":"83f2cc172699056d","name":"","topic":"esp32030/cmd/cmd_for_sender","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"dab2e4d677f055f8","x":2110,"y":300,"wires":[]},{"id":"72179907fa13982f","type":"function","z":"83f2cc172699056d","name":"035-11- motion ON","func":"\nmsg.payload =\n{ \"mac\": \"2a:01:01:01:00:35\", \"command\": \"11\" }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1650,"y":320,"wires":[["d040127c5efd153c","2665949582a5bcde","1ea982f20780e9d7"]]},{"id":"5a306171cf318312","type":"function","z":"83f2cc172699056d","name":"035-10- motion OFF","func":"\nmsg.payload =\n{ \"mac\": \"2a:01:01:01:00:35\", \"command\": \"10\" }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1660,"y":360,"wires":[["2665949582a5bcde","d040127c5efd153c","1ea982f20780e9d7"]]},{"id":"6520692f10c474ea","type":"function","z":"83f2cc172699056d","name":"035-1- FW update","func":"\nmsg.payload =\n{ \"mac\": \"2a:01:01:01:00:35\", \"command\": \"1\" }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1650,"y":160,"wires":[["2665949582a5bcde","d040127c5efd153c","1ea982f20780e9d7"]]},{"id":"ef310c3d72e56597","type":"function","z":"83f2cc172699056d","name":"035-2- restart","func":"\nmsg.payload =\n{ \"mac\": \"2a:01:01:01:00:35\", \"command\": \"2\" }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1640,"y":200,"wires":[["1ea982f20780e9d7","d040127c5efd153c","2665949582a5bcde"]]},{"id":"9c65e7214fe7f43f","type":"function","z":"83f2cc172699056d","name":"035-3- CP","func":"\nmsg.payload =\n{ \"mac\": \"2a:01:01:01:00:35\", \"command\": \"3\" }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1630,"y":240,"wires":[["1ea982f20780e9d7","d040127c5efd153c","2665949582a5bcde"]]},{"id":"9dbfc94a2662ef62","type":"function","z":"83f2cc172699056d","name":"035-4 - factory","func":"\nmsg.payload =\n{ \"mac\": \"2a:01:01:01:00:35\", \"command\": \"4\" }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1640,"y":280,"wires":[["1ea982f20780e9d7","d040127c5efd153c","2665949582a5bcde"]]},{"id":"17650982502d3136","type":"inject","z":"83f2cc172699056d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":160,"wires":[["6520692f10c474ea"]]},{"id":"f4a47b9206e4df15","type":"inject","z":"83f2cc172699056d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":200,"wires":[["ef310c3d72e56597"]]},{"id":"8c6abdfe6b7698af","type":"inject","z":"83f2cc172699056d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":240,"wires":[["9c65e7214fe7f43f"]]},{"id":"4e75d26ca5cba05b","type":"inject","z":"83f2cc172699056d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":280,"wires":[["9dbfc94a2662ef62"]]},{"id":"3274c8c843708894","type":"inject","z":"83f2cc172699056d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":320,"wires":[["72179907fa13982f"]]},{"id":"90a179b468c5d707","type":"inject","z":"83f2cc172699056d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":360,"wires":[["5a306171cf318312"]]},{"id":"dab2e4d677f055f8","type":"mqtt-broker","name":"HA MQTT","broker":"192.168.1.43","port":"1883","tls":"","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

from your approach:

[{"id":"5e83fee07ef4ea01","type":"inject","z":"83f2cc172699056d","name":"mac 1, command 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"mac\":\"2a:01:01:01:00:35\",\"command\":\"0\"}","payloadType":"json","x":1550,"y":500,"wires":[["bf5fbbafd94a885f"]]},{"id":"bf5fbbafd94a885f","type":"function","z":"83f2cc172699056d","name":"send to all gateways","func":"const topics =[\n    \"esp32027/cmd/cmd_for_sender\",\n    \"esp32029/cmd/cmd_for_sender\",\n    \"esp32030/cmd/cmd_for_sender\"\n];\ntopics.forEach(str => {\n    msg.topic = str;\n    node.send(msg);\n})\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1820,"y":520,"wires":[["948c50cb943cd974"]]},{"id":"d4accf76111fbe5b","type":"inject","z":"83f2cc172699056d","name":"mac 1, command 2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"mac\":\"2a:01:01:01:00:35\",\"command\":\"22\"}","payloadType":"json","x":1550,"y":540,"wires":[["bf5fbbafd94a885f"]]},{"id":"948c50cb943cd974","type":"mqtt out","z":"83f2cc172699056d","name":"","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"dab2e4d677f055f8","x":2050,"y":520,"wires":[]},{"id":"dab2e4d677f055f8","type":"mqtt-broker","name":"HA MQTT","broker":"192.168.1.43","port":"1883","tls":"","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

when the message or topic are in the inject node, there is no way to invoke it from any other automation, that is why I moved to: message in function node, topic in mqtt node
What I like in your approach is: all topics in 1 function node and only 1 mqtt node - it is already improvement

so similar approach (but I don't know yet how) would be for all commands and all macs
what you were suggesting (no, not you, @Vilnis_R ) is to:

  • all commands possible in the array
  • all macs possible (but that can be automatically gathered from MACs available on HA) in array
  • then, the command and the mac to be chose either from the drop list or by providing proper message payload i.e. {"mac":xxx, "1"} for command 1 to be send to mac xxx
    yet, although commands are fixed meaning: "1 - ota, 2 - something else" the macs have to be specified in full each time, right?

Send the command and mac from the other automation. You could also store commands and mac's then reference the store from any where.

1 Like

beautiful,
but believe me it is my first time with dashboard
where should I "click" to execute? (sorry for such blabla question :wink: )


you can add a button to execute then check topic is send before allowing the function to run.
e.g.

[{"id":"40af0b19a53969e3","type":"ui_dropdown","z":"da8a6ef0b3c9a5c8","name":"","label":"Mac's","tooltip":"","place":"Select option","group":"2d4fe667.28f8ba","order":19,"width":0,"height":0,"passthru":false,"multiple":false,"options":[{"label":"xx:01:01:01:zz:yy","value":"xx:01:01:01:zz:yy","type":"str"},{"label":"xx:01:01:01:zz:xx","value":"xx:01:01:01:zz:xx","type":"str"},{"label":"xx:01:01:01:zz:zz","value":"xx:01:01:01:zz:zz","type":"str"}],"payload":"","topic":"mac","topicType":"str","className":"","x":310,"y":4720,"wires":[["b5abf9ac49ebd729"]]},{"id":"b5abf9ac49ebd729","type":"join","z":"da8a6ef0b3c9a5c8","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":490,"y":4720,"wires":[["75a8cdcb7a2943f5"]]},{"id":"f993227bc94e2cd7","type":"ui_dropdown","z":"da8a6ef0b3c9a5c8","name":"","label":"commands","tooltip":"","place":"Select option","group":"2d4fe667.28f8ba","order":20,"width":0,"height":0,"passthru":true,"multiple":false,"options":[{"label":"ota","value":"1","type":"str"},{"label":"restart","value":"2","type":"str"},{"label":"captive","value":"3","type":"str"}],"payload":"","topic":"command","topicType":"str","className":"","x":350,"y":4780,"wires":[["b5abf9ac49ebd729"]]},{"id":"3d5993380e3ca2a9","type":"ui_button","z":"da8a6ef0b3c9a5c8","name":"","group":"2d4fe667.28f8ba","order":21,"width":0,"height":0,"passthru":false,"label":"send","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"send","topicType":"str","x":350,"y":4820,"wires":[["b5abf9ac49ebd729"]]},{"id":"75a8cdcb7a2943f5","type":"switch","z":"da8a6ef0b3c9a5c8","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"send","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":530,"y":4660,"wires":[["bf5fbbafd94a885f"]]},{"id":"bf5fbbafd94a885f","type":"function","z":"da8a6ef0b3c9a5c8","name":"function 20","func":"const topics =[\n    \"esp32027/cmd/cmd_for_sender\",\n    \"esp32029/cmd/cmd_for_sender\",\n    \"esp32030/cmd/cmd_for_sender\"\n];\nmsg.payload = {\n    command: msg.payload.command, \n    mac: msg.payload.mac\n};\n    \ntopics.forEach(str => {\n    msg.topic = str;\n    node.send(msg);\n})\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":4720,"wires":[["eb74b20280fde506"]]},{"id":"eb74b20280fde506","type":"debug","z":"da8a6ef0b3c9a5c8","name":"debug 231","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":810,"y":4720,"wires":[]},{"id":"2d4fe667.28f8ba","type":"ui_group","name":"demo","tab":"1caa8458.b17814","order":2,"disp":true,"width":"12","collapse":false},{"id":"1caa8458.b17814","type":"ui_tab","name":"Demo","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Also you may want to check that the mac and command is set before executing the function to send commands.