node-red-contrib-vscp-tcp
Version:

402 lines (329 loc) • 12.8 kB
JavaScript
///////////////////////////////////////////////////////////////////////////
// vscp-tcp-in
//
// VSCP tcp/ip input node.
//
// This file is part of the VSCP (http://www.vscp.org)
//
// The MIT License (MIT)
//
// Copyright © 2020 Ake Hedman, Grodans Paradis AB
// <info@grodansparadis.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Debug:
// https://nodejs.org/api/util.html
// export NODE_DEBUG=node-vscp-tcp* for all debug events
// export NODE_DEBUG=node-vscp-tcp-in for debug events from this module
// export NODE_DEBUG=node-vscp-tcp-in-msg to output events
const util = require('util');
const vscp_class = require('node-vscp-class');
const vscp_type = require('node-vscp-type');
const vscp = require('node-vscp');
const vscp_tcp_client = require('node-vscp-tcp');
module.exports = function(RED) {
'use strict';
const debuglog = util.debuglog('node-vscp-tcp-in');
const debuglogmsg = util.debuglog('node-vscp-tcp-in.msg');
///////////////////////////////////////////////////////////////////////////
// vscpTcpInputNode
//
// @param {*} config - configuration
// Create the tcp/ip input node
function vscpTcpInputNode(config) {
// Create the tcp/ip input node
RED.nodes.createNode(this, config);
const node = this;
var flowContext = this.context().flow;
// Retrieve the config node
this.username = this.credentials.username || "admin";
this.password = this.credentials.password || "secret";
this.tls = parseInt(config.tls) || 0;
// The channel id is saved in flow context if a key to
// save it under is entered.
this.keyctx = config.keyctx;
debuglog("keyctx: ",this.keyctx, typeof this.keyctx);
this.nodeid = config.id; // Id for node from node-red
if ('string' === typeof node.keyctx ) {
flowContext.set(node.keyctx+".nodeid", this.nodeid );
}
// Retrieve the config node
this.host = RED.nodes.getNode(config.host);
debuglog("Tcp/ip host node: " +
this.host.name + " " +
this.host.host + " " +
this.host.port + " " +
this.host.timeout + " " +
this.host.interface );
this.hostname = this.host.name;
this.host = this.host.host || 'localhost';
this.port = this.host.port || 9598;
this.timeout = this.host.timeout || 10000;
this.interface = this.host.interface || '';
this.keepalive = parseInt(this.host.keepalive) || 0;
this.filter = RED.nodes.getNode(config.filter);
if ( null == this.filter ) {
// Set a null filter that let everything through
this.filter = {
filterPriority: 0,
maskPriority: 0,
filterClass: 0,
maskClass: 0,
filterType: 0,
maskType: 0,
filterGuid: '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00',
maskGuid: '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'
};
}
debuglog("VSCP filter node: " + this.filter.filterGuid);
// Config node state
/* this.connTimeout = 0; */ // Timeout handle
this.connKeepAlive = 0; // Keepalive handle
this.connected = false;
this.connecting = false;
this.closing = false;
this.options = {};
this.queue = [];
this.connecting = true;
this.status({fill:"yellow",shape:"dot",text:"node-red:common.status.connecting..."});
node.log('Connecting to VSCP server ' +
this.hostname + ' - ' +
this.host + ' on port: ' + ":" +
this.port);
this.vscpclient = new vscp_tcp_client();
///////////////////////////////////////////////////////////////////////////
// callback: addEventListener
//
this.vscpclient.addEventListener( (strEvent) => {
var msg = {};
var ev = new vscp.Event(strEvent);
debuglogmsg("---------------- EVENT -------------------");
debuglogmsg("Event:",ev.toJSONObj());
msg.payload = ev.toJSONObj();
this.send(msg);
});
///////////////////////////////////////////////////////////////////////////
// callback: connect
//
this.vscpclient.on('connect', function() {
debuglog("---------------- CONNECT -------------------");
node.connected = true;
node.connecting = false;
node.status({fill:"yellow",shape:"dot",text:"connecting..."});
/* clearTimeout(node.connTimeout); */
});
///////////////////////////////////////////////////////////////////////////
// callback: disconnect
//
this.vscpclient.on('disconnect', function() {
debuglog("---------------- DISCONNECT -------------------");
node.connected = false;
node.connecting = false;
node.status({fill:"red",shape:"dot",text:"disconnected"});
});
///////////////////////////////////////////////////////////////////////////
// callback: timeout
//
this.vscpclient.on('timeout', function() {
debuglog("---------------- TIMEOUT -------------------");
node.connected = false;
node.connecting = false;
node.status({fill:"red",shape:"dot",text:"timeout"});
});
///////////////////////////////////////////////////////////////////////////
// alive
//
// Signal that connection is alive when in rcvloop mode
this.vscpclient.on('alive', function() {
debuglogmsg("---------------- ALIVE -------------------");
if ('string' === typeof node.keyctx ) {
flowContext.set(node.keyctx+".alive", new Date() );
}
});
///////////////////////////////////////////////////////////////////////////
// callback: error
//
this.vscpclient.on('error', function(err) {
debuglog("---------------- ERROR -------------------");
node.status({fill:"red",shape:"dot",text:"error "+err.message});
node.error(err, err.message);
});
///////////////////////////////////////////////////////////////////////////
// callback: close
//
this.on('close', function(removed, done) {
debuglog("---------------- node-red CLOSE -------------------");
this.vscpclient.disconnect({
"onSuccess": function() {
debuglog("Disconnected ----------------->");
node.log('Disconnect from VSCP server');
done();
}
});
// No reconnects during close down
clearInterval(timeoutHandle);
node.status({fill:"red",shape:"dot",text:"Connection error: "+err.message});
if (removed) {
// This node has been deleted
debuglog("Node deleted");
} else {
// This node is being restarted
debuglog("Node restarted");
}
// const closeAsync = async () => {
// await this.vscpclient.disconnect();
// node.log('Disconnect from VSCP server');
// }
// closeAsync().catch(err => {
// debuglog("Catching disconnect error");
// debuglog(err);
// node.error("Catch on close",err, err.message);
// })
// done();
});
///////////////////////////////////////////////////////////////////////////
// doConnect
//
function doConnect(client,options) {
debuglog(options);
let response = {}; // Command response object
const testAsync = async () => {
// Connect to VSCP server/device
debuglog("Calling Connect");
response = await client.connect(
{
host: options.host,
port: options.port,
timeout: options.timeout
});
debuglog("response: "+response);
if ( 'success' !== response.result ) {
throw new Error("Failed to connect to VSCP host.");
}
// Log on to server (step 1 user name)
// The response object is returned and logged
response = await client.sendCommand(
{
command: "user",
argument: options.username
});
debuglog("user: response: "+response);
// Log on to server (step 2 password)
response = await client.sendCommand(
{
command: "pass",
argument: options.password
});
debuglog("pass: response: "+response);
// Send no operation command (does nothing)
response = await client.sendCommand(
{
command: "noop"
})
debuglog("noop: response: "+response);
// Get channel id
let chid = await client.getChannelID();
debuglog("ChannelID: " + chid, options.keyctx);
// Get channel GUID
let guid = await client.getGUID();
debuglog("Channel GUID: " + guid);
// Save channel id in a flow context
if ('string' === typeof options.keyctx ) {
debuglog("Setting keyctx", chid, typeof chid );
flowContext.set(options.keyctx + ".chid", chid );
flowContext.set(options.keyctx + ".start", new Date() );
flowContext.set(options.keyctx + ".guid", guid );
}
// set FILTER if defined
if ( null != options.filter ) {
debuglog("*********** Setting Filter ***********" );
debuglog(options.filter.filterPriority + " " +
options.filter.filterClass + " " +
options.filter.filterType + " " +
options.filter.filterGuid);
response = await client.setFilter({
filterPriority: options.filter.filterPriority,
filterClass: options.filter.filterClass,
filterType: options.filter.filterType,
filterGuid: options.filter.filterGuid,
});
debuglog("Filter: Response: "+response);
debuglog("*********** Setting Mask ***********" );
debuglog(options.filter.maskPriority + " " +
options.filter.maskClass + " " +
options.filter.maskType + " " +
options.filter.maskGuid );
response = await client.setMask({
maskPriority: options.filter.maskPriority,
maskClass: options.filter.maskClass,
maskType: options.filter.maskType,
maskGuid: options.filter.maskGuid,
});
debuglog("Mask: response: " + response);
}
response = await client.startRcvLoop();
debuglog("rcvloop: response: " + response);
node.status({fill:"green",shape:"dot",text:"connected"});
}
testAsync().catch(err => {
node.connecting = false;
node.connected = false;
node.error(err.message);
debuglog("doConnect catch error : " + err.message);
node.status({fill:"red",shape:"dot",text:"Connection error: "+err.message});
})
}
function conn() {
node.connected = false;
node.connecting = true;
// Try to connect to the remote host
doConnect( node.vscpclient,
{
host: node.host,
port: node.port,
timeout: node.timeout,
username: node.username,
password: node.password,
keyctx: node.keyctx,
filter: node.filter
});
}
conn();
var timeoutHandle = setInterval( () => {
debuglog("Link timeout: connected=" +
node.connected +
" connecting=" +
node.connecting +
" keepalive=" +
node.keepalive );
if ( !node.connected && !node.connecting ) {
node.log("Reconnecting!");
node.status({fill:"black",shape:"dot",text:"Reconnecting "});
conn();
}
}, node.keepalive || 5000 );
}
RED.nodes.registerType('vscp-tcp-in', vscpTcpInputNode, {
credentials: {
username: {type:"text"},
password: {type:"password"}
}
});
};