Mqtt monitoring / syncing states, need bit of help

Hello,
I'm quite confident programmer, but as well quite new to node-red so I can solve my issue in other programming languages, but in nodered i need bit of help.

I have let's say two different MQTT topics, which I need to have synced when CMND is received (they are 2way switches with tasmota - it's not really important).

I have something like

home/switch1/state/POWER ON
home/switch2/state/POWER ON

when one switch receives CMND like this

home/switch1/cmnd/POWER OFF

I do need to sync 2nd topic with command option as well

home/switch2/cmnd/POWER OFF

So I've done this flow, which is doing exactly what I want:

[{"id":"de00f0ac.25ef","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"9f39a99.521b058","type":"mqtt in","z":"de00f0ac.25ef","name":"Hall Stairs","topic":"home/hallfloor/light/stat/POWER2","qos":"0","datatype":"utf8","broker":"e1def315.bf1db","x":120,"y":80,"wires":[["24d6ec68.e0a4d4","ea60d3cf.40c1f"]]},{"id":"39f7f1dc.cfe82e","type":"mqtt in","z":"de00f0ac.25ef","name":"Stairs (slave)","topic":"home/staircase/light/stat/POWER","qos":"0","datatype":"utf8","broker":"e1def315.bf1db","x":110,"y":120,"wires":[["ea60d3cf.40c1f","24d6ec68.e0a4d4"]]},{"id":"2923a833.269348","type":"change","z":"de00f0ac.25ef","name":"store","rules":[{"t":"set","p":"state-hall-pwr2","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":80,"wires":[[]]},{"id":"24d6ec68.e0a4d4","type":"switch","z":"de00f0ac.25ef","name":"state","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"state-hall-pwr2","vt":"flow"},{"t":"neq","v":"state-hall-pwr2","vt":"flow"}],"checkall":"true","repair":false,"outputs":2,"x":270,"y":80,"wires":[["2923a833.269348"],["2923a833.269348","dc735068.855ce"]]},{"id":"ea60d3cf.40c1f","type":"switch","z":"de00f0ac.25ef","name":"state","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"state-stairs","vt":"flow"},{"t":"neq","v":"state-stairs","vt":"flow"}],"checkall":"true","repair":false,"outputs":2,"x":270,"y":120,"wires":[["2231f9a.286b906"],["2231f9a.286b906","c7485e32.69b55"]]},{"id":"2231f9a.286b906","type":"change","z":"de00f0ac.25ef","name":"store","rules":[{"t":"set","p":"state-stairs","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":120,"wires":[[]]},{"id":"dc735068.855ce","type":"mqtt out","z":"de00f0ac.25ef","name":"CMND","topic":"home/hallfloor/light/cmnd/POWER2","qos":"","retain":"","broker":"e1def315.bf1db","x":530,"y":60,"wires":[]},{"id":"c7485e32.69b55","type":"mqtt out","z":"de00f0ac.25ef","name":"CMND","topic":"home/staircase/light/cmnd/POWER","qos":"","retain":"","broker":"e1def315.bf1db","x":530,"y":100,"wires":[]},{"id":"e1def315.bf1db","type":"mqtt-broker","z":"","name":"MQTT","broker":"10.10.1.10","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

But sometimes what is happening is rapid switching between on/off . It's not very often, but sometimes it for some not really clear reason does this. Eventho mqtt does not received CMND on and on other topic CMND off at same time.
Maybe there is sometimes some lag or smthing which causes it sometimes, dunno.

What I need to make sure somehow is, this flow will happen only once per like 3seconds or I do need to figure out some kind of safety check so one state will not override received CMND. Basically at any given time i need to have both mqtt topic synced ON or OFF.

In 99% of the time this flow works as I need to, so debugging is quite hard why it is sometimes starts rapid switching.

Any idea would be really appreciated.
Thanks

does the device send anything when it receives a CMND?

I would put a debug on both of the mqtt-in nodes and to the leg of the switch nodes going to the mqtt-out nodes.

You might want to look at the QOS and retain options on the mqtt-out nodes.

when cmnd is received devices broadcast /state/POWER which should be ignored by node-red

not really sure if qos would help, as rapid switching means both switches receiving commands for sure :wink:

You could add an RBE or similar node to effectively debounce the signals?

that's why I'm here asking for hand ...

not really familiar with all nodered nodes yet

Should it be fully symetrical? So if either one goes off or on then the other should follow it? If so then you might like to try this flow which is rather simpler and does not use the context so maybe is less likely to suffer from subtle edge case timing issues.


The inject buttons allow it to be tested without mqtt.

[{"id":"24ee01a6.0d516e","type":"mqtt in","z":"6f30c519.8daccc","name":"Hall Stairs","topic":"home/hallfloor/light/stat/POWER2","qos":"0","datatype":"utf8","broker":"62798fd.c796c7","x":90,"y":397,"wires":[["cd7e2522.8ee08"]]},{"id":"a04f3d03.87969","type":"mqtt in","z":"6f30c519.8daccc","name":"Stairs (slave)","topic":"home/staircase/light/stat/POWER","qos":"0","datatype":"utf8","broker":"62798fd.c796c7","x":99,"y":446,"wires":[["cd7e2522.8ee08"]]},{"id":"cd7e2522.8ee08","type":"join","z":"6f30c519.8daccc","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":301,"y":427,"wires":[["16071cb.120d5e3"]]},{"id":"16071cb.120d5e3","type":"function","z":"6f30c519.8daccc","name":"Follow","func":"// given payload containing \n// {\"home/staircase/light/stat/POWER\": \"ON\" or \"OFF\", \"home/hallfloor/light/stat/POWER2\": \"ON\" or \"OFF\"}\n// msg.topic indicates whether the most recent message was from POWER or POWER2\n// take no action if the two states are the same\nif (msg.payload[\"home/staircase/light/stat/POWER\"] == msg.payload[\"home/hallfloor/light/stat/POWER2\"]) {\n    // the same so do nothing\n    msg = null\n} else {\n    // they are different, we want to set the one that is not the one that triggered this message\n    // to the same value as the one that triggered the message\n    if (msg.topic === \"home/staircase/light/stat/POWER\") {\n        // The staircase state has changed, set the hall to follow it\n        msg.payload = msg.payload[\"home/staircase/light/stat/POWER\"]\n        msg.topic = \"home/hallfloor/light/cmnd/POWER2\"\n    } else {\n        // The hall state has changed, set the staircase to follow it\n        msg.payload = msg.payload[\"home/hallfloor/light/stat/POWER2\"]\n        msg.topic = \"home/staircase/light/cmnd/POWER\"        \n    }\n}\nreturn msg;","outputs":1,"noerr":0,"x":437,"y":426,"wires":[["4e0c41e8.7a0418","7181f77a.26d4f8"]]},{"id":"7181f77a.26d4f8","type":"mqtt out","z":"6f30c519.8daccc","name":"CMND","topic":"","qos":"","retain":"","broker":"62798fd.c796c7","x":612,"y":425,"wires":[]},{"id":"a0628f2c.0378c","type":"inject","z":"6f30c519.8daccc","name":"","topic":"home/hallfloor/light/stat/POWER2","payload":"ON","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":95,"y":353,"wires":[["cd7e2522.8ee08"]]},{"id":"f81c07e6.4f4a38","type":"inject","z":"6f30c519.8daccc","name":"","topic":"home/hallfloor/light/stat/POWER2","payload":"OFF","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":95,"y":308,"wires":[["cd7e2522.8ee08"]]},{"id":"b1a61cef.817a4","type":"inject","z":"6f30c519.8daccc","name":"","topic":"home/staircase/light/stat/POWER","payload":"ON","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":96,"y":550,"wires":[["cd7e2522.8ee08"]]},{"id":"4299d546.db7cbc","type":"inject","z":"6f30c519.8daccc","name":"","topic":"home/staircase/light/stat/POWER","payload":"OFF","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":98,"y":504,"wires":[["cd7e2522.8ee08"]]},{"id":"4e0c41e8.7a0418","type":"debug","z":"6f30c519.8daccc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":595,"y":503,"wires":[]},{"id":"62798fd.c796c7","type":"mqtt-broker","z":"","name":"MQTT","broker":"10.10.1.10","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

In fact I fed the inject buttons into your flow and it did not seem to be behaving as I thought it should, so maybe I misunderstand something.

Yes, it takes some playing with some of the core nodes to get the hang of them.

However, the flows site (link at the top of the forum) will help.

There are also some throttling nodes that might do the job, especially node-red-contrib-throttle:

https://flows.nodered.org/search?term=throttle

Node-RED is a prototyping tool after all so some playing is to be expected :slight_smile:

yes, it have to be fully symetrical.
Will try this one, thanks!

yeah, I still indeed find my programming languages more friendly as I clearly can see what my code is doing, nodered is bit different approach, but for applications like this I really like it.

Like anything else, Node-RED is a tool. You don't reach for a hammer when you need to unscrew something.

Node-RED is useful for prototyping. More than that, it lets you build quickly so that you can let it do all the boilerplate you've have to write if doing things purely by language. Letting you focus your programming skills on the more interesting and involved bits of logic.

You can still use JavaScript and I will often write function nodes because I find that quicker and easier to write than messing with loads of nodes. The nodes do the setup and generally the basic comms - which are really involved when doing it by code - the functions nodes do the core "business logic".

Best of both worlds! (Assuming you are happy using JavaScript!!)

1 Like

Just a thought, in order that it works immediately on node-red restart the two stat topics should be Retained topics in MQTT.

all my switches are retaining their states, so it should.
will report back when in production :wink:

edit: it works indeed, let's see if there will be some weird rapid switching after some time of using this

@Colin
how logic in Follow node would look like for three topics?

also, is this flow usable for one topic solution?
one topic means two or more switches using same mqtt topic (there is still need to invoke cmnd, as they are broadcasting only /state/ when pressed physically)
Or there is better to use something else?

Thanks!

You said you were a confident programmer so I expect you will be able to understand the existing logic in the function node. Javascript is not much different to C or other similar languages. Put a debug node showing the output of the Join node so you can see what comes out of there (and look at the Info tab for that node for an explanation). Also look in the node red docs at the pages on writing functions and on working with messages. First make sure you understand how the flow I posted works, then if you can't see how to extend it to one or three inputs come back and ask.

well I indeed can understand how that works but in your follow there is comparison between two topic and else

in case three topics logic would be 1 == 2 and 2 == 3 and 1 == 3
which sound bit complicated and full off if - elses, so I was asking basically if that's the way or if its there better one
:wink:

There may well be ways of refactoring the code for the case with three inputs. I will leave that as an exercise for the reader.

:wink:
I've done this below
This code works ok, but for some reason, it triggers cmnd 4times even there is function with 3 ouputs and rbe (its working same without rbe)

[{"id":"18be5b2c.d9e275","type":"mqtt in","z":"69c9331c.66234c","name":"Entrance","topic":"home/entrance/light/stat/POWER2","qos":"0","datatype":"utf8","broker":"e1def315.bf1db","x":420,"y":280,"wires":[["af3a41cb.e2cb3"]]},{"id":"25011ea9.c79b22","type":"mqtt in","z":"69c9331c.66234c","name":"Floor (slave)","topic":"home/hallfloor/light/stat/POWER3","qos":"0","datatype":"utf8","broker":"e1def315.bf1db","x":410,"y":360,"wires":[["af3a41cb.e2cb3"]]},{"id":"e48d33b2.80449","type":"mqtt in","z":"69c9331c.66234c","name":"Entrance (slaves)","topic":"home/entrance/lightslave/stat/POWER","qos":"0","datatype":"utf8","broker":"e1def315.bf1db","x":400,"y":320,"wires":[["af3a41cb.e2cb3"]]},{"id":"d8b23432.a44168","type":"inject","z":"69c9331c.66234c","name":"","topic":"home/entrance/light/stat/POWER2","payload":"ON","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":280,"wires":[["af3a41cb.e2cb3"]]},{"id":"4f8a472.8a830b8","type":"inject","z":"69c9331c.66234c","name":"","topic":"home/entrance/light/stat/POWER2","payload":"OFF","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":280,"wires":[["af3a41cb.e2cb3"]]},{"id":"593a370b.885898","type":"inject","z":"69c9331c.66234c","name":"","topic":"home/entrance/lightslave/stat/POWER","payload":"ON","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":320,"wires":[["af3a41cb.e2cb3"]]},{"id":"3e35acc8.afece4","type":"inject","z":"69c9331c.66234c","name":"","topic":"home/entrance/lightslave/stat/POWER","payload":"OFF","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":320,"wires":[["af3a41cb.e2cb3"]]},{"id":"3064d4af.a694fc","type":"inject","z":"69c9331c.66234c","name":"","topic":"home/hallfloor/light/stat/POWER3","payload":"ON","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":360,"wires":[["af3a41cb.e2cb3"]]},{"id":"81b25b20.449648","type":"inject","z":"69c9331c.66234c","name":"","topic":"home/hallfloor/light/stat/POWER3","payload":"OFF","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":360,"wires":[["af3a41cb.e2cb3"]]},{"id":"a33e1a26.2e5c98","type":"mqtt out","z":"69c9331c.66234c","name":"CMND","topic":"","qos":"","retain":"","broker":"e1def315.bf1db","x":930,"y":520,"wires":[]},{"id":"b269af22.19953","type":"function","z":"69c9331c.66234c","name":"Follow","func":"function areEqual(){\n   var len = arguments.length;\n   for (var i = 1; i< len; i++){\n      if (arguments[i] === null || arguments[i] !== arguments[i-1])\n         return false;\n   }\n   return true;\n}\n\nvar trigger = msg.topic;\nvar state   = msg.payload[msg.topic];\n\nvar ta = \"home/entrance/light/stat/POWER2\";\nvar tb = \"home/entrance/lightslave/stat/POWER\";\nvar tc = \"home/hallfloor/light/stat/POWER3\";\n\nvar a = msg.payload[ta];\nvar b = msg.payload[tb];\nvar c = msg.payload[tc];\n\nif( areEqual(a,b,c) )\n{\n    return false;\n}else{\n    ra = (trigger === ta) ? null : { topic: \"home/entrance/light/cmnd/POWER2\",      payload: state };\n    rb = (trigger === tb) ? null : { topic: \"home/entrance/lightslave/cmnd/POWER\",  payload: state };\n    rc = (trigger === tc) ? null : { topic: \"home/hallfloor/light/cmnd/POWER3\",     payload: state };\n}    \n\nreturn [ra,rb,rc];\n","outputs":3,"noerr":0,"x":710,"y":420,"wires":[["1b00dfeb.39b9c","a33e1a26.2e5c98"],["1b00dfeb.39b9c","a33e1a26.2e5c98"],["1b00dfeb.39b9c","a33e1a26.2e5c98"]]},{"id":"1b00dfeb.39b9c","type":"debug","z":"69c9331c.66234c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":910,"y":300,"wires":[]},{"id":"af3a41cb.e2cb3","type":"rbe","z":"69c9331c.66234c","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":570,"y":320,"wires":[["78ad2b0f.f85654"]]},{"id":"78ad2b0f.f85654","type":"join","z":"69c9331c.66234c","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":"num","reduceFixup":"","x":690,"y":320,"wires":[["b269af22.19953"]]},{"id":"e1def315.bf1db","type":"mqtt-broker","z":"","name":"MQTT","broker":"10.10.1.10","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]


10/25/2019, 9:55:25 PMnode: 4189398f.c516f8
home/entrance/lightslave/cmnd/POWER : msg : Object
{ topic: "home/entrance/lightslave/cmnd/…", payload: "ON", _msgid: "7bfbb908.70dea8" }
10/25/2019, 9:55:25 PMnode: 4189398f.c516f8
home/hallfloor/light/cmnd/POWER3 : msg : Object
{ topic: "home/hallfloor/light/cmnd/POWE…", payload: "ON", _msgid: "7bfbb908.70dea8" }
10/25/2019, 9:55:25 PMnode: 4189398f.c516f8
home/entrance/light/cmnd/POWER2 : msg : Object
{ topic: "home/entrance/light/cmnd/POWER…", payload: "ON", _msgid: "edfd6e73.23a78" }
10/25/2019, 9:55:25 PMnode: 4189398f.c516f8
home/hallfloor/light/cmnd/POWER3 : msg : Object
{ topic: "home/hallfloor/light/cmnd/POWE…", payload: "ON", _msgid: "edfd6e73.23a78" }

I'm probably overseeing something, but not really sure what, It always triggers home/hallfloor/ twice.

Any idea?

Some initial notes:
The return false should be return null. I have no idea what return false will do. Perhaps it has the same effect as return null but I don't know.
If you do return [[ra,rb,rc]] then it will send all three messages to the first output, so you only need one output.
The Join node should be set to wait for three messages, you still have it set to wait for two.
Does it work ok for you if you disconnect the mqtt in nodes and just use the inject nodes? If so then the problem may be caused by the fact that when you send a command out then soon after there will be a status command back as a result of that change, which may be messing up the algorithm. So possibly something like this happens
Assume all switches are off initially.
Suppose switch A goes on. This will trigger On messages to B and C.
An On status will then come in for B. this will trigger another On for C.
Finally the On status will come in for C. In fact there may be two On status messages for C, it depends whether you get a status message when you send a command to switch it to the state it is already in. That will be mopped up by the RBE node however. I guess the fact that you put the RBE in suggests you have already seen that situation.
Something like that anyway

ah I found somewhere that return [1.2.3] equals to ouput number of the function ...

oh my, thanks

looks like you are right, it works with injects only
but that's where I thought RBE will take a place and basically ignores returning confirmation value of the triggered topic.
But it looks like it's not

Look in the docs page on writing functions for details of how to return multiple messages to one or more outputs. Lots of other useful stuff there, if you haven't read it then do so.
The RBE node is working as it should, this is what could happen

A    B    C
On   On   On       This is the initial state
Off                Switch A Off - flow sends Off to B and C
     Off           Switch B is now off - flow sends another Off to C since status for it has not come in yet
          Off      This is the status from the first Off sent to C
          Off      this is the status from the second off to C, though I am not sure if this will actually come in, it depends on whether the device sends it or not.  This will not get to the function node because of the RBE node.

Note that there are two Off commands to C. The RBE on the front does not stop that. An RBE on the output would stop it.

thanks!
you elaboration makes perfect sense and moving RBE to the end did the trick