sclick-fins
Version:
Node.js implementation of the Omron FINS protocol
408 lines (340 loc) • 12.6 kB
JavaScript
var dgram = require('dgram');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var constants = require('./constants');
var isBit = []; // array to hold whether an SID read is a bit or a word
module.exports = FinsClient;
function FinsClient (port,host,options) {
if(!(this instanceof FinsClient)) return new FinsClient(port,host,options);
EventEmitter.call(this);
FinsClient.init.call(this,port,host,options);
};
inherits(FinsClient,EventEmitter);
_compareArrays = function(a,b) {
if(a.length !== b.length)
return false;
for(var i = a.length; i--;) {
if(a[i] !== b[i])
return false;
}
return true;
};
/* Credit to http://tech.karbassi.com/2009/12/17/pure-javascript-flatten-array/ */
_mergeArrays = function(array) {
var flat = [];
for (var i = 0, l = array.length; i < l; i++){
var type = Object.prototype.toString.call(array[i]).split(' ').pop().split(']').shift().toLowerCase();
if (type) { flat = flat.concat(/^(array|collection|arguments|object)$/.test(type) ? _mergeArrays(array[i]) : array[i]); }
}
return flat;
};
_keyFromValue = function(dict,value) {
var key = Object.keys(dict)
.filter(function(key){
return dict[key] === value
}
)[0];
return key;
};
_padHex = function (width,number) {
return("0"*width + number.toString(16).substr(-width));
};
_wordsToBytes = function(words) {
var bytes = [];
if(!words.length) {
bytes.push((words & 0xff00) >> 8);
bytes.push((words & 0x00ff));
} else {
for(var i in words) {
bytes.push((words[i] & 0xff00) >> 8);
bytes.push((words[i] & 0x00ff));
}
}
return bytes;
};
_translateMemoryAddress = function(memoryAddress, sid) {
var re = /([A-Z]*)([0-9]*):?([0-9]*)/;
var matches = memoryAddress.match(re);
var area = matches[1];
var addr = matches[2];
var bit = matches[3];
var temp = [];
if(!constants.MemoryAreas[area]) {
temp.push([0x82]);
} else {
temp.push(constants.MemoryAreas[area]);
}
temp.push(_wordsToBytes([addr]));
if (bit) {
isBit[sid]=true;
}
else{
isBit[sid]=false;
bit = 0x00;
}
bit &= 0x0f;
temp.push(bit);
return _mergeArrays(temp);
};
_incrementSID = function(sid) {
return (sid % 254) + 1;
};
_buildHeader = function(header) {
var builtHeader = [
header.ICF,
header.RSV,
header.GCT,
header.DNA,
header.DA1,
header.DA2,
header.SNA,
header.SA1,
header.SA2,
header.SID
];
return builtHeader;
};
_buildPacket = function(raw) {
var packet = [];
packet = _mergeArrays(raw);
return packet;
};
_getResponseType = function(buf) {
var response = [];
response.push(buf[10]);
response.push(buf[11]);
return response;
};
_processDefault = function(buf,rinfo) {
var sid = buf[9];
var command = (buf.slice(10,12)).toString("hex");
var response = (buf.slice(12,14)).toString("hex");
return {remotehost:rinfo.address,sid:sid,command:command,response:response};
};
_processStatusRead = function(buf,rinfo) {
var sid = buf[9];
var command = (buf.slice(10,12)).toString("hex");
var response = (buf.slice(12,14)).toString("hex");
var status = buf[14];
var mode = buf[15];
var fatalErrorData = {};
var nonFatalErrorData = {};
for(var i in constants.FatalErrorData) {
if((buf.readInt16BE(17) & constants.FatalErrorData[i]) !=0 )
fatalErrorData.push(i);
}
for(var i in constants.nonFatalErrorData) {
if((buf.readInt16BE(18) & constants.nonFatalErrorData[i]) !=0 )
nonFatalErrorData.push(i);
};
var statusCodes = constants.Status;
var runModes = constants.Modes;
return {
remotehost:rinfo.address,
sid:sid,
command:command,
response:response,
status:_keyFromValue(statusCodes,status),
mode:_keyFromValue(runModes,mode),
fatalErrorData : fatalErrorData || null,
nonFatalErrorData : nonFatalErrorData || null
};
};
_processMemoryAreaRead = function(buf,rinfo) {
var data = [];
var sid = buf[9];
var command = (buf.slice(10,12)).toString("hex");
var response = (buf.slice(12,14)).toString("hex");
var values = (buf.slice(14,buf.length));
var step = isBit[sid] ? 1 : 2;
for(var i = 0; i < values.length; i+=step) {
if(isBit[sid]){
data.push(values.readInt8(i));
}
else{
data.push(values.readInt16BE(i));
}
}
return {remotehost:rinfo.address,sid:sid,command:command,response:response,values:data};
};
_processMemoryAreaReadMultiple = function(buf,rinfo) {
var bits = ['30','31','32','33','B2','B3']; // hex values of memory addresses we support which read a bit
var data = [];
var sid = buf[9];
var command = (buf.slice(10,12)).toString("hex");
var response = (buf.slice(12,14)).toString("hex");
var values = (buf.slice(14,buf.length));
for(var i=0; i<values.length;){
var memarea = values[i++].toString(16); // get the HEX value of the mem area
if(bits.includes(memarea)){ // if we read a bit only read one element of the buffer
data.push(values.readInt8(i)); // not sure if readInt8() is needed, could we just push values[i] instead
i++; // move to the next memory area
}
else{
data.push(values.readInt16BE(i));
i=i+2; // move to the next memory area
}
}
return {remotehost:rinfo.address,sid:sid,command:command,response:response,values:data};
};
_processReply = function(buf,rinfo) {
var commands = constants.Commands;
var responseType = (_getResponseType(buf)).join(' ');
switch(responseType) {
case commands.CONTROLLER_STATUS_READ.join(' '):
return _processStatusRead(buf,rinfo);
break;
case commands.MEMORY_AREA_READ.join(' '):
return _processMemoryAreaRead(buf,rinfo);
break;
case commands.MEMORY_AREA_READ_MULTI.join(' '):
return _processMemoryAreaReadMultiple(buf,rinfo);
break;
default:
return _processDefault(buf,rinfo);
};
};
FinsClient.init = function (port,host,options) {
var self = this;
var defaultHost = constants.DefaultHostValues;
var defaultOptions = constants.DefaultOptions;
this.port = port || defaultHost.port;
this.host = host || defaultHost.host;
this.timeout = (options && options.timeout) || defaultOptions.timeout;
this.socket = dgram.createSocket('udp4');
this.responded = false;
this.header = constants.DefaultFinsHeader;
function receive (buf,rinfo) {
self.responded = true;
var msg = _processReply(buf,rinfo);
self.emit('reply',msg);
}
function listening() {
self.emit('open');
}
function close() {
self.emit('close');
}
function error(err) {
self.emit('error',err);
}
this.socket.on('message',receive);
this.socket.on('listening',listening);
this.socket.on('close',close);
this.socket.on('error',error);
if(this.timeout){
this.timer = setTimeout(function cb_setTimeout() {
if(self.responded == false){
self.timer = undefined;
self.emit('timeout',self.host);
}
},self.timeout);
}
};
FinsClient.prototype.read = function(address,regsToRead,callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var address = _translateMemoryAddress(address, self.header.SID);
var regsToRead = _wordsToBytes(regsToRead);
var command = constants.Commands.MEMORY_AREA_READ;
var commandData = [address,regsToRead];
var packet = _buildPacket([header,command,commandData]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.readMultiple = function() { // no callback not sure how to handle this with an unknown number of arguments
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var command = constants.Commands.MEMORY_AREA_READ_MULTI;
var commandData = []; // tmp array to push all the arguments memory address onto
for(i=0; i<arguments.length;i++){
commandData.push(_translateMemoryAddress(arguments[i]));
}
var packet = _buildPacket([header,command,commandData]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host); // we do not return a callback
};
FinsClient.prototype.transfer = function(sourceAddress,destAddress,regsToRead,callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var sourceAddress = _translateMemoryAddress(sourceAddress, self.header.SID);
var destAddress = _translateMemoryAddress(destAddress, self.header.SID);
var regsToRead = _wordsToBytes(regsToRead);
var command = constants.Commands.MEMORY_AREA_TRANSFER;
var commandData = [sourceAddress,destAddress,regsToRead];
var packet = _buildPacket([header,command,commandData]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.write = function(address,dataToBeWritten,callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var command = constants.Commands.MEMORY_AREA_WRITE;
var regsToWrite = _wordsToBytes((dataToBeWritten.length || 1));
// if address matches a bit, has a 2 character memory address eg HB, we validate the data to be written
if(address.match( /([A-Z]){2}([0-9]*)/ )){
if(dataToBeWritten>1){
console.log("Exiting before attempting to write: Bits can only be 0=0FF or 1=ON");
process.exit(1);
}
}
else{
var dataToBeWritten = _wordsToBytes(dataToBeWritten); // convert data to be written to a word
}
var address = _translateMemoryAddress(address);
var commandData = [address,regsToWrite,dataToBeWritten];
var packet = _buildPacket([header,command,commandData]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.fill = function(address,dataToBeWritten,regsToWrite,callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var address = _translateMemoryAddress(address);
var regsToWrite = _wordsToBytes(regsToWrite);
var command = constants.Commands.MEMORY_AREA_FILL;
var dataToBeWritten = _wordsToBytes(dataToBeWritten);
var commandData = [address,regsToWrite,dataToBeWritten];
var packet = _buildPacket([header,command,commandData]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.run = function(callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var command = constants.Commands.RUN;
var packet = _buildPacket([header,command]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.stop = function(callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var command = constants.Commands.STOP;
var packet = _buildPacket([header,command]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.status = function(callback) {
var self = this;
self.header.SID = _incrementSID(self.header.SID);
var header = _buildHeader(self.header);
var command = constants.Commands.CONTROLLER_STATUS_READ;
var packet = _buildPacket([header,command]);
var buffer = Buffer.from(packet);
this.socket.send(buffer,0,buffer.length,self.port,self.host,callback);
};
FinsClient.prototype.close = function(){
if (this.timer) {
clearTimeout(this.timer);
this.timer = undefined;
}
this.socket.close();
};