parnic / node-screenlogic

Pentair ScreenLogic Javascript library using Node.JS
https://www.npmjs.com/package/node-screenlogic
MIT License
53 stars 15 forks source link

Uncaught Exception crashing node-red #50

Closed EkiSkyten closed 3 years ago

EkiSkyten commented 3 years ago

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;

parnic commented 3 years ago

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

EkiSkyten commented 3 years ago

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

parnic commented 3 years ago

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.

EkiSkyten commented 3 years ago

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.

parnic commented 3 years ago

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.

EkiSkyten commented 3 years ago

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????

parnic commented 3 years ago

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

EkiSkyten commented 3 years ago

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

parnic commented 3 years ago

Does this help?

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

EkiSkyten commented 3 years ago

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;
EkiSkyten commented 3 years ago

line 135 already had the keep alive in there

EkiSkyten commented 3 years ago

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;
parnic commented 3 years ago

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.