omron-fins
Advanced tools
Comparing version 0.2.0-beta.0 to 0.3.0-beta.0
@@ -0,0 +0,0 @@ var fins = require('../lib/index'); |
@@ -0,0 +0,0 @@ /* ***************** UNTESTED ***************** */ |
@@ -8,3 +8,2 @@ | ||
module.exports.DefaultOptions = { | ||
@@ -30,58 +29,75 @@ timeout: 2000, | ||
module.exports.Commands = { | ||
/** Command 06 01 */ | ||
CONTROLLER_STATUS_READ : [0x06,0x01], | ||
/** Command 01 01 */ | ||
MEMORY_AREA_READ : [0x01,0x01], | ||
/** Command 01 02 */ | ||
MEMORY_AREA_WRITE : [0x01,0x02], | ||
/** Command 01 03 */ | ||
MEMORY_AREA_FILL : [0x01,0x03], | ||
/** Command 04 01 */ | ||
RUN : [0x04,0x01], | ||
/** Command 04 02 */ | ||
STOP : [0x04,0x02] | ||
}; | ||
//TODO: create CS/CJ/NJ/NX specific bit map | ||
//SEE CS - https://www.myomron.com/downloads/1.Manuals/Networks/W227E12_FINS_Commands_Reference_Manual.pdf Sec 2-21 | ||
//SEE CJ - https://www.support-omron.fr/telechargements/documentations/2019-01-18%20-%2014-40-23%20-%20616945746/FINS%20Command%20from%20W342-E1-16.pdf | ||
module.exports.FatalErrorData = { | ||
WATCHDOG_ERROR : 1 << 0, | ||
SYSTEM_ERROR : 1 << 6, | ||
CYCLE_TIME_OVER : 1 << 8, | ||
PROGRAM_ERROR : 1 << 9, | ||
IO_SETTING_ERROR : 1 << 10, | ||
IO_POINT_OVERFLOW : 1 << 11, | ||
INNER_BOARD_ERROR : 1 << 12, | ||
DUPLICATION_ERROR : 1 << 13, | ||
IO_BUS_ERROR : 1 << 14, | ||
MEMORY_ERROR : 1 << 15 | ||
}; | ||
//TODO: create CS/CJ/NJ/NX specific bit map | ||
module.exports.NonFatalErrorData = { | ||
PC_LINK_ERROR : 1 << 0, | ||
HOST_LINK_ERROR : 1 << 1, | ||
BATTERY_ERROR : 1 << 4, | ||
SYSMAC_BUS_ERROR : 1 << 5, | ||
SPECIAL_IO_UNIT_ERROR : 1 << 6, | ||
CPU_BUS_UNIT_ERROR : 1 << 7, | ||
INNER_BOARD_ERROR : 1 << 8, | ||
IO_VERIFICATION_ERROR : 1 << 9, | ||
PLC_SETUP_ERROR : 1 << 10, | ||
BASIC_IO_UNIT_ERROR : 1 << 12, | ||
INTERRUPT_TASK_ERROR : 1 << 13, | ||
DUPLEX_ERROR : 1 << 14, | ||
SYSTEM_ERROR : 1 << 15 | ||
}; | ||
module.exports.Status = { | ||
CPU_STANDBY : 0x80, | ||
STOP : 0x00, | ||
RUN : 0x01 | ||
const CSCJ_MODE_BIT_MemoryAreas = { | ||
"E0" : 0x20,//Extended Memories | ||
"E1" : 0x21,//Extended Memories | ||
"E2" : 0x22,//Extended Memories | ||
"E3" : 0x23,//Extended Memories | ||
"E4" : 0x24,//Extended Memories | ||
"E5" : 0x25,//Extended Memories | ||
"E6" : 0x26,//Extended Memories | ||
"E7" : 0x27,//Extended Memories | ||
"E8" : 0x28,//Extended Memories | ||
"E9" : 0x29,//Extended Memories | ||
"EA" : 0x2A,//Extended Memories | ||
"EB" : 0x2B,//Extended Memories | ||
"EC" : 0x2C,//Extended Memories | ||
"EE" : 0x2D,//Extended Memories | ||
"EF" : 0x2E,//Extended Memories | ||
"E10" : 0x60,//Extended Memories | ||
"E11" : 0xE1,//Extended Memories | ||
"E12" : 0xE2,//Extended Memories | ||
"E13" : 0xE3,//Extended Memories | ||
"E14" : 0xE4,//Extended Memories | ||
"E15" : 0xE5,//Extended Memories | ||
"E16" : 0xE6,//Extended Memories | ||
"E17" : 0xE7,//Extended Memories | ||
"E18" : 0xE8,//Extended Memories | ||
"T" : 0x09,//TIM PV | ||
"C" : 0x09,//CNT PV | ||
"CIO" : 0x30,//CIO | ||
"W" : 0x31,//Work Area | ||
"H" : 0x32,//Holding Bit | ||
"A" : 0x33,//Auxiliary Bit | ||
"D" : 0x02,//Data Memories | ||
/** | ||
* Calculates the correct FINS memory "Beginning Address" for given area and address | ||
* @param {String} memoryArea - e.g "D" or "CIO" | ||
* @param {Number} memoryAddress - The start word address | ||
* @returns FINS memory "Beginning Address" | ||
*/ | ||
CalculateMemoryAreaAddress : function(memoryArea, memoryAddress) { | ||
switch (memoryArea) { | ||
case "A": | ||
if(memoryAddress > 447) | ||
return memoryAddress+ 0x01C0; //read/write 448 ~ 959 | ||
return memoryAddress; //readonly 0 ~ 447 | ||
break; | ||
case "C": | ||
return memoryAddress+ 0x8000; | ||
break; | ||
default: | ||
return memoryAddress; | ||
break; | ||
} | ||
} | ||
}; | ||
//TODO: create CS/CJ/NJ/NX specific bit map | ||
module.exports.Modes = { | ||
MONITOR : 0x02, | ||
PROGRAM : 0x00, | ||
DEBUG : 0x01, | ||
RUN : 0x04 | ||
}; | ||
module.exports.CSCJ_MODE_WD_MemoryAreas = { | ||
const CSCJ_MODE_WD_MemoryAreas = { | ||
"E0" : 0xA0,//Extended Memories | ||
@@ -120,16 +136,18 @@ "E1" : 0xA1,//Extended Memories | ||
"DR" : 0xBC,//Data Registers | ||
/** | ||
* Calculates the correct FINS memory "Beginning Address" for given area and address | ||
* @param {String} memoryArea - e.g "D" or "CIO" | ||
* @param {Number} memoryAddress - The start word address | ||
* @returns FINS memory "Beginning Address" | ||
*/ | ||
CalculateMemoryAreaAddress : function(memoryArea, memoryAddress ) { | ||
switch (memoryArea) { | ||
case "A": | ||
if(memoryArea > 447) | ||
return memoryArea + 0x01C0; //read/write 448 ~ 959 | ||
return memoryArea; //readonly 0 ~ 447 | ||
break; | ||
if(memoryAddress > 447) | ||
return memoryAddress + 0x01C0; //read/write 448 ~ 959 | ||
return memoryAddress; //readonly 0 ~ 447 | ||
case "C": | ||
return memoryArea + 0x8000; | ||
break; | ||
return memoryAddress + 0x8000; | ||
default: | ||
return memoryAddress; | ||
break; | ||
} | ||
@@ -139,6 +157,5 @@ } | ||
//compat | ||
module.exports.NJNX_MODE_WD_MemoryAreas = module.exports.CSCJ_MODE_WD_MemoryAreas; | ||
module.exports.CV_MODE_WD_MemoryAreas = { | ||
const CV_MODE_BIT_MemoryAreas = { | ||
'E0' : 0x90,//Extended Memories | ||
@@ -152,2 +169,36 @@ 'E1' : 0x91,//Extended Memories | ||
'E7' : 0x97,//Extended Memories | ||
'T' : 0x01,//TIM PV | ||
'C' : 0x01,//CNT PV | ||
'CIO' : 0x00,//CIO | ||
'A' : 0x00,//Auxiliary Bit | ||
'D' : 0x02,//Data Memories | ||
/** | ||
* Calculates the correct FINS memory "Beginning Address" for given area and address | ||
* @param {String} memoryArea - e.g "D" or "CIO" | ||
* @param {Number} memoryAddress - The start word address | ||
* @returns FINS memory "Beginning Address" | ||
*/ | ||
CalculateMemoryAreaAddress : function(memoryArea, memoryAddress ) { | ||
switch (memoryArea) { | ||
case "A": | ||
if(memoryAddress > 447) | ||
return memoryAddress + 0x0CC0; | ||
return memoryAddress + 0xB000; | ||
case "C": | ||
return memoryAddress + 0x0800; | ||
default: | ||
return memoryAddress; | ||
} | ||
} | ||
}; | ||
const CV_MODE_WD_MemoryAreas = { | ||
'E0' : 0x90,//Extended Memories | ||
'E1' : 0x91,//Extended Memories | ||
'E2' : 0x92,//Extended Memories | ||
'E3' : 0x93,//Extended Memories | ||
'E4' : 0x94,//Extended Memories | ||
'E5' : 0x95,//Extended Memories | ||
'E6' : 0x96,//Extended Memories | ||
'E7' : 0x97,//Extended Memories | ||
'T' : 0x81,//TIM PV | ||
@@ -159,16 +210,18 @@ 'C' : 0x81,//CNT PV | ||
'DR' : 0x9C,//Data Registers | ||
/** | ||
* Calculates the correct FINS memory "Beginning Address" for given area and address | ||
* @param {String} memoryArea - e.g "D" or "CIO" | ||
* @param {Number} memoryAddress - The start word address | ||
* @returns FINS memory "Beginning Address" | ||
*/ | ||
CalculateMemoryAreaAddress : function(memoryArea, memoryAddress ) { | ||
switch (memoryArea) { | ||
case "A": | ||
if(memoryArea > 447) | ||
return memoryArea + 0x0CC0; | ||
return memoryArea + 0xB000; | ||
break; | ||
if(memoryAddress > 447) | ||
return memoryAddress + 0x0CC0; | ||
return memoryAddress + 0xB000; | ||
case "C": | ||
return memoryArea + 0x0800; | ||
break; | ||
return memoryAddress + 0x0800; | ||
default: | ||
return memoryAddress; | ||
break; | ||
} | ||
@@ -178,2 +231,68 @@ } | ||
const MemoryAreas = { | ||
CV: { | ||
bit: CV_MODE_BIT_MemoryAreas, | ||
word: CV_MODE_WD_MemoryAreas, | ||
}, | ||
CS: { | ||
bit: CSCJ_MODE_BIT_MemoryAreas, | ||
word: CSCJ_MODE_WD_MemoryAreas, | ||
} | ||
}; | ||
MemoryAreas.CJ = MemoryAreas.CS; | ||
MemoryAreas.NX = MemoryAreas.CS; | ||
MemoryAreas.CSCJ = MemoryAreas.CS; | ||
MemoryAreas.NJNX = MemoryAreas.CS; | ||
module.exports.MemoryAreas = MemoryAreas; | ||
//TODO: create CS/CJ/NJ/NX specific bit map | ||
//SEE CS - https://www.myomron.com/downloads/1.Manuals/Networks/W227E12_FINS_Commands_Reference_Manual.pdf Sec 2-21 | ||
//SEE CJ - https://www.support-omron.fr/telechargements/documentations/2019-01-18%20-%2014-40-23%20-%20616945746/FINS%20Command%20from%20W342-E1-16.pdf | ||
module.exports.FatalErrorData = { | ||
WATCHDOG_ERROR : 1 << 0, | ||
SYSTEM_ERROR : 1 << 6, | ||
CYCLE_TIME_OVER : 1 << 8, | ||
PROGRAM_ERROR : 1 << 9, | ||
IO_SETTING_ERROR : 1 << 10, | ||
IO_POINT_OVERFLOW : 1 << 11, | ||
INNER_BOARD_ERROR : 1 << 12, | ||
DUPLICATION_ERROR : 1 << 13, | ||
IO_BUS_ERROR : 1 << 14, | ||
MEMORY_ERROR : 1 << 15 | ||
}; | ||
//TODO: create CS/CJ/NJ/NX specific bit map | ||
module.exports.NonFatalErrorData = { | ||
PC_LINK_ERROR : 1 << 0, | ||
HOST_LINK_ERROR : 1 << 1, | ||
BATTERY_ERROR : 1 << 4, | ||
SYSMAC_BUS_ERROR : 1 << 5, | ||
SPECIAL_IO_UNIT_ERROR : 1 << 6, | ||
CPU_BUS_UNIT_ERROR : 1 << 7, | ||
INNER_BOARD_ERROR : 1 << 8, | ||
IO_VERIFICATION_ERROR : 1 << 9, | ||
PLC_SETUP_ERROR : 1 << 10, | ||
BASIC_IO_UNIT_ERROR : 1 << 12, | ||
INTERRUPT_TASK_ERROR : 1 << 13, | ||
DUPLEX_ERROR : 1 << 14, | ||
SYSTEM_ERROR : 1 << 15 | ||
}; | ||
module.exports.Status = { | ||
CPU_STANDBY : 0x80, | ||
STOP : 0x00, | ||
RUN : 0x01 | ||
}; | ||
//TODO: create CS/CJ/NJ/NX specific bit map | ||
module.exports.Modes = { | ||
MONITOR : 0x02, | ||
PROGRAM : 0x00, | ||
DEBUG : 0x01, | ||
RUN : 0x04 | ||
}; | ||
module.exports.EndCodeDescriptions = { | ||
@@ -180,0 +299,0 @@ "0000" : "Normal Completion.", |
@@ -1,5 +0,8 @@ | ||
var dgram = require('dgram'); | ||
var inherits = require('util').inherits; | ||
var EventEmitter = require('events').EventEmitter; | ||
var constants = require('./constants'); | ||
const dgram = require('dgram'); | ||
const inherits = require('util').inherits; | ||
const EventEmitter = require('events').EventEmitter; | ||
const constants = require('./constants'); | ||
const SequenceManager = require('./SequenceManager'); | ||
const FinsAddressUtil = require('./FinsAddressUtil'); | ||
const {normaliseBool, boolsToBytes, wordsToBytes, buildPacket, getKeyName, isInt} = require('./data_utilities'); | ||
@@ -16,513 +19,67 @@ module.exports = FinsClient; | ||
function SequenceManager(parent, opts) { | ||
const Statistics = function (sampleSize) { | ||
const size = sampleSize || 50; | ||
var index, replyCount, minReplyMS, maxReplyMS, errorCount, timeoutCount, array; | ||
var startTime = Date.now(); | ||
var mspTimer, mpsCounter, mps; | ||
_init(); | ||
function _init() { | ||
index = replyCount = errorCount = timeoutCount = maxReplyMS = 0; | ||
mpsCounter = mps = 0; | ||
minReplyMS = Number.MAX_VALUE; | ||
array = new Array(sampleSize); | ||
for (let index = 0; index < sampleSize; index++) { | ||
array[index] = 0; | ||
} | ||
if (mspTimer) clearInterval(mspTimer); | ||
mspTimer = setInterval(function interval() { | ||
mps = mpsCounter; | ||
mpsCounter = 0; | ||
}, 1000); | ||
} | ||
return { | ||
addReply: function (ms) { | ||
replyCount++; | ||
mpsCounter++; | ||
if (index >= array.length) index = 0; | ||
array[index++] = ms; | ||
if (ms > maxReplyMS) maxReplyMS = ms; | ||
if (ms < minReplyMS) minReplyMS = ms; | ||
return this.stats(); | ||
}, | ||
addError: function () { | ||
errorCount++; | ||
return this.stats(); | ||
}, | ||
addTimeout: function () { | ||
timeoutCount++; | ||
return this.stats(); | ||
}, | ||
stats: function () { | ||
var _count = (replyCount > sampleSize ? sampleSize : replyCount) || 1; | ||
var sum = array.reduce(function (a, b) { | ||
return a + b; | ||
}, 0); | ||
var avg = (sum / _count) || 0; | ||
return { | ||
replyCount: replyCount, | ||
errorCount: errorCount, | ||
timeoutCount: timeoutCount, | ||
minReplyMS: minReplyMS, | ||
maxReplyMS: maxReplyMS, | ||
msgPerSec: mps, | ||
averageReplyMS: avg, | ||
runtimeMS: Date.now() - startTime | ||
}; | ||
}, | ||
reset: function () { | ||
_init(); | ||
}, | ||
close: function () { | ||
console.debug("cleaing up "); | ||
if (mspTimer) clearInterval(mspTimer); | ||
} | ||
}; | ||
}; | ||
var statisics = Statistics(50); | ||
var parent = parent; | ||
opts = opts || {}; | ||
var options = { | ||
minSID: opts.minSID || 1, | ||
maxSID: opts.maxSID || 254, | ||
timeoutMS: opts.timeoutMS || 10000 | ||
}; | ||
const capacity = (options.maxSID - options.minSID) + 1; | ||
var sequences = {}; | ||
return { | ||
sequences: function () { | ||
return sequences; | ||
}, | ||
freeSpace: function () { | ||
//future speed up - dont recalculate, instead, inc/dec an in-use counter | ||
return capacity - this.activeCount(); | ||
}, | ||
activeCount: function () { | ||
//future speed up - dont recalculate, instead, inc/dec an in-use counter | ||
return Object.values(sequences).reduce(function (a, v) { | ||
return v && v.sid && !v.complete && !v.timeout && !v.error ? a + 1 : a; | ||
}, 0); | ||
}, | ||
add: function (SID, request, tag) { | ||
if (SID >= options.minSID && SID <= options.maxSID) { | ||
let seq = sequences[SID]; | ||
if (seq && !seq.complete && !seq.timeout) { | ||
throw new Error("This SID is already waiting a reply"); | ||
} | ||
seq = { | ||
sid: SID, | ||
request: request, | ||
tag: tag || null, | ||
sent: false, | ||
complete: false, | ||
timeout: false, | ||
error: false, | ||
createTime: Date.now(), | ||
}; | ||
sequences[SID] = seq; | ||
seq.timer = setTimeout(function () { | ||
if (seq.complete || seq.error) return; | ||
seq.timeout = true; | ||
seq.stats = statisics.addTimeout(); | ||
if (seq.request && seq.request.callback) { | ||
seq.request.callback(new Error("timeout"), seq); | ||
} else { | ||
parent.emit('timeout', parent.host, seq); | ||
} | ||
}, options.timeoutMS); | ||
return seq; | ||
} | ||
}, | ||
get: function (SID) { | ||
if (SID >= options.minSID && SID <= options.maxSID) { | ||
return sequences[SID]; | ||
} | ||
}, | ||
done: function (SID) { | ||
let seq = this.get(SID); | ||
if (seq) { | ||
seq.complete = true; | ||
seq.replyTime = Date.now(); | ||
if (seq.timer) { | ||
clearTimeout(seq.timer); | ||
seq.timer = null; | ||
delete seq.timer; | ||
} | ||
seq.timeTaken = seq.replyTime - seq.createTime; | ||
seq.stats = statisics.addReply(seq.timeTaken); | ||
} | ||
}, | ||
setError: function (SID, err) { | ||
let seq = this.get(SID); | ||
if (seq) { | ||
seq.stats = statisics.addError(); | ||
seq.error = err; | ||
if (seq.timer) { | ||
clearTimeout(seq.timer); | ||
seq.timer = null; | ||
delete seq.timer; | ||
} | ||
if (seq.request && seq.request.callback) { | ||
seq.request.callback(err, seq); | ||
} else { | ||
parent.emit('error', err, seq); | ||
} | ||
} | ||
}, | ||
confirmSent: function (SID) { | ||
let seq = this.get(SID); | ||
if (seq) { | ||
seq.sentTime = Date.now(); | ||
seq.sent = true; | ||
} | ||
}, | ||
delete: function (SID) { | ||
let seq = this.get(SID); | ||
if (seq) { | ||
if (seq.timer) { | ||
clearTimeout(seq.timer); | ||
seq.timer = null; | ||
delete seq.timer; | ||
} | ||
sequences[SID] = null; //TODO: consider object reuse! | ||
delete sequences[SID]; | ||
} | ||
}, | ||
close: function () { | ||
statisics.close(); | ||
for (let _SID = options.minSID; _SID < options.maxSID; _SID++) { | ||
try { | ||
this.delete(_SID); | ||
} catch (error) { } | ||
} | ||
} | ||
}; | ||
} | ||
_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; | ||
}; | ||
FinsClient.prototype.init = function (port, host, options) { | ||
/** @type {FinsClient}*/ const self = this; | ||
const defaultHost = constants.DefaultHostValues; | ||
const defaultOptions = constants.DefaultOptions; | ||
self.port = port || defaultHost.port; | ||
self.host = host || defaultHost.host; | ||
self.options = options || {}; | ||
self.options.MODE = self.options.MODE || "CS"; | ||
self.timeout = (self.options.timeout) || defaultOptions.timeout || 2000; | ||
self.max_queue = (self.options.max_queue) || defaultOptions.max_queue || 100; | ||
/** @type {FinsAddressUtil} */ self.finsAddresses = new FinsAddressUtil(self.options.MODE); | ||
_mergeArrays = function (array) { | ||
return array.reduce(function (flat, toFlatten) { | ||
return flat.concat(Array.isArray(toFlatten) ? _mergeArrays(toFlatten) : toFlatten); | ||
}, []); | ||
}; | ||
_keyFromValue = function (dict, value) { | ||
var key = Object.keys(dict) | ||
.filter(function (key) { | ||
return dict[key] === value; | ||
//self.sequenceManager = new SequenceManager(self, { timeoutMS: this.timeout }); | ||
this.sequenceManager = SequenceManager({ timeoutMS: this.timeout }, function(err, seq) { | ||
if(err) { | ||
self.emit("error", err, seq); | ||
} | ||
)[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)); | ||
} | ||
//cleanup (if reconnecting, socket might be initialised) | ||
if (self.socket) { | ||
self.socket.removeAllListeners(); | ||
delete self.socket; | ||
} | ||
return bytes; | ||
/** @type {dgram.Socket} */ | ||
self.socket = dgram.createSocket('udp4'); | ||
}; | ||
_decodedAddressToString = function (decodedMemoryAddress, offsetWD, offsetBit) { | ||
offsetWD = isInt(offsetWD, 0); | ||
if (decodedMemoryAddress.Bit) { | ||
offsetBit = isInt(offsetBit, 0); | ||
return `${decodedMemoryAddress.MemoryArea}${parseInt(decodedMemoryAddress.Address) + offsetWD}.${decodedMemoryAddress.Bit + offsetBit}`; | ||
self.header = Object.assign({}, constants.DefaultFinsHeader); | ||
self.header.ICF = isInt(self.options.ICF, constants.DefaultFinsHeader.ICF); | ||
self.header.DNA = isInt(self.options.DNA, constants.DefaultFinsHeader.DNA); | ||
self.header.DA1 = isInt(self.options.DA1, constants.DefaultFinsHeader.DA1); | ||
self.header.DA2 = isInt(self.options.DA2, constants.DefaultFinsHeader.DA2); | ||
self.header.SNA = isInt(self.options.SNA, constants.DefaultFinsHeader.SNA); | ||
self.header.SA1 = isInt(self.options.SA1, constants.DefaultFinsHeader.SA1); | ||
self.header.SA2 = isInt(self.options.SA2, constants.DefaultFinsHeader.SA2); | ||
self.header.incrementSID = function() { | ||
this.SID = (Math.abs(this.SID) % 254) + 1; | ||
return this.SID; | ||
} | ||
return `${decodedMemoryAddress.MemoryArea}${parseInt(decodedMemoryAddress.Address) + offsetWD}`; | ||
}; | ||
_decodeMemoryAddress = function (addressString) { | ||
var re = /([A-Z]*)([0-9]*)\.?([0-9]*)/;//normal address Dxxx Cxxx | ||
if (addressString.includes("_")) | ||
re = /(.+)_([0-9]*)\.?([0-9]*)/; //handle Ex_ basically E1_ is same as E + 1 up to 15 then E16_=0x60 ~ 0x68 | ||
var matches = addressString.match(re); | ||
var decodedMemory = { | ||
'MemoryArea': matches[1], | ||
'Address': matches[2], | ||
'Bit': matches[3] | ||
self.header.build = function() { | ||
var builtHeader = [ | ||
this.ICF, | ||
this.RSV, | ||
this.GCT, | ||
this.DNA, | ||
this.DA1, | ||
this.DA2, | ||
this.SNA, | ||
this.SA1, | ||
this.SA2, | ||
this.SID | ||
]; | ||
return builtHeader; | ||
}; | ||
return decodedMemory; | ||
}; | ||
_translateMemoryAddressString = function (addressString, memoryAreas) { | ||
var decodedMemoryAddress = _decodeMemoryAddress(addressString); | ||
return _translateMemoryAddress(decodedMemoryAddress, memoryAreas); | ||
}; | ||
_translateMemoryAddress = function (decodedMemoryAddress, memoryAreas) { | ||
var temp = []; | ||
var byteEncodedMemory = []; | ||
var memAreaCode = memoryAreas[decodedMemoryAddress.MemoryArea]; //get INT value for desired Memory Area (e.g. D=0x82) | ||
if (!memAreaCode) { | ||
return null;//null? something else? throw error? | ||
} | ||
var memAreaAddress = memoryAreas.CalculateMemoryAreaAddress(decodedMemoryAddress.MemoryArea, decodedMemoryAddress.Address);//Calculate memAreaAddress value (e.g. C12 = 12 + 0x8000 ) | ||
temp.push([memAreaCode]); | ||
temp.push(_wordsToBytes([memAreaAddress])); | ||
temp.push([0x00]);//TODO: handle bit addresses | ||
byteEncodedMemory = _mergeArrays(temp); | ||
return byteEncodedMemory; | ||
}; | ||
_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; | ||
}; | ||
_processEndCode = function (hiByte, loByte) { | ||
var MRES = hiByte, SRES = loByte; | ||
var NetworkRelayError = ((MRES & 0x80) > 0); | ||
var NonFatalCPUUnitErr = ((SRES & 0x40) > 0); | ||
var FatalCPUUnitErr = ((SRES & 0x80) > 0); | ||
MRES = (MRES & 0x3f); | ||
SRES = (SRES & 0x2f); | ||
var endCode = ((MRES << 8) + SRES).toString(16); //.padStart(4,"0"); NodeJS8+ | ||
while(endCode.length < 4) { | ||
endCode = "0" + endCode; | ||
} | ||
var endCodeDescription = constants.EndCodeDescriptions[endCode]; | ||
return { | ||
MRES: MRES, | ||
SRES: SRES, | ||
NetworkRelayError: NetworkRelayError, | ||
NonFatalCPUUnitErr: NonFatalCPUUnitErr, | ||
FatalCPUUnitErr: FatalCPUUnitErr, | ||
endCode: endCode, | ||
endCodeDescription: endCodeDescription | ||
} | ||
} | ||
_processDefault = function (buf, rinfo) { | ||
var sid = buf[9]; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
return { remoteHost: rinfo.address, sid: sid, command: command }; | ||
}; | ||
_processStatusRead = function (buf, rinfo) { | ||
var sid = buf[9]; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
var status = (buf[14] & 0x81); //Mask out battery[2] and CF[1] status or a direct lookup could fail. | ||
var mode = buf[15]; | ||
var fatalErrorData = {}; | ||
var nonFatalErrorData = {}; | ||
var fed = buf.readInt16BE(16); | ||
var nfed = buf.readInt16BE(18); | ||
var messageYN = buf.readInt16BE(20); | ||
var plcErrCode = buf.readInt16BE(22); | ||
var plcMessage = ""; | ||
if(messageYN) plcMessage = buf.slice(24,-1).toString(); //PLC Message | ||
//any fatal errors? | ||
if(fed){ | ||
for (var i in constants.FatalErrorData) { | ||
if ((fed & constants.FatalErrorData[i]) != 0) { | ||
fatalErrorData[i] = true; | ||
} | ||
} | ||
} | ||
//any non fatal errors? | ||
if(nfed) { | ||
for (var j in constants.nonFatalErrorData) { | ||
if ((nfed & constants.NonFatalErrorData[j]) != 0) { | ||
nonFatalErrorData[j] = true; | ||
} | ||
} | ||
} | ||
var statusCodes = constants.Status; | ||
var runModes = constants.Modes; | ||
return { | ||
remoteHost: rinfo.address, | ||
sid: sid, | ||
command: command, | ||
commandDescription: "status", | ||
status: _keyFromValue(statusCodes, status), | ||
mode: _keyFromValue(runModes, mode), | ||
fatalErrors: (fed ? fatalErrorData : null), | ||
nonFatalErrors: (nfed ? nonFatalErrorData : null), | ||
plcErrCode: plcErrCode, | ||
plcMessage: plcMessage | ||
}; | ||
}; | ||
_processMemoryAreaRead = function (buf, rinfo) { | ||
var data = []; | ||
var sid = buf[9]; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
var bufData = (buf.slice(14, buf.length)); | ||
for (var i = 0; i < bufData.length; i += 2) { | ||
data.push(bufData.readInt16BE(i)); | ||
} | ||
return { | ||
remoteHost: rinfo.address, | ||
sid: sid, | ||
command: command, | ||
commandDescription: "read", | ||
values: data, | ||
buffer: bufData | ||
}; | ||
}; | ||
_processReply = function (buf, rinfo) { | ||
var commands = constants.Commands; | ||
var responseType = (_getResponseType(buf)).join(' '); | ||
var processEndCode = _processEndCode(buf[12],buf[13]); | ||
var processResult; | ||
switch (responseType) { | ||
case commands.CONTROLLER_STATUS_READ.join(' '): | ||
processResult = _processStatusRead(buf, rinfo); | ||
break; | ||
case commands.MEMORY_AREA_READ.join(' '): | ||
processResult = _processMemoryAreaRead(buf, rinfo); | ||
break; | ||
default: | ||
processResult = _processDefault(buf, rinfo); | ||
break; | ||
} | ||
processResult.endCode = processEndCode.endCode; | ||
processResult.endCodeDescription = processEndCode.endCodeDescription; | ||
processResult.MRES = processEndCode.MRES; | ||
processResult.SRES = processEndCode.SRES; | ||
processResult.NetworkRelayError = processEndCode.NetworkRelayError; | ||
processResult.NonFatalCPUUnitErr = processEndCode.NonFatalCPUUnitErr; | ||
processResult.FatalCPUUnitErr = processEndCode.FatalCPUUnitErr; | ||
return processResult; | ||
}; | ||
_decodePacket = function (buf, rinfo) { | ||
var data = []; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
var endCode = (buf.slice(12, 14)).toString("hex"); | ||
var endCodeDescription = constants.EndCodeDescriptions[endCode]; | ||
var values = (buf.slice(14, buf.length)); | ||
for (var i = 0; i < values.length; i += 2) { | ||
data.push(values.readInt16BE(i)); | ||
} | ||
return { remoteHost: rinfo.address, command: command, endCode: endCode, endCodeDescription: endCodeDescription, values: data }; | ||
}; | ||
_sendParamError = function (message, callback, seq) { | ||
var addrErr = Error(message); | ||
if(callback) { | ||
callback(addrErr, seq) | ||
} else { | ||
self.emit('error', addrErr, seq); | ||
} | ||
} | ||
function isInt(x, def) { | ||
var v; | ||
try { | ||
v = parseInt(x); | ||
if (isNaN(v)) | ||
return def; | ||
} catch (e) { | ||
return def; | ||
} | ||
return v; | ||
} | ||
FinsClient.prototype.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.options = options || {}; | ||
this.timeout = (this.options.timeout) || defaultOptions.timeout || 5000; | ||
this.max_queue = (this.options.max_queue) || defaultOptions.max_queue || 100; | ||
this.memoryAreas = constants.CSCJ_MODE_WD_MemoryAreas; | ||
this.sequenceManager = SequenceManager(self, { timeoutMS: 1000 }); | ||
if (this.options.MODE == "NJNX") { | ||
this.memoryAreas = constants.NJNX_MODE_WD_MemoryAreas; | ||
} else if (this.options.MODE == "CV") { | ||
this.memoryAreas = constants.CV_MODE_WD_MemoryAreas; | ||
} | ||
//cleanup (if reconnecting, socket might be initialised) | ||
if (this.socket) { | ||
this.socket.removeAllListeners(); | ||
delete this.socket; | ||
} | ||
this.socket = dgram.createSocket('udp4'); | ||
this.header = Object.assign({}, constants.DefaultFinsHeader); | ||
this.header.ICF = isInt(this.options.ICF, constants.DefaultFinsHeader.ICF); | ||
this.header.DNA = isInt(this.options.DNA, constants.DefaultFinsHeader.DNA); | ||
this.header.DA1 = isInt(this.options.DA1, constants.DefaultFinsHeader.DA1); | ||
this.header.DA2 = isInt(this.options.DA2, constants.DefaultFinsHeader.DA2); | ||
this.header.SNA = isInt(this.options.SNA, constants.DefaultFinsHeader.SNA); | ||
this.header.SA1 = isInt(this.options.SA1, constants.DefaultFinsHeader.SA1); | ||
this.header.SA2 = isInt(this.options.SA2, constants.DefaultFinsHeader.SA2); | ||
this.connected = false; | ||
self.connected = false; | ||
self.requests = {}; | ||
self.emit('initialised', this.options); | ||
self.emit('initialised', self.options); | ||
function receive(buf, rinfo) { | ||
var response = _processReply(buf, rinfo); | ||
//console.warn("receive", buf) | ||
var response = _processReply(buf, rinfo, self.sequenceManager); | ||
if (response) { | ||
//console.warn(response.sid, "receive", response) | ||
self.sequenceManager.done(response.sid);//1st, cancel the timeout | ||
@@ -538,3 +95,3 @@ var seq = self.sequenceManager.get(response.sid);//now get the sequence | ||
} | ||
self.sequenceManager.delete(response.sid); | ||
self.sequenceManager.remove(response.sid); | ||
} | ||
@@ -565,6 +122,6 @@ } else { | ||
this.socket.on('message', receive); | ||
this.socket.on('listening', listening); | ||
this.socket.on('close', close); | ||
this.socket.on('error', error); | ||
self.socket.on('message', receive); | ||
self.socket.on('listening', listening); | ||
self.socket.on('close', close); | ||
self.socket.on('error', error); | ||
@@ -574,8 +131,23 @@ }; | ||
FinsClient.prototype.reconnect = function () { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
self.init(self.port, self.host, self.options); | ||
}; | ||
/** | ||
* This callback is displayed as a global member. | ||
* @callback commandCallback | ||
* @param {*} err - Error (if any) | ||
* @param {object} msg - The msg object containing the `request`, `response`, `tag` and more. | ||
*/ | ||
/** | ||
* Memory Area Read Command | ||
* @param {string} address - Memory area and the numerical start address e.g. `D100` or `CIO50.0` | ||
* @param {number} count - Number of registers to read | ||
* @param {commandCallback} [callback=null] - Optional callback method `(err, msg) => {}` | ||
* @param {*} [tag=null] - Optional tag item that is sent back in the callback method | ||
* @returns the SID of the request (returns `null` if any of the command parameters are invalid). | ||
*/ | ||
FinsClient.prototype.read = function (address, count, callback, tag) { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
if (self.queueCount() >= self.max_queue) { | ||
@@ -586,4 +158,4 @@ // console.warn("queue count exceeded") | ||
} | ||
var memoryAddress = _decodeMemoryAddress(address); | ||
var addressData = _translateMemoryAddress(memoryAddress, this.memoryAreas); | ||
const memoryAddress = self.finsAddresses.stringToAddress(address); | ||
const addressData = self.finsAddresses.addressToBytes(memoryAddress); | ||
if (!addressData) { | ||
@@ -597,9 +169,9 @@ _sendParamError("address is invalid", callback, {tag: tag}); | ||
} | ||
var SID = self.header.SID = _incrementSID(self.header.SID); | ||
var header = _buildHeader(self.header); | ||
var command = constants.Commands.MEMORY_AREA_READ; | ||
var commandData = [addressData, _wordsToBytes(count)]; | ||
var packet = _buildPacket([header, command, commandData]); | ||
var buffer = Buffer.from(packet); | ||
var _req = { | ||
const SID = self.header.incrementSID(); | ||
const header = self.header.build(); | ||
const command = constants.Commands.MEMORY_AREA_READ; | ||
const commandData = [addressData, wordsToBytes(count)]; | ||
const packet = buildPacket([header, command, commandData]); | ||
const buffer = Buffer.from(packet); | ||
const _req = { | ||
sid: SID, | ||
@@ -611,16 +183,16 @@ functionName: "read", | ||
}; | ||
var seq = self.sequenceManager.add(SID, _req, tag); | ||
seq.sendBuff = Buffer.from(buffer);//TEMP | ||
this.socket.send(buffer, 0, buffer.length, self.port, self.host, function (err) { | ||
if (err) { | ||
self.sequenceManager.setError(SID, err); | ||
} else { | ||
self.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
_transmitCommand(self, SID, buffer, _req, tag); | ||
return SID; | ||
}; | ||
/** | ||
* Memory Area Write Command. | ||
* @param {string} address - Memory area and the numerical start address e.g. `D100` or `CIO50.0` | ||
* @param {number} data - Data to write. This can be 1 value or an array values. For WD addresses, data value(s) should be 16 bit integer. For BIT addresses, data value(s) should be boolean or 1/0. | ||
* @param {commandCallback} [callback=null] - Optional callback method `(err, msg) => {}` | ||
* @param {*} [tag=null] - Optional tag item that is sent back in the callback method | ||
* @returns the SID of the request (returns `null` if any of the command parameters are invalid). | ||
*/ | ||
FinsClient.prototype.write = function (address, data, callback, tag) { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
if (self.queueCount() >= self.max_queue) { | ||
@@ -630,4 +202,4 @@ self.emit('full'); | ||
} | ||
var memoryAddress = _decodeMemoryAddress(address); | ||
var addressData = _translateMemoryAddress(memoryAddress, this.memoryAreas); | ||
const memoryAddress = self.finsAddresses.stringToAddress(address); | ||
const addressData = self.finsAddresses.addressToBytes(memoryAddress); | ||
if (!addressData) { | ||
@@ -641,11 +213,15 @@ _sendParamError("address is invalid", callback, {tag: tag}); | ||
} | ||
var SID = self.header.SID = _incrementSID(self.header.SID); | ||
var header = _buildHeader(self.header); | ||
var regsToWrite = _wordsToBytes((data.length || 1)); | ||
var command = constants.Commands.MEMORY_AREA_WRITE; | ||
var dataBytesToWrite = _wordsToBytes(data); | ||
var commandData = [addressData, regsToWrite, dataBytesToWrite]; | ||
var packet = _buildPacket([header, command, commandData]); | ||
var buffer = Buffer.from(packet); | ||
var _req = { | ||
const SID = self.header.incrementSID(); | ||
const header = self.header.build(); | ||
const regsToWrite = wordsToBytes((data.length || 1)); | ||
const command = constants.Commands.MEMORY_AREA_WRITE; | ||
if(memoryAddress.isBitAddress) { | ||
dataBytesToWrite = boolsToBytes(data); | ||
} else { | ||
dataBytesToWrite = wordsToBytes(data); | ||
} | ||
const commandData = [addressData, regsToWrite, dataBytesToWrite]; | ||
const packet = buildPacket([header, command, commandData]); | ||
const buffer = Buffer.from(packet); | ||
const _req = { | ||
sid: SID, | ||
@@ -657,15 +233,17 @@ functionName: "write", | ||
}; | ||
var seq = self.sequenceManager.add(SID, _req, tag); | ||
this.socket.send(buffer, 0, buffer.length, self.port, self.host, function (err) { | ||
if (err) { | ||
self.sequenceManager.setError(SID, err); | ||
} else { | ||
self.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
_transmitCommand(self, SID, buffer, _req, tag); | ||
return SID; | ||
}; | ||
/** | ||
* Memory Area Fill command. Fills 1 or more addresses with the same 16bit value. | ||
* @param {string} address - Memory area and the numerical start address e.g. `D100` or `CIO50` | ||
* @param {number} value - Value to write | ||
* @param {*} count - Number of registers to write | ||
* @param {commandCallback} [callback=null] - Optional callback method `(err, msg) => {}` | ||
* @param {*} [tag=null] - Optional tag item that is sent back in the callback method | ||
* @returns the SID of the request (returns `null` if any of the command parameters are invalid). | ||
*/ | ||
FinsClient.prototype.fill = function (address, value, count, callback, tag) { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
if (self.queueCount() >= self.max_queue) { | ||
@@ -675,4 +253,4 @@ self.emit('full'); | ||
} | ||
var memoryAddress = _decodeMemoryAddress(address); | ||
var addressData = _translateMemoryAddress(memoryAddress, this.memoryAreas); | ||
const memoryAddress = self.finsAddresses.stringToAddress(address); | ||
const addressData = self.finsAddresses.addressToBytes(memoryAddress); | ||
if (!addressData) { | ||
@@ -682,10 +260,14 @@ _sendParamError("address is invalid", callback, {tag: tag}); | ||
} | ||
var SID = self.header.SID = _incrementSID(self.header.SID); | ||
var header = _buildHeader(self.header); | ||
var command = constants.Commands.MEMORY_AREA_FILL; | ||
var dataBytesToWrite = _wordsToBytes(value); | ||
var commandData = [addressData, _wordsToBytes(count), dataBytesToWrite]; | ||
var packet = _buildPacket([header, command, commandData]); | ||
var buffer = Buffer.from(packet); | ||
var _req = { | ||
if (typeof value != "number") { | ||
_sendParamError("value is invalid", callback, {tag: tag}); | ||
return null; | ||
} | ||
const SID = self.header.incrementSID(); | ||
const header = self.header.build(); | ||
const command = constants.Commands.MEMORY_AREA_FILL; | ||
const dataBytesToWrite = wordsToBytes(value); | ||
const commandData = [addressData, wordsToBytes(count), dataBytesToWrite]; | ||
const packet = buildPacket([header, command, commandData]); | ||
const buffer = Buffer.from(packet); | ||
const _req = { | ||
sid: SID, | ||
@@ -698,15 +280,14 @@ functionName: "fill", | ||
}; | ||
var seq = self.sequenceManager.add(SID, _req, tag); | ||
this.socket.send(buffer, 0, buffer.length, self.port, self.host, function (err) { | ||
if (err) { | ||
self.sequenceManager.setError(SID, err); | ||
} else { | ||
self.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
_transmitCommand(self, SID, buffer, _req, tag); | ||
return SID; | ||
}; | ||
/** | ||
* Change PLC to MONITOR mode | ||
* @param {commandCallback} [callback=null] - Optional callback method `(err, msg) => {}` | ||
* @param {*} [tag=null] - Optional tag item that is sent back in the callback method | ||
* @returns the SID of the request (returns `null` if any of the command parameters are invalid). | ||
*/ | ||
FinsClient.prototype.run = function (callback, tag) { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
if (self.queueCount() >= self.max_queue) { | ||
@@ -716,8 +297,8 @@ self.emit('full'); | ||
} | ||
var SID = 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); | ||
var _req = { | ||
const SID = self.header.incrementSID(); | ||
const header = self.header.build(); | ||
const command = constants.Commands.RUN; | ||
const packet = buildPacket([header, command]); | ||
const buffer = Buffer.from(packet); | ||
const _req = { | ||
sid: SID, | ||
@@ -727,15 +308,15 @@ functionName: "run", | ||
}; | ||
var seq = self.sequenceManager.add(SID, _req, tag); | ||
this.socket.send(buffer, 0, buffer.length, self.port, self.host, function (err) { | ||
if (err) { | ||
self.sequenceManager.setError(SID, err); | ||
} else { | ||
self.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
_transmitCommand(self, SID, buffer, _req, tag); | ||
return SID; | ||
}; | ||
/** | ||
* Change PLC to PROGRAM mode | ||
* @param {commandCallback} [callback=null] - Optional callback method `(err, msg) => {}` | ||
* @param {*} [tag=null] - Optional tag item that is sent back in the callback method | ||
* @returns the SID of the request (returns `null` if any of the command parameters are invalid). | ||
*/ | ||
FinsClient.prototype.stop = function (callback, tag) { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
if (self.queueCount() >= self.max_queue) { | ||
@@ -745,8 +326,8 @@ self.emit('full'); | ||
} | ||
var SID = 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); | ||
var _req = { | ||
const SID = self.header.incrementSID(); | ||
const header = self.header.build(); | ||
const command = constants.Commands.STOP; | ||
const packet = buildPacket([header, command]); | ||
const buffer = Buffer.from(packet); | ||
const _req = { | ||
sid: SID, | ||
@@ -756,16 +337,15 @@ functionName: "stop", | ||
}; | ||
var seq = self.sequenceManager.add(SID, _req, tag); | ||
this.socket.send(buffer, 0, buffer.length, self.port, self.host, function (err) { | ||
if (err) { | ||
self.sequenceManager.setError(SID, err); | ||
} else { | ||
self.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
_transmitCommand(self, SID, buffer, _req, tag); | ||
return SID; | ||
}; | ||
/** | ||
* Get PLC status | ||
* @param {commandCallback} [callback=null] - Optional callback method `(err, msg) => {}` | ||
* @param {*} [tag=null] - Optional tag item that is sent back in the callback method | ||
* @returns the SID of the request (returns `null` if any of the command parameters are invalid). | ||
*/ | ||
FinsClient.prototype.status = function (callback, tag) { | ||
var self = this; | ||
/** @type {FinsClient}*/ const self = this; | ||
if (self.queueCount() >= self.max_queue) { | ||
@@ -775,8 +355,8 @@ self.emit('full'); | ||
} | ||
var SID = 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); | ||
var _req = { | ||
const SID = self.header.incrementSID(); | ||
const header = self.header.build(); | ||
const command = constants.Commands.CONTROLLER_STATUS_READ; | ||
const packet = buildPacket([header, command]); | ||
const buffer = Buffer.from(packet); | ||
const _req = { | ||
sid: SID, | ||
@@ -786,10 +366,3 @@ functionName: "status", | ||
}; | ||
var seq = self.sequenceManager.add(SID, _req, tag); | ||
this.socket.send(buffer, 0, buffer.length, self.port, self.host, function (err) { | ||
if (err) { | ||
self.sequenceManager.setError(SID, err); | ||
} else { | ||
self.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
_transmitCommand(self, SID, buffer, _req, tag); | ||
return SID; | ||
@@ -800,15 +373,16 @@ }; | ||
FinsClient.prototype.close = function () { | ||
this.connected = false; | ||
this.sequenceManager.close(); | ||
this.socket.close(); | ||
this.socket.removeAllListeners(); | ||
this.emit('close');//HACK: - cant get socket "close" event to fire | ||
/** @type {FinsClient}*/ const self = this; | ||
self.connected = false; | ||
self.sequenceManager.close(); | ||
self.socket.close(); | ||
self.socket.removeAllListeners(); | ||
self.emit('close');//HACK: - cant get socket "close" event to fire | ||
}; | ||
FinsClient.prototype.decodeMemoryAddress = function (addressString) { | ||
return _decodeMemoryAddress(addressString); | ||
FinsClient.prototype.stringToFinsAddress = function (addressString) { | ||
return this.finsAddresses.stringToAddress(addressString); | ||
}; | ||
FinsClient.prototype.decodedAddressToString = function (decodedAddress, offsetWD, offsetBit) { | ||
return _decodedAddressToString(decodedAddress, offsetWD, offsetBit); | ||
FinsClient.prototype.FinsAddressToString = function (finsAddress, offsetWD, offsetBit) { | ||
return this.finsAddresses.addressToString(finsAddress, offsetWD, offsetBit); | ||
}; | ||
@@ -820,1 +394,181 @@ | ||
}; | ||
function _getResponseType (buf) { | ||
var response = []; | ||
response.push(buf[10]); | ||
response.push(buf[11]); | ||
return response; | ||
}; | ||
/** | ||
* Transmit the command buffer to socket | ||
* @param {FinsClient} fcInstance - the FinsClient instance | ||
* @param {number} SID - Service ID for this transmission | ||
* @param {Buffer} buffer - the buffer to transmit | ||
* @param {Object} request - the request details object | ||
* @param {Any} tag - optional tag object to be sent in the request callback back after response is received | ||
*/ | ||
function _transmitCommand(fcInstance, SID, buffer, request, tag ) { | ||
setImmediate(function (SID, buffer, _req, tag){ | ||
fcInstance.sequenceManager.add(SID, _req, tag);//add the SID sequence manager for monitoring / timeout / stats etc | ||
fcInstance.socket.send(buffer, 0, buffer.length, fcInstance.port, fcInstance.host, function (err) { | ||
if (err) { | ||
fcInstance.sequenceManager.setError(SID, err); | ||
} else { | ||
fcInstance.sequenceManager.confirmSent(SID); | ||
} | ||
}); | ||
}, SID, buffer, request, tag); | ||
} | ||
function _processEndCode (/** @type {number} */hiByte, /** @type {number} */loByte) { | ||
var MRES = hiByte, SRES = loByte; | ||
var NetworkRelayError = ((MRES & 0x80) > 0); | ||
var NonFatalCPUUnitErr = ((SRES & 0x40) > 0); | ||
var FatalCPUUnitErr = ((SRES & 0x80) > 0); | ||
MRES = (MRES & 0x3f); | ||
SRES = (SRES & 0x2f); | ||
var endCode = ((MRES << 8) + SRES).toString(16) + ""; //.padStart(4,"0"); NodeJS8+ | ||
while(endCode.length < 4) { | ||
endCode = "0" + endCode; | ||
} | ||
var endCodeDescription = constants.EndCodeDescriptions[endCode] + ""; | ||
return { | ||
MRES: MRES, | ||
SRES: SRES, | ||
NetworkRelayError: NetworkRelayError, | ||
NonFatalCPUUnitErr: NonFatalCPUUnitErr, | ||
FatalCPUUnitErr: FatalCPUUnitErr, | ||
endCode: endCode, | ||
endCodeDescription: endCodeDescription | ||
} | ||
} | ||
function _processDefault (buf, rinfo) { | ||
var sid = buf[9]; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
return { remoteHost: rinfo.address, sid: sid, command: command }; | ||
}; | ||
function _processStatusRead (buf, rinfo) { | ||
var sid = buf[9]; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
var status = (buf[14] & 0x81); //Mask out battery[2] and CF[1] status or a direct lookup could fail. | ||
var mode = buf[15]; | ||
var fatalErrorData = {}; | ||
var nonFatalErrorData = {}; | ||
var fed = buf.readInt16BE(16); | ||
var nfed = buf.readInt16BE(18); | ||
var messageYN = buf.readInt16BE(20); | ||
var plcErrCode = buf.readInt16BE(22); | ||
var plcMessage = ""; | ||
if(messageYN) plcMessage = buf.slice(24,-1).toString(); //PLC Message | ||
//any fatal errors? | ||
if(fed){ | ||
for (var i in constants.FatalErrorData) { | ||
if ((fed & constants.FatalErrorData[i]) != 0) { | ||
fatalErrorData[i] = true; | ||
} | ||
} | ||
} | ||
//any non fatal errors? | ||
if(nfed) { | ||
for (var j in constants.NonFatalErrorData) { | ||
if ((nfed & constants.NonFatalErrorData[j]) != 0) { | ||
nonFatalErrorData[j] = true; | ||
} | ||
} | ||
} | ||
var statusCodes = constants.Status; | ||
var runModes = constants.Modes; | ||
return { | ||
remoteHost: rinfo.address, | ||
sid: sid, | ||
command: command, | ||
commandDescription: "status", | ||
status: getKeyName(statusCodes, status), | ||
mode: getKeyName(runModes, mode), | ||
fatalErrors: (fed ? fatalErrorData : null), | ||
nonFatalErrors: (nfed ? nonFatalErrorData : null), | ||
plcErrCode: plcErrCode, | ||
plcMessage: plcMessage | ||
}; | ||
}; | ||
function _processMemoryAreaRead (buf, rinfo, sequenceManager) { | ||
var WDs; | ||
var wdValues = false; | ||
var bits; | ||
var bitValues = false; | ||
var sid = buf[9]; | ||
var command = (buf.slice(10, 12)).toString("hex"); | ||
var bufData = (buf.slice(14, buf.length)); | ||
var plcAddress; | ||
if(sequenceManager) { | ||
let seq = sequenceManager.get(sid); | ||
plcAddress = seq && seq.request && seq.request.address; | ||
bitValues = plcAddress && plcAddress.isBitAddress == true; | ||
wdValues = plcAddress && plcAddress.isBitAddress == false; | ||
} | ||
if(bitValues){ | ||
bits = []; | ||
bits.push(...bufData); | ||
} else if(wdValues) { | ||
WDs = []; | ||
for (var i = 0; i < bufData.length; i += 2) { | ||
WDs.push(bufData.readInt16BE(i)); | ||
} | ||
} | ||
return { | ||
remoteHost: rinfo.address, | ||
sid: sid, | ||
command: command, | ||
commandDescription: "read", | ||
values: WDs ? WDs : bits, | ||
buffer: bufData, | ||
}; | ||
}; | ||
function _processReply (buf, rinfo, sequenceManager) { | ||
const commands = constants.Commands; | ||
const responseType = (_getResponseType(buf)).join(' '); | ||
const processEndCode = _processEndCode(buf[12],buf[13]); | ||
let processResult; | ||
switch (responseType) { | ||
case commands.CONTROLLER_STATUS_READ.join(' '): | ||
processResult = _processStatusRead(buf, rinfo); | ||
break; | ||
case commands.MEMORY_AREA_READ.join(' '): | ||
processResult = _processMemoryAreaRead(buf, rinfo, sequenceManager); | ||
break; | ||
default: | ||
processResult = _processDefault(buf, rinfo); | ||
break; | ||
} | ||
processResult.endCode = processEndCode.endCode; | ||
processResult.endCodeDescription = processEndCode.endCodeDescription; | ||
processResult.MRES = processEndCode.MRES; | ||
processResult.SRES = processEndCode.SRES; | ||
processResult.NetworkRelayError = processEndCode.NetworkRelayError; | ||
processResult.NonFatalCPUUnitErr = processEndCode.NonFatalCPUUnitErr; | ||
processResult.FatalCPUUnitErr = processEndCode.FatalCPUUnitErr; | ||
return processResult; | ||
}; | ||
function _sendParamError (message, callback, seq) { | ||
const addrErr = Error(message); | ||
if(callback) { | ||
callback(addrErr, seq) | ||
} else { | ||
self.emit('error', addrErr, seq); | ||
} | ||
} |
@@ -1,5 +0,7 @@ | ||
var FinsClient = require('./fins_client.js'); | ||
var FinsConstants = require('./constants.js'); | ||
const FinsClient = require('./fins_client.js'); | ||
const FinsConstants = require('./constants.js'); | ||
const FinsAddressUtil = require('./FinsAddressUtil.js'); | ||
exports.FinsClient = FinsClient; | ||
exports.FinsConstants = FinsConstants; | ||
exports.FinsAddressUtil = FinsAddressUtil; |
{ | ||
"name": "omron-fins", | ||
"description": "Node.js implementation of the Omron FINS protocol", | ||
"version": "0.2.0-beta.0", | ||
"version": "0.3.0-beta.0", | ||
"main": "lib/index.js", | ||
@@ -6,0 +6,0 @@ "author": { |
324
README.md
node-omron-fins | ||
=============== | ||
## Overview | ||
This is an implementation of the [OMRON FINS protocol](https://www.google.com/search?q=omrin+fins&oq=omrin+fins&aqs=chrome..69i57j0l5.945j0j7&sourceid=chrome&es_sm=93&ie=UTF-8#q=omron+fins&spell=1) using Node.js. This library allows for rapid development of network based services that need to communicate with FINS capable devices. Utilizing the awesome asynchronous abilities of Node.js communication with large numbers of devices is very fast. UDP was chosen as the first variant of the protocol to be implemented because of its extremely low overhead and performance advantages. Although UDP is connectionless this library makes use of software based timeouts and transaction identifiers to allow for better reliability. | ||
This is an implementation of the [OMRON FINS protocol](https://www.google.com/search?q=omron+fins+protocol+W342-E1-17) using Node.js. This library allows for rapid development of network based services that need to communicate with FINS capable devices. Utilizing the awesome asynchronous abilities of Node.js communication with large numbers of devices is very fast. UDP was chosen as the first variant of the protocol to be implemented because of its extremely low overhead and performance advantages. Although UDP is connectionless this library makes use of software based timeouts and transaction identifiers to allow for better reliability. | ||
This adaption of the library utilises a sequence manager to coordinate callbacks with data responses, providing the ability to clearly understand who called for data when the request returns. All of the commands (listed below) can be called not only with a callback (for deremining who called) but can also except a `tag` of any type (typically an object or key) permitting further routing in the users callback. If the callback is ommited, the approriate reply/timeout/error is emitted instead. | ||
This adaption of the library utilises a sequence manager to coordinate callbacks with data responses, providing the ability to clearly understand who called for data when the request returns. All of the commands (listed below) can be called not only with a callback (for determining who called) but can also except a `tag` of any type (typically an object or key) permitting further routing in the users callback. If the callback is omitted, the appropriate reply/timeout/error is emitted instead. | ||
## Version Update Notes | ||
This release (and possibly future releases upto V1.0.0) has breaking changes. | ||
This release (and possibly future releases up to V1.0.0) has breaking changes. | ||
Where possible, I make every attempt to keep things compatible, but sometimes, it becomes obvious a better way is worth the trouble - it happens (sorry) :) | ||
@@ -26,4 +26,4 @@ Semantic Versioning 2.0.0 will be followed after V1 however for now, where you see `V0.x.y`... | ||
* Memory area read | ||
* Memory area write | ||
* Memory area read (Word and Bit) | ||
* Memory area write (Word and Bit) | ||
* Memory area fill | ||
@@ -53,3 +53,3 @@ * Controller status read | ||
```js | ||
var fins = require('omron-fins'); | ||
const fins = require('omron-fins'); | ||
``` | ||
@@ -63,22 +63,31 @@ | ||
```js | ||
var options = {timeout: 5000, SA1: 2, DA1: 1}; | ||
var IP = '192.168.0.2'; | ||
var PORT = 9600; | ||
var client = fins.FinsClient(PORT, IP, options); | ||
const options = {timeout: 5000, SA1: 2, DA1: 1}; | ||
const IP = '192.168.0.2'; | ||
const PORT = 9600; | ||
const client = fins.FinsClient(PORT, IP, options); | ||
``` | ||
Add a reply listener. Response object content will vary depending on the command issued. However all responses are guaranteed to contain the following information: | ||
Add a reply listener. The `msg` parameters content will vary depending on the command issued. | ||
* `.sid` - Transaction identifier. Use this to track specific command/ response pairs. | ||
* `.command` - The issued command code. | ||
* `.response` - The response code returned after attempting to issue a command. | ||
* `.remotehost` - The IP address the response was sent from. | ||
* `.request` - An object containing values like the parsed `address` object and a string `functionName` | ||
* `.response` - An object containing the parsed values from the FINS command including the `remotehost`, `endCode` and `endCodeDescription`. | ||
* `.error` - This will normally be `false`. `Check msg.response.*` if `true` | ||
* `.complete` - true if the command has completed | ||
* `.values` - if the command returns values, they will be here | ||
* `.stats` - performance information | ||
* `.tag` - the tag value you sent (if any) with the command. | ||
* `.timeout` - a boolean flag to indicate if a transaction timeout occurred | ||
Example... | ||
```js | ||
client.on('reply',msg){ | ||
console.log('SID: ', msg.sid); | ||
console.log('Command Code: ', msg.command); | ||
console.log('Response Code: ', msg.response); | ||
console.log('Remote Host: ', msg.remotehost); | ||
client.on('reply', msg){ | ||
console.log("Reply from : ", msg.response.remoteHost); | ||
console.log("Sequence ID (SID) : ", msg.sid); | ||
console.log("Operation requested : ", msg.request.functionName); | ||
console.log("Response code : ", msg.response.endCode); | ||
console.log("Response desc : ", msg.response.endCodeDescription); | ||
console.log("Data returned : ", msg.response.values || ""); | ||
console.log("Round trip time : ", msg.timeTaken + "ms"); | ||
console.log("Your tag : ", msg.tag); | ||
}); | ||
@@ -95,11 +104,12 @@ ``` | ||
### .read(address, regsToRead, callback, tag) | ||
Memory Area Read Command | ||
* `address` - Memory area and the numerical start address | ||
* `regsToRead` - Number of registers to read | ||
* `callback` - Optional callback method | ||
* `tag` - Optional tag item to send in callback method | ||
### Memory Area Read Command | ||
`.read(address, count, callback, tag)` | ||
* `address` - Memory area and the numerical start address e.g. `D100` or `CIO50.0` | ||
* `count` - Number of registers to read | ||
* `callback` - Optional callback `(err, msg) => {}` | ||
* `tag` - Optional tag item that is sent back in the callback method | ||
```js | ||
/* Reads 10 registers starting from register 00000 in the DM Memory Area */ | ||
/* Read 10 registers starting from register 00000 in the DM Memory Area */ | ||
.read('D00000',10); | ||
@@ -113,9 +123,10 @@ | ||
### .write(address, dataToBeWritten, callback, tag) | ||
Memory Area Write Command | ||
* `address` - Memory area and the numerical start address | ||
* `dataToBeWritten` - An array of values or single value | ||
* `callback` - Optional callback method | ||
* `tag` - Optional tag item to send in callback method | ||
### Memory Area Write Command | ||
`.write(address, dataToBeWritten, callback, tag)` | ||
* `address` - Memory area and the numerical start address e.g. `D100` or `CIO50.0` | ||
* `data` - An array of values or single value | ||
* `callback` - Optional callback `(err, msg) => {}` | ||
* `tag` - Optional tag item that is sent back in the callback method | ||
```js | ||
@@ -128,6 +139,9 @@ /* Writes single value of 1337 into DM register 00000 */ | ||
/* Writes the values 12,34,56 into DM registers 00000 00001 000002 and callsback when done */ | ||
.write('D00000',[12,34,56], function(seq){ | ||
//check seq.timeout and seq.error | ||
console.log(seq.response) | ||
/* Writes 1 0 1 0 to DM0.4, DM0.5, DM0.6 & DM0.7 */ | ||
.write('D0.4',[true, false, 1, 0]); | ||
/* Writes the values 12,34,56 into DM registers 00000 00001 000002 and calls back when done */ | ||
.write('D00000',[12,34,56], function(msg){ | ||
//check msg.timeout and msg.error | ||
console.log(msg.response) | ||
}); | ||
@@ -137,15 +151,16 @@ | ||
/* Same as above with callback */ | ||
.write('D00000',[12,34,56],function(err, seq) { | ||
console.log("seq: ", seq); | ||
.write('D00000',[12,34,56],function(err, msg) { | ||
console.log("seq: ", msg); | ||
}); | ||
``` | ||
### .fill(address, dataToBeWritten, regsToBeWritten, callback, tag) | ||
Memory Area Fill Command | ||
* `address` - Memory area and the numerical start address | ||
* `dataToBeWritten` - Two bytes of data to be filled | ||
* `regsToBeWritten` - Number of registers to write | ||
* `callback` - Optional callback method | ||
* `tag` - Optional tag item to send in callback method | ||
### Memory Area Fill Command | ||
`.fill(address, value, regsToBeWritten, callback, tag)` | ||
* `address` - Memory area and the numerical start address e.g. `D100` or `CIO50` | ||
* `value` - Value to be filled | ||
* `count` - Number of registers to write | ||
* `callback` - Optional callback `(err, msg) => {}` | ||
* `tag` - Optional tag item that is sent back in the callback method | ||
```js | ||
@@ -156,33 +171,37 @@ | ||
/* Sames as above with callback */ | ||
.fill('D00100',1337,10,function(err, seq) { | ||
console.log("seq: ", seq); | ||
/* Same as above with callback */ | ||
.fill('D00100',1337,10,function(err, msg) { | ||
console.log("msg: ", msg); | ||
}); | ||
/* Writes 1111 in 4 consecutive CIO bit registers from CIO5.0 to CIO5.3 */ | ||
.fill('CIO5.0',true,4); | ||
``` | ||
### .run(callback, tag) | ||
RUN | ||
* `callback` Optional callback | ||
* `tag` - Optional tag item to send in callback method | ||
### Change PLC to MONITOR mode | ||
`.run(callback, tag)` | ||
* `callback` - Optional callback `(err, msg) => {}` | ||
* `tag` - Optional tag item that is sent back in the callback method | ||
```js | ||
/* Puts into Monitor mode */ | ||
.run(function(err, seq) { | ||
// | ||
/* Puts the PLC into Monitor mode */ | ||
.run(function(err, msg) { | ||
console.log(err, msg) | ||
}); | ||
``` | ||
### .stop(callback, tag) | ||
STOP | ||
* `callback` Optional callback | ||
* `tag` - Optional tag item to send in callback method | ||
### Change PLC to PROGRAM mode | ||
`.stop(callback, tag)` | ||
* `callback` - Optional callback `(err, msg) => {}` | ||
* `tag` - Optional tag item that is sent back in the callback method | ||
```js | ||
/* Stops program excution by putting into Program mode */ | ||
.stop(function(err, seq) { | ||
/* Stops program execution by putting the PLC into Program mode */ | ||
.stop(function(err, msg) { | ||
console.log(err, msg) | ||
}); | ||
@@ -194,10 +213,11 @@ | ||
### .status(callback, tag) | ||
STATUS | ||
* `callback` Optional callback | ||
* `tag` - Optional tag item to send in callback method | ||
### Get PLC Status | ||
`.status(callback, tag)` | ||
* `callback` - Optional callback `(err, msg) => {}` | ||
* `tag` - Optional tag item that is sent back in the callback method | ||
```js | ||
.status(function(err, seq) { | ||
console.log(err, seq) | ||
.status(function(err, msg) { | ||
console.log(err, msg) | ||
}, tag); | ||
@@ -213,32 +233,33 @@ | ||
### Basic Example | ||
Bare bones example that will show you how to read data from a single client. | ||
A basic example that will show you how to read, write and fill data for a single client. | ||
```js | ||
var fins = require('../lib/index'); | ||
//var fins = require('omron-fins'); | ||
const fins = require('./lib/index'); // << use this when running from src | ||
//const fins = require('omron-fins'); // << use this when running from npm | ||
// Connecting to remote FINS client on port 9600 with default timeout value. | ||
// PLC is expected to be at 192.168.0.2 and this PC is expected to be fins node 1 | ||
var client = fins.FinsClient(9600,'192.168.0.2', {SA1:1, DA1:1}); | ||
// Connecting to remote FINS client on port 9600 with timeout of 2s. | ||
// PLC is expected to be at 192.168.1.120 and this PC is expected to be fins node 36 (adjust as required) | ||
const client = fins.FinsClient(9600,'192.168.1.120', {SA1:36, DA1:120, timeout: 2000}); | ||
// Setting up our error listener | ||
client.on('error',function(error, seq) { | ||
console.log("Error: ", error, seq); | ||
client.on('error',function(error, msg) { | ||
console.log("Error: ", error, msg); | ||
}); | ||
// Setting up our timeout listener | ||
client.on('timeout',function(host, seq) { | ||
client.on('timeout',function(host, msg) { | ||
console.log("Timeout: ", host); | ||
}); | ||
// Setting up the genral response listener | ||
// Showing a selection of properties of a sequence response | ||
client.on('reply',function(seq) { | ||
console.log("Reply from : ", seq.response.remoteHost); | ||
console.log("Sequence ID (SID) : ", seq.sid); | ||
console.log("Operation requested : ", seq.request.functionName); | ||
console.log("Response code : ", seq.response.endCode); | ||
console.log("Response desc : ", seq.response.endCodeDescription); | ||
console.log("Data returned : ", seq.response.values || ""); | ||
console.log("Round trip time : ", seq.timeTaken + "ms"); | ||
console.log("Your tag: ", seq.tag); | ||
// Setting up the general response listener showing a selection of properties from the `msg` | ||
client.on('reply',function(msg) { | ||
console.log("############# client.on('reply'...) #################") | ||
console.log("Reply from : ", msg.response.remoteHost); | ||
console.log("Sequence ID (SID) : ", msg.sid); | ||
console.log("Operation requested : ", msg.request.functionName); | ||
console.log("Response code : ", msg.response.endCode); | ||
console.log("Response desc : ", msg.response.endCodeDescription); | ||
console.log("Data returned : ", msg.response.values || ""); | ||
console.log("Round trip time : ", msg.timeTaken + "ms"); | ||
console.log("Your tag : ", msg.tag); | ||
console.log("#####################################################") | ||
}); | ||
@@ -248,32 +269,79 @@ | ||
// a "reply" will be emitted - check general client reply on reply handler | ||
client.read('D0',10); | ||
console.log("Read 10 WD from D0") | ||
client.read('D0',10, null, {"tagdata":"I asked for 10 registers from D0"}); | ||
console.log("Read 32 bits from D0.0") | ||
client.read('D0.0',32, null, {"tagdata":"I asked for 32 bits from D0.0"}); | ||
// Read 10 registers starting at DM register D700 & callback with my tagged item upon reply from PLC | ||
// direct callback is usefull for getting direct responses to direct requests | ||
var cb = function(err, seq) { | ||
console.log("############# DIRECT CALLBACK #################") | ||
// direct callback is useful for getting direct responses to direct requests | ||
var cb = function(err, msg) { | ||
console.log("################ DIRECT CALLBACK ####################") | ||
if(err) | ||
console.error(err); | ||
else | ||
console.log(seq.request.functionName, seq.response.values); | ||
console.log("###############################################") | ||
console.log(msg.request.functionName, msg.tag || "", msg.response.endCodeDescription); | ||
console.log("#####################################################") | ||
}; | ||
client.read('D700',10, cb, new Date()); | ||
//example fill D700~D704 with 123 | ||
client.fill('D700',123, 5); | ||
//example fill D700~D704 with 123 & the callback `cb` for the response | ||
console.log("Fill D700~D709 with 123 - direct callback expected") | ||
client.fill('D700',123, 10, cb, "set D700~D709 to 123"); | ||
//example Read D700~D709 with the callback `cb` for the response | ||
console.log("Read D700~D709 - direct callback expected") | ||
client.read('D700',10, cb, "D700~D709") | ||
//example write 1010 1111 0000 0101 to D700.0~D700.15 - response will be sent to client 'reply' handler | ||
client.write('D700.0', [true, false, 1, 0, "true", true, 1, "1", "false", false, 0, "0", 0, 1, 0, 1], null, "write 1010 1111 0000 0101 to D700"); | ||
client.read('D700.0',16, null, "read D700.0 ~ D700.15 - should contain 1010 1111 0000 0101"); | ||
//example tagged data for sending with a status request | ||
var tag = {"source": "system-a", "sendto": "system-b"}; | ||
const tag = {"source": "system-a", "sendto": "system-b"}; | ||
getStatus(tag); | ||
client.status(function(err, seq) { | ||
if(err) { | ||
console.error(err); | ||
} else { | ||
//use the tag for post reply routing | ||
console.log(seq.tag, seq.response); | ||
} | ||
}, tag); | ||
function getStatus(_tag) { | ||
console.log("Get PLC Status...") | ||
client.status(function(err, msg) { | ||
if(err) { | ||
console.error(err, msg); | ||
} else { | ||
//use the tag for post reply routing or whatever you need | ||
console.log(msg.response, msg.tag); | ||
} | ||
}, _tag); | ||
} | ||
setTimeout(() => { | ||
console.log("Request PLC change to STOP mode...") | ||
client.stop((err, msg) => { | ||
if(err) { | ||
console.error(err) | ||
} else { | ||
console.log("should be stopped") | ||
setTimeout(() => { | ||
getStatus(); | ||
}, 150); | ||
} | ||
}) | ||
}, 500); | ||
setTimeout(() => { | ||
console.log("Request PLC change to RUN mode...") | ||
client.run((err, msg) => { | ||
if(err) { | ||
console.error(err) | ||
} else { | ||
console.log("should be running again") | ||
setTimeout(() => { | ||
getStatus(); | ||
}, 150); | ||
} | ||
}) | ||
}, 2000); | ||
``` | ||
@@ -286,5 +354,5 @@ | ||
Example of instantiating multiple objects to allow for asynchronous communications. Because this code doesn't wait for a response from any client before sending/receiving packets it is incredibly fast. In this example we attempt to read a memory area from a list of remote hosts. Each command will either return with a response or timeout. Every transaction will be recorded to the `responses` array with the `ip` as a key and the `seq.response.values` as the associated value. | ||
Example of instantiating multiple objects to allow for asynchronous communications. Because this code doesn't wait for a response from any client before sending/receiving packets it is incredibly fast. In this example we attempt to read a memory area from a list of remote hosts. Each command will either return with a response or timeout. Every transaction will be recorded to the `responses` array with the `ip` as a key and the `msg.response.values` as the associated value. | ||
If a timeout occurs and you have provided a callback, the `seq.timeout` flag will be set. | ||
If a timeout occurs and you have provided a callback, the `msg.timeout` flag will be set. | ||
If a timeout occurs and you have not provided a callback, to can get a response by listening for `'timeout'` being emitted. | ||
@@ -297,9 +365,9 @@ Once the size of the responses array is equal to the number of units we tried to communicate with we know we have gotten a response or timeout from every unit | ||
var fins = require('omron-fins'); | ||
var debug = true; | ||
var clients = []; | ||
var responses = {}; | ||
const fins = require('omron-fins'); | ||
const debug = true; | ||
const clients = []; | ||
const responses = {}; | ||
/* List of remote hosts can be generated from local or remote resource */ | ||
var remoteHosts = [ | ||
const remoteHosts = [ | ||
{ KEY: "PLC1", IP:'192.168.0.1', OPTS: {DA1:1, SA1:99} }, | ||
@@ -311,3 +379,3 @@ { KEY: "PLC2", IP:'192.168.0.2', OPTS: {DA1:2, SA1:99} }, | ||
/* Data is ready to be processed (sent to API,DB,etc) */ | ||
var finished = function(responses) { | ||
const finished = function(responses) { | ||
console.log("All responses and or timeouts received"); | ||
@@ -317,16 +385,16 @@ console.log(responses); | ||
var pollUnits = function() { | ||
const pollUnits = function() { | ||
/* We use number of hosts to compare to the length of the response array */ | ||
var numberOfRemoteHosts = remoteHosts.length; | ||
var options = {timeout:2000}; | ||
for (var remHost in remoteHosts) { | ||
const numberOfRemoteHosts = remoteHosts.length; | ||
const options = {timeout:2000}; | ||
for (let remHost in remoteHosts) { | ||
/* Add key value entry into responses array */ | ||
clients[remHost.KEY] = fins.FinsClient(9600,remHost.IP,remHost.OPTS); | ||
clients[remHost.KEY].on('reply', function(seq) { | ||
if(debug) console.log("Got reply from: ", seq.response.remotehost); | ||
clients[remHost.KEY].on('reply', function(err, msg) { | ||
if(debug) console.log("Got reply from: ", msg.response.remotehost); | ||
/* Add key value pair of [ipAddress] = values from read */ | ||
responses[seq.response.remotehost] = seq.response.values; | ||
responses[msg.response.remotehost] = msg.response.values; | ||
@@ -340,3 +408,3 @@ /* Check to see size of response array is equal to number of hosts */ | ||
/* If timeout occurs log response for that IP as null */ | ||
clients[remHost.KEY].on('timeout',function(host, seq) { | ||
clients[remHost.KEY].on('timeout',function(host, msg) { | ||
responses[host] = null; | ||
@@ -349,5 +417,5 @@ if(Object.keys(responses).length == numberOfRemoteHosts){ | ||
clients[remHost.KEY].on('error',function(error, seq) { | ||
//depending where the error occured, seq may contain relevant info | ||
console.error(error) | ||
clients[remHost.KEY].on('error',function(err, msg) { | ||
//depending where the error occurred, seq may contain relevant info | ||
console.error(err) | ||
}); | ||
@@ -367,3 +435,3 @@ | ||
### Logging Data & Troubleshooting | ||
If you have Wirshark installed it is very simple to analyse OMRON FINS/UDP traffic: | ||
If you have Wireshark installed it is very simple to analyse OMRON FINS/UDP traffic: | ||
@@ -370,0 +438,0 @@ Simply select your network interface and then hit "Start" |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
72041
12
1470
429
2