Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notify on keydown #9

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
7 changes: 7 additions & 0 deletions lutron-config.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
},
timeout: {
value: 45000
},
includeAction: {
value: false
}
},
label: function () {
Expand Down Expand Up @@ -99,6 +102,10 @@
<label for="node-config-input-timeout"><i class="fa fa-globe"></i> Timeout (ms)</label>
<input type="text" id="node-config-input-timeout" placeholder="30000"></input>
</div>
<div class="form-row">
<label for="node-config-input-includeAction"><i class="fa fa-plus-circle"></i> Action Data</label>
<input type="checkbox" id="node-config-input-includeAction"></input>
</div>
<!-- Table Class -->
<h2 style="color:brown; margin-left:490px;margin-top:0px;">Device mapping table</h2>

Expand Down
76 changes: 64 additions & 12 deletions lutron-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ module.exports = function (RED) {
function LutronConfigNode(config) {
RED.nodes.createNode(this, config);
this.lutronLoc = config.ipaddress;
var node = this;
const node = this;
node.connected = false;
node.telnet = new Telnet();
node.port = 23;
this.deviceMap = config.deviceMap;
node.deviceMap = config.deviceMap;
node.devices = {};
node.lutronEvent = new events.EventEmitter();
var params = {
node.statusEvent = new events.EventEmitter();
node.includeAction = config.includeAction;
const params = {
host: this.lutronLoc,
port: this.port,
shellPrompt: 'GNET>',
Expand All @@ -31,27 +33,67 @@ module.exports = function (RED) {
var str = '?OUTPUT,' + devId + ',1';
this.telnet.getSocket().write(str + '\n');
};

const updateStatus = (connected, msg) => {
node.statusEvent.emit('update', {
fill: connected ? 'green' : 'red',
shape: 'dot',
text: msg
});
};

const reconnect = () => {
// Try reconnecting in 1 minute
node.log('reconnecting telnet')
setTimeout(() => this.telnet.connect(params), 60000);
}

// Telnet handlers
this.telnet.on('data', (function (self, pkt) {
self.lutronRecv(pkt);
}).bind(null, node));
this.telnet.on('connect', function () {
this.connected = true;
console.log('telnet connect');
node.connected = true;
node.log('telnet connect');
updateStatus(true, 'connected');
});
this.telnet.on('close', function () {
this.connected = false;
console.log('telnet close');
if (node.connected) {
node.log('telnet close');
}
node.connected = false;
updateStatus(false, 'closed');
});
this.telnet.on('error', function () {
console.log('telent error');
if (node.connected) {
node.warn('telnet error');
}
updateStatus(false, 'telnet error');
});
this.telnet.on('failedlogin', function () {
console.log('telent failed login');
node.warn('telnet failed login');
updateStatus(false, 'login failed');
});
this.telnet.on('timeout', function () {
if (node.connected) {
node.log('telnet timeout');
}
});
this.telnet.on('end', function () {
if (node.connected) {
// This happens periodically (on bridge updates?)
// so try reconnecting afterwards
node.warn('telnet remote ended connection');
reconnect();
}
updateStatus(false, 'ended');
});

// Lutron handlers
this.lutronSend = function (msg, fn) {
this.telent.getSocket().write(msg + '\n', fn);
this.telnet.getSocket().write(msg + '\n', fn);
}
this.lutrongUpdate = function (deviceId, fn) {
this.lutronUpdate = function (deviceId, fn) {
this.lutronSend('?OUTPUT,' + deviceId + ',1', fn);
}
this.lutronSend = function (deviceId, val, fn) {
Expand All @@ -66,7 +108,7 @@ module.exports = function (RED) {
var deviceId = parseInt(cs[1])
var action = parseInt(cs[2])
var param = parseFloat(cs[3])
// console.log('[',cmd,',', type, ',',deviceId,
// this.log('[',cmd,',', type, ',',deviceId,
// ',', action,',', param,']')
this.lutronEvent.emit('data', {
cmd: cmd,
Expand All @@ -86,6 +128,16 @@ module.exports = function (RED) {
}
}
}

// Cleanup on close
node.on('close', function(done) {
node.log('Node shutting down');
node.connected = false;
node.telnet.end()
.then(() => done())
.catch(() => this.telnet.destroy().then(() => done()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this run done() a second time if the end() succeeds and the first done() throws an exception?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, done shouldn't throw an exception since it's a callback provided by nodered. The catch is in case the end() throws an exception to ensure we still force the cleanup and call done().

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't throw, but you don't control nodered's code. This would be safer and simpler to write as

Suggested change
node.telnet.end()
.then(() => done())
.catch(() => this.telnet.destroy().then(() => done()));
node.telnet.end()
.catch(() => this.telnet.destroy())
.then(() => done());

This way an exception from done() won't cause it to repeat (or to call destroy()), and you're not repeating the callback either.

Also, question: What happens if destroy() throws an exception? Neither version of the code will call done() in that case. Maybe it should? If it should, then my version would use .finally(() => done()) instead (or just .finally(done) as finally already passes no arguments).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, forgot finally was available with promises. Thanks, I'll update.

});

this.telnet.connect(params);
}
RED.nodes.registerType('lutron-config', LutronConfigNode);
Expand Down
28 changes: 20 additions & 8 deletions lutron-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = function (RED) {
var configNode = RED.nodes.getNode(status.confignode);
this.devName = status.name;
this.devId = parseInt(configNode.deviceMap[this.devName]);
this.sendObj = !!configNode.includeAction;
// register a callback on config node so that it can call this
// node
// then it will call the this.send(msg), msg = {payload: "hi"}
Expand All @@ -17,20 +18,20 @@ module.exports = function (RED) {
/*
for dimmer action is always 1
for pico action.param
FullOn => a=2, p=4
up => a=5, p=4
down => a=6 p=4
off => a=4, p=4
FullOn => a=2, p=3
up => a=5, p=3
down => a=6 p=3
off => a=4, p=3
Comment on lines +21 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing issue, but this is missing Button 2 (a=3), and it's also missing the buttons for the PJ2-4B model (8/9/10/11), which is rather a problem as that's the model I have 😅

for on off switch
action =1 p=0 or 100
*/
if (action == '1') {
// either dimmre of switch
node.send({
payload: value
payload: node.sendObj ? d : value
});
} else if (value === 4) {
var m = '';
} else if (value === 3 || node.sendObj) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should likely test || (value === 4 && node.sendObj). I know the only documented actions for pico remotes are 3 and 4 but that's no reason to make assumptions.

var m = action;
if (action === 2)
m = 'on';
else if (action === 5)
Expand All @@ -39,8 +40,11 @@ module.exports = function (RED) {
m = 'down';
else if (action === 4)
m = 'off';

d.action = m;
d.param = value === 3 ? 'keydown' : 'keyup';
node.send({
payload: m
payload: node.sendObj ? d : m
});
}
}
Expand All @@ -50,6 +54,14 @@ module.exports = function (RED) {
});
}
}).bind(null, this));

const statusHandler = status => this.status(status);

// Update node status
configNode.statusEvent.on('update', statusHandler);

// Cleanup on close
this.on('close', () => configNode.statusEvent.removeListener('update', statusHandler));
}
RED.nodes.registerType('lutron-status', LutronStatusNode);
}