node-red-contrib-omron-fins
Advanced tools
Comparing version 1.0.0-beta-0 to 1.0.0-beta-1
@@ -60,6 +60,8 @@ (function (exports) { | ||
} | ||
const uint16array = new Uint16Array( | ||
buffer.buffer, | ||
buffer.byteOffset, | ||
buffer.length / Uint16Array.BYTES_PER_ELEMENT); | ||
const uint16array = [] | ||
for (let index = 0; index < buffer.length; index+=2) { | ||
const element = buffer.readUInt16BE(index) | ||
uint16array.push(element) | ||
} | ||
buffer.readUInt16BE() | ||
return uint16array | ||
@@ -71,12 +73,3 @@ } | ||
// debugger | ||
let buffer; | ||
if (typeof buf === "string") { | ||
buffer = Buffer.from(buf, "hex"); | ||
} else { | ||
buffer = Buffer.from(buf) | ||
} | ||
const uint16array = new Uint16Array( | ||
buffer.buffer, | ||
buffer.byteOffset, | ||
buffer.length / Uint16Array.BYTES_PER_ELEMENT); | ||
const uint16array = to_uint16_parser(buf) | ||
return uint16array.map(e => number2bcd(e)) | ||
@@ -139,4 +132,4 @@ } | ||
const to_string_parser = (buf) => { | ||
return Buffer.from(buf, "hex").toString(); | ||
const to_string_parser = (buf, enc) => { | ||
return Buffer.from(buf, enc || "hex").toString(); | ||
} | ||
@@ -429,3 +422,3 @@ | ||
response: [ | ||
{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Data read from PLC", parser: to_uint16_parser } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser } | ||
], | ||
@@ -442,3 +435,3 @@ }, | ||
response: [ | ||
{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Data read from PLC", parser: to_uint16_parser } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser } | ||
], | ||
@@ -453,3 +446,3 @@ }, | ||
{ name: "Address", type: "bcd4", min: 0, max: 2555, required: true, hint: "Beginning Word 0 to 2555" }, | ||
{ name: "Data", type: "uint[]", min: 1, max: 2556, required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
@@ -464,3 +457,3 @@ response: [], | ||
{ name: "Address", type: "bcd4", min: 0, max: 63, required: true, hint: "Beginning Word 0 to 63" }, | ||
{ name: "Data", type: "uint[]", min: 1, max: 64, required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
@@ -475,3 +468,3 @@ response: [], | ||
{ name: "Address", type: "bcd4", min: 0, max: 99, required: true, hint: "Beginning Word 0 to 99" }, | ||
{ name: "Data", type: "uint[]", min: 1, max: 100, required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
@@ -486,3 +479,3 @@ "response": [], | ||
{ name: "Address", type: "bcd4", min: 0, max: 99, required: true, hint: "Beginning timer/counter" }, | ||
{ name: "Data", type: "uint[]", min: 1, max: 100, required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
@@ -497,3 +490,3 @@ "response": [], | ||
{ name: "Address", type: "bcd4", required: true, hint: "Beginning Word" }, | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
@@ -508,3 +501,3 @@ response: [], | ||
{ name: "Address", type: "bcd4", required: true, hint: "Beginning Word" }, | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
@@ -606,3 +599,3 @@ response: [], | ||
], | ||
response: [{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
response: [{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
}, | ||
@@ -617,3 +610,3 @@ { | ||
], | ||
response: [{ name: "Data", type: "uint[]", min: 1, max: 200, required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
response: [{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
}, | ||
@@ -628,3 +621,3 @@ { | ||
], | ||
response: [{ name: "Data", type: "uint[]", min: 1, max: 200, required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
response: [{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
}, | ||
@@ -659,3 +652,3 @@ { | ||
], | ||
response: [{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
response: [{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
}, | ||
@@ -671,3 +664,3 @@ { | ||
], | ||
response: [{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
response: [{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
}, | ||
@@ -682,6 +675,4 @@ { | ||
], | ||
response: [{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
response: [{ name: "Data", type: "uint[]", required: true, hint: "Data read from PLC", parser: to_uint16_parser }], | ||
}, | ||
{ | ||
@@ -693,9 +684,59 @@ headerCode: "WR", | ||
{ name: "Address", type: "bcd4", min: 0, max: 6143, required: true, hint: "Beginning Word 0 to 6143" }, | ||
{ name: "Data", type: "uint[]", min: 1, max: 6144, required: true, hint: "Write data (single or array)" } | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
response: [], | ||
}, | ||
{ | ||
headerCode: "WL", | ||
name: "LR AREA WRITE - WL", | ||
hint: "Writes data to the Link Area (CIO 1000 to CIO 1199) starting from the specified word. Writing is done in word units", | ||
request: [ | ||
{ name: "Address", type: "bcd4", min: 0, max: 199, required: true, hint: "Beginning Word 0 to 199" }, | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
response: [], | ||
}, | ||
{ | ||
headerCode: "WH", | ||
name: "HR AREA WRITE - WH", | ||
hint: "Writes data to the HR Area (H000 to H511) starting from the specified word. Writing is done in word units", | ||
request: [ | ||
{ name: "Address", type: "bcd4", min: 0, max: 511, required: true, hint: "Beginning Word 0 to 511" }, | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
response: [], | ||
}, | ||
// TODO: Impilment T/C PV Write | ||
// { | ||
// headerCode: "WC", | ||
// name: "TIMER/COUNTER PV WRITE - WC", | ||
// hint: "Writes the PVs (present values T0000 to T2047 or C0000 to C2047) of timers/counters starting from the specified word", | ||
// request: [ | ||
// { name: "Address", type: "bcd4", min: 0, max: 6143, required: true, hint: "Beginning Word 0 to 6143" }, | ||
// { name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
// ], | ||
// response: [], | ||
// }, | ||
{ | ||
headerCode: "WD", | ||
name: "DM AREA WRITE - WD", | ||
hint: "Writes data to the DM Area starting from the specified word (D00000 to D09999). Writing is done in word units", | ||
request: [ | ||
{ name: "Address", type: "bcd4", min: 0, max: 9999, required: true, hint: "Beginning Word 0 to 9999" }, | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
response: [], | ||
}, | ||
{ | ||
headerCode: "WJ", | ||
name: "AR AREA WRITE - WJ", | ||
hint: "Writes data to the Auxiliary Area (A448 to A959) starting from the specified word. Writing is done in word units", | ||
request: [ | ||
{ name: "Address", type: "bcd4", min: 448, max: 959, required: true, hint: "Beginning Word 448 to 959" }, | ||
{ name: "Data", type: "uint[]", required: true, hint: "Write data (single UINT or array or UINT). \nNOTE: If the value is a string it must be JSON e.g. 1234 or [123,555,66]" } | ||
], | ||
response: [], | ||
}, | ||
{ | ||
@@ -709,3 +750,3 @@ headerCode: "TS", | ||
name: "Data", type: "str", min: 1, max: 118, required: true, hint: "The same characters specified in the command will be returned unaltered if the test is successful", | ||
parser: to_string_parser | ||
//parser: to_string_parser | ||
} | ||
@@ -872,23 +913,30 @@ ], | ||
const ep = expectedParams[index]; | ||
let min = ep.min | ||
let max = ep.max | ||
let pv = params[index]; | ||
let v = pv; | ||
if (!Array.isArray(v)) { v = [v]; } | ||
switch (ep.type) { | ||
case "bcd4": | ||
//TODO: min/max/isNumeric tests | ||
v = f(v, 4, "0"); | ||
case "bcd4[]": | ||
min = min || 0 | ||
max = max || 9999 | ||
v = v.map(e => f(v, 4, "0")); | ||
break; | ||
case "uint": | ||
//TODO: min/max/isNumeric tests | ||
v = f(parseInt(v).toString(16), 4, "0"); | ||
break; | ||
case "uint[]": | ||
//TODO: min/max/isNumeric tests | ||
if (!Array.isArray(v)) { v = [v]; } | ||
min = min || 0 | ||
max = max || 65535 | ||
v = v.map(e => f(parseInt(e).toString(16), 4, "0")); | ||
v = v.join(""); | ||
break; | ||
default: | ||
break; | ||
} | ||
if (min != null && v.some((e) => parseInt(e) < min)) { | ||
throw new Error(`A value in parameter '${ep.name}' is less than min (${min})`) | ||
} | ||
if (max != null && v.some((e) => parseInt(e) > max)) { | ||
throw new Error(`A value in parameter '${ep.name}' is greater than max (${max})`) | ||
} | ||
v = v.join(""); | ||
commandParamsArr.push(v); | ||
@@ -900,7 +948,7 @@ } | ||
const commandLength = command.length; | ||
const macLenFF = this.frame.firstFrame.maxLength; | ||
const macLenIF = this.frame.intermediateFrame.maxLength; | ||
const maxLenFF = this.frame.firstFrame.maxLength; | ||
const maxLenIF = this.frame.intermediateFrame.maxLength; | ||
*/ | ||
const FCS = CModeHelper.prototype.calculateFCS(command); | ||
let payload = command + FCS + terminator; | ||
const CRC = f(CModeHelper.calculateFCS(command).toString(16).toUpperCase(), 2, '0'); | ||
let payload = command + CRC + terminator; | ||
return payload; | ||
@@ -910,3 +958,3 @@ } | ||
} | ||
CModeHelper.prototype.calculateFCS = function (data) { | ||
CModeHelper.calculateFCS = function (data) { | ||
let CRC = 0; | ||
@@ -917,4 +965,3 @@ const dataLen = data.length; | ||
} | ||
const FCS = CRC.toString(16).toUpperCase(); | ||
return FCS; | ||
return CRC; | ||
} | ||
@@ -921,0 +968,0 @@ |
@@ -42,4 +42,3 @@ module.exports = function (RED) { | ||
const paramName = "p" + paramNo; | ||
const paramValue = getParamValue(paramNo, msg); | ||
cModeCommand[paramName] = paramValue; | ||
let paramValue = getParamValue(paramNo, msg); | ||
if(param.required && paramValue == null) { | ||
@@ -51,2 +50,6 @@ throw new Error(`Param ${paramName} (${param.name}) is required`) | ||
} | ||
if(param.type === 'uint[]' && typeof paramValue === "string") { | ||
paramValue = JSON.parse(paramValue) | ||
} | ||
cModeCommand[paramName] = paramValue; | ||
param.value = paramValue; | ||
@@ -53,0 +56,0 @@ cModeCommand.params.push(param); |
@@ -20,6 +20,20 @@ module.exports = function (RED) { | ||
try { | ||
const response = (msg.payload || '').trim();//remove CR etc | ||
const startMarkOK = response.startsWith("@");//get startMark | ||
const endMarkOK = response.endsWith("*");//get endMark | ||
const endCode = response.substr(5, 2); | ||
const response = (msg.payload ? msg.payload.toString() : '').trim() | ||
msg.cModeResponse = {timestamp: Date.now(), response} | ||
const startMark = response.slice(0,1) | ||
const endMark = response.slice(-1) | ||
const startMarkOK = startMark === "@"; | ||
const endMarkOK = endMark === "*";//TODO: Support frames | ||
const hostNumber = response.substr(1,2); | ||
const headerCode = response.substr(3,2); | ||
let endCode = response.substr(5, 2); | ||
let dataStart = 7; | ||
msg.cModeResponse.startMark = startMark | ||
msg.cModeResponse.endMark = endMark | ||
msg.cModeResponse.hostNumber = hostNumber | ||
msg.cModeResponse.headerCode = headerCode | ||
msg.cModeResponse.endCode = endCode | ||
if(!startMarkOK) { | ||
@@ -39,2 +53,9 @@ throw new Error("Invalid C-Mode Response. Expected a string beginning with @") | ||
} | ||
if(headerCode === 'TS') { | ||
if(response === (msg.request_payload || '').trim()) { | ||
endCode = '00' | ||
msg.cModeResponse.endCode = endCode | ||
dataStart -= 2 | ||
} | ||
} | ||
if (endCode !== '00') { | ||
@@ -46,16 +67,11 @@ const ec = cmode.parseEndCode(endCode); | ||
//TODO: move the parsing into C_MODE | ||
const dataStart = 7; | ||
const totalLength = response.length; | ||
const dataLength = totalLength - 10; | ||
const hostNumber = response.substr(1,2); | ||
const headerCode = response.substr(3,2); | ||
const dataLength = totalLength - dataStart - 3 /* 3 = 2 for CRC and 1 for asterix */; | ||
const data = response.substr(dataStart, dataLength); | ||
const CRCin = parseInt(response.substr(dataStart+dataLength, 2), 16); | ||
let CRC = 0; | ||
for (let ch = 0; ch <= (totalLength - 4); ch++) { | ||
CRC = response.charCodeAt(ch) ^ CRC; | ||
const CRC = parseInt(response.substr(dataStart + dataLength, 2), 16) | ||
const CRCcalc = C_MODE.CModeHelper.calculateFCS(response.slice(0, -3)) | ||
if(CRC !== CRCcalc) { | ||
throw new Error(`Invalid frame check sequence. Received CRC ${CRC.toString(16)}, expected ${CRCcalc.toString(16)}`) | ||
} | ||
if(CRCin !== CRC) { | ||
throw new Error(`Invalid frame check sequence. Received CRC ${CRCin.toString(16)}, expected ${CRC.toString(16)}`) | ||
} | ||
if(msg.cModeCommand.hostNumber != hostNumber) { | ||
@@ -67,12 +83,7 @@ throw new Error(`Unexpected host number. Received ${hostNumber}, expected ${msg.cModeCommand.hostNumber}`) | ||
} | ||
msg.cModeResponse.data = data | ||
msg.cModeResponse.CRC = CRC | ||
msg.cModeResponse.CRCCalc = CRCcalc | ||
const command = cmode.getCommand(headerCode); | ||
msg.cModeResponse = { | ||
timestamp: Date.now(), | ||
response, | ||
hostNumber, | ||
headerCode, | ||
data, | ||
CRC: CRCin, | ||
CRCCalc: CRC | ||
} | ||
if(!command) { | ||
@@ -86,17 +97,22 @@ //command not explicitly supported, lets just return the data | ||
msg.cModeResponse.params = []; | ||
msg.payload = {}; | ||
for (let index = 0; index < command.response.length; index++) { | ||
const param = { ...command.response[index] }; | ||
const paramNo = index + 1; | ||
const paramName = param.name; | ||
const paramType = param.type; | ||
const paramHint = param.hint; | ||
const parser = param.parser || (e => e); | ||
const paramValue = parser(data); | ||
msg.cModeResponse.params.push({ | ||
paramNo, paramName, paramType, paramHint, paramValue | ||
}); | ||
msg.payload[paramName] = paramValue; | ||
msg.payload.buffer = Buffer.from(data,"hex"); | ||
if(command.response.length === 0) { | ||
msg.payload = true | ||
node.send(msg); | ||
} else { | ||
msg.payload = {}; | ||
for (let index = 0; index < command.response.length; index++) { | ||
const param = { ...command.response[index] }; | ||
const paramNo = index + 1; | ||
const paramName = param.name; | ||
const paramType = param.type; | ||
const paramHint = param.hint; | ||
const parser = param.parser || (e => e); | ||
const paramValue = parser(data); | ||
msg.cModeResponse.params.push({ | ||
paramNo, paramName, paramType, paramHint, paramValue | ||
}); | ||
msg.payload[paramName] = paramValue; | ||
msg.payload.buffer = Buffer.from(data,"hex"); | ||
node.send(msg); | ||
} | ||
} | ||
@@ -103,0 +119,0 @@ } |
{ | ||
"name": "node-red-contrib-omron-fins", | ||
"version": "1.0.0-beta-0", | ||
"version": "1.0.0-beta-1", | ||
"author": { | ||
@@ -18,2 +18,3 @@ "name": "Steve-Mcl", | ||
"node-red": { | ||
"version": ">=1.0.0", | ||
"nodes": { | ||
@@ -42,2 +43,5 @@ "omronRead": "nodes/read.js", | ||
}, | ||
"engines": { | ||
"node": ">=8" | ||
}, | ||
"homepage": "https://github.com/steve-mcl/node-red-contrib-omron-fins#readme", | ||
@@ -44,0 +48,0 @@ "repository": { |
@@ -5,3 +5,3 @@ node-red-contrib-omron-fins | ||
## Overview | ||
This is a Node-RED node module to directly interface with OMRON PLCs over FINS Ethernet protocol. | ||
This is a Node-RED node module to directly interface with OMRON PLCs over Ethernet (FINS protocol) or Serial (Hostlink protocol). | ||
Tested on CV, CP, CS, CJ, NJ and NX PLCs (the ones with FINS support) | ||
@@ -27,13 +27,10 @@ | ||
* Set Clock | ||
* C-Mode Command - A node to build a C-Mode command that can be transmitted to a serial port (hostlink protocol) | ||
* C-Mode Response - A node to decode the C-Mode command response (hostlink protocol) | ||
## Version Update Notes | ||
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 as node-red improves (typedInput widgets for example) I too improve this node to make things easier or better. And sometimes, it becomes plain obvious a wrong decision was made that needs to be rectified before it becomes too late to change - it happens :) | ||
Semantic Versioning 2.0.0 will be followed after V1 however for now, where you see `V0.x.y`... | ||
* `x` = major / minor change | ||
* `y` = patch / bugfix | ||
## Tips | ||
* On a reasonable VM, I have managed to achieve polling speeds less than 10ms (100+ reads per second) HOWEVER this really taxes NODE and Node-red. Through usage and testing, this node works very well polling 10 times per second (100ms poll time). This is often more than enough for UI type applications | ||
* Where possible, group your items together in the PLC and do 1 large read as opposed to multiple small reads. Reading 20 WDs from 1 location is much faster than reading 20 single items. An additional benefit of reading multiple items in one poll is the data is consistent (i.e. all values were read at on the same PLC poll) | ||
* On a reasonable VM, I have managed to achieve polling over UDP at speeds less than 10ms (100+ reads per second) | ||
* HOWEVER this really taxes NODE and Node-red. Through usage and testing, this node works very well polling 10 times per second (100ms poll time). This is often more than enough for UI type applications | ||
* Where possible, group your items together in the PLC and do 1 large read as opposed to multiple small reads. | ||
* Reading 20 WDs from 1 location is much faster than reading 20 single items. An additional benefit of reading multiple items in one poll is the data is consistent (i.e. all values were read at on the same PLC poll) | ||
@@ -40,0 +37,0 @@ ## Prerequisites |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
428092
41
2926
116