Counting to three

Hello,

I currently have several automations in Home Assistant that will roll down the covers and awning, they however rely on a poll state node which monitors one of my outdoor sensors (wind and rain).

I would like to refine the logic a bit and have NR count to three before triggering the action. I'm very new to function nodes (as a matter of fact I've been avoiding them for months) but I came up with this:

var count = flow.get('count')||0;
count++;
msg.payload=count;
flow.set('count',count);
if (count>=3) {
    msg.payload=count;
    count=0;
    flow.set('count',count);
    return msg;
}

I think it works but I'm not happy with the looks of it. I'm sure it can be done more efficiently and nicely, specially with the condition "count>=3"; I had to give in and include ">" because at some point I notice count had gone beyond 3, and I was in a forever loop.

Many thanks in advance,

You initialize count and immediately increment it with 1, puzzling...

What are you counting ? You can possibly solve it by using the trigger node instead.

Don’t be too hard on me please, I did not code for 12 years. It may feel trivial to most of the people here, but I feel I have a very long road ahead... functions, messages, topics, etc. feel daunting but I see so much potential in NR that it has brought back my interest in coding.

count++;

could very well be the last sentence now that you mention it :blush:

I am just trying to refrain my automations from triggering the first time the condition I’ve defined us true. For example, I have an automation that rolls down the shutters when the outdoor rain gauge detects rain. What I want to do is for that condition to be true 3 times before I roll down the shutters and not the very first time rain is detected.

Looks fine to me. Easy to read and understand in 6 months time when you need to. Leave the count++ where it is. Only thing you don't need to do is the first msg.payload=count

Yes you are right that it is daunting from your perspective, I am looking at NR too much now I think of it :wink:

It can be simplified like:

c = context.get('count')||0
node.status({text:"count: "+c})

if(c>2){
    context.set('count',0)
    return msg
}else{
    c++
    context.set('count',c)
}

I am using node context here so that the count applies to the function node itself, it will send the payload on 3, but it starts at 0, so that is at the 4th time.

Flow example

[{"id":"3668033d.c117ec","type":"inject","z":"681f8bde.8887bc","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":168,"y":588,"wires":[["b13355ec.4394a8"]]},{"id":"b13355ec.4394a8","type":"function","z":"681f8bde.8887bc","name":"","func":"c = context.get('count')||0\nnode.status({text:\"count: \"+c})\n\nif(c>2){\n    context.set('count',0)\n    return msg\n}else{\n    c++\n    context.set('count',c)\n}\n\n\n    ","outputs":1,"noerr":0,"x":302,"y":588,"wires":[["aa7d9085.c1b55"]]},{"id":"aa7d9085.c1b55","type":"debug","z":"681f8bde.8887bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":462,"y":588,"wires":[]}]

As is often the case there are numerous ways of coding stuff, and it is down to personal preference which way to go. I would probably do it slightly differently

let count = context.get('count')||0
if (++count >=3) {
    count = 0
} else {
    msg = null
}
node.status({text:`count: ${count}`})
context.set('count',count)
return msg

I like to have all the context gets at the start and save them all at the end. It is very easy with a more complex function to forget to save variables back.
Also I don't like to have returns in the middle of a function, it can easily cause confusion. Setting msg to null in the else clause means that the return msg at the end does nothing.

I'll play "devil's advocate" here.

var count = flow.get('count')||0;
count++;

What am I missing?
He sets/gets count from flow.get( ) and increments it.
So?

@kitus
Just one thing looking at your first post:

var count = flow.get('count')||0;
count++;
msg.payload=count;
flow.set('count',count);
if (count>=3) {
   msg.payload=count;
   count=0;
   flow.set('count',count);
   return msg;
}

I don't get why you flow.set( ) twice?
Line 4 saves the value. Then you check if count >= 3
If it is, increment and save - - - agin?

Good luck with it. It is good to see you are trying. There is nothing wrong with making mistakes.
I know and can speak from experience.
(As I am sure many will confirm)

Think about the else case.

