cs-acn
Version:
Control Solutions Adaptive Control Network
448 lines (352 loc) • 12.6 kB
JavaScript
/**
* Example/demo for Control Solutions Advanced Control Network interface package
*
* Run the demo from the command line. The port settings in the config.json
* file will be used to connect to the ACN device and execute the command
*
*/
;
// the json file where our default configuration is located
var CONFIG_FILE = __dirname + '/config.json';
// get application path
var path = require('path');
// console text formatting
var chalk = require('chalk');
// command-line options
var args = require('minimist')(process.argv.slice(2));
// Configuration defaults
var config = require( CONFIG_FILE );
// Keep track of mode for output purposes
var isAscii = (config.master.transport.type === 'ascii');
// Load the object that handles communication to the device
var AcnPort = require('./acn-port');
// Load the object that handles communication to the device
var map = require('./lib/Map');
// override config file port name if necessary
config.port.name = args.port || process.env.MODBUS_PORT || config.port.name;
// override config file baud rate if necessary
config.port.options.baudrate = args.baud || process.env.MODBUS_BAUD || config.port.options.baudrate;
// override slave id if necessary
config.master.defaultUnit = args.slave ||
process.env.MODBUS_SLAVE || config.master.defaultUnit;
// override transport if necessary
config.master.transport.type = args.transport ||
process.env.MODBUS_TRANSPORT ||
config.master.transport.type;
// override connection if necessary
config.master.transport.connection.type = args.connection ||
process.env.MODBUS_CONNECTION ||
config.master.transport.connection.type;
// override default timeout if necessary
config.master.defaultTimeout = args.defaultTimeout ||
process.env.MODBUS_TIMEOUT ||
config.master.defaultTimeout;
// if the user included the --save option, write the
// actual configuration back to the config.json file to be
// the defaults for next time
if( args.save ) {
var fs = require('fs');
console.info( chalk.green('Writing configuration file\r'));
fs.writeFileSync( CONFIG_FILE, JSON.stringify(config, null, 4));
}
/**
* Parses a string into a number with bounds check
*
* String can be decimal, or if it starts with 0x
* it is interpreted as hex
*
* @param {[string]} s string to parse
* @param {[number]} default if string can't be parsed
* @return {[number]} the parsed number or the default
*/
function parseNumber( s, def )
{
var number;
if( 'undefined' === typeof( s )) {
return def;
}
if( s.toString().substring(0,1) === '0x') {
number = parseInt(s.substring(2), 16);
}
else {
number = parseInt(s);
}
return number;
}
/**
* Convert an array of args to an array of numbers
*
* Parses 0x as hex numbers, else decimal
* @param {[array]} args string array
* @param {[number]} start offset in args to start parsing
* @return {[array]} array of numbers
*/
function argsToByteBuf( args, start ){
var values = [];
for( var i = start; i< args.length; i++ ) {
var number;
if( args[i].toString().substring(0,1) === '0x') {
number = parseInt(args[i].substring(2), 16);
}
else {
number = parseInt(args[i]);
}
if( number < 0 || number > 255 ) {
console.error( chalk.red('Invalid data value: ' + args[i] ));
exit(1);
}
values.push(number);
}
return new Buffer(values);
}
/**
* Cleanup and terminate the process
*
* @param {[type]} code [description]
* @return {[type]} [description]
*/
function exit(code ) {
port.destroy();
process.exit(code);
}
if( args.h ) {
console.info( '\r--------ACN Utility: ' + config.port.name + '----------');
console.info( 'Reads or writes from an ACN device\r');
console.info( '\rCommand format:\r');
console.info(
path.basename(__filename, '.js') + '[-h -v] action [type] [...]\r');
console.info( ' action:\r');
console.info( chalk.bold(' read') + ' item\r');
console.info( chalk.bold(' write') + ' item [value]\r');
console.info( chalk.bold(' scan') + ' [noise|active] [duration]\r');
console.info(
chalk.bold(' slaveId') + ': Report Identity information\r');
console.info( chalk.bold(' reset') + ' : Reset the device\r');
console.info( chalk.bold(' clear') + ' : clear network pairing\r');
console.info( chalk.bold(' pair') + ' : Initiate Pairing\r');
console.info(
chalk.bold(' ping') + ' [address] : Ping remote station\r');
console.info( chalk.underline('Items for read/write:\r'));
Object.keys(map).forEach(function (key) {
if( ['Register',
].indexOf(key) === -1) {
console.info( chalk.bold(key) );
}
});
console.info( chalk.underline( '\rOptions\r'));
console.info( ' -h This help output\r');
console.info( ' -l List all ports on the system\r');
console.info( ' -v Verbose output (for debugging)\r');
console.info( ' --save Write configuration to defaults\r');
console.info( ' --port Specify serial port to use\r');
console.info( ' --loop Run the command continuously\r');
console.info( ' --baud Sets the port baudrate\r');
console.info(
' --slave Specify MODBUS slave ID to communicate with\r');
console.info( ' --transport ' +
'Specify type of transport to use (ascii/rtu/tunnel/ip/socketcand\r');
console.info( ' --connection ' +
'Specify type of connection to use (serial/tcp/udp/generic\r');
console.info( ' --defaultTimeout ' +
'default timeout for MODBUS messages (in milliseconds)\r');
console.info( chalk.underline( '\rEnvironment Variables\r'));
console.info( 'You can set the following environment variables:');
console.info( ' MODBUS_PORT=COM1 Specify the serial port');
console.info( ' MODBUS_BAUD=19200 Specify the baud rate');
console.info( ' MODBUS_SLAVE=17 Specify the MODBUS slave address');
console.info( ' MODBUS_TRANSPORT=rtu Specify the MODBUS transport type');
console.info( ' MODBUS_CONNECTION=serial Specify the type of physical connection');
console.info( ' MODBUS_TIMEOUT=1000 messge timeout in milliseconds');
console.info( chalk.underline( '\rResult\r'));
console.info( 'Return value is 0 if successful\r');
console.info( 'Output may be directed to a file\r');
console.info( ' e.g. ' +
chalk.dim('acn read config >> myConfig.json') + '\r');
process.exit(0);
}
function onSuccess() {
// On success, if looping do it again. otherwise exit
if( args.loop ) {
setImmediate( doAction );
}
else {
exit( 0 );
}
}
function doAction(){
var type;
// Now do the action that was requested
switch( action ) {
case 'read':
// Validate what we are supposed to get
type = args._[1] || 'unknown';
port.read( map[type] )
.then(function(output) {
console.log( map[type].title + ': ', output.format() );
onSuccess();
})
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'write':
// Validate what we are supposed to get
type = args._[1] || 'unknown';
var value = args._[2];
port.write( map[type], value )
.then(function() {
console.log( map[type].title + ' written to ',
map[type].format() );
onSuccess();
})
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'scan':
// Validate what we are supposed to get
switch(args._[1] ) {
case 'active': type = 2; break;
case 'both': type = 3; break;
case 'noise':
type = 1;
break;
default:
type = 1;
break;
}
var duration = args._[2];
port.scan( type, duration )
.then(function(result) {
console.log( 'Scan Result: ', result );
onSuccess();
})
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'slaveId':
port.getSlaveId()
.then(function(output) { console.log(output); onSuccess(); })
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'reset':
port.reset()
.then(function(d) { console.log( 'Result: ' + d);onSuccess();})
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'clear':
port.clear()
.then(function(d) { console.log( 'Result: ' + d); onSuccess();})
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'pair':
port.pair()
.then(function(d) { console.log( 'Result: ' + d); onSuccess(); })
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'command':
if( port.commands.indexOf( args._[1] ) < 0 ) {
console.error(chalk.red( 'Unknown Command ' + action ));
exit(1);
}
var buf = argsToByteBuf( args._, 2 );
port.command( args._[1], buf )
.then(function(response) {
console.log(response.toString());
})
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'ping':
port.ping( parseNumber(args._[1], 0) )
.then(function(response) { console.log(response); onSuccess(); })
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'unlock':
port.unlock()
.then(function(response) { console.log(response); onSuccess(); })
.catch( function(e) { console.log( e); exit(1); } );
break;
case 'factory':
port.getFactoryConfig()
.then(function(response) { console.log(response); onSuccess(); })
.catch( function(e) { console.log( e); exit(1); } );
break;
default:
console.error( chalk.underline.bold( 'Unknown Command' ));
exit(1);
break;
}
}
// Check for the list ports option
if( args.l ) {
var port = new AcnPort( config.port.name, config );
// Retrieve a list of all ports detected on the system
port.list(function (err, ports) {
if( err ) {
console.error( err );
}
if( ports ) {
// ports is now an array of port descriptions.
ports.forEach(function(port) {
// print each port description
console.log(port.comName +
' : ' + port.pnpId + ' : ' + port.manufacturer );
});
}
process.exit(0);
});
}
else {
// Check the action argument for validity
var action = args._[0];
var port = new AcnPort( config.port.name, config );
// Attach event handler for the port opening
port.master.once( 'connected', doAction );
// port errors
port.on('error', function( err ) {
console.error( chalk.underline.bold( err.message ));
exit(1);
});
// Hook events for verbose output
if( args.v ) {
var connection = port.master.getConnection();
connection.on('open', function()
{
console.log( chalk.green('[connection#open]'));
});
connection.on('close', function()
{
console.log(chalk.green('[connection#close]'));
});
connection.on('error', function(err)
{
console.log(chalk.red('Error: ', '[connection#error] ' + err.message));
exit(1);
});
connection.on('write', function(data)
{
if( isAscii ) {
console.log(chalk.green('[connection#write] ' + data.toString()));
}
else {
console.log(chalk.green('[connection#write] '), data );
}
});
connection.on('data', function(data)
{
if( isAscii ) {
console.log(chalk.green('[connection#data] ' + data.toString()));
}
else {
console.log(chalk.green('[connection#data] %d ' ),data.length, data );
}
});
}
if( args.v ) {
console.log( 'Opening ' + config.port.name );
}
// Open the port
// the 'open' event is triggered when complete
port.open(function(err) {
if( err ) {
console.log(err);
exit(1);
}
});
}