Inject node with 1s interval not being launched 3600 times per hour

#1

Hi Node-REDers !

On one of my flow I am using an inject node with an interval of 1s, to check the status of a machine and calculate later on some indicators like its availability.
I have another inject node with an interval of 1h that resets my values. Unfortunately when the 1h interval occur, I do not have 3600 injections of my 1s interval. It´s lower and changes every hour. Over the past days the Min I had is 3400 and the max 3542.

I put the extract of the flow herebelow:

[{"id":"588e2ecc.702e1","type":"inject","z":"110bffaa.86774","name":"1s Timestamp","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":60,"wires":[["848b147f.00bdc8"]]},{"id":"848b147f.00bdc8","type":"function","z":"110bffaa.86774","name":"1h Machine Availability","func":"var machine_status = flow.get('machine_status')||0;\nvar machine_1h_running = flow.get('machine_1h_running')||0;\nvar machine_1h_stopped = flow.get('machine_1h_stopped')||0;\nvar timestamp1h_count = flow.get('timestamp1h_count')||0;\n\n\nif (machine_status === 0 )\n{   \n    machine_1h_stopped++;\n    flow.set('machine_1h_stopped',machine_1h_stopped);\n\n}\nelse if (machine_status === 1)\n{\n    machine_1h_running++;\n    flow.set('machine_1h_running',machine_1h_running);\n}\ntimestamp1h_count++;\nflow.set('timestamp1h_count',timestamp1h_count);\n\nmsg.payload=timestamp1h_count;\nreturn msg;\n","outputs":1,"noerr":0,"x":470,"y":100,"wires":[["69312c40.a93354"]]},{"id":"40a5ffed.c0fdd","type":"inject","z":"110bffaa.86774","name":"1h Timestamp","topic":"","payload":"","payloadType":"date","repeat":"3600","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":120,"wires":[["d31e7176.04f06"]]},{"id":"d31e7176.04f06","type":"function","z":"110bffaa.86774","name":"Hourly Machine Performance & Reset","func":"var machine_1h_running = flow.get('machine_1h_running')||0;\nvar machine_1h_stopped = flow.get('machine_1h_stopped')||0;\nvar timestamp1h_count = flow.get('timestamp1h_count')||0;\n\nflow.set('machine_1h_running',0);\nflow.set('machine_1h_stopped',0);\nflow.set('timestamp1h_count',0);","outputs":7,"noerr":0,"x":550,"y":180,"wires":[[],[],[],[],[],[],[]]},{"id":"69312c40.a93354","type":"debug","z":"110bffaa.86774","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":830,"y":100,"wires":[]}]

Should I try to use the bigtimer node, modify the inject node to use a timeout instead of an interval, or do that totally differently ?
I am keen to all your suggestions.

Cheers,

Antoine

#2

Node.JS on which Node-RED is built is not a real-time system. It uses an inherent loop and exact timing can never be guaranteed.

If you want exact timing, you will need to use an external source and even then, high-accuracy timings will be disrupted by other events that may happen including the Node.JS garbage collection routines.

1 Like
#3

Obligatory SO thread with some good links... https://stackoverflow.com/questions/985670/will-setinterval-drift,

Could you use the "Interval between times" ? which only has a minimum of 1 minute - as that uses a cron mechanism so should be more accurate.

1 Like
#4

Thanks @TotallyInformation & @dceejay.

Doing a reading every 1 min is not a solution, I would loose too much precision I would still be better of with the not timely accurate 1s interval.
If I change to event base & use the date of my event start & stop to have a duration would that be accurate ?

#5

One very robust way might help. You can try.
[{"id":"32dad663.4e3f4a","type":"inject","z":"898969b8.c071f8","name":"200ms Timestamp","topic":"","payload":"","payloadType":"date","repeat":"0.2","crontab":"","once":false,"onceDelay":0.1,"x":490,"y":300,"wires":[["ea10908d.831b8"]]},{"id":"101f2f58.4117f1","type":"function","z":"898969b8.c071f8","name":"1h Machine Availability","func":"var machine_status = flow.get('machine_status')||0;\nvar machine_1h_running = flow.get('machine_1h_running')||0;\nvar machine_1h_stopped = flow.get('machine_1h_stopped')||0;\nvar timestamp1h_count = flow.get('timestamp1h_count')||0;\n\n\nif (machine_status === 0 )\n{ \n machine_1h_stopped++;\n flow.set('machine_1h_stopped',machine_1h_stopped);\n\n}\nelse if (machine_status === 1)\n{\n machine_1h_running++;\n flow.set('machine_1h_running',machine_1h_running);\n}\ntimestamp1h_count++;\nflow.set('timestamp1h_count',timestamp1h_count);\n\nmsg.payload=timestamp1h_count;\nreturn msg;\n","outputs":1,"noerr":0,"x":980,"y":300,"wires":[["a8ffe825.a7ad88"]]},{"id":"a8ffe825.a7ad88","type":"debug","z":"898969b8.c071f8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":1190,"y":300,"wires":[]},{"id":"ea10908d.831b8","type":"function","z":"898969b8.c071f8","name":"ensure msg every second","func":"var sec = flow.get(\"sec\") || -1;\nvar s = new Date().getSeconds();\nif(s != sec){\n flow.set(\"sec\",s); \n return msg; \n}\n","outputs":1,"noerr":0,"x":720,"y":300,"wires":[["101f2f58.4117f1"]]}]

