parnic/node-screenlogic

Uncaught Exception crashing node-red

EkiSkyten opened this issue · 13 comments

This is the only information I can find in the logs:

9 May 07:31:47 - [red] Uncaught Exception:
9 May 07:31:47 - Error: read ECONNRESET at TCP.onStreamRead (internal/stream_base_commons.js:209:20)

This occurs 30 seconds after node-red starts. I have node-red auto starting on failure. So, this puts it into a cycle of start/die in 30 seconds/restart/die in 30 seconds... for ever.

Below is the node-red function on start code. Other than the fatal crash, everything appears to be working normally:


node.status({fill:'yellow',shape:'dot',text:'Loading'});

const systemName = 'Pentair: xx-xx-xx;
const password = '';

var screenLogic = global.get('screenlogic');
var remote = new screenLogic.RemoteLogin(systemName);

var pool = {};

remote.on('gatewayFound', function(unit) {
  if (unit && unit.gatewayFound) {
    var client = new screenLogic.UnitConnection(unit.port, unit.ipAddr, password);
    client.on('loggedIn', function() {
        pool.systemName = remote.systemName;
        pool.ip = unit.ipAddr;
        pool.port = unit.port;
        node.status({fill:'green',shape:'dot',text:'connected, getting version'});
        this.getVersion();
    }).on('version', function(version) {
        pool.version = version.version;
        node.status({fill:'green',shape:'dot',text:'connected, getting status'});
        this.getPoolStatus();
    }).on('poolStatus', function(status) {
        pool.status = status;
        pool.isOk = status.ok==1?"ok":"fault";
        pool.airTemp = status.airTemp;
        pool.waterTemp = status.currentTemp[0];
        pool.running = status.isPoolActive();
        pool.lightState = status.circuitArray[0].state==1?true:false;
        pool.highspeedState = status.circuitArray[1].state==1?true:false;
        node.status({fill:'green',shape:'dot',text:'connected, getting configuration'});
        this.getControllerConfig();
    }).on('controllerConfig', function(config) {
        node.status({fill:'green',shape:'dot',text: (pool.running?'running':'off') + ' ' + pool.waterTemp + pool.degreesUnit});
        pool.config=config;
        pool.degreesUnit = config.degC?"C":"F";
        pool.lightName = config.bodyArray[0].name;
        pool.highspeedName = config.bodyArray[1].name;
        pool.client = client;
        pool.remote = remote;
        node.status({fill:'green',shape:'dot',text:'connected, saving pool'});
        flow.set("pool", pool);
    }).on('loginFailed', function() {
        node.status({fill:'red',shape:'dot',text:'could not log in'});
        client.close();
    });
    client.connect();
  } else {
    node.status({fill:'red',shape:'dot',text:'no unit found by that name'});
    remote.close();
  }
 });

remote.connect();

return;

Seems like it's probably not connecting to the remote. Have you tried wrapping your remote.connect() in a try/catch block?

I hadn't, but I just tried it.

No change.

The code essentially works once. I get all the data back from ScreenLogic that I expect. But, it seems like there is some sort of thread or background task that is causing the error ... which is probably why try/catch didn;t catch anything

Try issuing a client.close() after receiving the controller config event. The Pentair equipment isn't made to hold a connection unless you're using the addClient api. So with this implementation you'll need to schedule the updates to run periodically. I have never run node-red myself.

Yeah ... so I started with that approach. It worked. But, I wanted to get events from ScreenLogic when something changed, so that I would not have to keep polling constantly. So, I saved the client and remote objects in the flow context and then close them when the UI goes away.

Unfortunately the equipment itself doesn't work that way and closes the idle connection, which is what you're seeing. See if the docs for the addClient api help since that's how you get real-time updates.

OK ... I can see that .... I was planning on adding the addClient() call ... then this exception happened.

But, if I make the addClient() call ... I still need to keep the remote/client connection in order to get the poolStatus event????

It will fire the documented events as new stuff happens and your handler for those will run.

So, I added addClient(). Still the connection was closed 30 seconds into it.

Does this help?

edit: or, rather, which version of the library are you using and what does your new code look like?


flow.set("pool", pool);

