node-red-contrib-omron-fins
Advanced tools
Comparing version 0.1.3 to 0.3.0-beta.1
@@ -76,3 +76,3 @@ /* | ||
_instances: 0, | ||
write: function (addr, data, callback) { | ||
write: function (addr, data, callback, msg) { | ||
if (!client.connected && options.preventAutoReconnect) { | ||
@@ -85,10 +85,10 @@ throw new Error("Not connected!") | ||
} | ||
var sid = client.write(addr, data, callback); | ||
var sid = client.write(addr, data, callback, msg); | ||
return sid; | ||
}, | ||
read: function (addr, len, callback) { | ||
read: function (addr, len, callback, msg) { | ||
if (!client.connected && options.preventAutoReconnect) { | ||
throw new Error("Not connected!") | ||
} | ||
var sid = client.read(addr, parseInt(len), callback); | ||
var sid = client.read(addr, parseInt(len), callback, msg); | ||
return sid; | ||
@@ -95,0 +95,0 @@ }, |
@@ -26,213 +26,217 @@ /* | ||
module.exports = function (RED) { | ||
var connection_pool = require("../connection_pool.js"); | ||
function omronRead(config) { | ||
RED.nodes.createNode(this, config); | ||
var node = this; | ||
var connection_pool = require("../connection_pool.js"); | ||
function omronRead(config) { | ||
RED.nodes.createNode(this, config); | ||
var node = this; | ||
node.name = config.name; | ||
node.topic = config.topic; | ||
node.connection = config.connection; | ||
node.address = config.address || "topic"; | ||
node.addressType = config.addressType || "msg"; | ||
node.count = config.count || 1; | ||
node.countType = config.countType || "num"; | ||
node.sign = config.sign; | ||
node.address = config.address || "topic"; | ||
node.addressType = config.addressType || "msg"; | ||
node.count = config.count || 1; | ||
node.countType = config.countType || "num"; | ||
node.outputFormat = config.outputFormat || "buffer"; | ||
node.outputFormatType = config.outputFormatType || "list"; | ||
node.outputProperty = config.outputProperty || "payload"; | ||
node.outputPropertyType = config.outputPropertyType || "str"; | ||
node.connectionConfig = RED.nodes.getNode(node.connection); | ||
var context = node.context(); | ||
node.busy = false; | ||
node.busyMonitor; | ||
node.busyTimeMax = 1000;//TODO: Parameterise hard coded value! | ||
var fins = require('../omron-fins.js'); | ||
if (this.connectionConfig) { | ||
var options = Object.assign({}, node.connectionConfig.options); | ||
var options = Object.assign({}, node.connectionConfig.options); | ||
node.client = connection_pool.get(this.connectionConfig.port, this.connectionConfig.host, options); | ||
node.status({fill:"yellow",shape:"ring",text:"initialising"}); | ||
node.status({ fill: "yellow", shape: "ring", text: "initialising" }); | ||
this.client.on('error', function (error) { | ||
console.log("Error: ", error); | ||
node.status({fill:"red",shape:"ring",text:"error"}); | ||
node.busy = false; | ||
this.client.on('error', function (error, seq) { | ||
node.status({ fill: "red", shape: "ring", text: "error" }); | ||
node.error(error, (seq && seq.tag ? tag : seq) ); | ||
}); | ||
this.client.on('full', function () { | ||
node.throttleUntil = Date.now() + 1000; | ||
node.warn("Client buffer is saturated. Requests for the next 1000ms will be ignored. Consider reducing poll rate of reads and writes to this connection."); | ||
node.status({ fill: "red", shape: "dot", text: "queue full" }); | ||
}); | ||
this.client.on('open', function (error) { | ||
node.status({fill:"green",shape:"dot",text:"connected"}); | ||
node.status({ fill: "green", shape: "dot", text: "connected" }); | ||
}); | ||
this.client.on('close', function (error) { | ||
node.status({fill:"red",shape:"dot",text:"not connected"}); | ||
node.busy = false; | ||
node.status({ fill: "red", shape: "dot", text: "not connected" }); | ||
}); | ||
this.client.on('initialised', function (error) { | ||
node.status({fill:"yellow",shape:"dot",text:"initialised"}); | ||
node.status({ fill: "yellow", shape: "dot", text: "initialised" }); | ||
}); | ||
function myReply(msg) { | ||
node.busy = false;//reset busy - allow node to be triggered | ||
clearTimeout(node.busyMonitor); | ||
if(msg.timeout) { | ||
node.status({fill:"red",shape:"ring",text:"timeout"}); | ||
node.error("timeout",msg); | ||
var dbgmsg = { | ||
f: 'myReply(msg)', | ||
msg: msg, | ||
error: 'timeout' | ||
} | ||
console.error(dbgmsg); | ||
function finsReply(err, sequence) { | ||
if(!err && !sequence) { | ||
return; | ||
} | ||
if(node.sid && msg.response && node.sid != msg.response.sid){ | ||
node.status({fill:"red",shape:"dot",text:"Incorrect SID"}); | ||
node.error(`SID does not match! My SID: ${node.sid}, reply SID:${msg.response.sid}`,msg); | ||
var dbgmsg = { | ||
f: 'myReply(msg)', | ||
msg: msg, | ||
error: `SID does not match! My SID = ${node.sid}, reply SID = ${msg.response.sid}` | ||
} | ||
console.error(dbgmsg); | ||
return; | ||
} | ||
var cmdExpected = "0101"; | ||
if(!msg || !msg.response || msg.response.endCode !== "0000" || msg.response.command !== cmdExpected ) { | ||
var ecd = "bad response"; | ||
if(msg.response && msg.response.command !== cmdExpected) | ||
ecd = `Unexpected response. Expected command '${cmdExpected}' but received " ${msg.response.command}` | ||
else if(msg.response && msg.response.endCodeDescription) | ||
ecd = msg.response.endCodeDescription; | ||
node.status({fill:"red", shape:"dot", text:ecd}); | ||
node.error(`Response is NG! endCode: ${msg.response.endCode}, endCodeDescription:${msg.response.endCodeDescription}`,msg); | ||
var dbgmsg = { | ||
f: 'myReply(msg)', | ||
msg: msg, | ||
error: ecd | ||
var origInputMsg = (sequence && sequence.tag) || {}; | ||
try { | ||
if (err || sequence.error) { | ||
node.status({ fill: "red", shape: "ring", text: "error" }); | ||
node.error(err || sequence.error, origInputMsg); | ||
return; | ||
} | ||
if (sequence.timeout) { | ||
node.status({ fill: "red", shape: "ring", text: "timeout" }); | ||
node.error("timeout", origInputMsg); | ||
return; | ||
} | ||
console.error(dbgmsg); | ||
return; | ||
} | ||
var cmdExpected = "0101"; | ||
if (sequence.response && sequence.sid != sequence.response.sid) { | ||
node.status({ fill: "red", shape: "dot", text: "Incorrect SID" }); | ||
node.error(`SID does not match! My SID: ${sequence.sid}, reply SID:${sequence.response.sid}`, origInputMsg); | ||
return; | ||
} | ||
if (!sequence || !sequence.response || sequence.response.endCode !== "0000" || sequence.response.command !== cmdExpected) { | ||
var ecd = "bad response"; | ||
if (sequence.response && sequence.response.command !== cmdExpected) | ||
ecd = `Unexpected response. Expected command '${cmdExpected}' but received " ${sequence.response.command}`; | ||
else if (sequence.response && sequence.response.endCodeDescription) | ||
ecd = sequence.response.endCodeDescription; | ||
node.status({ fill: "red", shape: "dot", text: ecd }); | ||
node.error(`Response is NG! endCode: ${sequence.response ? sequence.response.endCode : "????"}, endCodeDescription:${sequence.response ? sequence.response.endCodeDescription : ""}`, origInputMsg); | ||
return; | ||
} | ||
var kvMaker = function (pkt) { | ||
let kvs = {}; | ||
if (pkt.response.values) { | ||
let iWD = 0; | ||
for (let x in pkt.response.values) { | ||
let item_addr = node.client.decodedAddressToString(pkt.request.address, iWD, 0); | ||
kvs[item_addr] = pkt.response.values[x]; | ||
iWD++; | ||
} | ||
} | ||
return kvs; | ||
}; | ||
var outDetail = {}; | ||
var iWD = 0; | ||
if(msg.response.values) { | ||
if(node.sign == "unsigned"){ | ||
msg.response.values = Uint16Array.from(msg.response.values) | ||
} | ||
for (var x in msg.response.values) { | ||
//var buff_address = config.address.charAt(0) + ":" + String(parseInt(config.address.slice(1)) + x); | ||
var buff_address = node.client.decodedAddressToString(msg.request.address,iWD,0) | ||
var buff_value = String(msg.response.values[x]); | ||
outDetail[buff_address] = buff_value; | ||
iWD++; | ||
} | ||
} | ||
var newMsg = {payload: msg.response.values, request: msg.request, response: msg.response, name: node.name, topic : node.topic, data: outDetail}; | ||
node.status({fill:"green",shape:"dot",text:"done"}); | ||
node.send(newMsg); | ||
var outputFormat = RED.util.evaluateNodeProperty(node.outputFormat, node.outputFormatType, node, origInputMsg); | ||
var value; | ||
switch (outputFormat) { | ||
case "signed": | ||
value = sequence.response.values; | ||
break; | ||
case "unsigned": | ||
sequence.response.values = Uint16Array.from(sequence.response.values); | ||
value = sequence.response.values; | ||
break; | ||
case "signedkv": | ||
value = kvMaker(sequence); | ||
break; | ||
case "unsignedkv": | ||
sequence.response.values = Uint16Array.from(sequence.response.values); | ||
value = kvMaker(sequence); | ||
break; | ||
default: //buffer | ||
value = sequence.response.buffer; | ||
break; | ||
} | ||
//set the output property | ||
var outputProperty = RED.util.evaluateNodeProperty(node.outputProperty, node.outputPropertyType, node, origInputMsg); | ||
RED.util.setObjectProperty(origInputMsg, outputProperty, value, true); | ||
//include additional detail in msg.fins | ||
origInputMsg.fins = {}; | ||
origInputMsg.fins.request = { | ||
address: sequence.request.address, | ||
count: sequence.request.count, | ||
sid: sequence.request.sid, | ||
}; | ||
origInputMsg.fins.response = sequence.response; | ||
origInputMsg.fins.stats = sequence.stats; | ||
origInputMsg.fins.createTime = sequence.createTime; | ||
origInputMsg.fins.replyTime = sequence.replyTime; | ||
origInputMsg.fins.timeTaken = sequence.timeTaken; | ||
node.status({ fill: "green", shape: "dot", text: "done" }); | ||
node.send(origInputMsg); | ||
} catch (error) { | ||
node.status({ fill: "red", shape: "ring", text: "error" }); | ||
node.error(error, origInputMsg); | ||
} | ||
} | ||
this.on('input', function (msg) { | ||
node.status({});//clear status | ||
this.on('close', function (done) { | ||
if (done) done(); | ||
}); | ||
if(msg.disconnect === true || msg.topic === 'disconnect'){ | ||
node.connection.closeConnection(); | ||
return; | ||
} else if(msg.connect === true || msg.topic === 'connect'){ | ||
node.connection.connect(); | ||
return; | ||
} | ||
this.on('input', function (msg) { | ||
if (node.throttleUntil) { | ||
if (node.throttleUntil > Date.now()) return; //throttled | ||
node.throttleUntil = null; //throttle time over | ||
} | ||
node.status({});//clear status | ||
if(node.busy) | ||
return;//TODO: Consider queueing inputs? | ||
/* **************** Node status **************** */ | ||
var nodeStatusError = function(err,msg,statusText){ | ||
if(err){ | ||
console.error(err); | ||
node.error(err,msg); | ||
} else { | ||
console.error(statusText); | ||
node.error(statusText,msg); | ||
} | ||
node.status({fill:"red",shape:"dot",text:statusText}); | ||
} | ||
var nodeStatusParameterError = function(err, msg, propName){ | ||
nodeStatusError(err, msg, "Unable to evaluate property '" + propName + "' value") | ||
} | ||
if (msg.disconnect === true || msg.topic === 'disconnect') { | ||
node.connection.closeConnection(); | ||
return; | ||
} else if (msg.connect === true || msg.topic === 'connect') { | ||
node.connection.connect(); | ||
return; | ||
} | ||
/* **************** Get address Parameter **************** */ | ||
var address; | ||
RED.util.evaluateNodeProperty(node.address,node.addressType,node,msg,(err,value) => { | ||
if (err) { | ||
nodeStatusParameterError(err,msg,"address"); | ||
return;//halt flow! | ||
} else { | ||
address = value; | ||
} | ||
}); | ||
/* **************** Node status **************** */ | ||
var nodeStatusError = function (err, msg, statusText) { | ||
if (err) { | ||
console.error(err); | ||
node.error(err, msg); | ||
} else { | ||
console.error(statusText); | ||
node.error(statusText, msg); | ||
} | ||
node.status({ fill: "red", shape: "dot", text: statusText }); | ||
}; | ||
/* **************** Get count Parameter **************** */ | ||
var count; | ||
RED.util.evaluateNodeProperty(node.count,node.countType,node,msg,(err,value) => { | ||
if (err) { | ||
nodeStatusParameterError(err,msg,"count"); | ||
return;//halt flow! | ||
} else { | ||
count = value; | ||
} | ||
}); | ||
/* **************** Get address Parameter **************** */ | ||
var address = RED.util.evaluateNodeProperty(node.address, node.addressType, node, msg); | ||
if(address == "") { | ||
nodeStatusError(null,msg,"address is empty"); | ||
return; | ||
} | ||
count = parseInt(count); | ||
if(Number.isNaN(count)) { | ||
nodeStatusError(null,msg,"count is not valid"); | ||
return; | ||
} | ||
//if node | ||
if(!config.sign && msg.payload.sign){ | ||
node.sign = "unsigned"; | ||
} | ||
/* **************** Get count Parameter **************** */ | ||
var count = RED.util.evaluateNodeProperty(node.count, node.countType, node, msg); | ||
try { | ||
node.status({fill:"yellow",shape:"ring",text:"read"}); | ||
node.busy = true; | ||
if (node.busyTimeMax) { | ||
node.busyMonitor = setTimeout(function() { | ||
if(node.busy){ | ||
nodeStatusError(null,msg,"timeout") | ||
node.busy = false; | ||
return; | ||
} | ||
}, node.busyTimeMax); | ||
} | ||
node.sid = this.client.read(address, parseInt(count), myReply); | ||
} catch (error) { | ||
node.sid = null; | ||
node.busy = false; | ||
nodeStatusError(error,msg,"Error") | ||
var dbgmsg = { | ||
info: "read.js-->on 'input'", | ||
connection: `host: ${node.connectionConfig.host}, port: ${node.connectionConfig.port}`, | ||
if (address == "") { | ||
nodeStatusError(null, msg, "address is empty"); | ||
return; | ||
} | ||
count = parseInt(count); | ||
if (Number.isNaN(count)) { | ||
nodeStatusError(null, msg, "count is not valid"); | ||
return; | ||
} | ||
try { | ||
let sid = this.client.read(address, parseInt(count), finsReply, msg); | ||
if (sid > 0) { | ||
node.status({ fill: "yellow", shape: "ring", text: "reading" }); | ||
} | ||
} catch (error) { | ||
node.sid = null; | ||
nodeStatusError(error, msg, "error"); | ||
let dbgmsg = { | ||
info: "read.js-->on 'input'", | ||
connection: `host: ${node.connectionConfig.host}, port: ${node.connectionConfig.port}`, | ||
address: address, | ||
size: count, | ||
}; | ||
console.debug(dbgmsg); | ||
}; | ||
console.debug(dbgmsg); | ||
return; | ||
} | ||
}); | ||
node.status({fill:"green",shape:"ring",text:"ready"}); | ||
} | ||
} else { | ||
nodeStatusError(null,msg,"configuration not setup") | ||
}); | ||
node.status({ fill: "green", shape: "ring", text: "ready" }); | ||
} else { | ||
node.status({ fill: "red", shape: "dot", text: "configuration not setup" }); | ||
} | ||
} | ||
RED.nodes.registerType("FINS Read", omronRead); | ||
omronRead.prototype.close = function() { | ||
if (this.client) { | ||
this.client.disconnect(); | ||
} | ||
} | ||
} | ||
RED.nodes.registerType("FINS Read", omronRead); | ||
omronRead.prototype.close = function () { | ||
if (this.client) { | ||
this.client.disconnect(); | ||
} | ||
}; | ||
}; | ||
@@ -27,4 +27,2 @@ /* | ||
var connection_pool = require("../connection_pool.js"); | ||
var util = require("util"); | ||
function omronWrite(config) { | ||
@@ -35,16 +33,13 @@ RED.nodes.createNode(this, config); | ||
node.connection = config.connection; | ||
node.address = config.address || "topic"; | ||
node.addressType = config.addressType || "msg"; | ||
node.data = config.data || "payload"; | ||
node.dataType = config.dataType || "msg"; | ||
node.address = config.address || "topic"; | ||
node.addressType = config.addressType || "msg"; | ||
node.data = config.data || "payload"; | ||
node.dataType = config.dataType || "msg"; | ||
node.outputProperty = config.outputProperty || "payload"; | ||
node.outputPropertyType = config.outputPropertyType || "str"; | ||
node.connectionConfig = RED.nodes.getNode(node.connection); | ||
var context = node.context(); | ||
node.sid = ""; | ||
node.busy = false; | ||
node.busyTimeMax = 1000;//TODO: Parameterise hard coded value! | ||
var fins = require('../omron-fins.js'); | ||
if (this.connectionConfig) { | ||
node.status({fill:"yellow",shape:"ring",text:"initialising"}); | ||
var options = Object.assign({}, node.connectionConfig.options); | ||
node.status({ fill: "yellow", shape: "ring", text: "initialising" }); | ||
var options = Object.assign({}, node.connectionConfig.options); | ||
this.client = connection_pool.get(this.connectionConfig.port, this.connectionConfig.host, options); | ||
@@ -54,160 +49,157 @@ | ||
console.log("Error: ", error); | ||
node.status({fill:"red",shape:"ring",text:"error"}); | ||
node.status({ fill: "red", shape: "ring", text: "error" }); | ||
node.error(error, (seq && seq.tag ? tag : seq) ); | ||
}); | ||
this.client.on('full', function () { | ||
node.status({ fill: "red", shape: "dot", text: "queue full" }); | ||
node.throttleUntil = Date.now() + 1000; | ||
node.warn("Client buffer is saturated. Requests for the next 1000ms will be ignored. Consider reducing poll rate of reads and writes to this connection."); | ||
}); | ||
this.client.on('open', function (error) { | ||
node.status({fill:"green",shape:"dot",text:"connected"}); | ||
node.status({ fill: "green", shape: "dot", text: "connected" }); | ||
}); | ||
this.client.on('close', function (error) { | ||
node.status({fill:"red",shape:"dot",text:"not connected"}); | ||
node.status({ fill: "red", shape: "dot", text: "not connected" }); | ||
}); | ||
this.client.on('initialised', function (error) { | ||
node.status({fill:"yellow",shape:"dot",text:"initialised"}); | ||
node.status({ fill: "yellow", shape: "dot", text: "initialised" }); | ||
}); | ||
function myReply(msg) { | ||
node.busy = false;//reset busy - allow node to be triggered | ||
clearTimeout(node.busyMonitor); | ||
if(msg.timeout) { | ||
node.status({fill:"red",shape:"ring",text:"timeout"}); | ||
node.error("timeout"); | ||
var dbgmsg = { | ||
f: 'myReply(msg)', | ||
msg: msg, | ||
error: 'timeout' | ||
} | ||
console.error(dbgmsg); | ||
function finsReply(err, sequence) { | ||
if(!err && !sequence) { | ||
return; | ||
} | ||
if(node.sid && msg.response && node.sid != msg.response.sid){ | ||
node.status({fill:"red",shape:"dot",text:"Incorrect SID"}); | ||
node.error(`SID does not match! My SID: ${node.sid}, reply SID:${msg.response.sid}`); | ||
var dbgmsg = { | ||
f: 'myReply(msg)', | ||
msg: msg, | ||
error: `SID does not match! My SID = ${node.sid}, reply SID = ${msg.response.sid}` | ||
var origInputMsg = (sequence && sequence.tag) || {}; | ||
try { | ||
if (err || sequence.error) { | ||
node.status({ fill: "red", shape: "ring", text: "error" }); | ||
node.error(err || sequence.error, origInputMsg); | ||
return; | ||
} | ||
if (sequence.timeout) { | ||
node.status({ fill: "red", shape: "ring", text: "timeout" }); | ||
node.error("timeout", origInputMsg); | ||
return; | ||
} | ||
console.error(dbgmsg); | ||
return; | ||
} | ||
var cmdExpected = "0102"; | ||
if(!msg || !msg.response || msg.response.endCode !== "0000" || msg.response.command !== cmdExpected ) { | ||
var ecd = "bad response"; | ||
if(msg.response && msg.response.command !== cmdExpected) | ||
ecd = `Unexpected response. Expected command '${cmdExpected}' but received " ${msg.response.command}` | ||
else if(msg.response && msg.response.endCodeDescription) | ||
ecd = msg.response.endCodeDescription; | ||
node.status({fill:"red", shape:"dot", text:ecd}); | ||
node.error(`Response is NG! endCode: ${msg.response.endCode}, endCodeDescription:${msg.response.endCodeDescription}`); | ||
var dbgmsg = { | ||
f: 'myReply(msg)', | ||
msg: msg, | ||
error: ecd | ||
if (sequence.response && sequence.sid != sequence.response.sid) { | ||
node.status({ fill: "red", shape: "dot", text: "Incorrect SID" }); | ||
node.error(`SID does not match! My SID: ${sequence.sid}, reply SID:${sequence.response.sid}`, origInputMsg); | ||
return; | ||
} | ||
console.error(dbgmsg); | ||
return; | ||
} | ||
var cmdExpected = "0102"; | ||
if (!sequence || !sequence.response || sequence.response.endCode !== "0000" || sequence.response.command !== cmdExpected) { | ||
var ecd = "bad response"; | ||
if (sequence.response && sequence.response.command !== cmdExpected) | ||
ecd = `Unexpected response. Expected command '${cmdExpected}' but received " ${sequence.response.command}`; | ||
else if (sequence.response && sequence.response.endCodeDescription) | ||
ecd = sequence.response.endCodeDescription; | ||
node.status({ fill: "red", shape: "dot", text: ecd }); | ||
node.error(`Response is NG! endCode: ${sequence.response ? sequence.response.endCode : "????"}, endCodeDescription:${sequence.response ? sequence.response.endCodeDescription : ""}`, origInputMsg); | ||
return; | ||
} | ||
//set the output property | ||
var outputProperty = RED.util.evaluateNodeProperty(node.outputProperty, node.outputPropertyType, node, origInputMsg); | ||
RED.util.setObjectProperty(origInputMsg, outputProperty, sequence.sid || 0, true); | ||
//TODO: consider payload - what to send! True? SID? IDK! | ||
var newMsg = {payload: node.sid, request: msg.request, response: msg.response, name: node.name, topic : node.topic}; | ||
//include additional detail in msg.fins | ||
origInputMsg.fins = {}; | ||
origInputMsg.fins.request = { | ||
address: sequence.request.address, | ||
dataToBeWritten: sequence.request.dataToBeWritten, | ||
sid: sequence.request.sid, | ||
}; | ||
origInputMsg.fins.response = sequence.response; | ||
origInputMsg.fins.stats = sequence.stats; | ||
origInputMsg.fins.createTime = sequence.createTime; | ||
origInputMsg.fins.replyTime = sequence.replyTime; | ||
origInputMsg.fins.timeTaken = sequence.timeTaken; | ||
node.status({fill:"green",shape:"dot",text:"done"}); | ||
node.send(newMsg); | ||
node.status({ fill: "green", shape: "dot", text: "done" }); | ||
node.send(origInputMsg); | ||
} catch (error) { | ||
node.status({ fill: "red", shape: "ring", text: "error" }); | ||
node.error(error, origInputMsg); | ||
} | ||
} | ||
this.on('close', function (done) { | ||
if (done) done(); | ||
}); | ||
this.on('input', function (msg) { | ||
if (node.throttleUntil) { | ||
if (node.throttleUntil > Date.now()) return; //throttled | ||
node.throttleUntil = null; //throttle time over | ||
} | ||
node.status({});//clear status | ||
if(msg.disconnect === true || msg.topic === 'disconnect'){ | ||
node.client.closeConnection(); | ||
return; | ||
} else if(msg.connect === true || msg.topic === 'connect'){ | ||
node.client.connect(); | ||
return; | ||
} | ||
if (msg.disconnect === true || msg.topic === 'disconnect') { | ||
node.client.closeConnection(); | ||
return; | ||
} else if (msg.connect === true || msg.topic === 'connect') { | ||
node.client.connect(); | ||
return; | ||
} | ||
if(node.busy) | ||
return;//TODO: Consider queueing inputs? | ||
/* **************** Node status **************** */ | ||
var nodeStatusError = function (err, msg, statusText) { | ||
if (err) { | ||
console.error(err); | ||
node.error(err, msg); | ||
} else { | ||
console.error(statusText); | ||
node.error(statusText, msg); | ||
} | ||
node.status({ fill: "red", shape: "dot", text: statusText }); | ||
}; | ||
var nodeStatusParameterError = function (err, msg, propName) { | ||
nodeStatusError(err, msg, "Unable to evaluate property '" + propName + "' value"); | ||
}; | ||
/* **************** Node status **************** */ | ||
var nodeStatusError = function(err,msg,statusText){ | ||
if(err){ | ||
console.error(err); | ||
node.error(err,msg); | ||
} else { | ||
console.error(statusText); | ||
node.error(statusText,msg); | ||
} | ||
node.status({fill:"red",shape:"dot",text:statusText}); | ||
} | ||
var nodeStatusParameterError = function(err, msg, propName){ | ||
nodeStatusError(err, msg, "Unable to evaluate property '" + propName + "' value") | ||
/* **************** Get address Parameter **************** */ | ||
var address = RED.util.evaluateNodeProperty(node.address, node.addressType, node, msg); | ||
/* **************** Get data Parameter **************** */ | ||
var data; | ||
RED.util.evaluateNodeProperty(node.data, node.dataType, node, msg, function (err, value) { | ||
if (err) { | ||
nodeStatusParameterError(err, msg, "data"); | ||
return;//halt flow! | ||
} else { | ||
data = value; | ||
} | ||
}); | ||
if (address == "") { | ||
nodeStatusError(null, msg, "address is empty"); | ||
return; | ||
} | ||
/* **************** Get address Parameter **************** */ | ||
var address; | ||
RED.util.evaluateNodeProperty(node.address,node.addressType,node,msg,(err,value) => { | ||
if (err) { | ||
nodeStatusParameterError(err,msg,"address"); | ||
return;//halt flow! | ||
} else { | ||
address = value; | ||
} | ||
}); | ||
if (!data && data !== 0) { | ||
nodeStatusError(null, msg, "data is not valid"); | ||
return; | ||
} | ||
/* **************** Get data Parameter **************** */ | ||
var data; | ||
RED.util.evaluateNodeProperty(node.data,node.dataType,node,msg,(err,value) => { | ||
if (err) { | ||
nodeStatusParameterError(err,msg,"data"); | ||
return;//halt flow! | ||
} else { | ||
data = value; | ||
} | ||
}); | ||
if(address == "") { | ||
nodeStatusError(null,msg,"address is empty"); | ||
return; | ||
} | ||
if(!data && data !== 0) { | ||
nodeStatusError(null,msg,"data is not valid"); | ||
return; | ||
} | ||
try { | ||
node.status({fill:"yellow",shape:"ring",text:"write"}); | ||
node.busy = true; | ||
if (node.busyTimeMax) { | ||
this.busyMonitor = setTimeout(function() { | ||
if(node.busy){ | ||
node.status({fill:"red",shape:"ring",text:"timeout"}); | ||
node.error("timeout"); | ||
node.busy = false; | ||
return; | ||
} | ||
}, node.busyTimeMax); | ||
} | ||
node.sid = this.client.write(address, data, myReply); | ||
var sid = this.client.write(address, data, finsReply, msg); | ||
if (sid > 0) node.status({ fill: "yellow", shape: "ring", text: "write" }); | ||
} catch (error) { | ||
node.sid = null; | ||
node.busy = false; | ||
nodeStatusError(error,msg,"Error") | ||
var dbgmsg = { | ||
info: "write.js-->on 'input' - try this.client.write(address, data, myReply)", | ||
connection: `host: ${node.connectionConfig.host}, port: ${node.connectionConfig.port}`, | ||
nodeStatusError(error, msg, "error"); | ||
var dbgmsg = { | ||
info: "write.js-->on 'input' - try this.client.write(address, data, finsReply)", | ||
connection: `host: ${node.connectionConfig.host}, port: ${node.connectionConfig.port}`, | ||
address: address, | ||
data: data, | ||
error: error | ||
}; | ||
console.debug(dbgmsg); | ||
error: error | ||
}; | ||
console.debug(dbgmsg); | ||
return; | ||
} | ||
}); | ||
node.status({fill:"green",shape:"ring",text:"ready"}); | ||
node.status({ fill: "green", shape: "ring", text: "ready" }); | ||
} else { | ||
nodeStatusError(null,msg,"configuration not setup") | ||
node.status({ fill: "red", shape: "dot", text: "configuration not setup" }); | ||
} | ||
@@ -217,8 +209,8 @@ | ||
RED.nodes.registerType("FINS Write", omronWrite); | ||
omronWrite.prototype.close = function() { | ||
if (this.client) { | ||
this.client.disconnect(); | ||
} | ||
} | ||
omronWrite.prototype.close = function () { | ||
if (this.client) { | ||
this.client.disconnect(); | ||
} | ||
}; | ||
}; | ||
{ | ||
"name": "node-red-contrib-omron-fins", | ||
"version": "0.1.3", | ||
"version": "0.3.0-beta.1", | ||
"author": { | ||
@@ -24,4 +24,4 @@ "name": "Steve-Mcl", | ||
"dependencies": { | ||
"omron-fins": "^0.1.3" | ||
"omron-fins": "^0.2.0-beta.0" | ||
} | ||
} |
103
README.md
node-red-contrib-omron-fins | ||
=========================== | ||
### Overview | ||
## Overview | ||
This is a Node-RED node module to directly interface with OMRON PLCs over FINS Ethernet protocol. | ||
For now it only supports CS/CJ and CV mode for READ and WRITE operations over FINS UDP. | ||
For now it only supports READ and WRITE operations over FINS UDP. | ||
Works with CV, CS, CJ, NJ and NX PLCs (the ones with FINS support) | ||
Credits to [Patrick Servello (patrick--)](https://github.com/patrick--) for his original implementation FINS | ||
Credits to [Jozo132](https://github.com/Jozo132/node-omron-read.git) for his original implementation node-omron-read on which this is based | ||
## Version Update Notes | ||
This release (and possibly future releases upto 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 descision was made - 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 | ||
### Prerequisites for Windows | ||
* git (Used for repository cloning/downloads) | ||
* node.js (Background system for Node-RED) | ||
## Prerequisites | ||
* node.js | ||
* Node-RED | ||
* git (optional) (Used for repository cloning/downloads) | ||
### Install | ||
## Credits | ||
Credit to [Patrick Servello (patrick--)](https://github.com/patrick--) for his original implementation of FINS | ||
```sh | ||
npm install Steve-Mcl/node-red-contrib-omron-fins | ||
``` | ||
## Install | ||
### Usage | ||
### Pallet Manager... | ||
* Restart Node-RED and there's the thingy now | ||
The simplest method is to install via the pallet manager in node red. Simply search for **node-red-contrib-omron-fins** then click install | ||
### Terminal... | ||
Run the following command in the root directory of your Node-RED install (usually `~/.node-red` or `%userprofile%\.node-red`) | ||
npm node-red-contrib-omron-fins | ||
Or, install direct from github | ||
npm install steve-mcl/node-red-contrib-omron-fins | ||
Or clone to a local folder and install using NPM | ||
git clone https://github.com/Steve-Mcl/node-red-contrib-omron-fins.git | ||
npm install /source-path/node-red-contrib-omron-fins | ||
## A working example... | ||
### PLC Setup | ||
| Setting | Value | | ||
|----|------| | ||
| IP | 192.168.4.88 | | ||
| MASK | 255.255.255.0 | | ||
| Node | 88 | | ||
| UDP | 9600 | | ||
### Node-red Setup | ||
| Setting | Value | | ||
|----|------| | ||
| IP | 192.168.4.179 | | ||
| MASK | 255.255.255.0 | | ||
### FINS Connection Node Settings | ||
| Option | Value | | ||
|----|------| | ||
Host | 192.168.4.88 | ||
Port | 9600 | ||
MODE | NJ/NX | ||
ICF | 0x80 | ||
DNA | 0 | ||
DA1 | 88 | ||
DA2 | 0 | ||
SNA | 0 | ||
SA1 | 179 | ||
SA2 | 0 | ||
![image](https://user-images.githubusercontent.com/44235289/85577974-9c4a7700-b631-11ea-8320-99992892b39d.png) | ||
#### Other notes: | ||
* If the subnet mask is bigger than /24 (e.g. is bigger than 255.255.255.0) you might need to enter the IP and NODE number (of the node-red server) into the FINS **IP address table** so that the PLC understands which IP to respond to when responding to the SA1 NODE number | ||
* FINS works with PLC Addresses. NJ and NX PLCs do NOT have direct addresses to addresses like DM or E0_, E1_. | ||
* In order to communicate to a variable in N Series PLCs via FINS you must create a Global TAG and set its `AT` property. E.G. If you want to read and write 40 WDs from E0_9000 ~ E0_9039 then you need to add a TAG like this `TAG_NAME ARRAY[0..39] Of WORD %E0_9000` | ||
![image](https://user-images.githubusercontent.com/44235289/85562713-a619ad80-b624-11ea-971b-dc22754d7cf1.png) | ||
## Data formats and conversion | ||
NOTE: This node returns a buffer, 16bit signed or 16bit unsigned data only. While that may seem restrictive, it was a deliberate design decision to keep the node mean and lean. | ||
### Read on... | ||
As I use multiple PLCs and didn't want to write boolean / 32bit / float / double functionality into each node (it's best to keep things atomic and good at what they do) so I wrote a separate second node for handling data conversions. | ||
This node "node-red-contrib-buffer-parser" https://flows.nodered.org/node/node-red-contrib-buffer-parser is capable of pretty much anything you will need for this or any PLC that returns 16bit data or a NodeJS Buffer. | ||
In essence, you pull a bunch of data from the plc in one go & convert it all in the buffer-parser node to almost any format you could wish for (bits, floats, 32bit signed / unsigned, byteswapping etc etc). It can do 1 or many conversions all at once. It can send a [grouped item](https://github.com/Steve-Mcl/node-red-contrib-buffer-parser#example-2---array-of-data-to-an-named-objects) (object) or individual items [with a `topic`](https://github.com/Steve-Mcl/node-red-contrib-buffer-parser#example-1---array-of-data-to-mqtt-multiple-topics--payloads) ready for pushing your data directly from the PLC to MQTT. |
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
191503
103
594
+ Addedomron-fins@0.2.0-beta.0(transitive)
- Removedomron-fins@0.1.3(transitive)
Updatedomron-fins@^0.2.0-beta.0