cs-acn
Version:
Control Solutions Adaptive Control Network
361 lines (276 loc) • 8.29 kB
JavaScript
;
/**
* Implements a web/websocket server interface to an ACN device
*
*
*
*/
// the json file where our default configuration is located
var CONFIG_FILE = __dirname + '/config.json';
// Web server component
var http = require('http');
// File system
var fs = require('fs');
// Utility library
var _ = require('underscore');
// Load the object that handles communication to the device
var AcnPort = require('./acn-port');
// Register map for the ACN device
var map = require('./lib/Map');
// command-line options will be available in the args variable
var args = require('minimist')(process.argv.slice(2));
// Configuration defaults
var config = require( CONFIG_FILE );
// Default status (initialization value)
var resetStatus = {
slaveId: {},
networkStatus: {},
scanResult: {},
connectionTable: {},
coordStatus: {},
bank1: {},
config: {},
};
// Keep track of the most recent device status
var last = resetStatus;
// Whether we should keep polling the device
var polling = false;
// use environment variable for port name if specified
config.port.name = args.port || process.env.MODBUS_PORT || config.port.name;
// override slave id if necessary
config.master.defaultUnit = args.slave ||
process.env.MODBUS_SLAVE ||
config.master.defaultUnit;
// override web port if necessary
config.ws.httpPort = args.http || config.ws.httpPort;
// Create the device interface
var port = new AcnPort( config.port.name, config );
// Create a webserver to supply the HTML page
var app = http.createServer(function (req, res) {
fs.createReadStream( __dirname + '/index.html').pipe(res);
});
// Attach the websocket handler to the HTTP server
var io = require('socket.io')(app);
// Start the webserver
app.listen(config.ws.httpPort, function() {
console.log('Server listening on http://localhost:' + config.ws.httpPort);
});
/**
* Sends a full set of status.
*
* If a socket is specified, send only to that socket
* Otherwise send to all connected sockets
*
* @return {[type]} [description]
*/
function emitLast( socket ) {
if( 'undefined' === typeof( socket )) {
socket = io;
}
socket.emit( 'device-connect', last.slaveId );
socket.emit( 'networkStatus', last.networkStatus );
socket.emit( 'scanResult', last.scanResult );
socket.emit( 'connectionTable', last.connectionTable );
socket.emit( 'coordStatus', last.coordStatus );
socket.emit( 'status', last.bank1 );
socket.emit( 'config', last.config );
}
/**
* Read out all the basic identifying information about the device,
* and update socket clients if anything has changed.
*
*/
function inspectDevice() {
// Network status
port.read( map.networkStatus )
.then( function( result ) {
if( !_.isEqual(result.value, last.networkStatus ) ) {
last.networkStatus = result.value;
io.emit( 'networkStatus', last.networkStatus );
//console.log( result.value );
}
})
// ActiveScan results
.then( function() { return port.read( map.scanResult ); })
.then( function( result ) {
if( !_.isEqual(result.value, last.scanResult ) ) {
last.scanResult = result.value;
io.emit( 'scanResult', last.scanResult );
//console.log( result.value );
}
})
// Connection Table
.then( function() { return port.read( map.connectionTable ); })
.then( function( result ) {
if( !_.isEqual(result.value, last.connectionTable ) ) {
last.connectionTable = result.value;
io.emit( 'connectionTable', last.connectionTable );
//console.log( result.value );
}
})
// Coordinator status
.then( function() { return port.read( map.coordStatus ); })
.then( function( result ) {
last.coordStatus = result.value;
io.emit( 'coordStatus', last.coordStatus );
//console.log( result.value );
})
.catch( function() { } );
}
// When a web page opens a socket connection
io.on('connection', function(socket){
emitLast( socket );
console.log('Socket ' + socket.id + ' connected');
/**
* When a socket client emits a command message
*/
socket.on('command', function( msg, fn) {
console.log( 'Command from ' + socket.id + ': ' + msg.action );
switch(msg.action){
case 'write':
port.write( map[msg.item], msg.value )
.then(function(output) { fn(output.format() ); })
.catch( function(e) { fn(e); } );
break;
case 'read':
port.read( map[msg.item] )
.then(function(output) { fn(output.format() ); })
.catch( function(e) { fn(e); } );
break;
case 'scan':
port.scan( msg.type, msg.duration )
.then(function(output) {
console.log( output);
fn(output );
})
.catch( function(e) { fn(e); } );
break;
case 'reset':
port.reset()
.finally( function() { fn(true); } );
break;
case 'clear':
port.clear()
.then( function() { inspectDevice(); } )
.finally( function() { fn(true); } );
break;
case 'pair':
port.pair()
.then( function() { inspectDevice(); } )
.finally( function() { fn(true); } );
break;
default:
fn( new Error('Unknown Action') );
break;
}
});
});
/**
* Clean up and exit the application.
*
* @param {[type]} code [description]
* @return {[type]} [description]
*/
function exit(code) {
try {
port.destroy();
}
catch(e) {
}
process.exit(code);
}
/**
* Requests status information from the device.
*
* Emits it to all socket clients if it changes
*
* Polls continuously unless the global 'polling' boolean is false
*
*/
function pollDevice() {
port.read( map.bank1 )
.then( function(result ) {
var status = result.format();
if( !_.isEqual(status, last.bank1) ) {
last.bank1 = status;
io.emit( 'status', status );
}
})
.then( function() { return port.read( map.sensorData ); })
.then( function( result ) {
var msg = result.format();
if( msg.msgtype > 0 ) {
console.log(msg);
io.emit( 'sensorData', msg );
}
})
.catch(function(e){ console.log(e); })
.finally( function() { if( polling ) { setImmediate(pollDevice); } } );
}
var inspectTimer = null;
/**
* Initiate polling of the ACN device
*
*/
function startPollingDevice() {
// Device slaveId
port.getSlaveId()
.then( function(id) {
if( !_.isEqual(id, last.slaveId) ) {
last.slaveId = id;
io.emit( 'device-connect', last.slaveId );
}
})
.then( function() { return port.read(map.config); })
.then( function(config) {
var result = config.format();
if( !_.isEqual(result, last.config ) ) {
last.config = result;
io.emit( 'config', last.config );
}
})
.catch( function(e) {
console.error('error starting polling', e);
});
inspectTimer = setInterval( inspectDevice, 10000);
polling = true;
pollDevice();
}
/**
* Quit polling the ACN device
* Note: we don't try to cancel outstanding requests, just
* don't restart the polling loop when finished.
*/
function stopPollingDevice() {
polling = false;
if( inspectTimer ) {
clearInterval( inspectTimer );
inspectTimer = null;
}
}
/**
* When the device serial port is opened...
*
*/
port.on( 'connected', function () {
console.log('MODBUS port Connected');
startPollingDevice();
});
/**
* When the device serial port is closed...
*
*/
port.on( 'disconnected', function() {
last = resetStatus;
emitLast();
console.log('MODBUS port Disconnected');
stopPollingDevice();
});
// Open the modbus port
port.open(function(err) {
if( err ) {
console.log(err);
exit(1);
}
});