1 Like
#6

I have a similar problem, and a simple(-ish) solution. I'm considering making a patch for the inject node (adding a checkbox), or making a fork of it. The idea is that if you want something done every one minute, it does every time the clock passes 0 seconds. Or more accurately: Every time we pass a value in unixtimestamp that is a multiple of the selected delay time.

  • This does not make it a real-time system. It will not fire at EXACTLY the the right time. But will be pretty close.
  • It will not drift. You will get 606024 seconds per day, unless the machine is running so slow it can't fire events that quickly. (Like if the thing you are doing blocks for more than a second.)
  • If you have several timers with the same delay, they will fire as simultaneously as is possible in Javascripts single-threaded nature. And if you have one with 1m delay and one with 1s delay, they will also fire "simultaneously" every minute.

It could be as simple as a checkbox with the label "Synchronized", and some help text.

I already have the code running built-in in another custom node, so the logic already exists. That's the easy part.

1 Like
#7

Isn't that what the Interval between times does?

1 Like
#8

Can I just reiterate - shoot me down if you think I'm wrong - Node.JS running on a general purpose operating system on a general purpose compute platform, has some externally and internally imposed limitations.

External factors will impact execution timing and internal factors will as well. Especially if you are relying on the single-thread, single-loop default approach of Node.JS and the underlying V8 engine.

Whilst this may be good enough to give you reliable 1 second interval timings, it cannot - I don't think - be guaranteed.

For example, if you were running on - lets say - a Raspberry Pi with a standard SD card as your filing system and the OS needs to page data out to the swap file, you could get a quite noticeable delay in response I think. Again, correct me if I'm wrong, I'm no expert.

At a 1 minute cycle, I don't think you'd likely notice. At a few milliseconds, I think you would notice a lot. I'm just not sure of the reliability of a 1 second cycle.

1 Like
#9

@abojolle, what is it that is important in your application? That it runs every second within a defined time period after the 1 second tick (if so then what delay can you tolerate) or that it runs 3600 times an hour, and it wouldn't matter if some of them were a few seconds late, provided there are 3600 in each hour.
In fact both the above actually the same, it is just the amount of delay you can tolerate that is different.
It might help if you gave more details of your application in case we can see a better solution.

2 Likes
#10

@myplacedk - as @colin points out (as did I previously) the "Interval Between TImes" option already uses cron which does exactly what you suggest - at the resolution of one minute.

1 Like
#11

@Colin I am using those 3600 seconds to indicate every1h how much time my machine has been running & stopped.

The 1h interval is definitely accurate no problem with that, but then when I show up my 2 values, converted back in minute/seconds the total is far from matching 1h.
I also store those values to use them for my daily indicators, and after 24h the total is so far from that 86400.

With the limitations you highlighted due to the Node.js I am thinking the best would be not to do look at it every second but based on my machine start/stop with their date/time.
Every time I have this triggered I will have the duration of this running or stopped time, add it to my counters. Then when my hourly interval comes, adding a last value between now and the last start/stop should give me a sum of 3600 for my 2 counters.

If you have better suggestions I am all ears.

#12

Thanks, I will try this on later and see how accurate it is.

Edit1:I am trying it right now, after 20minutes I have 78 extra seconds. I will let it run for an hour to confirm but that is not very promising.

Edit2:It passed the 1800s mark 110s too early, the 3600s mark 220s too early, and at the end of my one hour the counter was at 3836.
I could see the counter drifting sometimes, while most of the time the pace was regular.

#13

Absolutely the way to do this is to timestamp each sample you get and use that information to calculate running time and so on.

3 Likes