mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'master' into runtime-api
This commit is contained in:
@@ -237,10 +237,9 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
|
||||
} else {
|
||||
return this._("inject.timestamp")+suffix;
|
||||
}
|
||||
} else if (this.payloadType === 'flow' && this.payload.length < 19) {
|
||||
return 'flow.'+this.payload+suffix;
|
||||
} else if (this.payloadType === 'global' && this.payload.length < 17) {
|
||||
return 'global.'+this.payload+suffix;
|
||||
} else if (this.payloadType === 'flow' || this.payloadType === 'global') {
|
||||
var key = RED.utils.parseContextKey(this.payload);
|
||||
return this.payloadType+"."+key.key+suffix;
|
||||
} else {
|
||||
return this._("inject.inject")+suffix;
|
||||
}
|
||||
@@ -502,7 +501,13 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
|
||||
if (this.changed) {
|
||||
return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
|
||||
}
|
||||
var label = (this.name||this.payload);
|
||||
var payload = this.payload;
|
||||
if ((this.payloadType === 'flow') ||
|
||||
(this.payloadType === 'global')) {
|
||||
var key = RED.utils.parseContextKey(payload);
|
||||
payload = this.payloadType+"."+key.key;
|
||||
}
|
||||
var label = (this.name||payload);
|
||||
if (label.length > 30) {
|
||||
label = label.substring(0,50)+"...";
|
||||
}
|
||||
|
@@ -63,21 +63,33 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
this.on("input",function(msg) {
|
||||
try {
|
||||
msg.topic = this.topic;
|
||||
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
||||
msg.payload = Date.now();
|
||||
} else if (this.payloadType == null) {
|
||||
msg.payload = this.payload;
|
||||
} else if (this.payloadType === 'none') {
|
||||
msg.payload = "";
|
||||
} else {
|
||||
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
|
||||
msg.topic = this.topic;
|
||||
if (this.payloadType !== 'flow' && this.payloadType !== 'global') {
|
||||
try {
|
||||
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
||||
msg.payload = Date.now();
|
||||
} else if (this.payloadType == null) {
|
||||
msg.payload = this.payload;
|
||||
} else if (this.payloadType === 'none') {
|
||||
msg.payload = "";
|
||||
} else {
|
||||
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
|
||||
}
|
||||
this.send(msg);
|
||||
msg = null;
|
||||
} catch(err) {
|
||||
this.error(err,msg);
|
||||
}
|
||||
this.send(msg);
|
||||
msg = null;
|
||||
} catch(err) {
|
||||
this.error(err,msg);
|
||||
} else {
|
||||
RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) {
|
||||
if (err) {
|
||||
node.error(err,msg);
|
||||
} else {
|
||||
msg.payload = res;
|
||||
node.send(msg);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -156,7 +156,7 @@
|
||||
toolbar: uiComponents.footer,
|
||||
enableOnEdit: true,
|
||||
pinned: true,
|
||||
iconClass: "fa fa-list-alt"
|
||||
iconClass: "fa fa-bug"
|
||||
});
|
||||
RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); });
|
||||
|
||||
|
@@ -4,7 +4,6 @@ module.exports = function(RED) {
|
||||
var util = require("util");
|
||||
var events = require("events");
|
||||
var path = require("path");
|
||||
var safeJSONStringify = require("json-stringify-safe");
|
||||
var debuglength = RED.settings.debugMaxLength || 1000;
|
||||
var useColors = RED.settings.debugUseColors || false;
|
||||
util.inspect.styles.boolean = "red";
|
||||
@@ -104,111 +103,7 @@ module.exports = function(RED) {
|
||||
function sendDebug(msg) {
|
||||
// don't put blank errors in sidebar (but do add to logs)
|
||||
//if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; }
|
||||
if (msg.msg instanceof Error) {
|
||||
msg.format = "error";
|
||||
var errorMsg = {};
|
||||
if (msg.msg.name) {
|
||||
errorMsg.name = msg.msg.name;
|
||||
}
|
||||
if (msg.msg.hasOwnProperty('message')) {
|
||||
errorMsg.message = msg.msg.message;
|
||||
} else {
|
||||
errorMsg.message = msg.msg.toString();
|
||||
}
|
||||
msg.msg = JSON.stringify(errorMsg);
|
||||
} else if (msg.msg instanceof Buffer) {
|
||||
msg.format = "buffer["+msg.msg.length+"]";
|
||||
msg.msg = msg.msg.toString('hex');
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substring(0,debuglength);
|
||||
}
|
||||
} else if (msg.msg && typeof msg.msg === 'object') {
|
||||
try {
|
||||
msg.format = msg.msg.constructor.name || "Object";
|
||||
// Handle special case of msg.req/res objects from HTTP In node
|
||||
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
|
||||
msg.format = "Object";
|
||||
}
|
||||
} catch(err) {
|
||||
msg.format = "Object";
|
||||
}
|
||||
if (/error/i.test(msg.format)) {
|
||||
msg.msg = JSON.stringify({
|
||||
name: msg.msg.name,
|
||||
message: msg.msg.message
|
||||
});
|
||||
} else {
|
||||
var isArray = util.isArray(msg.msg);
|
||||
if (isArray) {
|
||||
msg.format = "array["+msg.msg.length+"]";
|
||||
if (msg.msg.length > debuglength) {
|
||||
// msg.msg = msg.msg.slice(0,debuglength);
|
||||
msg.msg = {
|
||||
__encoded__: true,
|
||||
type: "array",
|
||||
data: msg.msg.slice(0,debuglength),
|
||||
length: msg.msg.length
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isArray || (msg.format === "Object")) {
|
||||
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
|
||||
if (key === '_req' || key === '_res') {
|
||||
value = "[internal]"
|
||||
} else if (value instanceof Error) {
|
||||
value = value.toString()
|
||||
} else if (util.isArray(value) && value.length > debuglength) {
|
||||
value = {
|
||||
__encoded__: true,
|
||||
type: "array",
|
||||
data: value.slice(0,debuglength),
|
||||
length: value.length
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
if (value.length > debuglength) {
|
||||
value = value.substring(0,debuglength)+"...";
|
||||
}
|
||||
} else if (value && value.constructor) {
|
||||
if (value.type === "Buffer") {
|
||||
value.__encoded__ = true;
|
||||
value.length = value.data.length;
|
||||
if (value.length > debuglength) {
|
||||
value.data = value.data.slice(0,debuglength);
|
||||
}
|
||||
} else if (value.constructor.name === "ServerResponse") {
|
||||
value = "[internal]"
|
||||
} else if (value.constructor.name === "Socket") {
|
||||
value = "[internal]"
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}," ");
|
||||
} else {
|
||||
try { msg.msg = msg.msg.toString(); }
|
||||
catch(e) { msg.msg = "[Type not printable]"; }
|
||||
}
|
||||
}
|
||||
} else if (typeof msg.msg === "boolean") {
|
||||
msg.format = "boolean";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (typeof msg.msg === "number") {
|
||||
msg.format = "number";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (msg.msg === 0) {
|
||||
msg.format = "number";
|
||||
msg.msg = "0";
|
||||
} else if (msg.msg === null || typeof msg.msg === "undefined") {
|
||||
msg.format = (msg.msg === null)?"null":"undefined";
|
||||
msg.msg = "(undefined)";
|
||||
} else {
|
||||
msg.format = "string["+msg.msg.length+"]";
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substring(0,debuglength)+"...";
|
||||
}
|
||||
}
|
||||
// if (msg.msg.length > debuglength) {
|
||||
// msg.msg = msg.msg.substr(0,debuglength) +" ....";
|
||||
// }
|
||||
msg = RED.util.encodeObject(msg,{maxLength:debuglength});
|
||||
RED.comms.publish("debug",msg);
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,8 @@
|
||||
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
||||
<input type="hidden" id="node-input-noerr">
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div class="form-row node-text-editor-row" style="position:relative">
|
||||
<div style="position: absolute; right:0; bottom:calc(100% + 3px);"><button id="node-function-expand-js" class="editor-button editor-button-small"><i class="fa fa-expand"></i></button></div>
|
||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -119,6 +120,22 @@
|
||||
fields:['name','outputs']
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
$("#node-function-expand-js").click(function(e) {
|
||||
e.preventDefault();
|
||||
var value = that.editor.getValue();
|
||||
RED.editor.editJavaScript({
|
||||
value: value,
|
||||
cursor: that.editor.getCursorPosition(),
|
||||
complete: function(v,cursor) {
|
||||
that.editor.setValue(v, -1);
|
||||
that.editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
that.editor.focus();
|
||||
},300);
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
oneditsave: function() {
|
||||
var annot = this.editor.getSession().getAnnotations();
|
||||
|
@@ -77,7 +77,7 @@
|
||||
}</pre>
|
||||
<p>The resulting property will be:
|
||||
<pre>Hello Fred. Today is Monday</pre>
|
||||
<p>It is possible to use a property from the flow context or global context. Just use <code>{{flow.name}}</code> or <code>{{global.name}}</code>.
|
||||
<p>It is possible to use a property from the flow context or global context. Just use <code>{{flow.name}}</code> or <code>{{global.name}}</code>, or for persistable store <code>store</code> use <code>{{flow[store].name}}</code> or <code>{{flobal[store].name}}</code>.
|
||||
<p><b>Note: </b>By default, <i>mustache</i> will escape any HTML entities in the values it substitutes.
|
||||
To prevent this, use <code>{{{triple}}}</code> braces.
|
||||
</script>
|
||||
|
@@ -19,20 +19,39 @@ module.exports = function(RED) {
|
||||
var mustache = require("mustache");
|
||||
var yaml = require("js-yaml");
|
||||
|
||||
function parseContext(key) {
|
||||
var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
|
||||
if (match) {
|
||||
var parts = {};
|
||||
parts.type = match[1];
|
||||
parts.store = (match[3] === '') ? "default" : match[3];
|
||||
parts.field = match[4];
|
||||
return parts;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Mustache Context capable to resolve message property and node
|
||||
* Custom Mustache Context capable to collect message property and node
|
||||
* flow and global context
|
||||
*/
|
||||
function NodeContext(msg, nodeContext, parent, escapeStrings) {
|
||||
|
||||
function NodeContext(msg, nodeContext, parent, escapeStrings, promises, results) {
|
||||
this.msgContext = new mustache.Context(msg,parent);
|
||||
this.nodeContext = nodeContext;
|
||||
this.escapeStrings = escapeStrings;
|
||||
this.promises = promises;
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
NodeContext.prototype = new mustache.Context();
|
||||
|
||||
NodeContext.prototype.lookup = function (name) {
|
||||
var results = this.results;
|
||||
if (results) {
|
||||
var val = results.shift();
|
||||
return val;
|
||||
}
|
||||
// try message first:
|
||||
try {
|
||||
var value = this.msgContext.lookup(name);
|
||||
@@ -45,23 +64,40 @@ module.exports = function(RED) {
|
||||
value = value.replace(/\f/g, "\\f");
|
||||
value = value.replace(/[\b]/g, "\\b");
|
||||
}
|
||||
this.promises.push(Promise.resolve(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
// try node context:
|
||||
var dot = name.indexOf(".");
|
||||
/* istanbul ignore else */
|
||||
if (dot > 0) {
|
||||
var contextName = name.substr(0, dot);
|
||||
var variableName = name.substr(dot + 1);
|
||||
|
||||
if (contextName === "flow" && this.nodeContext.flow) {
|
||||
return this.nodeContext.flow.get(variableName);
|
||||
// try flow/global context:
|
||||
var context = parseContext(name);
|
||||
if (context) {
|
||||
var type = context.type;
|
||||
var store = context.store;
|
||||
var field = context.field;
|
||||
var target = this.nodeContext[type];
|
||||
if (target) {
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
var callback = (err, val) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(val);
|
||||
}
|
||||
};
|
||||
target.get(field, store, callback);
|
||||
});
|
||||
this.promises.push(promise);
|
||||
return '';
|
||||
}
|
||||
else if (contextName === "global" && this.nodeContext.global) {
|
||||
return this.nodeContext.global.get(variableName);
|
||||
else {
|
||||
this.promises.push(Promise.resolve(''));
|
||||
return '';
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.promises.push(Promise.resolve(''));
|
||||
return '';
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
throw err;
|
||||
@@ -69,7 +105,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
NodeContext.prototype.push = function push (view) {
|
||||
return new NodeContext(view, this.nodeContext,this.msgContext);
|
||||
return new NodeContext(view, this.nodeContext, this.msgContext, undefined, this.promises, this.results);
|
||||
};
|
||||
|
||||
function TemplateNode(n) {
|
||||
@@ -83,8 +119,33 @@ module.exports = function(RED) {
|
||||
|
||||
var node = this;
|
||||
node.on("input", function(msg) {
|
||||
function output(value) {
|
||||
/* istanbul ignore else */
|
||||
if (node.outputFormat === "json") {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (node.outputFormat === "yaml") {
|
||||
value = yaml.load(value);
|
||||
}
|
||||
|
||||
if (node.fieldType === 'msg') {
|
||||
RED.util.setMessageProperty(msg, node.field, value);
|
||||
node.send(msg);
|
||||
} else if ((node.fieldType === 'flow') ||
|
||||
(node.fieldType === 'global')) {
|
||||
var context = RED.util.parseContextStore(node.field);
|
||||
var target = node.context()[node.fieldType];
|
||||
target.set(context.key, value, context.store, function (err) {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
} else {
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
var value;
|
||||
/***
|
||||
* Allow template contents to be defined externally
|
||||
* through inbound msg.template IFF node.template empty
|
||||
@@ -97,31 +158,18 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
if (node.syntax === "mustache") {
|
||||
if (node.outputFormat === "json") {
|
||||
value = mustache.render(template,new NodeContext(msg, node.context(), null, true));
|
||||
} else {
|
||||
value = mustache.render(template,new NodeContext(msg, node.context(), null, false));
|
||||
}
|
||||
var is_json = (node.outputFormat === "json");
|
||||
var promises = [];
|
||||
mustache.render(template, new NodeContext(msg, node.context(), null, is_json, promises, null));
|
||||
Promise.all(promises).then(function (values) {
|
||||
var value = mustache.render(template, new NodeContext(msg, node.context(), null, is_json, null, values));
|
||||
output(value);
|
||||
}).catch(function (err) {
|
||||
node.error(err.message);
|
||||
});
|
||||
} else {
|
||||
value = template;
|
||||
output(template);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (node.outputFormat === "json") {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (node.outputFormat === "yaml") {
|
||||
value = yaml.load(value);
|
||||
}
|
||||
|
||||
if (node.fieldType === 'msg') {
|
||||
RED.util.setMessageProperty(msg,node.field,value);
|
||||
} else if (node.fieldType === 'flow') {
|
||||
node.context().flow.set(node.field,value);
|
||||
} else if (node.fieldType === 'global') {
|
||||
node.context().global.set(node.field,value);
|
||||
}
|
||||
node.send(msg);
|
||||
}
|
||||
catch(err) {
|
||||
node.error(err.message);
|
||||
|
@@ -162,7 +162,7 @@
|
||||
$("#node-input-op1").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-op1type"),
|
||||
types:['flow','global','str','num','bool','json',
|
||||
types:['flow','global','str','num','bool','json','bin','date','env',
|
||||
optionPayload,
|
||||
optionNothing
|
||||
]
|
||||
@@ -170,7 +170,7 @@
|
||||
$("#node-input-op2").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-op2type"),
|
||||
types:['flow','global','str','num','bool','json',
|
||||
types:['flow','global','str','num','bool','json','bin','date','env',
|
||||
optionOriginalPayload,
|
||||
optionLatestPayload,
|
||||
optionNothing
|
||||
|
@@ -76,8 +76,43 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
node.topics = {};
|
||||
|
||||
this.on("input", function(msg) {
|
||||
var pendingMessages = [];
|
||||
var activeMessagePromise = null;
|
||||
var processMessageQueue = function(msg) {
|
||||
if (msg) {
|
||||
// A new message has arrived - add it to the message queue
|
||||
pendingMessages.push(msg);
|
||||
if (activeMessagePromise !== null) {
|
||||
// The node is currently processing a message, so do nothing
|
||||
// more with this message
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pendingMessages.length === 0) {
|
||||
// There are no more messages to process, clear the active flag
|
||||
// and return
|
||||
activeMessagePromise = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There are more messages to process. Get the next message and
|
||||
// start processing it. Recurse back in to check for any more
|
||||
var nextMsg = pendingMessages.shift();
|
||||
activeMessagePromise = processMessage(nextMsg)
|
||||
.then(processMessageQueue)
|
||||
.catch((err) => {
|
||||
node.error(err,nextMsg);
|
||||
return processMessageQueue();
|
||||
});
|
||||
}
|
||||
|
||||
this.on('input', function(msg) {
|
||||
processMessageQueue(msg);
|
||||
});
|
||||
|
||||
var processMessage = function(msg) {
|
||||
var topic = msg.topic || "_none";
|
||||
var promise;
|
||||
if (node.bytopic === "all") { topic = "_none"; }
|
||||
node.topics[topic] = node.topics[topic] || {};
|
||||
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
|
||||
@@ -88,48 +123,88 @@ module.exports = function(RED) {
|
||||
}
|
||||
else {
|
||||
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
|
||||
promise = Promise.resolve();
|
||||
if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
|
||||
else if (node.op2type !== "nul") {
|
||||
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
|
||||
}
|
||||
|
||||
if (node.op1type === "pay") { }
|
||||
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
|
||||
else if (node.op1type !== "nul") {
|
||||
msg.payload = RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg);
|
||||
}
|
||||
|
||||
if (node.duration === 0) { node.topics[topic].tout = 0; }
|
||||
else if (node.loop === true) {
|
||||
/* istanbul ignore else */
|
||||
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
|
||||
/* istanbul ignore else */
|
||||
if (node.op1type !== "nul") {
|
||||
var msg2 = RED.util.cloneMessage(msg);
|
||||
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!node.topics[topic].tout) {
|
||||
node.topics[topic].tout = setTimeout(function() {
|
||||
var msg2 = null;
|
||||
if (node.op2type !== "nul") {
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
if (node.op2type === "flow" || node.op2type === "global") {
|
||||
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
|
||||
}
|
||||
msg2.payload = node.topics[topic].m2;
|
||||
delete node.topics[topic];
|
||||
node.send(msg2);
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
node.topics[topic].m2 = value;
|
||||
resolve();
|
||||
}
|
||||
else { delete node.topics[topic]; }
|
||||
node.status({});
|
||||
}, node.duration);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
node.status({fill:"blue",shape:"dot",text:" "});
|
||||
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
|
||||
|
||||
return promise.then(() => {
|
||||
promise = Promise.resolve();
|
||||
if (node.op1type === "pay") { }
|
||||
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
|
||||
else if (node.op1type !== "nul") {
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
msg.payload = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return promise.then(() => {
|
||||
if (node.duration === 0) { node.topics[topic].tout = 0; }
|
||||
else if (node.loop === true) {
|
||||
/* istanbul ignore else */
|
||||
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
|
||||
/* istanbul ignore else */
|
||||
if (node.op1type !== "nul") {
|
||||
var msg2 = RED.util.cloneMessage(msg);
|
||||
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!node.topics[topic].tout) {
|
||||
node.topics[topic].tout = setTimeout(function() {
|
||||
var msg2 = null;
|
||||
if (node.op2type !== "nul") {
|
||||
var promise = Promise.resolve();
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
if (node.op2type === "flow" || node.op2type === "global") {
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
node.topics[topic].m2 = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
promise.then(() => {
|
||||
msg2.payload = node.topics[topic].m2;
|
||||
delete node.topics[topic];
|
||||
node.send(msg2);
|
||||
node.status({});
|
||||
}).catch(err => {
|
||||
node.error(err);
|
||||
});
|
||||
} else {
|
||||
delete node.topics[topic];
|
||||
node.status({});
|
||||
}
|
||||
|
||||
}, node.duration);
|
||||
}
|
||||
}
|
||||
node.status({fill:"blue",shape:"dot",text:" "});
|
||||
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
|
||||
});
|
||||
});
|
||||
}
|
||||
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
|
||||
/* istanbul ignore else */
|
||||
@@ -138,25 +213,43 @@ module.exports = function(RED) {
|
||||
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
|
||||
node.topics[topic].tout = setTimeout(function() {
|
||||
var msg2 = null;
|
||||
var promise = Promise.resolve();
|
||||
|
||||
if (node.op2type !== "nul") {
|
||||
if (node.op2type === "flow" || node.op2type === "global") {
|
||||
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
|
||||
}
|
||||
if (node.topics[topic] !== undefined) {
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
msg2.payload = node.topics[topic].m2;
|
||||
promise = new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
node.topics[topic].m2 = value;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
delete node.topics[topic];
|
||||
node.status({});
|
||||
node.send(msg2);
|
||||
promise.then(() => {
|
||||
if (node.op2type !== "nul") {
|
||||
if (node.topics[topic] !== undefined) {
|
||||
msg2 = RED.util.cloneMessage(msg);
|
||||
msg2.payload = node.topics[topic].m2;
|
||||
}
|
||||
}
|
||||
delete node.topics[topic];
|
||||
node.status({});
|
||||
node.send(msg2);
|
||||
}).catch(err => {
|
||||
node.error(err);
|
||||
});
|
||||
}, node.duration);
|
||||
}
|
||||
else {
|
||||
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||
}
|
||||
}
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.on("close", function() {
|
||||
for (var t in node.topics) {
|
||||
/* istanbul ignore else */
|
||||
|
@@ -455,24 +455,8 @@ RED.debug = (function() {
|
||||
$('<span class="debug-message-name">'+name+'</span>').appendTo(metaRow);
|
||||
}
|
||||
|
||||
if ((format === 'number') && (payload === "NaN")) {
|
||||
payload = Number.NaN;
|
||||
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
|
||||
payload = JSON.parse(payload);
|
||||
} else if (/error/i.test(format)) {
|
||||
payload = JSON.parse(payload);
|
||||
payload = (payload.name?payload.name+": ":"")+payload.message;
|
||||
} else if (format === 'null') {
|
||||
payload = null;
|
||||
} else if (format === 'undefined') {
|
||||
payload = undefined;
|
||||
} else if (/^buffer/.test(format)) {
|
||||
var buffer = payload;
|
||||
payload = [];
|
||||
for (var c = 0; c < buffer.length; c += 2) {
|
||||
payload.push(parseInt(buffer.substr(c, 2), 16));
|
||||
}
|
||||
}
|
||||
payload = RED.utils.decodeObject(payload,format);
|
||||
|
||||
var el = $('<span class="debug-message-payload"></span>').appendTo(msg);
|
||||
var path = o.property||'';
|
||||
var debugMessage = RED.utils.createObjectElement(payload, {
|
||||
|
@@ -6,35 +6,36 @@ module.exports = function(RED) {
|
||||
var fs = require('fs');
|
||||
|
||||
var gpioCommand = __dirname+'/nrgpio';
|
||||
var allOK = true;
|
||||
|
||||
try {
|
||||
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
|
||||
if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
|
||||
} catch(err) {
|
||||
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
|
||||
}
|
||||
|
||||
try {
|
||||
fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
|
||||
// /usr/lib/python2.7/dist-packages/RPi/GPIO
|
||||
} catch(err) {
|
||||
if (cpuinfo.indexOf(": BCM") === -1) {
|
||||
allOK = false;
|
||||
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
|
||||
}
|
||||
try {
|
||||
fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
|
||||
}
|
||||
catch(err) {
|
||||
fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
|
||||
// /usr/lib/python2.7/dist-packages/RPi/GPIO
|
||||
} catch(err) {
|
||||
try {
|
||||
fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
|
||||
}
|
||||
catch(err) {
|
||||
RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
|
||||
throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
|
||||
fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
|
||||
} catch(err) {
|
||||
try {
|
||||
fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
|
||||
} catch(err) {
|
||||
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound"));
|
||||
allOK = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
|
||||
RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
|
||||
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
|
||||
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
|
||||
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
|
||||
allOK = false;
|
||||
}
|
||||
} catch(err) {
|
||||
allOK = false;
|
||||
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
|
||||
}
|
||||
|
||||
// the magic to make python print stuff immediately
|
||||
@@ -61,48 +62,62 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.pin !== undefined) {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
|
||||
node.running = true;
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
if (allOK === true) {
|
||||
if (node.pin !== undefined) {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
|
||||
node.running = true;
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var d = data.toString().trim().split("\n");
|
||||
for (var i = 0; i < d.length; i++) {
|
||||
if (d[i] === '') { return; }
|
||||
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
|
||||
node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var d = data.toString().trim().split("\n");
|
||||
for (var i = 0; i < d.length; i++) {
|
||||
if (d[i] === '') { return; }
|
||||
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
|
||||
node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
|
||||
}
|
||||
node.buttonState = d[i];
|
||||
node.status({fill:"green",shape:"dot",text:d[i]});
|
||||
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
|
||||
}
|
||||
node.buttonState = d[i];
|
||||
node.status({fill:"green",shape:"dot",text:d[i]});
|
||||
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
|
||||
});
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
|
||||
if (node.read === true) {
|
||||
var val;
|
||||
if (node.intype == "up") { val = 1; }
|
||||
if (node.intype == "down") { val = 0; }
|
||||
setTimeout(function(){
|
||||
node.send({ topic:"pi/"+node.pin, payload:val });
|
||||
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})});
|
||||
},250);
|
||||
}
|
||||
}
|
||||
|
||||
node.on("close", function(done) {
|
||||
@@ -155,20 +170,83 @@ module.exports = function(RED) {
|
||||
else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
|
||||
}
|
||||
|
||||
if (node.pin !== undefined) {
|
||||
if (node.set && (node.out === "out")) {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
|
||||
node.status({fill:"green",shape:"dot",text:node.level});
|
||||
} else {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
}
|
||||
node.running = true;
|
||||
if (allOK === true) {
|
||||
if (node.pin !== undefined) {
|
||||
if (node.set && (node.out === "out")) {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
|
||||
node.status({fill:"green",shape:"dot",text:node.level});
|
||||
} else {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
}
|
||||
node.running = true;
|
||||
|
||||
node.on("input", inputlistener);
|
||||
node.on("input", inputlistener);
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
|
||||
node.on("input", function(msg){
|
||||
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})});
|
||||
});
|
||||
}
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.stdin.write("close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
|
||||
}
|
||||
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
|
||||
|
||||
function PiMouseNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.butt = n.butt || 7;
|
||||
var node = this;
|
||||
|
||||
if (allOK === true) {
|
||||
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
|
||||
data = Number(data);
|
||||
if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
|
||||
else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
@@ -192,69 +270,19 @@ module.exports = function(RED) {
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.kill('SIGINT');
|
||||
node.child = null;
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
|
||||
}
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.stdin.write("close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
|
||||
}
|
||||
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
|
||||
|
||||
function PiMouseNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.butt = n.butt || 7;
|
||||
var node = this;
|
||||
|
||||
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
data = Number(data);
|
||||
if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
|
||||
else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.kill('SIGINT');
|
||||
node.child = null;
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-mouse",PiMouseNode);
|
||||
|
||||
@@ -262,39 +290,40 @@ module.exports = function(RED) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
|
||||
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
if (allOK === true) {
|
||||
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var b = data.toString().trim().split(",");
|
||||
var act = "up";
|
||||
if (b[1] === "1") { act = "down"; }
|
||||
if (b[1] === "2") { act = "repeat"; }
|
||||
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
|
||||
});
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var b = data.toString().trim().split(",");
|
||||
var act = "up";
|
||||
if (b[1] === "1") { act = "down"; }
|
||||
if (b[1] === "2") { act = "repeat"; }
|
||||
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.on("close", function(done) {
|
||||
node.status({});
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
@@ -303,24 +332,30 @@ module.exports = function(RED) {
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
|
||||
|
||||
var pitype = { type:"" };
|
||||
exec(gpioCommand+" info", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.version"));
|
||||
}
|
||||
else {
|
||||
try {
|
||||
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
|
||||
pitype.type = info["TYPE"];
|
||||
if (allOK === true) {
|
||||
exec(gpioCommand+" info", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.version"));
|
||||
}
|
||||
catch(e) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
|
||||
else {
|
||||
try {
|
||||
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
|
||||
pitype.type = info["TYPE"];
|
||||
}
|
||||
catch(e) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
|
||||
res.json(pitype);
|
||||
|
@@ -23,10 +23,6 @@ from time import sleep
|
||||
|
||||
bounce = 25;
|
||||
|
||||
if sys.version_info >= (3,0):
|
||||
print("Sorry - currently only configured to work with python 2.x")
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
cmd = sys.argv[1].lower()
|
||||
pin = int(sys.argv[2])
|
||||
@@ -34,7 +30,7 @@ if len(sys.argv) > 2:
|
||||
GPIO.setwarnings(False)
|
||||
|
||||
if cmd == "pwm":
|
||||
#print "Initialised pin "+str(pin)+" to PWM"
|
||||
#print("Initialised pin "+str(pin)+" to PWM")
|
||||
try:
|
||||
freq = int(sys.argv[3])
|
||||
except:
|
||||
@@ -54,10 +50,10 @@ if len(sys.argv) > 2:
|
||||
GPIO.cleanup(pin)
|
||||
sys.exit(0)
|
||||
except Exception as ex:
|
||||
print "bad data: "+data
|
||||
print("bad data: "+data)
|
||||
|
||||
elif cmd == "buzz":
|
||||
#print "Initialised pin "+str(pin)+" to Buzz"
|
||||
#print("Initialised pin "+str(pin)+" to Buzz")
|
||||
GPIO.setup(pin,GPIO.OUT)
|
||||
p = GPIO.PWM(pin, 100)
|
||||
p.stop()
|
||||
@@ -76,10 +72,10 @@ if len(sys.argv) > 2:
|
||||
GPIO.cleanup(pin)
|
||||
sys.exit(0)
|
||||
except Exception as ex:
|
||||
print "bad data: "+data
|
||||
print("bad data: "+data)
|
||||
|
||||
elif cmd == "out":
|
||||
#print "Initialised pin "+str(pin)+" to OUT"
|
||||
#print("Initialised pin "+str(pin)+" to OUT")
|
||||
GPIO.setup(pin,GPIO.OUT)
|
||||
if len(sys.argv) == 4:
|
||||
GPIO.output(pin,int(sys.argv[3]))
|
||||
@@ -103,11 +99,11 @@ if len(sys.argv) > 2:
|
||||
GPIO.output(pin,data)
|
||||
|
||||
elif cmd == "in":
|
||||
#print "Initialised pin "+str(pin)+" to IN"
|
||||
#print("Initialised pin "+str(pin)+" to IN")
|
||||
bounce = float(sys.argv[4])
|
||||
def handle_callback(chan):
|
||||
sleep(bounce/1000.0)
|
||||
print GPIO.input(chan)
|
||||
print(GPIO.input(chan))
|
||||
|
||||
if sys.argv[3].lower() == "up":
|
||||
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
|
||||
@@ -116,7 +112,7 @@ if len(sys.argv) > 2:
|
||||
else:
|
||||
GPIO.setup(pin,GPIO.IN)
|
||||
|
||||
print GPIO.input(pin)
|
||||
print(GPIO.input(pin))
|
||||
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce))
|
||||
|
||||
while True:
|
||||
@@ -129,7 +125,7 @@ if len(sys.argv) > 2:
|
||||
sys.exit(0)
|
||||
|
||||
elif cmd == "byte":
|
||||
#print "Initialised BYTE mode - "+str(pin)+
|
||||
#print("Initialised BYTE mode - "+str(pin)+)
|
||||
list = [7,11,13,12,15,16,18,22]
|
||||
GPIO.setup(list,GPIO.OUT)
|
||||
|
||||
@@ -152,7 +148,7 @@ if len(sys.argv) > 2:
|
||||
GPIO.output(list[bit], data & mask)
|
||||
|
||||
elif cmd == "borg":
|
||||
#print "Initialised BORG mode - "+str(pin)+
|
||||
#print("Initialised BORG mode - "+str(pin)+)
|
||||
GPIO.setup(11,GPIO.OUT)
|
||||
GPIO.setup(13,GPIO.OUT)
|
||||
GPIO.setup(15,GPIO.OUT)
|
||||
@@ -190,7 +186,7 @@ if len(sys.argv) > 2:
|
||||
button = ord( buf[0] ) & pin # mask out just the required button(s)
|
||||
if button != oldbutt: # only send if changed
|
||||
oldbutt = button
|
||||
print button
|
||||
print(button)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@@ -215,7 +211,7 @@ if len(sys.argv) > 2:
|
||||
# type,code,value
|
||||
print("%u,%u" % (code, value))
|
||||
event = file.read(EVENT_SIZE)
|
||||
print "0,0"
|
||||
print("0,0")
|
||||
file.close()
|
||||
sys.exit(0)
|
||||
except:
|
||||
@@ -225,14 +221,14 @@ if len(sys.argv) > 2:
|
||||
elif len(sys.argv) > 1:
|
||||
cmd = sys.argv[1].lower()
|
||||
if cmd == "rev":
|
||||
print GPIO.RPI_REVISION
|
||||
print(GPIO.RPI_REVISION)
|
||||
elif cmd == "ver":
|
||||
print GPIO.VERSION
|
||||
print(GPIO.VERSION)
|
||||
elif cmd == "info":
|
||||
print GPIO.RPI_INFO
|
||||
print(GPIO.RPI_INFO)
|
||||
else:
|
||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
|
||||
print " only ver (gpio version) and info (board information) accept no pin parameter."
|
||||
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")
|
||||
print(" only ver (gpio version) and info (board information) accept no pin parameter.")
|
||||
|
||||
else:
|
||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
|
||||
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")
|
||||
|
@@ -63,6 +63,11 @@
|
||||
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-config-input-verifyservercert" style="width: calc(100% - 170px);" data-i18n="tls.label.verify-server-cert"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label style="width: 120px;" for="node-config-input-servername"><i class="fa fa-server"></i> <span data-i18n="tls.label.servername"></span></label>
|
||||
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-servername" data-i18n="[placeholder]tls.placeholder.servername">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-row">
|
||||
<label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
|
||||
@@ -96,6 +101,7 @@
|
||||
certname: {value:""},
|
||||
keyname: {value:""},
|
||||
caname: {value:""},
|
||||
servername: {value:""},
|
||||
verifyservercert: {value: true}
|
||||
},
|
||||
credentials: {
|
||||
|
@@ -25,6 +25,7 @@ module.exports = function(RED) {
|
||||
var certPath = n.cert.trim();
|
||||
var keyPath = n.key.trim();
|
||||
var caPath = n.ca.trim();
|
||||
this.servername = (n.servername||"").trim();
|
||||
|
||||
if ((certPath.length > 0) || (keyPath.length > 0)) {
|
||||
|
||||
@@ -102,6 +103,9 @@ module.exports = function(RED) {
|
||||
if (this.credentials && this.credentials.passphrase) {
|
||||
opts.passphrase = this.credentials.passphrase;
|
||||
}
|
||||
if (this.servername) {
|
||||
opts.servername = this.servername;
|
||||
}
|
||||
opts.rejectUnauthorized = this.verifyservercert;
|
||||
}
|
||||
return opts;
|
||||
|
@@ -212,11 +212,11 @@ module.exports = function(RED) {
|
||||
if (this.serverConfig) {
|
||||
this.serverConfig.registerInputNode(this);
|
||||
// TODO: nls
|
||||
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
|
||||
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
|
||||
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
|
||||
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
|
||||
this.serverConfig.on('closed', function(n) {
|
||||
if (n > 0) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }
|
||||
else { node.status({fill:"red",shape:"ring",text:"disconnected"}); }
|
||||
if (n > 0) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); }
|
||||
else { node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); }
|
||||
});
|
||||
} else {
|
||||
this.error(RED._("websocket.errors.missing-conf"));
|
||||
@@ -240,11 +240,11 @@ module.exports = function(RED) {
|
||||
}
|
||||
else {
|
||||
// TODO: nls
|
||||
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
|
||||
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
|
||||
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
|
||||
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
|
||||
this.serverConfig.on('closed', function(n) {
|
||||
if (n > 0) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }
|
||||
else { node.status({fill:"red",shape:"ring",text:"disconnected"}); }
|
||||
if (n > 0) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); }
|
||||
else { node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); }
|
||||
});
|
||||
}
|
||||
this.on("input", function(msg) {
|
||||
|
@@ -18,10 +18,36 @@ module.exports = function(RED) {
|
||||
"use strict";
|
||||
var reconnectTime = RED.settings.socketReconnectTime||10000;
|
||||
var socketTimeout = RED.settings.socketTimeout||null;
|
||||
const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000;
|
||||
const Denque = require('denque');
|
||||
var net = require('net');
|
||||
|
||||
var connectionPool = {};
|
||||
|
||||
/**
|
||||
* Enqueue `item` in `queue`
|
||||
* @param {Denque} queue - Queue
|
||||
* @param {*} item - Item to enqueue
|
||||
* @private
|
||||
* @returns {Denque} `queue`
|
||||
*/
|
||||
const enqueue = (queue, item) => {
|
||||
// drop msgs from front of queue if size is going to be exceeded
|
||||
if (queue.size() === msgQueueSize) {
|
||||
queue.shift();
|
||||
}
|
||||
queue.push(item);
|
||||
return queue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shifts item off front of queue
|
||||
* @param {Deque} queue - Queue
|
||||
* @private
|
||||
* @returns {*} Item previously at front of queue
|
||||
*/
|
||||
const dequeue = queue => queue.shift();
|
||||
|
||||
function TcpIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.host = n.host;
|
||||
@@ -435,11 +461,14 @@ module.exports = function(RED) {
|
||||
// the clients object will have:
|
||||
// clients[id].client, clients[id].msg, clients[id].timeout
|
||||
var connection_id = host + ":" + port;
|
||||
clients[connection_id] = clients[connection_id] || {};
|
||||
clients[connection_id].msg = msg;
|
||||
clients[connection_id].connected = clients[connection_id].connected || false;
|
||||
clients[connection_id] = clients[connection_id] || {
|
||||
msgQueue: new Denque(),
|
||||
connected: false,
|
||||
connecting: false
|
||||
};
|
||||
enqueue(clients[connection_id].msgQueue, msg);
|
||||
|
||||
if (!clients[connection_id].connected) {
|
||||
if (!clients[connection_id].connecting && !clients[connection_id].connected) {
|
||||
var buf;
|
||||
if (this.out == "count") {
|
||||
if (this.splitc === 0) { buf = Buffer.alloc(1); }
|
||||
@@ -451,14 +480,19 @@ module.exports = function(RED) {
|
||||
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
|
||||
|
||||
if (host && port) {
|
||||
clients[connection_id].connecting = true;
|
||||
clients[connection_id].client.connect(port, host, function() {
|
||||
//node.log(RED._("tcpin.errors.client-connected"));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
clients[connection_id].connected = true;
|
||||
clients[connection_id].client.write(clients[connection_id].msg.payload);
|
||||
clients[connection_id].connecting = false;
|
||||
let msg;
|
||||
while (msg = dequeue(clients[connection_id].msgQueue)) {
|
||||
clients[connection_id].client.write(msg.payload);
|
||||
}
|
||||
if (node.out === "time" && node.splitc < 0) {
|
||||
clients[connection_id].connected = false;
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
clients[connection_id].client.end();
|
||||
delete clients[connection_id];
|
||||
node.status({});
|
||||
@@ -473,9 +507,10 @@ module.exports = function(RED) {
|
||||
clients[connection_id].client.on('data', function(data) {
|
||||
if (node.out === "sit") { // if we are staying connected just send the buffer
|
||||
if (clients[connection_id]) {
|
||||
if (!clients[connection_id].hasOwnProperty("msg")) { clients[connection_id].msg = {}; }
|
||||
clients[connection_id].msg.payload = data;
|
||||
node.send(RED.util.cloneMessage(clients[connection_id].msg));
|
||||
let msg = dequeue(clients[connection_id].msgQueue) || {};
|
||||
clients[connection_id].msgQueue.unshift(msg);
|
||||
msg.payload = data;
|
||||
node.send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
}
|
||||
// else if (node.splitc === 0) {
|
||||
@@ -495,9 +530,11 @@ module.exports = function(RED) {
|
||||
clients[connection_id].timeout = setTimeout(function () {
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].timeout = null;
|
||||
clients[connection_id].msg.payload = Buffer.alloc(i+1);
|
||||
buf.copy(clients[connection_id].msg.payload,0,0,i+1);
|
||||
node.send(clients[connection_id].msg);
|
||||
let msg = dequeue(clients[connection_id].msgQueue) || {};
|
||||
clients[connection_id].msgQueue.unshift(msg);
|
||||
msg.payload = Buffer.alloc(i+1);
|
||||
buf.copy(msg.payload,0,0,i+1);
|
||||
node.send(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
@@ -516,9 +553,11 @@ module.exports = function(RED) {
|
||||
i += 1;
|
||||
if ( i >= node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].msg.payload = Buffer.alloc(i);
|
||||
buf.copy(clients[connection_id].msg.payload,0,0,i);
|
||||
node.send(clients[connection_id].msg);
|
||||
let msg = dequeue(clients[connection_id].msgQueue) || {};
|
||||
clients[connection_id].msgQueue.unshift(msg);
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
node.send(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
@@ -534,9 +573,11 @@ module.exports = function(RED) {
|
||||
i += 1;
|
||||
if (data[j] == node.splitc) {
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].msg.payload = Buffer.alloc(i);
|
||||
buf.copy(clients[connection_id].msg.payload,0,0,i);
|
||||
node.send(clients[connection_id].msg);
|
||||
let msg = dequeue(clients[connection_id].msgQueue) || {};
|
||||
clients[connection_id].msgQueue.unshift(msg);
|
||||
msg.payload = Buffer.alloc(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
node.send(msg);
|
||||
if (clients[connection_id].client) {
|
||||
node.status({});
|
||||
clients[connection_id].client.destroy();
|
||||
@@ -554,7 +595,7 @@ module.exports = function(RED) {
|
||||
//console.log("END");
|
||||
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
clients[connection_id].connected = false;
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
clients[connection_id].client = null;
|
||||
}
|
||||
});
|
||||
@@ -562,7 +603,7 @@ module.exports = function(RED) {
|
||||
clients[connection_id].client.on('close', function() {
|
||||
//console.log("CLOSE");
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].connected = false;
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
}
|
||||
|
||||
var anyConnected = false;
|
||||
@@ -592,21 +633,23 @@ module.exports = function(RED) {
|
||||
clients[connection_id].client.on('timeout',function() {
|
||||
//console.log("TIMEOUT");
|
||||
if (clients[connection_id]) {
|
||||
clients[connection_id].connected = false;
|
||||
clients[connection_id].connected = clients[connection_id].connecting = false;
|
||||
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
|
||||
//node.warn(RED._("tcpin.errors.connect-timeout"));
|
||||
if (clients[connection_id].client) {
|
||||
clients[connection_id].connecting = true;
|
||||
clients[connection_id].client.connect(port, host, function() {
|
||||
clients[connection_id].connected = true;
|
||||
clients[connection_id].connecting = false;
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
else if (!clients[connection_id].connecting && clients[connection_id].connected) {
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
clients[connection_id].client.write(clients[connection_id].msg.payload);
|
||||
clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -63,7 +63,7 @@ module.exports = function(RED) {
|
||||
udpInputPortsInUse[this.port] = server;
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("udp.errors.alreadyused",{port:node.port}));
|
||||
node.log(RED._("udp.errors.alreadyused",{port:node.port}));
|
||||
server = udpInputPortsInUse[this.port]; // re-use existing
|
||||
}
|
||||
|
||||
@@ -172,8 +172,7 @@ module.exports = function(RED) {
|
||||
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
|
||||
|
||||
var sock;
|
||||
var p = this.port;
|
||||
if (node.multicast != "false") { p = this.outport||"0"; }
|
||||
var p = this.outport || this.port || "0";
|
||||
if (udpInputPortsInUse[p]) {
|
||||
sock = udpInputPortsInUse[p];
|
||||
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
|
||||
|
@@ -153,13 +153,15 @@
|
||||
"key": "Private Key",
|
||||
"passphrase": "Passphrase",
|
||||
"ca": "CA Certificate",
|
||||
"verify-server-cert":"Verify server certificate"
|
||||
"verify-server-cert":"Verify server certificate",
|
||||
"servername": "Server Name"
|
||||
},
|
||||
"placeholder": {
|
||||
"cert":"path to certificate (PEM format)",
|
||||
"key":"path to private key (PEM format)",
|
||||
"ca":"path to CA certificate (PEM format)",
|
||||
"passphrase":"private key passphrase (optional)"
|
||||
"passphrase":"private key passphrase (optional)",
|
||||
"servername":"for use with SNI"
|
||||
},
|
||||
"error": {
|
||||
"missing-file": "No certificate/key file provided"
|
||||
@@ -420,6 +422,10 @@
|
||||
"url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.",
|
||||
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
|
||||
},
|
||||
"status": {
|
||||
"connected": "connected __count__",
|
||||
"connected_plural": "connected __count__"
|
||||
},
|
||||
"errors": {
|
||||
"connect-error": "An error occured on the ws connection: ",
|
||||
"send-error": "An error occurred while sending: ",
|
||||
@@ -575,6 +581,8 @@
|
||||
"null":"is null",
|
||||
"nnull":"is not null",
|
||||
"istype":"is of type",
|
||||
"empty":"is empty",
|
||||
"nempty":"is not empty",
|
||||
"head":"head",
|
||||
"tail":"tail",
|
||||
"index":"index between",
|
||||
@@ -697,7 +705,9 @@
|
||||
"errors": {
|
||||
"dropped-object": "Ignored non-object payload",
|
||||
"dropped": "Ignored unsupported payload type",
|
||||
"dropped-error": "Failed to convert payload"
|
||||
"dropped-error": "Failed to convert payload",
|
||||
"schema-error": "JSON Schema error",
|
||||
"schema-error-compile": "JSON Schema error: failed to compile schema"
|
||||
},
|
||||
"label": {
|
||||
"o2j": "Object to JSON options",
|
||||
@@ -786,8 +796,8 @@
|
||||
"na": "N/A : __value__"
|
||||
},
|
||||
"errors": {
|
||||
"ignorenode": "Ignoring Raspberry Pi specific node",
|
||||
"version": "Version command failed",
|
||||
"ignorenode": "Raspberry Pi specific node set inactive",
|
||||
"version": "Failed to get version from Pi",
|
||||
"sawpitype": "Saw Pi Type",
|
||||
"libnotfound": "Cannot find Pi RPi.GPIO python library",
|
||||
"alreadyset": "GPIO pin __pin__ already set as type: __type__",
|
||||
@@ -924,8 +934,8 @@
|
||||
"ascending" : "ascending",
|
||||
"descending" : "descending",
|
||||
"as-number" : "as number",
|
||||
"invalid-exp" : "invalid JSONata expression in sort node",
|
||||
"too-many" : "too many pending messages in sort node",
|
||||
"invalid-exp" : "Invalid JSONata expression in sort node: __message__",
|
||||
"too-many" : "Too many pending messages in sort node",
|
||||
"clear" : "clear pending message in sort node"
|
||||
},
|
||||
"batch" : {
|
||||
|
@@ -26,7 +26,7 @@
|
||||
<dt class="optional">kill <span class="property-type">文字列</span></dt>
|
||||
<dd>execノードのプロセスに対して送るシグナルの種別を指定します</dd>
|
||||
<dt class="optional">pid <span class="property-type">数値|文字列</span></dt>
|
||||
<dd>シグナル送信対象のexecノードのプロセスID</dd>
|
||||
<dd>シグナル送信対象のexecノードのプロセスIDを指定します</dd>
|
||||
</dl>
|
||||
|
||||
<h3>出力</h3>
|
||||
@@ -60,13 +60,12 @@
|
||||
</ol>
|
||||
<h3>詳細</h3>
|
||||
<p>デフォルトでは、<code>exec</code>システムコールを用いてコマンドを呼び出してその完了を待ち、出力を返します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
|
||||
<p><code>spawn</code>を使ってコマンドを実行し、
|
||||
標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
|
||||
<p><code>spawn</code>を使ってコマンドを実行し、標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>という返却値を返します。</p>
|
||||
<p>エラー発生時には、3番目の端子の<code>msg.payload</code>に<code>message</code>、<code>signal</code>など付加情報を返します。</p>
|
||||
<p>実行対象のコマンドはノード設定で定義します。<code>msg.payload</code>や追加引数をコマンドに追加することもできます。</p>
|
||||
<p>コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- <code>"This is a single parameter"</code></p>
|
||||
<p>コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- <code>"これは一つのパラメータです"</code></p>
|
||||
<p>返却する<code>payload</code>は通常<i>文字列</i>ですが、UTF8文字以外が存在すると<i>バッファ</i>となります。</p>
|
||||
<p>ノードが実行中の場合、ステータスアイコンとPIDを表示します。この状態変化は<code>status</code>ノードで検知できます。</p>
|
||||
<p>ノードが実行中の場合、ステータスアイコンとPIDを表示します。この状態変化は<code>Status</code>ノードで検知できます。</p>
|
||||
<h4>プロセスの停止</h4>
|
||||
<p><code>msg.kill</code>を受信すると、実行中のプロセスを停止することができます。<code>msg.kill</code>には送出するシグナルの種別を指定します。例えば、<code>SIGINT</code>、<code>SIGQUIT</code>、<code>SIGHUP</code>などです。空の文字列を指定した場合には、<code>SIGTERM</code>を指定したものとみなします。</p>
|
||||
<p>ノードが1つ以上のプロセスを実行している場合、<code>msg.pid</code>に停止対象のPIDを指定しなければなりません。</p>
|
||||
|
@@ -18,7 +18,7 @@
|
||||
<p>受信メッセージに対して処理を行うJavaScriptコード(関数の本体)を定義します。</p>
|
||||
<p>入力メッセージは<code>msg</code>という名称のJavaScriptオブジェクトで受け渡されます。</p>
|
||||
<p><code>msg</code>オブジェクトは<code>msg.payload</code>プロパティにメッセージ本体を保持するのが慣例です。</p>
|
||||
<p>通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。何も返却しない場合には、フロー実行を停止します。</p>
|
||||
<p>通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。後続フローの実行を停止したい場合は、オブジェクトを返却しなくてもかまいません。</p>
|
||||
<h3>詳細</h3>
|
||||
<p>コードの書き方の詳細については、<a target="_blank" href="http://nodered.org/docs/writing-functions.html">オンラインドキュメント</a>を参照してください。</p>
|
||||
<h4>メッセージの送信</h4>
|
||||
@@ -40,4 +40,10 @@
|
||||
</p>
|
||||
<p>catchノードを用いてエラー処理が可能です。catchノードで処理させるためには、<code>msg</code>を<code>node.error</code>の第二引数として渡します:</p>
|
||||
<pre>node.error("エラー",msg);</pre>
|
||||
<h4>ノード情報の参照</h4>
|
||||
<p>コード中ではノードのIDおよび名前を以下のプロパティで参照できます:</p>
|
||||
<ul>
|
||||
<li><code>node.id</code> - ノードのID</li>
|
||||
<li><code>node.name</code> - ノードの名称</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
@@ -63,6 +63,8 @@
|
||||
<p>ノードにクライアントIDを設定しておらずセッションの初期化を設定している場合、ランダムなクライアントIDを生成します。クライアントIDを設定する場合、接続先のブローカで一意となるようにしてください。</p>
|
||||
<h4>Birthメッセージ</h4>
|
||||
<p>接続を確立した際に、設定したトピックに対して発行するメッセージ</p>
|
||||
<h4>Closeメッセージ</h4>
|
||||
<p>接続が正常に終了する前に、ノードの再デプロイまたはシャットダウンした場合に、設定したトピックに対して発行するメッセージ</p>
|
||||
<h4>Willメッセージ</h4>
|
||||
<p>予期せず接続が切断された場合にブローカが発行するメッセージ</p>
|
||||
<h4>WebSocket</h4>
|
||||
|
@@ -30,7 +30,9 @@
|
||||
<dt class="optional">payload</dt>
|
||||
<dd>リクエストボディとして送るデータ</dd>
|
||||
<dt class="optional">rejectUnauthorized</dt>
|
||||
<dd><code>true</code>をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。</dd>
|
||||
<dd><code>false</code>をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。</dd>
|
||||
<dt class="optional">followRedirects</dt>
|
||||
<dd><code>false</code>をセットすると、リダイレクトを行いません。デフォルトは<code>true</code>です。</dd>
|
||||
</dl>
|
||||
<h3>出力</h3>
|
||||
<dl class="message-properties">
|
||||
|
@@ -29,7 +29,11 @@
|
||||
<li><b>その他</b> - これより前のルールにマッチするものがなかった場合に適用</li>
|
||||
</ol>
|
||||
|
||||
<h3>注釈</h3>
|
||||
<p><code>is true/false</code>と<code>is null</code>のルールは、型に対して厳密な比較を行います。型変換した上での比較はしません。</p>
|
||||
<p><code>is empty</code>のルールは、長さ0の文字列・配列・バッファ、またはプロパティを持たないオブジェクトを出力します。<code>null</code>や<code>undefined</code>は出力しません。</p>
|
||||
|
||||
<h3>メッセージ列の扱い</h3>
|
||||
<p>switchノードは入力メッセージの列に関する情報を保持する<code>msg.parts</code>をデフォルトでは変更しません。</p>
|
||||
<p>「<b>メッセージ列の補正</b>」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchノードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。<code>nodeMessageBufferMaxLength</code>を設定すると、蓄積するメッセージ数を制限できます。</p>
|
||||
<p>「<b>メッセージ列の補正</b>」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchノードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。<b>settings.js</b>の<code>nodeMessageBufferMaxLength</code>を設定すると、蓄積するメッセージ数を制限できます。</p>
|
||||
</script>
|
||||
|
@@ -30,5 +30,5 @@
|
||||
</dd>
|
||||
</dl>
|
||||
<h4>メッセージの蓄積</h4>
|
||||
<p>このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。<code>nodeMessageBufferMaxLength</code>を指定することで蓄積するメッセージの最大値を制限することができます。</p>
|
||||
<p>このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。<b>settings.js</b>の<code>nodeMessageBufferMaxLength</code>を指定することで蓄積するメッセージの最大値を制限することができます。</p>
|
||||
</script>
|
||||
|
@@ -153,19 +153,23 @@
|
||||
"key": "秘密鍵",
|
||||
"passphrase": "パスフレーズ",
|
||||
"ca": "CA証明書",
|
||||
"verify-server-cert": "サーバ証明書を確認"
|
||||
"verify-server-cert": "サーバ証明書を確認",
|
||||
"servername": "サーバ名"
|
||||
},
|
||||
"placeholder": {
|
||||
"cert": "証明書(PEM形式)のパス",
|
||||
"key": "秘密鍵(PEM形式)のパス",
|
||||
"ca": "CA証明書(PEM形式)のパス",
|
||||
"passphrase":"秘密鍵のパスフレーズ (任意)"
|
||||
"passphrase": "秘密鍵のパスフレーズ (任意)",
|
||||
"servername": "SNIで使用"
|
||||
},
|
||||
"error": {
|
||||
"missing-file": "証明書と秘密鍵のファイルが設定されていません"
|
||||
}
|
||||
},
|
||||
"exec": {
|
||||
"exec": "exec",
|
||||
"spawn": "spawn",
|
||||
"label": {
|
||||
"command": "コマンド",
|
||||
"append": "引数",
|
||||
@@ -184,6 +188,7 @@
|
||||
"oldrc": "旧型式の出力を使用(互換モード)"
|
||||
},
|
||||
"function": {
|
||||
"function": "",
|
||||
"label": {
|
||||
"function": "コード",
|
||||
"outputs": "出力数"
|
||||
@@ -195,6 +200,7 @@
|
||||
"tip": "コードの記述方法はノードの「情報」を参照してください。"
|
||||
},
|
||||
"template": {
|
||||
"template": "template",
|
||||
"label": {
|
||||
"template": "テンプレート",
|
||||
"property": "設定先",
|
||||
@@ -301,6 +307,7 @@
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
"comment": "comment",
|
||||
"label": {
|
||||
"title": "タイトル",
|
||||
"body": "本文"
|
||||
@@ -318,6 +325,7 @@
|
||||
"broker": "サーバ",
|
||||
"example": "例) localhost",
|
||||
"qos": "QoS",
|
||||
"retain": "保持",
|
||||
"clientid": "クライアント",
|
||||
"port": "ポート",
|
||||
"keepalive": "キープアライブ時間",
|
||||
@@ -327,10 +335,10 @@
|
||||
"verify-server-cert": "サーバの証明書を確認",
|
||||
"compatmode": "旧MQTT 3.1のサポート"
|
||||
},
|
||||
"sections-label":{
|
||||
"sections-label": {
|
||||
"birth-message": "接続時の送信メッセージ(Birthメッセージ)",
|
||||
"will-message":"予期しない切断時の送信メッセージ(Willメッセージ)",
|
||||
"close-message":"切断前の送信メッセージ(Closeメッセージ)"
|
||||
"will-message": "予期しない切断時の送信メッセージ(Willメッセージ)",
|
||||
"close-message": "切断前の送信メッセージ(Closeメッセージ)"
|
||||
},
|
||||
"tabs-label": {
|
||||
"connection": "接続",
|
||||
@@ -414,6 +422,10 @@
|
||||
"url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。",
|
||||
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
|
||||
},
|
||||
"status": {
|
||||
"connected": "接続数 __count__",
|
||||
"connected_plural": "接続数 __count__"
|
||||
},
|
||||
"errors": {
|
||||
"connect-error": "ws接続でエラーが発生しました: ",
|
||||
"send-error": "送信中にエラーが発生しました: ",
|
||||
@@ -421,6 +433,7 @@
|
||||
}
|
||||
},
|
||||
"watch": {
|
||||
"watch": "watch",
|
||||
"label": {
|
||||
"files": "ファイル",
|
||||
"recursive": "サブディレクトリを再帰的に監視"
|
||||
@@ -543,15 +556,15 @@
|
||||
"port-notset": "udp: ポートが設定されていません",
|
||||
"port-invalid": "udp: ポート番号が不正です",
|
||||
"alreadyused": "udp: 既に__port__番ポートが使用されています",
|
||||
"ifnotfound": "udp: インターフェイス __iface__ がありません",
|
||||
"alreadyused": "udp: 既にポートが使用されています"
|
||||
"ifnotfound": "udp: インターフェイス __iface__ がありません"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": "switch",
|
||||
"label": {
|
||||
"property": "プロパティ",
|
||||
"rule": "条件",
|
||||
"repair" : "メッセージ列の補正"
|
||||
"repair": "メッセージ列の補正"
|
||||
},
|
||||
"and": "~",
|
||||
"checkall": "全ての条件を適用",
|
||||
@@ -565,15 +578,18 @@
|
||||
"false": "is false",
|
||||
"null": "is null",
|
||||
"nnull": "is not null",
|
||||
"head":"head",
|
||||
"tail":"tail",
|
||||
"index":"index between",
|
||||
"exp":"JSONata式",
|
||||
"istype": "is of type",
|
||||
"empty": "is empty",
|
||||
"nempty": "is not empty",
|
||||
"head": "head",
|
||||
"tail": "tail",
|
||||
"index": "index between",
|
||||
"exp": "JSONata式",
|
||||
"else": "その他"
|
||||
},
|
||||
"errors": {
|
||||
"invalid-expr": "不正な表現: __error__",
|
||||
"too-many" : "switchノード内で保持しているメッセージが多すぎます"
|
||||
"too-many": "switchノード内で保持しているメッセージが多すぎます"
|
||||
}
|
||||
},
|
||||
"change": {
|
||||
@@ -603,6 +619,7 @@
|
||||
}
|
||||
},
|
||||
"range": {
|
||||
"range": "range",
|
||||
"label": {
|
||||
"action": "動作",
|
||||
"inputrange": "入力値の範囲",
|
||||
@@ -670,7 +687,7 @@
|
||||
"label": {
|
||||
"select": "抽出する要素",
|
||||
"output": "出力",
|
||||
"in": "対象:"
|
||||
"in": "対象:"
|
||||
},
|
||||
"output": {
|
||||
"html": "要素内のHTML",
|
||||
@@ -686,7 +703,9 @@
|
||||
"errors": {
|
||||
"dropped-object": "オブジェクト形式でないペイロードを無視しました",
|
||||
"dropped": "対応していない形式のペイロードを無視しました",
|
||||
"dropped-error": "ペイロードの変換処理が失敗しました"
|
||||
"dropped-error": "ペイロードの変換処理が失敗しました",
|
||||
"schema-error": "JSONスキーマエラー",
|
||||
"schema-error-compile": "JSONスキーマエラー: スキーマのコンパイルが失敗しました"
|
||||
},
|
||||
"label": {
|
||||
"o2j": "オブジェクトからJSONへ変換",
|
||||
@@ -695,8 +714,8 @@
|
||||
"property": "プロパティ",
|
||||
"actions": {
|
||||
"toggle": "JSON文字列とオブジェクト間の相互変換",
|
||||
"str":"常にJSON文字列に変換",
|
||||
"obj":"常にJavaScriptオブジェクトに変換"
|
||||
"str": "常にJSON文字列に変換",
|
||||
"obj": "常にJavaScriptオブジェクトに変換"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -791,6 +810,7 @@
|
||||
}
|
||||
},
|
||||
"tail": {
|
||||
"tail": "tail",
|
||||
"label": {
|
||||
"filename": "ファイル名",
|
||||
"type": "ファイル形式",
|
||||
@@ -844,6 +864,7 @@
|
||||
"tip": "注釈: 「ファイル名」はフルパスを設定する必要があります。"
|
||||
},
|
||||
"split": {
|
||||
"split": "split",
|
||||
"intro": "型に基づいて <code>msg.payload</code> を分割:",
|
||||
"object": "<b>オブジェクト</b>",
|
||||
"objectSend": "各key/valueペアのメッセージを送信",
|
||||
@@ -855,11 +876,12 @@
|
||||
"addname": " keyのコピー先"
|
||||
},
|
||||
"join": {
|
||||
"join": "join",
|
||||
"mode": {
|
||||
"mode": "動作",
|
||||
"auto": "自動",
|
||||
"merge":"列のマージ",
|
||||
"reduce":"列の集約",
|
||||
"merge": "列のマージ",
|
||||
"reduce": "列の集約",
|
||||
"custom": "手動"
|
||||
},
|
||||
"combine": "結合",
|
||||
@@ -882,12 +904,12 @@
|
||||
"seconds": "秒",
|
||||
"complete": "<code>msg.complete</code> プロパティが設定されたメッセージ受信後",
|
||||
"tip": "このモードでは、本ノードが <i>split</i> ノードと組となるか、 <code>msg.parts</code> プロパティが設定されたメッセージを受け取ることが前提となります。",
|
||||
"too-many" : "joinノード内部で保持しているメッセージが多すぎます",
|
||||
"too-many": "joinノード内部で保持しているメッセージが多すぎます",
|
||||
"merge": {
|
||||
"topics-label":"対象トピック",
|
||||
"topics":"トピック",
|
||||
"topic" : "トピック",
|
||||
"on-change":"新規トピックを受け取るとメッセージを送信する"
|
||||
"topics-label": "対象トピック",
|
||||
"topics": "トピック",
|
||||
"topic": "トピック",
|
||||
"on-change": "新規トピックを受け取るとメッセージを送信する"
|
||||
},
|
||||
"reduce": {
|
||||
"exp": "集約式",
|
||||
@@ -900,43 +922,45 @@
|
||||
"invalid-expr": "JSONata式が不正: __error__"
|
||||
}
|
||||
},
|
||||
"sort" : {
|
||||
"target" : "対象",
|
||||
"seq" : "メッセージ列",
|
||||
"key" : "キー",
|
||||
"elem" : "要素の値",
|
||||
"order" : "順序",
|
||||
"ascending" : "昇順",
|
||||
"descending" : "降順",
|
||||
"as-number" : "数値として比較",
|
||||
"invalid-exp" : "sortノードで不正なJSONata式が指定されました",
|
||||
"too-many" : "sortノードの未処理メッセージの数が許容数を超えました",
|
||||
"clear" : "sortノードの未処理メッセージを破棄しました"
|
||||
"sort": {
|
||||
"sort": "sort",
|
||||
"target": "対象",
|
||||
"seq": "メッセージ列",
|
||||
"key": "キー",
|
||||
"elem": "要素の値",
|
||||
"order": "順序",
|
||||
"ascending": "昇順",
|
||||
"descending": "降順",
|
||||
"as-number": "数値として比較",
|
||||
"invalid-exp": "sortノードで不正なJSONata式が指定されました",
|
||||
"too-many": "sortノードの未処理メッセージの数が許容数を超えました",
|
||||
"clear": "sortノードの未処理メッセージを破棄しました"
|
||||
},
|
||||
"batch" : {
|
||||
"batch": {
|
||||
"batch": "batch",
|
||||
"mode": {
|
||||
"label" : "モード",
|
||||
"num-msgs" : "メッセージ数でグループ化",
|
||||
"interval" : "時間間隔でグループ化",
|
||||
"concat" : "列の結合"
|
||||
"label": "モード",
|
||||
"num-msgs": "メッセージ数でグループ化",
|
||||
"interval": "時間間隔でグループ化",
|
||||
"concat": "列の結合"
|
||||
},
|
||||
"count": {
|
||||
"label" : "メッセージ数",
|
||||
"overlap" : "オーバラップ",
|
||||
"count" : "数",
|
||||
"invalid" : "メッセージ数とオーバラップ数が不正"
|
||||
"label": "メッセージ数",
|
||||
"overlap": "オーバラップ",
|
||||
"count": "数",
|
||||
"invalid": "メッセージ数とオーバラップ数が不正"
|
||||
},
|
||||
"interval": {
|
||||
"label" : "時間間隔",
|
||||
"seconds" : "秒",
|
||||
"empty" : "メッセージを受信しない場合、空のメッセージを送信"
|
||||
"label": "時間間隔",
|
||||
"seconds": "秒",
|
||||
"empty": "メッセージを受信しない場合、空のメッセージを送信"
|
||||
},
|
||||
"concat": {
|
||||
"topics-label": "トピック",
|
||||
"topic" : "トピック"
|
||||
"topic": "トピック"
|
||||
},
|
||||
"too-many" : "batchノード内で保持しているメッセージが多すぎます",
|
||||
"unexpected" : "想定外のモード",
|
||||
"no-parts" : "メッセージにpartsプロパティがありません"
|
||||
"too-many": "batchノード内で保持しているメッセージが多すぎます",
|
||||
"unexpected": "想定外のモード",
|
||||
"no-parts": "メッセージにpartsプロパティがありません"
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,8 @@
|
||||
<dl class="message-properties">
|
||||
<dt>payload<span class="property-type">オブジェクト | 文字列</span></dt>
|
||||
<dd>JavaScriptオブジェクトもしくはJSON文字列</dd>
|
||||
<dt>schema<span class="property-type">オブジェクト</span></dt>
|
||||
<dd>JSONの検証に利用するJSONスキーマ。設定されていない場合は検証を行いません。</dd>
|
||||
</dl>
|
||||
<h3>出力</h3>
|
||||
<dl class="message-properties">
|
||||
@@ -30,9 +32,12 @@
|
||||
<li>入力がJavaScriptオブジェクトの場合、JSON文字列に変換します。JSON文字列は整形することも可能です。</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>schemaError<span class="property-type">配列</span></dt>
|
||||
<dd>JSONの検証でエラーが発生した場合、Catchノードを利用し、エラーを配列として<code>schemaError</code>プロパティから取得することができます。</dd>
|
||||
</dl>
|
||||
<h3>詳細</h3>
|
||||
<p>デフォルトの変換対象は<code>msg.payload</code>ですが、他のメッセージプロパティを変換対象とすることも可能です。</p>
|
||||
<p>双方向の変換を自動選択するのではなく、特定の変換のみ行うように設定できます。この機能は、例えば、<code>HTTP In</code>ノードに対するリクエストがcontent-typeを正しく設定していない場合であっても、JSONノードによる変換結果がJavaScriptオブジェクトであることを保証するために利用します。</p>
|
||||
<p>JSON文字列への変換が指定されている場合、受信した文字列に対してさらなるチェックは行いません。すなわち、文字列がJSONとして正しいかどうかの検査や、整形オプションを指定していたとしても整形処理を実施しません。</p>
|
||||
<p>JSONスキーマの詳細については、<a href="http://json-schema.org/latest/json-schema-validation.html">こちら</a>を参照してください。</p>
|
||||
</script>
|
||||
|
@@ -21,6 +21,8 @@
|
||||
<dt class="optional">filename <span class="property-type">文字列</span></dt>
|
||||
<dd>対象ファイル名をノードに設定していない場合、このプロパティでファイルを指定できます</dd>
|
||||
</dl>
|
||||
<h3>出力</h3>
|
||||
<p>書き込みの完了時、入力メッセージを出力端子に送出します。</p>
|
||||
<h3>詳細</h3>
|
||||
<p>入力メッセージのペイロードをファイルの最後に追記します。改行(\n)を各データの最後に追加することもできます。</p>
|
||||
<p><code>msg.filename</code>を使う場合、書き込みを行う毎にファイルをクローズします。より良い性能を得るためにはファイル名をノードに設定してください。</p>
|
||||
|
@@ -60,6 +60,12 @@
|
||||
<li>An <b>Otherwise</b> rule can be used to match if none of the preceeding
|
||||
rules have matched.</li>
|
||||
</ol>
|
||||
<h4>Notes</h4>
|
||||
<p>The <code>is true/false</code> and <code>is null</code> rules perform strict
|
||||
comparisons against those types. They do not convert between types.</p>
|
||||
<p>The <code>is empty</code> rule passes for Strings, Arrays and Buffers that have
|
||||
a length of 0, or Objects that have no properties. It does not pass for <code>null</code>
|
||||
or <code>undefined</code> values.</p>
|
||||
<h4>Handling message sequences</h4>
|
||||
<p>By default, the node does not modify the <code>msg.parts</code> property of messages
|
||||
that are part of a sequence.</p>
|
||||
@@ -86,6 +92,8 @@
|
||||
{v:"null",t:"switch.rules.null",kind:'V'},
|
||||
{v:"nnull",t:"switch.rules.nnull",kind:'V'},
|
||||
{v:"istype",t:"switch.rules.istype",kind:'V'},
|
||||
{v:"empty",t:"switch.rules.empty",kind:'V'},
|
||||
{v:"nempty",t:"switch.rules.nempty",kind:'V'},
|
||||
{v:"head",t:"switch.rules.head",kind:'S'},
|
||||
{v:"index",t:"switch.rules.index",kind:'S'},
|
||||
{v:"tail",t:"switch.rules.tail",kind:'S'},
|
||||
@@ -99,11 +107,17 @@
|
||||
}
|
||||
return v;
|
||||
}
|
||||
function prop2name(key) {
|
||||
var result = RED.utils.parseContextKey(key);
|
||||
return result.key;
|
||||
}
|
||||
function getValueLabel(t,v) {
|
||||
if (t === 'str') {
|
||||
return '"'+clipValueLength(v)+'"';
|
||||
} else if (t === 'msg' || t==='flow' || t==='global') {
|
||||
} else if (t === 'msg') {
|
||||
return t+"."+clipValueLength(v);
|
||||
} else if (t === 'flow' || t === 'global') {
|
||||
return t+"."+clipValueLength(prop2name(v));
|
||||
}
|
||||
return clipValueLength(v);
|
||||
}
|
||||
@@ -133,7 +147,7 @@
|
||||
}
|
||||
if ((rule.t === 'btwn') || (rule.t === 'index')) {
|
||||
label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
|
||||
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'else' ) {
|
||||
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'empty' && rule.t !== 'nempty' && rule.t !== 'else' ) {
|
||||
label += " "+getValueLabel(rule.vt,rule.v);
|
||||
}
|
||||
return label;
|
||||
@@ -185,7 +199,7 @@
|
||||
} else if (type === "istype") {
|
||||
typeField.typedInput("width",(newWidth-selectWidth-70));
|
||||
} else {
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else") {
|
||||
// valueField.hide();
|
||||
} else {
|
||||
valueField.typedInput("width",(newWidth-selectWidth-70));
|
||||
@@ -281,7 +295,7 @@
|
||||
numValueField.typedInput('hide');
|
||||
typeValueField.typedInput('hide');
|
||||
valueField.typedInput('hide');
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else") {
|
||||
valueField.typedInput('hide');
|
||||
typeValueField.typedInput('hide');
|
||||
}
|
||||
@@ -382,7 +396,7 @@
|
||||
var rule = $(this);
|
||||
var type = rule.find("select").val();
|
||||
var r = {t:type};
|
||||
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
|
||||
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else")) {
|
||||
if ((type === "btwn") || (type === "index")) {
|
||||
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
|
||||
|
@@ -31,6 +31,23 @@ module.exports = function(RED) {
|
||||
'false': function(a) { return a === false; },
|
||||
'null': function(a) { return (typeof a == "undefined" || a === null); },
|
||||
'nnull': function(a) { return (typeof a != "undefined" && a !== null); },
|
||||
'empty': function(a) {
|
||||
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
|
||||
return a.length === 0;
|
||||
} else if (typeof a === 'object' && a !== null) {
|
||||
return Object.keys(a).length === 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
'nempty': function(a) {
|
||||
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
|
||||
return a.length !== 0;
|
||||
} else if (typeof a === 'object' && a !== null) {
|
||||
return Object.keys(a).length !== 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
'istype': function(a, b) {
|
||||
if (b === "array") { return Array.isArray(a); }
|
||||
else if (b === "buffer") { return Buffer.isBuffer(a); }
|
||||
@@ -59,21 +76,157 @@ module.exports = function(RED) {
|
||||
'else': function(a) { return a === true; }
|
||||
};
|
||||
|
||||
var _max_kept_msgs_count = undefined;
|
||||
var _maxKeptCount;
|
||||
|
||||
function max_kept_msgs_count(node) {
|
||||
if (_max_kept_msgs_count === undefined) {
|
||||
function getMaxKeptCount() {
|
||||
if (_maxKeptCount === undefined) {
|
||||
var name = "nodeMessageBufferMaxLength";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_max_kept_msgs_count = RED.settings[name];
|
||||
_maxKeptCount = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_max_kept_msgs_count = 0;
|
||||
_maxKeptCount = 0;
|
||||
}
|
||||
}
|
||||
return _max_kept_msgs_count;
|
||||
return _maxKeptCount;
|
||||
}
|
||||
|
||||
function getProperty(node,msg) {
|
||||
return new Promise((resolve,reject) => {
|
||||
if (node.propertyType === 'jsonata') {
|
||||
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
|
||||
if (err) {
|
||||
resolve(undefined);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getV1(node,msg,rule,hasParts) {
|
||||
return new Promise( (resolve,reject) => {
|
||||
if (rule.vt === 'prev') {
|
||||
resolve(node.previousValue);
|
||||
} else if (rule.vt === 'jsonata') {
|
||||
var exp = rule.v;
|
||||
if (rule.t === 'jsonata_exp') {
|
||||
if (hasParts) {
|
||||
exp.assign("I", msg.parts.index);
|
||||
exp.assign("N", msg.parts.count);
|
||||
}
|
||||
}
|
||||
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
} else if (rule.vt === 'json') {
|
||||
resolve("json");
|
||||
} else if (rule.vt === 'null') {
|
||||
resolve("null");
|
||||
} else {
|
||||
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
|
||||
if (err) {
|
||||
resolve(undefined);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getV2(node,msg,rule) {
|
||||
return new Promise((resolve,reject) => {
|
||||
var v2 = rule.v2;
|
||||
if (rule.v2t === 'prev') {
|
||||
resolve(node.previousValue);
|
||||
} else if (rule.v2t === 'jsonata') {
|
||||
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
|
||||
if (err) {
|
||||
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
} else if (typeof v2 !== 'undefined') {
|
||||
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
|
||||
if (err) {
|
||||
resolve(undefined);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(v2);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function applyRule(node, msg, property, state) {
|
||||
return new Promise((resolve,reject) => {
|
||||
|
||||
var rule = node.rules[state.currentRule];
|
||||
var v1,v2;
|
||||
|
||||
getV1(node,msg,rule,state.hasParts).then(value => {
|
||||
v1 = value;
|
||||
}).then(()=>getV2(node,msg,rule)).then(value => {
|
||||
v2 = value;
|
||||
}).then(() => {
|
||||
if (rule.t == "else") {
|
||||
property = state.elseflag;
|
||||
state.elseflag = true;
|
||||
}
|
||||
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
|
||||
state.onward.push(msg);
|
||||
state.elseflag = false;
|
||||
if (node.checkall == "false") {
|
||||
return resolve(false);
|
||||
}
|
||||
} else {
|
||||
state.onward.push(null);
|
||||
}
|
||||
resolve(state.currentRule < node.rules.length - 1);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function applyRules(node, msg, property,state) {
|
||||
if (!state) {
|
||||
state = {
|
||||
currentRule: 0,
|
||||
elseflag: true,
|
||||
onward: [],
|
||||
hasParts: msg.hasOwnProperty("parts") &&
|
||||
msg.parts.hasOwnProperty("id") &&
|
||||
msg.parts.hasOwnProperty("index")
|
||||
}
|
||||
}
|
||||
return applyRule(node,msg,property,state).then(hasMore => {
|
||||
if (hasMore) {
|
||||
state.currentRule++;
|
||||
return applyRules(node,msg,property,state);
|
||||
} else {
|
||||
node.previousValue = property;
|
||||
return state.onward;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function SwitchNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.rules = n.rules || [];
|
||||
@@ -94,10 +247,10 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
var valid = true;
|
||||
var repair = n.repair;
|
||||
var needs_count = repair;
|
||||
var needsCount = repair;
|
||||
for (var i=0; i<this.rules.length; i+=1) {
|
||||
var rule = this.rules[i];
|
||||
needs_count = needs_count || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
|
||||
needsCount = needsCount || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
|
||||
if (!rule.vt) {
|
||||
if (!isNaN(Number(rule.v))) {
|
||||
rule.vt = 'num';
|
||||
@@ -142,26 +295,26 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pending_count = 0;
|
||||
var pending_id = 0;
|
||||
var pending_in = {};
|
||||
var pending_out = {};
|
||||
var pendingCount = 0;
|
||||
var pendingId = 0;
|
||||
var pendingIn = {};
|
||||
var pendingOut = {};
|
||||
var received = {};
|
||||
|
||||
function add2group_in(id, msg, parts) {
|
||||
if (!(id in pending_in)) {
|
||||
pending_in[id] = {
|
||||
function addMessageToGroup(id, msg, parts) {
|
||||
if (!(id in pendingIn)) {
|
||||
pendingIn[id] = {
|
||||
count: undefined,
|
||||
msgs: [],
|
||||
seq_no: pending_id++
|
||||
seq_no: pendingId++
|
||||
};
|
||||
}
|
||||
var group = pending_in[id];
|
||||
var group = pendingIn[id];
|
||||
group.msgs.push(msg);
|
||||
pending_count++;
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
clear_pending();
|
||||
pendingCount++;
|
||||
var max_msgs = getMaxKeptCount();
|
||||
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
|
||||
clearPending();
|
||||
node.error(RED._("switch.errors.too-many"), msg);
|
||||
}
|
||||
if (parts.hasOwnProperty("count")) {
|
||||
@@ -170,32 +323,29 @@ module.exports = function(RED) {
|
||||
return group;
|
||||
}
|
||||
|
||||
function del_group_in(id, group) {
|
||||
pending_count -= group.msgs.length;
|
||||
delete pending_in[id];
|
||||
}
|
||||
|
||||
function add2pending_in(msg) {
|
||||
function addMessageToPending(msg) {
|
||||
var parts = msg.parts;
|
||||
if (parts.hasOwnProperty("id") &&
|
||||
parts.hasOwnProperty("index")) {
|
||||
var group = add2group_in(parts.id, msg, parts);
|
||||
var msgs = group.msgs;
|
||||
var count = group.count;
|
||||
if (count === msgs.length) {
|
||||
for (var i = 0; i < msgs.length; i++) {
|
||||
var msg = msgs[i];
|
||||
// We've already checked the msg.parts has the require bits
|
||||
var group = addMessageToGroup(parts.id, msg, parts);
|
||||
var msgs = group.msgs;
|
||||
var count = group.count;
|
||||
if (count === msgs.length) {
|
||||
// We have a complete group - send the individual parts
|
||||
return msgs.reduce((promise, msg) => {
|
||||
return promise.then((result) => {
|
||||
msg.parts.count = count;
|
||||
process_msg(msg, false);
|
||||
}
|
||||
del_group_in(parts.id, group);
|
||||
}
|
||||
return true;
|
||||
return processMessage(msg, false);
|
||||
})
|
||||
}, Promise.resolve()).then( () => {
|
||||
pendingCount -= group.msgs.length;
|
||||
delete pendingIn[parts.id];
|
||||
});
|
||||
}
|
||||
return false;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function send_group(onwards, port_count) {
|
||||
function sendGroup(onwards, port_count) {
|
||||
var counts = new Array(port_count).fill(0);
|
||||
for (var i = 0; i < onwards.length; i++) {
|
||||
var onward = onwards[i];
|
||||
@@ -230,141 +380,104 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
function send2ports(onward, msg) {
|
||||
function sendGroupMessages(onward, msg) {
|
||||
var parts = msg.parts;
|
||||
var gid = parts.id;
|
||||
received[gid] = ((gid in received) ? received[gid] : 0) +1;
|
||||
var send_ok = (received[gid] === parts.count);
|
||||
|
||||
if (!(gid in pending_out)) {
|
||||
pending_out[gid] = {
|
||||
if (!(gid in pendingOut)) {
|
||||
pendingOut[gid] = {
|
||||
onwards: []
|
||||
};
|
||||
}
|
||||
var group = pending_out[gid];
|
||||
var group = pendingOut[gid];
|
||||
var onwards = group.onwards;
|
||||
onwards.push(onward);
|
||||
pending_count++;
|
||||
pendingCount++;
|
||||
if (send_ok) {
|
||||
send_group(onwards, onward.length, msg);
|
||||
pending_count -= onward.length;
|
||||
delete pending_out[gid];
|
||||
sendGroup(onwards, onward.length, msg);
|
||||
pendingCount -= onward.length;
|
||||
delete pendingOut[gid];
|
||||
delete received[gid];
|
||||
}
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
clear_pending();
|
||||
var max_msgs = getMaxKeptCount();
|
||||
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
|
||||
clearPending();
|
||||
node.error(RED._("switch.errors.too-many"), msg);
|
||||
}
|
||||
}
|
||||
|
||||
function msg_has_parts(msg) {
|
||||
if (msg.hasOwnProperty("parts")) {
|
||||
var parts = msg.parts;
|
||||
return (parts.hasOwnProperty("id") &&
|
||||
parts.hasOwnProperty("index"));
|
||||
|
||||
|
||||
|
||||
|
||||
function processMessage(msg, checkParts) {
|
||||
var hasParts = msg.hasOwnProperty("parts") &&
|
||||
msg.parts.hasOwnProperty("id") &&
|
||||
msg.parts.hasOwnProperty("index");
|
||||
|
||||
if (needsCount && checkParts && hasParts) {
|
||||
return addMessageToPending(msg);
|
||||
}
|
||||
return false;
|
||||
return getProperty(node,msg)
|
||||
.then(property => applyRules(node,msg,property))
|
||||
.then(onward => {
|
||||
if (!repair || !hasParts) {
|
||||
node.send(onward);
|
||||
}
|
||||
else {
|
||||
sendGroupMessages(onward, msg);
|
||||
}
|
||||
}).catch(err => {
|
||||
node.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
function process_msg(msg, check_parts) {
|
||||
var has_parts = msg_has_parts(msg);
|
||||
if (needs_count && check_parts && has_parts &&
|
||||
add2pending_in(msg)) {
|
||||
return;
|
||||
}
|
||||
var onward = [];
|
||||
try {
|
||||
var prop;
|
||||
if (node.propertyType === 'jsonata') {
|
||||
prop = RED.util.evaluateJSONataExpression(node.property,msg);
|
||||
} else {
|
||||
prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
|
||||
}
|
||||
var elseflag = true;
|
||||
for (var i=0; i<node.rules.length; i+=1) {
|
||||
var rule = node.rules[i];
|
||||
var test = prop;
|
||||
var v1,v2;
|
||||
if (rule.vt === 'prev') {
|
||||
v1 = node.previousValue;
|
||||
} else if (rule.vt === 'jsonata') {
|
||||
try {
|
||||
var exp = rule.v;
|
||||
if (rule.t === 'jsonata_exp') {
|
||||
if (has_parts) {
|
||||
exp.assign("I", msg.parts.index);
|
||||
exp.assign("N", msg.parts.count);
|
||||
}
|
||||
}
|
||||
v1 = RED.util.evaluateJSONataExpression(exp,msg);
|
||||
} catch(err) {
|
||||
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
return;
|
||||
}
|
||||
} else if (rule.vt === 'json') {
|
||||
v1 = "json";
|
||||
} else if (rule.vt === 'null') {
|
||||
v1 = "null";
|
||||
} else {
|
||||
try {
|
||||
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
|
||||
} catch(err) {
|
||||
v1 = undefined;
|
||||
}
|
||||
}
|
||||
v2 = rule.v2;
|
||||
if (rule.v2t === 'prev') {
|
||||
v2 = node.previousValue;
|
||||
} else if (rule.v2t === 'jsonata') {
|
||||
try {
|
||||
v2 = RED.util.evaluateJSONataExpression(rule.v2,msg);
|
||||
} catch(err) {
|
||||
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||
return;
|
||||
}
|
||||
} else if (typeof v2 !== 'undefined') {
|
||||
try {
|
||||
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
|
||||
} catch(err) {
|
||||
v2 = undefined;
|
||||
}
|
||||
}
|
||||
if (rule.t == "else") { test = elseflag; elseflag = true; }
|
||||
if (operators[rule.t](test,v1,v2,rule.case,msg.parts)) {
|
||||
onward.push(msg);
|
||||
elseflag = false;
|
||||
if (node.checkall == "false") { break; }
|
||||
} else {
|
||||
onward.push(null);
|
||||
}
|
||||
}
|
||||
node.previousValue = prop;
|
||||
if (!repair || !has_parts) {
|
||||
node.send(onward);
|
||||
}
|
||||
else {
|
||||
send2ports(onward, msg);
|
||||
}
|
||||
} catch(err) {
|
||||
node.warn(err);
|
||||
}
|
||||
}
|
||||
|
||||
function clear_pending() {
|
||||
pending_count = 0;
|
||||
pending_id = 0;
|
||||
pending_in = {};
|
||||
pending_out = {};
|
||||
function clearPending() {
|
||||
pendingCount = 0;
|
||||
pendingId = 0;
|
||||
pendingIn = {};
|
||||
pendingOut = {};
|
||||
received = {};
|
||||
}
|
||||
|
||||
var pendingMessages = [];
|
||||
var activeMessagePromise = null;
|
||||
var processMessageQueue = function(msg) {
|
||||
if (msg) {
|
||||
// A new message has arrived - add it to the message queue
|
||||
pendingMessages.push(msg);
|
||||
if (activeMessagePromise !== null) {
|
||||
// The node is currently processing a message, so do nothing
|
||||
// more with this message
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pendingMessages.length === 0) {
|
||||
// There are no more messages to process, clear the active flag
|
||||
// and return
|
||||
activeMessagePromise = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There are more messages to process. Get the next message and
|
||||
// start processing it. Recurse back in to check for any more
|
||||
var nextMsg = pendingMessages.shift();
|
||||
activeMessagePromise = processMessage(nextMsg,true)
|
||||
.then(processMessageQueue)
|
||||
.catch((err) => {
|
||||
node.error(err,nextMsg);
|
||||
return processMessageQueue();
|
||||
});
|
||||
}
|
||||
|
||||
this.on('input', function(msg) {
|
||||
process_msg(msg, true);
|
||||
processMessageQueue(msg);
|
||||
});
|
||||
|
||||
this.on('close', function() {
|
||||
clear_pending();
|
||||
clearPending();
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -54,6 +54,10 @@
|
||||
outputs: 1,
|
||||
icon: "swap.png",
|
||||
label: function() {
|
||||
function prop2name(type, key) {
|
||||
var result = RED.utils.parseContextKey(key);
|
||||
return type +"." +result.key;
|
||||
}
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
@@ -70,13 +74,13 @@
|
||||
} else {
|
||||
if (this.rules.length == 1) {
|
||||
if (this.rules[0].t === "set") {
|
||||
return this._("change.label.set",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
return this._("change.label.set",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
} else if (this.rules[0].t === "change") {
|
||||
return this._("change.label.change",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
return this._("change.label.change",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
} else if (this.rules[0].t === "move") {
|
||||
return this._("change.label.move",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
return this._("change.label.move",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
} else {
|
||||
return this._("change.label.delete",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
return this._("change.label.delete",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||
}
|
||||
} else {
|
||||
return this._("change.label.changeCount",{count:this.rules.length});
|
||||
|
@@ -98,44 +98,61 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
function applyRule(msg,rule) {
|
||||
try {
|
||||
var property = rule.p;
|
||||
var value = rule.to;
|
||||
if (rule.tot === 'json') {
|
||||
value = JSON.parse(rule.to);
|
||||
} else if (rule.tot === 'bin') {
|
||||
value = Buffer.from(JSON.parse(rule.to))
|
||||
}
|
||||
var current;
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
if (rule.tot === "msg") {
|
||||
value = RED.util.getMessageProperty(msg,rule.to);
|
||||
} else if (rule.tot === 'flow') {
|
||||
value = node.context().flow.get(rule.to);
|
||||
} else if (rule.tot === 'global') {
|
||||
value = node.context().global.get(rule.to);
|
||||
} else if (rule.tot === 'date') {
|
||||
value = Date.now();
|
||||
} else if (rule.tot === 'jsonata') {
|
||||
try{
|
||||
value = RED.util.evaluateJSONataExpression(rule.to,msg);
|
||||
} catch(err) {
|
||||
node.error(RED._("change.errors.invalid-expr",{error:err.message}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (rule.t === 'change') {
|
||||
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
if (rule.fromt === "msg") {
|
||||
fromValue = RED.util.getMessageProperty(msg,rule.from);
|
||||
} else if (rule.fromt === 'flow') {
|
||||
fromValue = node.context().flow.get(rule.from);
|
||||
} else if (rule.fromt === 'global') {
|
||||
fromValue = node.context().global.get(rule.from);
|
||||
function getToValue(msg,rule) {
|
||||
var value = rule.to;
|
||||
if (rule.tot === 'json') {
|
||||
value = JSON.parse(rule.to);
|
||||
} else if (rule.tot === 'bin') {
|
||||
value = Buffer.from(JSON.parse(rule.to))
|
||||
}
|
||||
if (rule.tot === "msg") {
|
||||
value = RED.util.getMessageProperty(msg,rule.to);
|
||||
} else if ((rule.tot === 'flow') ||
|
||||
(rule.tot === 'global')) {
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
|
||||
if (err) {
|
||||
resolve(undefined);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (rule.tot === 'date') {
|
||||
value = Date.now();
|
||||
} else if (rule.tot === 'jsonata') {
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
|
||||
if (err) {
|
||||
reject(RED._("change.errors.invalid-expr",{error:err.message}))
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve(value);
|
||||
}
|
||||
function getFromValue(msg,rule) {
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
if (rule.t === 'change') {
|
||||
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
return new Promise((resolve,reject) => {
|
||||
if (rule.fromt === "msg") {
|
||||
resolve(RED.util.getMessageProperty(msg,rule.from));
|
||||
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
var contextKey = RED.util.parseContextStore(rule.from);
|
||||
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(fromValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).then(fromValue => {
|
||||
if (typeof fromValue === 'number' || fromValue instanceof Number) {
|
||||
fromType = 'num';
|
||||
} else if (typeof fromValue === 'boolean') {
|
||||
@@ -149,108 +166,160 @@ module.exports = function(RED) {
|
||||
try {
|
||||
fromRE = new RegExp(fromRE, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
node.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
return;
|
||||
return Promise.reject(new Error(RED._("change.errors.invalid-from",{error:e.message})));
|
||||
}
|
||||
} else {
|
||||
node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}));
|
||||
return
|
||||
return Promise.reject(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
|
||||
}
|
||||
return {
|
||||
fromType,
|
||||
fromValue,
|
||||
fromRE
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fromType = rule.fromt;
|
||||
fromValue = rule.from;
|
||||
fromRE = rule.fromRE;
|
||||
}
|
||||
}
|
||||
return Promise.resolve({
|
||||
fromType,
|
||||
fromValue,
|
||||
fromRE
|
||||
});
|
||||
}
|
||||
function applyRule(msg,rule) {
|
||||
var property = rule.p;
|
||||
var current;
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
try {
|
||||
return getToValue(msg,rule).then(value => {
|
||||
return getFromValue(msg,rule).then(fromParts => {
|
||||
fromValue = fromParts.fromValue;
|
||||
fromType = fromParts.fromType;
|
||||
fromRE = fromParts.fromRE;
|
||||
if (rule.pt === 'msg') {
|
||||
try {
|
||||
if (rule.t === 'delete') {
|
||||
RED.util.setMessageProperty(msg,property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
current = RED.util.getMessageProperty(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
RED.util.setMessageProperty(msg,property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(err) {}
|
||||
return msg;
|
||||
} else if (rule.pt === 'flow' || rule.pt === 'global') {
|
||||
var contextKey = RED.util.parseContextStore(property);
|
||||
return new Promise((resolve,reject) => {
|
||||
var target = node.context()[rule.pt];
|
||||
var callback = err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(msg);
|
||||
}
|
||||
}
|
||||
if (rule.t === 'delete') {
|
||||
target.set(contextKey.key,undefined,contextKey.store,callback);
|
||||
} else if (rule.t === 'set') {
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
} else if (rule.t === 'change') {
|
||||
target.get(contextKey.key,contextKey.store,(err,current) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
target.set(contextKey.key,current,contextKey.store,callback);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
target.set(contextKey.key,value,contextKey.store,callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
node.error(err, msg);
|
||||
return null;
|
||||
});
|
||||
} catch(err) {
|
||||
return Promise.resolve(msg);
|
||||
}
|
||||
}
|
||||
function applyRules(msg, currentRule) {
|
||||
var r = node.rules[currentRule];
|
||||
var rulePromise;
|
||||
if (r.t === "move") {
|
||||
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
|
||||
rulePromise = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt}).then(
|
||||
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
|
||||
);
|
||||
}
|
||||
else { // 2 step move if we are moving from a child
|
||||
rulePromise = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt}).then(
|
||||
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
|
||||
).then(
|
||||
msg => applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt})
|
||||
).then(
|
||||
msg => applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
rulePromise = applyRule(msg,r);
|
||||
}
|
||||
return rulePromise.then(
|
||||
msg => {
|
||||
if (!msg) {
|
||||
return
|
||||
} else if (currentRule === node.rules.length - 1) {
|
||||
return msg;
|
||||
} else {
|
||||
fromType = rule.fromt;
|
||||
fromValue = rule.from;
|
||||
fromRE = rule.fromRE;
|
||||
return applyRules(msg, currentRule+1);
|
||||
}
|
||||
}
|
||||
if (rule.pt === 'msg') {
|
||||
if (rule.t === 'delete') {
|
||||
RED.util.setMessageProperty(msg,property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
current = RED.util.getMessageProperty(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
RED.util.setMessageProperty(msg,property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var target;
|
||||
if (rule.pt === 'flow') {
|
||||
target = node.context().flow;
|
||||
} else if (rule.pt === 'global') {
|
||||
target = node.context().global;
|
||||
}
|
||||
if (target) {
|
||||
if (rule.t === 'delete') {
|
||||
target.set(property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
target.set(property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
current = target.get(property);
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
target.set(property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
target.set(property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
target.set(property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
target.set(property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(err) {/*console.log(err.stack)*/}
|
||||
return msg;
|
||||
);
|
||||
}
|
||||
if (valid) {
|
||||
this.on('input', function(msg) {
|
||||
for (var i=0; i<this.rules.length; i++) {
|
||||
if (this.rules[i].t === "move") {
|
||||
var r = this.rules[i];
|
||||
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
|
||||
msg = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt});
|
||||
applyRule(msg,{t:"delete", p:r.p, pt:r.pt});
|
||||
}
|
||||
else { // 2 step move if we are moving from a child
|
||||
msg = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt});
|
||||
applyRule(msg,{t:"delete", p:r.p, pt:r.pt});
|
||||
msg = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt});
|
||||
applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt});
|
||||
}
|
||||
} else {
|
||||
msg = applyRule(msg,this.rules[i]);
|
||||
}
|
||||
if (msg === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
node.send(msg);
|
||||
applyRules(msg, 0)
|
||||
.then( msg => { if (msg) { node.send(msg) }} )
|
||||
.catch( err => node.error(err, msg))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -415,7 +415,7 @@
|
||||
$("#node-input-reduceExp").typedInput({types:[jsonata_or_empty]});
|
||||
$("#node-input-reduceInit").typedInput({
|
||||
default: 'num',
|
||||
types:['flow','global','str','num','bool','json','bin','date','jsonata'],
|
||||
types:['flow','global','str','num','bool','json','bin','date','jsonata','env'],
|
||||
typeField: $("#node-input-reduceInitType")
|
||||
});
|
||||
$("#node-input-reduceFixup").typedInput({types:[jsonata_or_empty]});
|
||||
|
@@ -233,7 +233,7 @@ module.exports = function(RED) {
|
||||
RED.nodes.registerType("split",SplitNode);
|
||||
|
||||
|
||||
var _max_kept_msgs_count = undefined;
|
||||
var _max_kept_msgs_count;
|
||||
|
||||
function max_kept_msgs_count(node) {
|
||||
if (_max_kept_msgs_count === undefined) {
|
||||
@@ -252,13 +252,29 @@ module.exports = function(RED) {
|
||||
exp.assign("I", index);
|
||||
exp.assign("N", count);
|
||||
exp.assign("A", accum);
|
||||
return RED.util.evaluateJSONataExpression(exp, msg);
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateJSONataExpression(exp, msg, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function apply_f(exp, accum, count) {
|
||||
exp.assign("N", count);
|
||||
exp.assign("A", accum);
|
||||
return RED.util.evaluateJSONataExpression(exp, {});
|
||||
return new Promise((resolve,reject) => {
|
||||
return RED.util.evaluateJSONataExpression(exp, {}, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function exp_or_undefined(exp) {
|
||||
@@ -269,32 +285,40 @@ module.exports = function(RED) {
|
||||
return exp
|
||||
}
|
||||
|
||||
function reduce_and_send_group(node, group) {
|
||||
function reduceAndSendGroup(node, group) {
|
||||
var is_right = node.reduce_right;
|
||||
var flag = is_right ? -1 : 1;
|
||||
var msgs = group.msgs;
|
||||
var accum = eval_exp(node, node.exp_init, node.exp_init_type);
|
||||
var reduce_exp = node.reduce_exp;
|
||||
var reduce_fixup = node.reduce_fixup;
|
||||
var count = group.count;
|
||||
msgs.sort(function(x,y) {
|
||||
var ix = x.parts.index;
|
||||
var iy = y.parts.index;
|
||||
if (ix < iy) return -flag;
|
||||
if (ix > iy) return flag;
|
||||
return 0;
|
||||
return getInitialReduceValue(node, node.exp_init, node.exp_init_type).then(accum => {
|
||||
var reduce_exp = node.reduce_exp;
|
||||
var reduce_fixup = node.reduce_fixup;
|
||||
var count = group.count;
|
||||
msgs.sort(function(x,y) {
|
||||
var ix = x.parts.index;
|
||||
var iy = y.parts.index;
|
||||
if (ix < iy) {return -flag;}
|
||||
if (ix > iy) {return flag;}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return msgs.reduce((promise, msg) => promise.then(accum => apply_r(reduce_exp, accum, msg, msg.parts.index, count)), Promise.resolve(accum))
|
||||
.then(accum => {
|
||||
if(reduce_fixup !== undefined) {
|
||||
return apply_f(reduce_fixup, accum, count).then(accum => {
|
||||
node.send({payload: accum});
|
||||
});
|
||||
} else {
|
||||
node.send({payload: accum});
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
throw new Error(RED._("join.errors.invalid-expr",{error:err.message}));
|
||||
});
|
||||
for(var msg of msgs) {
|
||||
accum = apply_r(reduce_exp, accum, msg, msg.parts.index, count);
|
||||
}
|
||||
if(reduce_fixup !== undefined) {
|
||||
accum = apply_f(reduce_fixup, accum, count);
|
||||
}
|
||||
node.send({payload: accum});
|
||||
}
|
||||
|
||||
function reduce_msg(node, msg) {
|
||||
if(msg.hasOwnProperty('parts')) {
|
||||
var promise;
|
||||
if (msg.hasOwnProperty('parts')) {
|
||||
var parts = msg.parts;
|
||||
var pending = node.pending;
|
||||
var pending_count = node.pending_count;
|
||||
@@ -311,66 +335,51 @@ module.exports = function(RED) {
|
||||
}
|
||||
var group = pending[gid];
|
||||
var msgs = group.msgs;
|
||||
if(parts.hasOwnProperty('count') &&
|
||||
(group.count === undefined)) {
|
||||
group.count = count;
|
||||
if(parts.hasOwnProperty('count') && (group.count === undefined)) {
|
||||
group.count = parts.count;
|
||||
}
|
||||
msgs.push(msg);
|
||||
pending_count++;
|
||||
var completeProcess = function() {
|
||||
node.pending_count = pending_count;
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
node.pending = {};
|
||||
node.pending_count = 0;
|
||||
var promise = Promise.reject(RED._("join.too-many"));
|
||||
promise.catch(()=>{});
|
||||
return promise;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
if(msgs.length === group.count) {
|
||||
delete pending[gid];
|
||||
try {
|
||||
pending_count -= msgs.length;
|
||||
reduce_and_send_group(node, group);
|
||||
} catch(e) {
|
||||
node.error(RED._("join.errors.invalid-expr",{error:e.message})); }
|
||||
pending_count -= msgs.length;
|
||||
promise = reduceAndSendGroup(node, group).then(completeProcess);
|
||||
} else {
|
||||
promise = completeProcess();
|
||||
}
|
||||
node.pending_count = pending_count;
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
node.pending = {};
|
||||
node.pending_count = 0;
|
||||
node.error(RED._("join.too-many"), msg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
node.send(msg);
|
||||
}
|
||||
if (!promise) {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
function eval_exp(node, exp, exp_type) {
|
||||
if(exp_type === "flow") {
|
||||
return node.context().flow.get(exp);
|
||||
}
|
||||
else if(exp_type === "global") {
|
||||
return node.context().global.get(exp);
|
||||
}
|
||||
else if(exp_type === "str") {
|
||||
return exp;
|
||||
}
|
||||
else if(exp_type === "num") {
|
||||
return Number(exp);
|
||||
}
|
||||
else if(exp_type === "bool") {
|
||||
if (exp === 'true') {
|
||||
return true;
|
||||
}
|
||||
else if (exp === 'false') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ((exp_type === "bin") ||
|
||||
(exp_type === "json")) {
|
||||
return JSON.parse(exp);
|
||||
}
|
||||
else if(exp_type === "date") {
|
||||
return Date.now();
|
||||
}
|
||||
else if(exp_type === "jsonata") {
|
||||
var jexp = RED.util.prepareJSONataExpression(exp, node);
|
||||
return RED.util.evaluateJSONataExpression(jexp, {});
|
||||
}
|
||||
throw new Error("unexpected initial value type");
|
||||
function getInitialReduceValue(node, exp, exp_type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
RED.util.evaluateNodeProperty(exp, exp_type, node, {},
|
||||
(err, result) => {
|
||||
if(err) {
|
||||
return reject(err);
|
||||
}
|
||||
else {
|
||||
return resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function JoinNode(n) {
|
||||
@@ -399,6 +408,7 @@ module.exports = function(RED) {
|
||||
this.reduce_fixup = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
|
||||
} catch(e) {
|
||||
this.error(RED._("join.errors.invalid-expr",{error:e.message}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +447,8 @@ module.exports = function(RED) {
|
||||
newArray = newArray.concat(n);
|
||||
})
|
||||
group.payload = newArray;
|
||||
} else if (group.type === 'buffer') {
|
||||
}
|
||||
else if (group.type === 'buffer') {
|
||||
var buffers = [];
|
||||
var bufferLen = 0;
|
||||
if (group.joinChar !== undefined) {
|
||||
@@ -450,7 +461,8 @@ module.exports = function(RED) {
|
||||
buffers.push(group.payload[i]);
|
||||
bufferLen += group.payload[i].length;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
bufferLen = group.bufferLen;
|
||||
buffers = group.payload;
|
||||
}
|
||||
@@ -463,7 +475,8 @@ module.exports = function(RED) {
|
||||
groupJoinChar = group.joinChar.toString();
|
||||
}
|
||||
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (node.propertyType === 'full') {
|
||||
group.msg = RED.util.cloneMessage(group.msg);
|
||||
}
|
||||
@@ -471,13 +484,48 @@ module.exports = function(RED) {
|
||||
}
|
||||
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
|
||||
group.msg.parts = group.msg.parts.parts;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
delete group.msg.parts;
|
||||
}
|
||||
delete group.msg.complete;
|
||||
node.send(group.msg);
|
||||
}
|
||||
|
||||
var pendingMessages = [];
|
||||
var activeMessagePromise = null;
|
||||
// In reduce mode, we must process messages fully in order otherwise
|
||||
// groups may overlap and cause unexpected results. The use of JSONata
|
||||
// means some async processing *might* occur if flow/global context is
|
||||
// accessed.
|
||||
var processReduceMessageQueue = function(msg) {
|
||||
if (msg) {
|
||||
// A new message has arrived - add it to the message queue
|
||||
pendingMessages.push(msg);
|
||||
if (activeMessagePromise !== null) {
|
||||
// The node is currently processing a message, so do nothing
|
||||
// more with this message
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pendingMessages.length === 0) {
|
||||
// There are no more messages to process, clear the active flag
|
||||
// and return
|
||||
activeMessagePromise = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There are more messages to process. Get the next message and
|
||||
// start processing it. Recurse back in to check for any more
|
||||
var nextMsg = pendingMessages.shift();
|
||||
activeMessagePromise = reduce_msg(node, nextMsg)
|
||||
.then(processReduceMessageQueue)
|
||||
.catch((err) => {
|
||||
node.error(err,nextMsg);
|
||||
return processReduceMessageQueue();
|
||||
});
|
||||
}
|
||||
|
||||
this.on("input", function(msg) {
|
||||
try {
|
||||
var property;
|
||||
@@ -516,8 +564,7 @@ module.exports = function(RED) {
|
||||
propertyIndex = msg.parts.index;
|
||||
}
|
||||
else if (node.mode === 'reduce') {
|
||||
reduce_msg(node, msg);
|
||||
return;
|
||||
return processReduceMessageQueue(msg);
|
||||
}
|
||||
else {
|
||||
// Use the node configuration to identify all of the group information
|
||||
@@ -525,7 +572,7 @@ module.exports = function(RED) {
|
||||
payloadType = node.build;
|
||||
targetCount = node.count;
|
||||
joinChar = node.joiner;
|
||||
if (targetCount === 0 && msg.hasOwnProperty('parts')) {
|
||||
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
||||
targetCount = msg.parts.count || 0;
|
||||
}
|
||||
if (node.build === 'object') {
|
||||
@@ -554,7 +601,7 @@ module.exports = function(RED) {
|
||||
payload:{},
|
||||
targetCount:targetCount,
|
||||
type:"object",
|
||||
msg:msg
|
||||
msg:RED.util.cloneMessage(msg)
|
||||
};
|
||||
}
|
||||
else if (node.accumulate === true) {
|
||||
@@ -564,7 +611,7 @@ module.exports = function(RED) {
|
||||
payload:{},
|
||||
targetCount:targetCount,
|
||||
type:payloadType,
|
||||
msg:msg
|
||||
msg:RED.util.cloneMessage(msg)
|
||||
}
|
||||
if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') {
|
||||
inflight[partId].payload = [];
|
||||
@@ -576,7 +623,7 @@ module.exports = function(RED) {
|
||||
payload:[],
|
||||
targetCount:targetCount,
|
||||
type:payloadType,
|
||||
msg:msg
|
||||
msg:RED.util.cloneMessage(msg)
|
||||
};
|
||||
if (payloadType === 'string') {
|
||||
inflight[partId].joinChar = joinChar;
|
||||
@@ -619,19 +666,22 @@ module.exports = function(RED) {
|
||||
} else {
|
||||
if (!isNaN(propertyIndex)) {
|
||||
group.payload[propertyIndex] = property;
|
||||
group.currentCount++;
|
||||
} else {
|
||||
group.payload.push(property);
|
||||
if (property !== undefined) {
|
||||
group.payload.push(property);
|
||||
group.currentCount++;
|
||||
}
|
||||
}
|
||||
group.currentCount++;
|
||||
}
|
||||
// TODO: currently reuse the last received - add option to pick first received
|
||||
group.msg = msg;
|
||||
group.msg = Object.assign(group.msg, msg);
|
||||
var tcnt = group.targetCount;
|
||||
if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; }
|
||||
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
|
||||
completeSend(partId);
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err.stack);
|
||||
}
|
||||
});
|
||||
|
@@ -17,7 +17,7 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
var _max_kept_msgs_count = undefined;
|
||||
var _max_kept_msgs_count;
|
||||
|
||||
function max_kept_msgs_count(node) {
|
||||
if (_max_kept_msgs_count === undefined) {
|
||||
@@ -32,30 +32,20 @@ module.exports = function(RED) {
|
||||
return _max_kept_msgs_count;
|
||||
}
|
||||
|
||||
function eval_jsonata(node, code, val) {
|
||||
try {
|
||||
return RED.util.evaluateJSONataExpression(code, val);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("sort.invalid-exp"));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function get_context_val(node, name, dval) {
|
||||
var context = node.context();
|
||||
var val = context.get(name);
|
||||
if (val === undefined) {
|
||||
context.set(name, dval);
|
||||
return dval;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
// function get_context_val(node, name, dval) {
|
||||
// var context = node.context();
|
||||
// var val = context.get(name);
|
||||
// if (val === undefined) {
|
||||
// context.set(name, dval);
|
||||
// return dval;
|
||||
// }
|
||||
// return val;
|
||||
// }
|
||||
|
||||
function SortNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
var node = this;
|
||||
var pending = get_context_val(node, 'pending', {})
|
||||
var pending = {};//get_context_val(node, 'pending', {})
|
||||
var pending_count = 0;
|
||||
var pending_id = 0;
|
||||
var order = n.order || "ascending";
|
||||
@@ -71,16 +61,15 @@ module.exports = function(RED) {
|
||||
key_exp = RED.util.prepareJSONataExpression(key_exp, this);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("sort.invalid-exp"));
|
||||
node.error(RED._("sort.invalid-exp",{message:e.toString()}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
var dir = (order === "ascending") ? 1 : -1;
|
||||
var conv = as_num
|
||||
? function(x) { return Number(x); }
|
||||
: function(x) { return x; };
|
||||
var conv = as_num ? function(x) { return Number(x); }
|
||||
: function(x) { return x; };
|
||||
|
||||
function gen_comp(key) {
|
||||
function generateComparisonFunction(key) {
|
||||
return function(x, y) {
|
||||
var xp = conv(key(x));
|
||||
var yp = conv(key(y));
|
||||
@@ -90,74 +79,105 @@ module.exports = function(RED) {
|
||||
};
|
||||
}
|
||||
|
||||
function send_group(group) {
|
||||
var key = key_is_exp
|
||||
? function(msg) {
|
||||
return eval_jsonata(node, key_exp, msg);
|
||||
}
|
||||
: function(msg) {
|
||||
return RED.util.getMessageProperty(msg, key_prop);
|
||||
};
|
||||
var comp = gen_comp(key);
|
||||
function sortMessageGroup(group) {
|
||||
var promise;
|
||||
var msgs = group.msgs;
|
||||
try {
|
||||
msgs.sort(comp);
|
||||
}
|
||||
catch (e) {
|
||||
return; // not send when error
|
||||
}
|
||||
for (var i = 0; i < msgs.length; i++) {
|
||||
var msg = msgs[i];
|
||||
msg.parts.index = i;
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function sort_payload(msg) {
|
||||
var data = RED.util.getMessageProperty(msg, target_prop);
|
||||
if (Array.isArray(data)) {
|
||||
var key = key_is_exp
|
||||
? function(elem) {
|
||||
return eval_jsonata(node, key_exp, elem);
|
||||
}
|
||||
: function(elem) { return elem; };
|
||||
var comp = gen_comp(key);
|
||||
if (key_is_exp) {
|
||||
var evaluatedDataPromises = msgs.map(msg => {
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateJSONataExpression(key_exp, msg, (err, result) => {
|
||||
if (err) {
|
||||
reject(RED._("sort.invalid-exp",{message:err.toString()}));
|
||||
} else {
|
||||
resolve({
|
||||
item: msg,
|
||||
sortValue: result
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
promise = Promise.all(evaluatedDataPromises).then(evaluatedElements => {
|
||||
// Once all of the sort keys are evaluated, sort by them
|
||||
var comp = generateComparisonFunction(elem=>elem.sortValue);
|
||||
return evaluatedElements.sort(comp).map(elem=>elem.item);
|
||||
});
|
||||
} else {
|
||||
var key = function(msg) {
|
||||
return ;
|
||||
}
|
||||
var comp = generateComparisonFunction(msg => RED.util.getMessageProperty(msg, key_prop));
|
||||
try {
|
||||
data.sort(comp);
|
||||
msgs.sort(comp);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
return; // not send when error
|
||||
}
|
||||
return true;
|
||||
promise = Promise.resolve(msgs);
|
||||
}
|
||||
return false;
|
||||
return promise.then(msgs => {
|
||||
for (var i = 0; i < msgs.length; i++) {
|
||||
var msg = msgs[i];
|
||||
msg.parts.index = i;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function check_parts(parts) {
|
||||
if (parts.hasOwnProperty("id") &&
|
||||
parts.hasOwnProperty("index")) {
|
||||
return true;
|
||||
function sortMessageProperty(msg) {
|
||||
var data = RED.util.getMessageProperty(msg, target_prop);
|
||||
if (Array.isArray(data)) {
|
||||
if (key_is_exp) {
|
||||
// key is an expression. Evaluated the expression for each item
|
||||
// to get its sort value. As this could be async, need to do
|
||||
// it first.
|
||||
var evaluatedDataPromises = data.map(elem => {
|
||||
return new Promise((resolve,reject) => {
|
||||
RED.util.evaluateJSONataExpression(key_exp, elem, (err, result) => {
|
||||
if (err) {
|
||||
reject(RED._("sort.invalid-exp",{message:err.toString()}));
|
||||
} else {
|
||||
resolve({
|
||||
item: elem,
|
||||
sortValue: result
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
return Promise.all(evaluatedDataPromises).then(evaluatedElements => {
|
||||
// Once all of the sort keys are evaluated, sort by them
|
||||
// and reconstruct the original message item with the newly
|
||||
// sorted values.
|
||||
var comp = generateComparisonFunction(elem=>elem.sortValue);
|
||||
data = evaluatedElements.sort(comp).map(elem=>elem.item);
|
||||
RED.util.setMessageProperty(msg, target_prop,data);
|
||||
return true;
|
||||
})
|
||||
} else {
|
||||
var comp = generateComparisonFunction(elem=>elem);
|
||||
try {
|
||||
data.sort(comp);
|
||||
} catch (e) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
function clear_pending() {
|
||||
function removeOldestPending() {
|
||||
var oldest;
|
||||
var oldest_key;
|
||||
for(var key in pending) {
|
||||
node.log(RED._("sort.clear"), pending[key].msgs[0]);
|
||||
delete pending[key];
|
||||
}
|
||||
pending_count = 0;
|
||||
}
|
||||
|
||||
function remove_oldest_pending() {
|
||||
var oldest = undefined;
|
||||
var oldest_key = undefined;
|
||||
for(var key in pending) {
|
||||
var item = pending[key];
|
||||
if((oldest === undefined) ||
|
||||
(oldest.seq_no > item.seq_no)) {
|
||||
oldest = item;
|
||||
oldest_key = key;
|
||||
if (pending.hasOwnProperty(key)) {
|
||||
var item = pending[key];
|
||||
if((oldest === undefined) ||
|
||||
(oldest.seq_no > item.seq_no)) {
|
||||
oldest = item;
|
||||
oldest_key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(oldest !== undefined) {
|
||||
@@ -166,16 +186,19 @@ module.exports = function(RED) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function process_msg(msg) {
|
||||
|
||||
function processMessage(msg) {
|
||||
if (target_is_prop) {
|
||||
if (sort_payload(msg)) {
|
||||
node.send(msg);
|
||||
}
|
||||
return;
|
||||
sortMessageProperty(msg).then(send => {
|
||||
if (send) {
|
||||
node.send(msg);
|
||||
}
|
||||
}).catch(err => {
|
||||
node.error(err,msg);
|
||||
});
|
||||
}
|
||||
var parts = msg.parts;
|
||||
if (!check_parts(parts)) {
|
||||
if (!parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
|
||||
return;
|
||||
}
|
||||
var gid = parts.id;
|
||||
@@ -195,23 +218,31 @@ module.exports = function(RED) {
|
||||
pending_count++;
|
||||
if (group.count === msgs.length) {
|
||||
delete pending[gid]
|
||||
send_group(group);
|
||||
sortMessageGroup(group).catch(err => {
|
||||
node.error(err,msg);
|
||||
});
|
||||
pending_count -= msgs.length;
|
||||
}
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
pending_count -= remove_oldest_pending();
|
||||
node.error(RED._("sort.too-many"), msg);
|
||||
} else {
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
pending_count -= removeOldestPending();
|
||||
node.error(RED._("sort.too-many"), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.on("input", function(msg) {
|
||||
process_msg(msg);
|
||||
processMessage(msg);
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
clear_pending();
|
||||
})
|
||||
for(var key in pending) {
|
||||
if (pending.hasOwnProperty(key)) {
|
||||
node.log(RED._("sort.clear"), pending[key].msgs[0]);
|
||||
delete pending[key];
|
||||
}
|
||||
}
|
||||
pending_count = 0; })
|
||||
}
|
||||
|
||||
RED.nodes.registerType("sort", SortNode);
|
||||
|
@@ -31,6 +31,8 @@
|
||||
<dl class="message-properties">
|
||||
<dt>payload<span class="property-type">object | string</span></dt>
|
||||
<dd>A JavaScript object or JSON string.</dd>
|
||||
<dt>schema<span class="property-type">object</span></dt>
|
||||
<dd>An optional JSON Schema object to validate the payload against.</dd>
|
||||
</dl>
|
||||
<h3>Outputs</h3>
|
||||
<dl class="message-properties">
|
||||
@@ -41,6 +43,9 @@
|
||||
<li>If the input is a JavaScript object it creates a JSON string. The string can optionally be well-formatted.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>schemaError<span class="property-type">array</span></dt>
|
||||
<dd>If JSON schema validation fails, the catch node will have a <code>schemaError</code> property
|
||||
containing an array of errors.</dd>
|
||||
</dl>
|
||||
<h3>Details</h3>
|
||||
<p>By default, the node operates on <code>msg.payload</code>, but can be configured
|
||||
@@ -53,6 +58,8 @@
|
||||
receives a String, no further checks will be made of the property. It will
|
||||
not check the String is valid JSON nor will it reformat it if the format option
|
||||
is selected.</p>
|
||||
<p>For more details about JSON Schema you can consult the specification
|
||||
<a href="http://json-schema.org/latest/json-schema-validation.html">here</a>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
@@ -16,21 +16,52 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
const Ajv = require('ajv');
|
||||
const ajv = new Ajv({allErrors: true, schemaId: 'auto'});
|
||||
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
|
||||
|
||||
function JSONNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.indent = n.pretty ? 4 : 0;
|
||||
this.action = n.action||"";
|
||||
this.property = n.property||"payload";
|
||||
this.schema = null;
|
||||
this.compiledSchema = null;
|
||||
|
||||
var node = this;
|
||||
this.on("input", function(msg) {
|
||||
var validate = false;
|
||||
if (msg.schema) {
|
||||
// If input schema is different, re-compile it
|
||||
if (JSON.stringify(this.schema) != JSON.stringify(msg.schema)) {
|
||||
try {
|
||||
this.compiledSchema = ajv.compile(msg.schema);
|
||||
this.schema = msg.schema;
|
||||
} catch(e) {
|
||||
this.schema = null;
|
||||
this.compiledSchema = null;
|
||||
node.error(RED._("json.errors.schema-error-compile"), msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
validate = true;
|
||||
}
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
if (typeof value === "string") {
|
||||
if (node.action === "" || node.action === "obj") {
|
||||
try {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
|
||||
node.send(msg);
|
||||
if (validate) {
|
||||
if (this.compiledSchema(msg[node.property])) {
|
||||
node.send(msg);
|
||||
} else {
|
||||
msg.schemaError = this.compiledSchema.errors;
|
||||
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
|
||||
}
|
||||
} else {
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
catch(e) { node.error(e.message,msg); }
|
||||
} else {
|
||||
@@ -41,8 +72,19 @@ module.exports = function(RED) {
|
||||
if (node.action === "" || node.action === "str") {
|
||||
if (!Buffer.isBuffer(value)) {
|
||||
try {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
|
||||
node.send(msg);
|
||||
if (validate) {
|
||||
if (this.compiledSchema(value)) {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
|
||||
node.send(msg);
|
||||
} else {
|
||||
msg.schemaError = this.compiledSchema.errors;
|
||||
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
|
||||
}
|
||||
} else {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
|
||||
node.send(msg);
|
||||
}
|
||||
|
||||
}
|
||||
catch(e) { node.error(RED._("json.errors.dropped-error")); }
|
||||
}
|
||||
|
@@ -37,6 +37,8 @@
|
||||
<dt class="optional">filename <span class="property-type">string</span></dt>
|
||||
<dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
|
||||
</dl>
|
||||
<h3>Output</h3>
|
||||
<p>On completion of write, input message is sent to output port.</p>
|
||||
<h3>Details</h3>
|
||||
<p>Each message payload will be added to the end of the file, optionally appending
|
||||
a newline (\n) character between each one.</p>
|
||||
@@ -123,9 +125,8 @@
|
||||
},
|
||||
color:"BurlyWood",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "file.png",
|
||||
align: "right",
|
||||
outputs:1,
|
||||
icon: "file-out.png",
|
||||
label: function() {
|
||||
if (this.overwriteFile === "delete") {
|
||||
return this.name||this._("file.label.deletelabel",{file:this.filename});
|
||||
@@ -159,7 +160,7 @@
|
||||
outputLabels: function(i) {
|
||||
return (this.format === "utf8") ? "UTF8 string" : "binary buffer";
|
||||
},
|
||||
icon: "file.png",
|
||||
icon: "file-in.png",
|
||||
label: function() {
|
||||
return this.name||this.filename||this._("file.label.filelabel");
|
||||
},
|
||||
|
@@ -39,14 +39,20 @@ module.exports = function(RED) {
|
||||
node.tout = null;
|
||||
},333);
|
||||
}
|
||||
if (filename === "") { node.warn(RED._("file.errors.nofilename")); }
|
||||
else if (node.overwriteFile === "delete") {
|
||||
if (filename === "") {
|
||||
node.warn(RED._("file.errors.nofilename"));
|
||||
} else if (node.overwriteFile === "delete") {
|
||||
fs.unlink(filename, function (err) {
|
||||
if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); }
|
||||
else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); }
|
||||
if (err) {
|
||||
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
|
||||
} else {
|
||||
if (RED.settings.verbose) {
|
||||
node.log(RED._("file.status.deletedfile",{file:filename}));
|
||||
}
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
|
||||
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
|
||||
var dir = path.dirname(filename);
|
||||
if (node.createDir) {
|
||||
try {
|
||||
@@ -64,15 +70,21 @@ module.exports = function(RED) {
|
||||
if (typeof data === "boolean") { data = data.toString(); }
|
||||
if (typeof data === "number") { data = data.toString(); }
|
||||
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
|
||||
node.data.push(Buffer.from(data));
|
||||
node.data.push({msg:msg,data:Buffer.from(data)});
|
||||
|
||||
while (node.data.length > 0) {
|
||||
if (node.overwriteFile === "true") {
|
||||
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
|
||||
node.wstream.on("error", function(err) {
|
||||
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
|
||||
});
|
||||
node.wstream.end(node.data.shift());
|
||||
(function(packet) {
|
||||
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
|
||||
node.wstream.on("error", function(err) {
|
||||
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
|
||||
});
|
||||
node.wstream.on("open", function() {
|
||||
node.wstream.end(packet.data, function() {
|
||||
node.send(packet.msg);
|
||||
});
|
||||
})
|
||||
})(node.data.shift());
|
||||
}
|
||||
else {
|
||||
// Append mode
|
||||
@@ -115,10 +127,17 @@ module.exports = function(RED) {
|
||||
}
|
||||
if (node.filename) {
|
||||
// Static filename - write and reuse the stream next time
|
||||
node.wstream.write(node.data.shift());
|
||||
var packet = node.data.shift()
|
||||
node.wstream.write(packet.data, function() {
|
||||
node.send(packet.msg);
|
||||
});
|
||||
|
||||
} else {
|
||||
// Dynamic filename - write and close the stream
|
||||
node.wstream.end(node.data.shift());
|
||||
var packet = node.data.shift()
|
||||
node.wstream.end(packet.data, function() {
|
||||
node.send(packet.msg);
|
||||
});
|
||||
delete node.wstream;
|
||||
delete node.wstreamIno;
|
||||
}
|
||||
|
Reference in New Issue
Block a user