setTimeout issue on a node with callback function

#1

Hi dear Node-RED community.

I am connecting some digital inputs on my Siemens Simatic IoT2000 products, unfortunately none of the existing nodes are working well, so I have started to create my own. Now the behaviour is fine since I have added a callback function. But I still have an annoying bug. If I redeploy my flow then I have 2 readings of the pin, if I deploy a third time then I have 3 readings ... so to avoid that each time I deploy a modified flow with those nodes I have to restart my Node-RED.
I am pretty sure it has to do with a poor clearance of the TimeOut, but I have not been able to figure it out so far :roll_eyes:.

Here is the js code of the node:

var sys = require('util')
var exec = require('child_process').exec;

module.exports = function(RED) 
{
  var mraa = require('mraa');
  function newSetInterval(callback, duration, callbackArguments)
	{
    callback.apply(this, callbackArguments);
    var args = arguments,
    scope=this;
    setTimeout(function() {newSetInterval.apply(scope, args);}, duration);
	}
  exec("echo 63 > /sys/class/gpio/export");
  function gpioDin(n) {
    RED.nodes.createNode(this,n);
    var node = this;
    node.pin = n.pin;
    node.updateInterval = n.updateInterval;
    node.mGpio = new mraa.Gpio(parseInt(node.pin));
    node.mGpio.mode(mraa.PIN_GPIO);
    node.mGpio.dir(mraa.DIR_IN);
    var lastPinState = node.mGpio.read();
    var currentPinState = 0;
    var timerId = newSetInterval(function()
		{
			if(node.pin != "ub")
			{			
				currentPinState = node.mGpio.read();
			}
			else
			{
				exec("cat /sys/class/gpio/gpio63/value", function(error, stdout, stderr) {
				currentPinState = parseInt(stdout)});
			}
			if(currentPinState === 0 || currentPinState === 1)
			{
				if (currentPinState != lastPinState)
				{
					if(currentPinState === 0)
						node.status({fill:"grey",shape:"ring",text:"Low"});
					else if(currentPinState === 1)
						node.status({fill:"grey",shape:"dot",text:"High"});
					var msg = { payload:currentPinState, topic:"D"+node.pin };
					node.send(msg);
				}
				lastPinState = currentPinState;
			}
		},parseInt(node.updateInterval));   
		
		this.on('close', function() {
			clearTimeout(timerId);
		});
	}	
  RED.nodes.registerType("IoT2000-gpio-din", gpioDin);
}

Any suggestions as to how do a proper reset of my Timeout when I redeploy ?

Cheers,

Antoine

0 Likes

#2

You need to use the close event in order to remove any timeouts and close out anything that needs it.

It was a while back that I created it but my example library called jktesting may help you navigate the intricacies of a node's structure.

Specifically, you will need to do a clearTimeout(timerId) in the close section.

2 Likes

#3

Hi Julian,

Thanks I will have a look at this, and hopefully get the insight I need to fix this bug.

Nevertheless I already have the clearTimeout in my node at the bottom, but it does not seem to have the correct behaviour.

0 Likes

#4

You will need to return setInterval at the end of your newSetInterval function.

1 Like

#5

Hi Nathanael,

Simply like that or am I missing something ?

0 Likes

#6
return setTimeout(function() {newSetInterval.apply(scope, args);}, duration);
1 Like

#7

I have modified the function as per below, but the behaviour is still the same after a reboot.

  function newSetInterval(callback, duration, callbackArguments)
	{
    callback.apply(this, callbackArguments);
    var args = arguments,
    scope=this;
    return setTimeout(function() {newSetInterval.apply(scope, args);}, duration);
	}

If I deploy without rebooting I get multiple reads. The missing return was a clear reason not to work I got that now, so maybe it seems clearTimeout is not implemented correctly.

0 Likes

#8

From what I understand your setTimeout calls itself so it repeats, why not avoid all this and use setInterval (and clearInterval) instead?

1 Like

#9

The initial node were using setInterval/clearInterval, but they bug, and crash Node-RED within minutes of use. I saw that a generic issue with setInterval was due to the function running longer than the interval time, hence I try to add a callback, and since I added it I have no more problem like that, it works perfectly but needs a restart.

