setTimeout issue on a node with callback function

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

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

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.

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

1 Like

Hi Nathanael,

Simply like that or am I missing something ?

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

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.

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

1 Like

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.

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.