it is set again because it is (re)set to 0 before being saved - but only if it was >-3. So there is no mistake there. (hereby confirming what you said :wink:

Sorry Colin. which else?

I say @dceejay's reply. Ok, got it now. But now sort of confused with your mentioning the else - which I take it as being implied from the if / then

I haven't loaded up your flow, but shouldn't there be a time element in this, so it only triggers if you have 3 alerts within say, 30 minutes?
Presently, if you get 1 alert at 10am, another at 3pm, and then a third alert 3 hours later at 6pm, the shutter would close.

The same would apply if you got 2 alerts on Thursday, and didn't get the 3rd alert until Saturday - 2 days later.

Absolutely. I feel dumb!! How stupid of me!!

Will work on addressing that too. If you have a clever way to control that and care to share it, I would appreciate it.

Thanks

Many thanks for your support!! :slight_smile:

There is only one if so there can be no confusion over which else case I was referring to. There is no else code, but the case can still occur (which is the test failing). If the first write were removed then there would be nothing saving the incremented value as there is no code for the else case. In the solution I proposed the write is delayed till the end so only one is needed, but it is necessary to set msg to null instead.

I've written a little flow which will only provide an output if 3 messages are passed in within a certain time period.
The output is currently true but could be anything that you want.
The function node code is;

// number of seconds before timestamps expire
// eg for 30 minutes const t = 1800;
const t = 20;

// get the saved array
ar = flow.get("ar")||[];

// obtain a timestamp
var d = new Date();
var ts = d.getTime();

// add new timestamp to array
ar.push(ts);

// delete timestamps from array which are older than 't'
var oldest = ts - (1000 * t);
ar = ar.filter(function(v) { return v > oldest; });

// if number of timestamps in array is equal to or
// greater than 3, send a msg & clear the array
if (ar.length >= 3) {
    node.send({payload:true});
    ar = [];
    node.status({text:ar.length});
} else {
    node.status({text:ar.length});
}

// save the array
flow.set("ar", ar);

and the flow is;

[{"id":"602346eb.660218","type":"inject","z":"c53060.842a0fa","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":910,"wires":[["d016499f.e065c8"]]},{"id":"858f959.d528868","type":"debug","z":"c53060.842a0fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":540,"y":910,"wires":[]},{"id":"d016499f.e065c8","type":"function","z":"c53060.842a0fa","name":"","func":"// number of seconds before timestamps expire\nconst t = (10);\n\n// get the saved array\nar = flow.get(\"ar\")||[];\n\n// obtain a timestamp\nvar d = new Date();\nvar ts = d.getTime();\n\n// add new timestamp to array\nar.push(ts);\n\n// delete timestamps from array which are older than 't'\nvar oldest = ts - (1000 * t);\nar = ar.filter(function(v) { return v > oldest; });\n\n// if number of timestamps in array is equal to or\n// greater than 3, send a msg & clear the array\nif (ar.length >= 3) {\n    node.send({payload:true});\n    ar = [];\n    node.status({text:ar.length});\n} else {\n    node.status({text:ar.length});\n}\n\n// save the array\nflow.set(\"ar\", ar);\n","outputs":1,"noerr":0,"x":370,"y":910,"wires":[["858f959.d528868"]]}]

I bow to you. Thank you so much!! Will use your code in my automations :slight_smile:

Many many thanks!!!

Thanks @Paul-Reed for the code above. I like how clean it looks. I see you are using functions that I did not even now existed. I like how clean it looks.

1 Like

You are welcome
I enjoyed the challenge!

I wonder whether a slightly different approach would achieve what you need without resorting to function nodes. Rather than requiring a certain number of messages before passing the value on it requires the input to be maintain the same state for a certain time (5 seconds in this example).

[{"id":"ec018307.e0a388","type":"debug","z":"514a90a5.c7bae8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":641,"y":325,"wires":[]},{"id":"c6db66aa.9c3688","type":"inject","z":"514a90a5.c7bae8","name":"","topic":"","payload":"open","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":157,"y":293,"wires":[["8510b965.3f5868"]]},{"id":"614774fa.f90814","type":"inject","z":"514a90a5.c7bae8","name":"","topic":"","payload":"close","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":157,"y":350,"wires":[["8510b965.3f5868"]]},{"id":"25e323f7.6d88f4","type":"trigger","z":"514a90a5.c7bae8","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"5","extend":true,"units":"s","reset":"","bytopic":"all","name":"Debounce 5 sec","x":458,"y":325,"wires":[["ec018307.e0a388"]]},{"id":"8510b965.3f5868","type":"rbe","z":"514a90a5.c7bae8","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":299,"y":325,"wires":[["25e323f7.6d88f4"]]}]

If I were being picky, there are some vars in there that could be const. Doesn't really make much difference to code this small but would still be good practice to use const where a variable shouldn't change after being first set.

Also a bug, ar = should be const ar = - note that, in JavaScript, objects and arrays can be changed even when set with const, you just can't completely replace them with some thing different.

// Won't work
const ar = {}
ar = "fred"

// Will work
const ar = {}
ar.myproperty = "fred"

OK, actually I see that you do replace ar later on so the line should be let ar = or var ar =