var sys = require('util')
var exec = require('child_process').exec;

module.exports = function(RED) {
    var mraa = require('mraa');
    
	exec("echo 63 > /sys/class/gpio/export");
    function gpioDin(n) {
        RED.nodes.createNode(this,n);
        var node = this;
		node.pin = n.pin;
		node.updateInterval = n.updateInterval;
        node.mGpio = new mraa.Gpio(parseInt(node.pin));
        node.mGpio.mode(mraa.PIN_GPIO);
        node.mGpio.dir(mraa.DIR_IN);
		var lastPinState = node.mGpio.read();
        var currentPinState = 0;
                
        var timerId = setInterval(function()
{
			if(node.pin != "ub")
			{			
				currentPinState = node.mGpio.read();
			}
			else
			{
				exec("cat /sys/class/gpio/gpio63/value", function(error, stdout, stderr) {
					currentPinState = parseInt(stdout);
				});
			}
			if(currentPinState == 0 || currentPinState == 1)
			{
				if (currentPinState != lastPinState)
				{
					var msg = { payload:currentPinState, topic:"D"+node.pin };
					node.send(msg);
				}
				lastPinState = currentPinState;
			}
		},parseInt(node.updateInterval));   
		
		this.on('close', function() {
			clearInterval(timerId);
		});
	}	
    RED.nodes.registerType("IoT2000-gpio-din", gpioDin);

So I am not sure which direction to go to solve this issue.

0 Likes

#10

Hi,

I have figured it out, here is the modified node:

var sys = require('util')
var exec = require('child_process').exec;

module.exports = function(RED) 
{
  var mraa = require('mraa');
/****************************************************/
/*                     New functions                */
/****************************************************/
function newSetInterval(func, interval)
{
    newSetInterval.Ids = newSetInterval.Ids || {};
    newSetInterval.IdCount = newSetInterval.IdCount || 0;
    var That = this;
	var Id = newSetInterval.IdCount++;
    var FFBug = arguments.length - 2;//To prevent Firefox bug that adds an extra element to the arguments
    (
		function Fn()
		{
			var args = [].slice.call(arguments, 0, FFBug);//To prevent Firefox bug that adds an extra element to the arguments
			func.apply(this, args);
			newSetInterval.Ids[Id] = setTimeout.apply(this, [Fn, interval].concat(args));
		}
	).apply(That, [].slice.call(arguments, 2, arguments.length));
    return Id;
}
function clearInterval(Id)
{
    if(!newSetInterval.Ids || !newSetInterval.Ids[Id])
	{
        return false;
    }
    clearTimeout(newSetInterval.Ids[Id]);
    return true;
}
/****************************************************/
  exec("echo 63 > /sys/class/gpio/export");
  function gpioDin(n) {
    RED.nodes.createNode(this,n);
    var node = this;
    node.pin = n.pin;
    node.updateInterval = n.updateInterval;
    node.mGpio = new mraa.Gpio(parseInt(node.pin));
    node.mGpio.mode(mraa.PIN_GPIO);
    node.mGpio.dir(mraa.DIR_IN);
    var lastPinState = 0;
    var currentPinState = node.mGpio.read();
    var timerId = newSetInterval(function()
		{
			if(node.pin != "ub")
			{			
				currentPinState = node.mGpio.read();
			}
			else
			{
				exec("cat /sys/class/gpio/gpio63/value", function(error, stdout, stderr) {
				currentPinState = parseInt(stdout)});
			}
			if(currentPinState === 0 || currentPinState === 1)
			{
				if (currentPinState != lastPinState)
				{
					if(currentPinState === 0)
						node.status({fill:"grey",shape:"ring",text:"Low"});
					else if(currentPinState === 1)
						node.status({fill:"grey",shape:"dot",text:"High"});
					var msg = { payload:currentPinState, topic:"D"+node.pin };
					node.send(msg);
				}
				lastPinState = currentPinState;
			}
		},parseInt(node.updateInterval));   
		
		this.on('close', function() {
			clearInterval(timerId);
		});
	}	
  RED.nodes.registerType("IoT2000-gpio-din", gpioDin);
}

This works perfectly in case someone else ever need the same thing.

0 Likes