remote.on('gatewayFound', function(unit) {
  if (unit && unit.gatewayFound) {
    var client = new screenLogic.UnitConnection(unit.port, unit.ipAddr, password);
    client.on('loggedIn', function() {
        client.addClient(123);
        pool.systemName = remote.systemName;
        pool.ip = unit.ipAddr;
        pool.port = unit.port;
        node.status({fill:'green',shape:'dot',text:'connected, getting version'});
        this.getVersion();
    }).on('version', function(version) {
        pool.version = version.version;
        node.status({fill:'green',shape:'dot',text:'connected, getting status'});
        this.getControllerConfig();
    }).on('controllerConfig', function(config) {
        node.status({fill:'green',shape:'dot',text: (pool.running?'running':'off') + ' ' + pool.waterTemp + pool.degreesUnit});
        pool.config=config;
        pool.degreesUnit = config.degC?"C":"F";
        pool.lightName = config.bodyArray[0].name;
        pool.highspeedName = config.bodyArray[1].name;
        pool.client = client;
        pool.remote = remote;
        node.status({fill:'green',shape:'dot',text:'connected, saving pool'});
        this.getPoolStatus();
    }).on('poolStatus', function(status) {
        pool = flow.get("pool");
        pool.status = status;
        pool.isOk = status.ok==1?"ok":"fault";
        pool.airTemp = status.airTemp;
        pool.waterTemp = status.currentTemp[0];
        pool.running = status.isPoolActive();
        pool.lightState = status.circuitArray[0].state==1?true:false;
        pool.highspeedState = status.circuitArray[1].state==1?true:false;
        node.status({fill:'green',shape:'dot',text:'connected, getting configuration'});

        var msg = {};
        msg.pool = pool;
        node.send(msg);
    }).on('loginFailed', function() {
        node.status({fill:'red',shape:'dot',text:'could not log in'});
        client.close();
    });
    
    client.connect();
    
  } else {
    node.status({fill:'red',shape:'dot',text:'no unit found by that name'});
    remote.close();
  }
 });

remote.connect();

return;

line 135 already had the keep alive in there

I have a work around. First, the problem I had with the process crashing was because I did not have an error handler. Once I coded the error handler, the process did not crash, and I received the error message.

10 May 11:07:45 - [error] [function:Screen Logic] {"errno":-4077,"code":"ECONNRESET","syscall":"read"}

I use that error message to close out the old connection and reconnect.

A little bit cheesy I think, but it works.

The addClient() call did not prevent the connection from closing, but it works to deliver status events.

So, I think this issue is closed, unless the connection reset itself can be addressed somehow.

Here is the updated code:

node.status({fill:'yellow',shape:'dot',text:'Loading'});

const systemName = 'Pentair: xx-xx-xx';
const password = '';

var screenLogic = global.get('screenlogic');
var remote = new screenLogic.RemoteLogin(systemName);

var pool = {};
global.set("pool", pool);

remote.on('gatewayFound', function(unit) {
  if (unit && unit.gatewayFound) {
    var client = new screenLogic.UnitConnection(unit.port, unit.ipAddr, password);
    client.on('loggedIn', function() {
        client.addClient(123);
        pool.systemName = remote.systemName;
        pool.ip = unit.ipAddr;
        pool.port = unit.port;
        node.status({fill:'green',shape:'dot',text:'connected, getting version'});
        this.getVersion();
    }).on('version', function(version) {
        pool.version = version.version;
        node.status({fill:'green',shape:'dot',text:'connected, getting configuration'});
        this.getControllerConfig();
    }).on('controllerConfig', function(config) {
        node.status({fill:'green',shape:'dot',text: (pool.running?'running':'off') + ' ' + pool.waterTemp + pool.degreesUnit});
        pool.config=config;
        pool.degreesUnit = config.degC?"C":"F";
        pool.lightName = config.bodyArray[0].name;
        pool.highspeedName = config.bodyArray[1].name;
        node.status({fill:'green',shape:'dot',text:'connected, getting status'});
        this.getPoolStatus();
    }).on('poolStatus', function(status) {
        node.log("status event");
        pool.status = status;
        pool.isOk = status.ok==1?"ok":"fault";
        pool.airTemp = status.airTemp;
        pool.waterTemp = status.currentTemp[0];
        pool.running = status.isPoolActive();
        pool.lightState = status.circuitArray[0].state==1?true:false;
        pool.highspeedState = status.circuitArray[1].state==1?true:false;
        node.status({fill:'green',shape:'dot',text:'connected, initialized'});
        msg.pool = pool;
        node.send(msg);
    }).on('loginFailed', function() {
        node.status({fill:'red',shape:'dot',text:'could not log in'});
        node.error("login failed");
        client.close();
        remote.close();
    }).on('badParameter', function() {
        node.status({fill:'red',shape:'dot',text:'bad parameter'});
        node.error("bad parameter");
        client.close();
        remote.close();
    }).on('error', function(error) {
        node.status({fill:'red',shape:'dot',text:'error'});
        node.error(JSON.stringify(error));
        client.removeClient(123);
        client.close();
        remote.close();
        if (error.errno == -4077 ) {
            // Pentair pump dropped the connection
            remote.connect();
        }
    }).on('unknownCommand', function() {
        node.status({fill:'red',shape:'dot',text:'unknownCommand'});
        node.error("unknownCommand");
        client.close();
        remote.close();
    });
    
    client.connect();
    
  } else {
    node.error("Could not find pool controller");
    node.status({fill:'red',shape:'dot',text:'no unit found by that name'});
    remote.close();
  }
 });

remote.connect();

return;

Glad you found a workaround. Sorry for my failure to remember about the error handler -- it's been a while since I've had a look at this code. Hopefully your solution will help others in the future.

I'm not sure why it's still timing out, though. Maybe it's specific to node-red or the js interpreter being used by it.