@@ -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();
diff --git a/nodes/core/core/80-template.html b/nodes/core/core/80-template.html
index 2c121b587..31dfe97ca 100644
--- a/nodes/core/core/80-template.html
+++ b/nodes/core/core/80-template.html
@@ -77,7 +77,7 @@
}
The resulting property will be:
Hello Fred. Today is Monday
-
It is possible to use a property from the flow context or global context. Just use {{flow.name}}
or {{global.name}}
.
+
It is possible to use a property from the flow context or global context. Just use {{flow.name}}
or {{global.name}}
, or for persistable store store
use {{flow[store].name}}
or {{flobal[store].name}}
.
Note: By default, mustache will escape any HTML entities in the values it substitutes.
To prevent this, use {{{triple}}}
braces.
diff --git a/nodes/core/core/80-template.js b/nodes/core/core/80-template.js
index e257dd2fa..f6e96a857 100644
--- a/nodes/core/core/80-template.js
+++ b/nodes/core/core/80-template.js
@@ -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);
diff --git a/nodes/core/core/89-trigger.html b/nodes/core/core/89-trigger.html
index 57b8e4ba8..3bbdfe603 100644
--- a/nodes/core/core/89-trigger.html
+++ b/nodes/core/core/89-trigger.html
@@ -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
diff --git a/nodes/core/core/89-trigger.js b/nodes/core/core/89-trigger.js
index f5fd28b7f..48a595ccc 100644
--- a/nodes/core/core/89-trigger.js
+++ b/nodes/core/core/89-trigger.js
@@ -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 */
diff --git a/nodes/core/core/lib/debug/debug-utils.js b/nodes/core/core/lib/debug/debug-utils.js
index 17b850327..2d47155ba 100644
--- a/nodes/core/core/lib/debug/debug-utils.js
+++ b/nodes/core/core/lib/debug/debug-utils.js
@@ -455,24 +455,8 @@ RED.debug = (function() {
$(''+name+'').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 = $('').appendTo(msg);
var path = o.property||'';
var debugMessage = RED.utils.createObjectElement(payload, {
diff --git a/nodes/core/hardware/36-rpi-gpio.js b/nodes/core/hardware/36-rpi-gpio.js
index cc1a8ab2a..0a21f578a 100644
--- a/nodes/core/hardware/36-rpi-gpio.js
+++ b/nodes/core/hardware/36-rpi-gpio.js
@@ -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);
diff --git a/nodes/core/hardware/nrgpio.py b/nodes/core/hardware/nrgpio.py
index 6bbcddbbe..0cde0e4df 100755
--- a/nodes/core/hardware/nrgpio.py
+++ b/nodes/core/hardware/nrgpio.py
@@ -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}")
diff --git a/nodes/core/io/05-tls.html b/nodes/core/io/05-tls.html
index 704564c0e..1f68d497f 100644
--- a/nodes/core/io/05-tls.html
+++ b/nodes/core/io/05-tls.html
@@ -63,6 +63,11 @@
@@ -96,6 +101,7 @@
certname: {value:""},
keyname: {value:""},
caname: {value:""},
+ servername: {value:""},
verifyservercert: {value: true}
},
credentials: {
diff --git a/nodes/core/io/05-tls.js b/nodes/core/io/05-tls.js
index c4370257e..f5dca7beb 100644
--- a/nodes/core/io/05-tls.js
+++ b/nodes/core/io/05-tls.js
@@ -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;
diff --git a/nodes/core/io/22-websocket.js b/nodes/core/io/22-websocket.js
index 0d23ab84c..c2fab34e3 100644
--- a/nodes/core/io/22-websocket.js
+++ b/nodes/core/io/22-websocket.js
@@ -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) {
diff --git a/nodes/core/io/31-tcpin.js b/nodes/core/io/31-tcpin.js
index c3bc2ab8c..63eb4a478 100644
--- a/nodes/core/io/31-tcpin.js
+++ b/nodes/core/io/31-tcpin.js
@@ -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));
}
}
});
diff --git a/nodes/core/io/32-udp.js b/nodes/core/io/32-udp.js
index 096de479c..400f4bf18 100644
--- a/nodes/core/io/32-udp.js
+++ b/nodes/core/io/32-udp.js
@@ -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}));
diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json
index 74ec0bcd4..a8f9490f0 100644
--- a/nodes/core/locales/en-US/messages.json
+++ b/nodes/core/locales/en-US/messages.json
@@ -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,
payload
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" : {
diff --git a/nodes/core/locales/ja/core/75-exec.html b/nodes/core/locales/ja/core/75-exec.html
index bc7d9b5de..7eb75b10a 100644
--- a/nodes/core/locales/ja/core/75-exec.html
+++ b/nodes/core/locales/ja/core/75-exec.html
@@ -26,7 +26,7 @@
kill 文字列
execノードのプロセスに対して送るシグナルの種別を指定します
pid 数値|文字列
-
シグナル送信対象のexecノードのプロセスID
+
シグナル送信対象のexecノードのプロセスIDを指定します
出力
@@ -60,13 +60,12 @@
詳細
デフォルトでは、exec
システムコールを用いてコマンドを呼び出してその完了を待ち、出力を返します。例えば、コマンドの実行が成功した場合には、{ code: 0 }
と言う返却値を返します。
-
spawn
を使ってコマンドを実行し、
-標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、{ code: 0 }
と言う返却値を返します。
+
spawn
を使ってコマンドを実行し、標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、{ code: 0 }
という返却値を返します。
エラー発生時には、3番目の端子のmsg.payload
にmessage
、signal
など付加情報を返します。
実行対象のコマンドはノード設定で定義します。msg.payload
や追加引数をコマンドに追加することもできます。
-
コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- "This is a single parameter"
+
コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- "これは一つのパラメータです"
返却するpayload
は通常文字列ですが、UTF8文字以外が存在するとバッファとなります。
-
ノードが実行中の場合、ステータスアイコンとPIDを表示します。この状態変化はstatus
ノードで検知できます。
+
ノードが実行中の場合、ステータスアイコンとPIDを表示します。この状態変化はStatus
ノードで検知できます。
プロセスの停止
msg.kill
を受信すると、実行中のプロセスを停止することができます。msg.kill
には送出するシグナルの種別を指定します。例えば、SIGINT
、SIGQUIT
、SIGHUP
などです。空の文字列を指定した場合には、SIGTERM
を指定したものとみなします。
ノードが1つ以上のプロセスを実行している場合、msg.pid
に停止対象のPIDを指定しなければなりません。
diff --git a/nodes/core/locales/ja/core/80-function.html b/nodes/core/locales/ja/core/80-function.html
index 176951e33..2638abaca 100644
--- a/nodes/core/locales/ja/core/80-function.html
+++ b/nodes/core/locales/ja/core/80-function.html
@@ -18,7 +18,7 @@
受信メッセージに対して処理を行うJavaScriptコード(関数の本体)を定義します。
入力メッセージはmsg
という名称のJavaScriptオブジェクトで受け渡されます。
msg
オブジェクトはmsg.payload
プロパティにメッセージ本体を保持するのが慣例です。
-
通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。何も返却しない場合には、フロー実行を停止します。
+
通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。後続フローの実行を停止したい場合は、オブジェクトを返却しなくてもかまいません。
詳細
コードの書き方の詳細については、オンラインドキュメントを参照してください。
メッセージの送信
@@ -40,4 +40,10 @@
catchノードを用いてエラー処理が可能です。catchノードで処理させるためには、msg
をnode.error
の第二引数として渡します:
node.error("エラー",msg);
+
ノード情報の参照
+
コード中ではノードのIDおよび名前を以下のプロパティで参照できます:
+
+ node.id
- ノードのID
+ node.name
- ノードの名称
+
diff --git a/nodes/core/locales/ja/io/10-mqtt.html b/nodes/core/locales/ja/io/10-mqtt.html
index 0ee6976f2..200149565 100644
--- a/nodes/core/locales/ja/io/10-mqtt.html
+++ b/nodes/core/locales/ja/io/10-mqtt.html
@@ -63,6 +63,8 @@
ノードにクライアントIDを設定しておらずセッションの初期化を設定している場合、ランダムなクライアントIDを生成します。クライアントIDを設定する場合、接続先のブローカで一意となるようにしてください。
Birthメッセージ
接続を確立した際に、設定したトピックに対して発行するメッセージ
+
Closeメッセージ
+
接続が正常に終了する前に、ノードの再デプロイまたはシャットダウンした場合に、設定したトピックに対して発行するメッセージ
Willメッセージ
予期せず接続が切断された場合にブローカが発行するメッセージ
WebSocket
diff --git a/nodes/core/locales/ja/io/21-httprequest.html b/nodes/core/locales/ja/io/21-httprequest.html
index 7740632c1..662367343 100644
--- a/nodes/core/locales/ja/io/21-httprequest.html
+++ b/nodes/core/locales/ja/io/21-httprequest.html
@@ -30,7 +30,9 @@
payload
リクエストボディとして送るデータ
rejectUnauthorized
-
true
をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。
+
false
をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。
+
followRedirects
+
false
をセットすると、リダイレクトを行いません。デフォルトはtrue
です。
出力
diff --git a/nodes/core/locales/ja/logic/10-switch.html b/nodes/core/locales/ja/logic/10-switch.html
index bb0e20ac8..2417ad164 100644
--- a/nodes/core/locales/ja/logic/10-switch.html
+++ b/nodes/core/locales/ja/logic/10-switch.html
@@ -29,7 +29,11 @@
その他 - これより前のルールにマッチするものがなかった場合に適用
+
注釈
+
is true/false
とis null
のルールは、型に対して厳密な比較を行います。型変換した上での比較はしません。
+
is empty
のルールは、長さ0の文字列・配列・バッファ、またはプロパティを持たないオブジェクトを出力します。null
やundefined
は出力しません。
+
メッセージ列の扱い
switchノードは入力メッセージの列に関する情報を保持するmsg.parts
をデフォルトでは変更しません。
-
「メッセージ列の補正」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchノードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。nodeMessageBufferMaxLength
を設定すると、蓄積するメッセージ数を制限できます。
+
「メッセージ列の補正」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchノードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。settings.jsのnodeMessageBufferMaxLength
を設定すると、蓄積するメッセージ数を制限できます。
diff --git a/nodes/core/locales/ja/logic/19-batch.html b/nodes/core/locales/ja/logic/19-batch.html
index 6109866ff..41cef364c 100644
--- a/nodes/core/locales/ja/logic/19-batch.html
+++ b/nodes/core/locales/ja/logic/19-batch.html
@@ -30,5 +30,5 @@
メッセージの蓄積
-
このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。nodeMessageBufferMaxLength
を指定することで蓄積するメッセージの最大値を制限することができます。
+
このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。settings.jsのnodeMessageBufferMaxLength
を指定することで蓄積するメッセージの最大値を制限することができます。
diff --git a/nodes/core/locales/ja/messages.json b/nodes/core/locales/ja/messages.json
index 921314ea8..f160164d3 100644
--- a/nodes/core/locales/ja/messages.json
+++ b/nodes/core/locales/ja/messages.json
@@ -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": "標準では
payload
が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": "型に基づいて
msg.payload
を分割:",
"object": "
オブジェクト",
"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": "
msg.complete
プロパティが設定されたメッセージ受信後",
"tip": "このモードでは、本ノードが
split ノードと組となるか、
msg.parts
プロパティが設定されたメッセージを受け取ることが前提となります。",
- "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プロパティがありません"
}
}
diff --git a/nodes/core/locales/ja/parsers/70-JSON.html b/nodes/core/locales/ja/parsers/70-JSON.html
index 2199d7f89..80595789f 100644
--- a/nodes/core/locales/ja/parsers/70-JSON.html
+++ b/nodes/core/locales/ja/parsers/70-JSON.html
@@ -20,6 +20,8 @@
- payloadオブジェクト | 文字列
- JavaScriptオブジェクトもしくはJSON文字列
+ - schemaオブジェクト
+ - JSONの検証に利用するJSONスキーマ。設定されていない場合は検証を行いません。
出力
@@ -30,9 +32,12 @@
入力がJavaScriptオブジェクトの場合、JSON文字列に変換します。JSON文字列は整形することも可能です。
+
schemaError配列
+
JSONの検証でエラーが発生した場合、Catchノードを利用し、エラーを配列としてschemaError
プロパティから取得することができます。
詳細
デフォルトの変換対象はmsg.payload
ですが、他のメッセージプロパティを変換対象とすることも可能です。
双方向の変換を自動選択するのではなく、特定の変換のみ行うように設定できます。この機能は、例えば、HTTP In
ノードに対するリクエストがcontent-typeを正しく設定していない場合であっても、JSONノードによる変換結果がJavaScriptオブジェクトであることを保証するために利用します。
JSON文字列への変換が指定されている場合、受信した文字列に対してさらなるチェックは行いません。すなわち、文字列がJSONとして正しいかどうかの検査や、整形オプションを指定していたとしても整形処理を実施しません。
+
JSONスキーマの詳細については、こちらを参照してください。
diff --git a/nodes/core/locales/ja/storage/50-file.html b/nodes/core/locales/ja/storage/50-file.html
index 2e9bb839b..8bd5cdbaf 100644
--- a/nodes/core/locales/ja/storage/50-file.html
+++ b/nodes/core/locales/ja/storage/50-file.html
@@ -21,6 +21,8 @@
filename 文字列
対象ファイル名をノードに設定していない場合、このプロパティでファイルを指定できます
+
出力
+
書き込みの完了時、入力メッセージを出力端子に送出します。
詳細
入力メッセージのペイロードをファイルの最後に追記します。改行(\n)を各データの最後に追加することもできます。
msg.filename
を使う場合、書き込みを行う毎にファイルをクローズします。より良い性能を得るためにはファイル名をノードに設定してください。
diff --git a/nodes/core/logic/10-switch.html b/nodes/core/logic/10-switch.html
index f4ffc7dea..4b8c8405b 100644
--- a/nodes/core/logic/10-switch.html
+++ b/nodes/core/logic/10-switch.html
@@ -60,6 +60,12 @@
An Otherwise rule can be used to match if none of the preceeding
rules have matched.
+
Notes
+
The is true/false
and is null
rules perform strict
+ comparisons against those types. They do not convert between types.
+
The is empty
rule passes for Strings, Arrays and Buffers that have
+ a length of 0, or Objects that have no properties. It does not pass for null
+ or undefined
values.
Handling message sequences
By default, the node does not modify the msg.parts
property of messages
that are part of a sequence.
@@ -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');
diff --git a/nodes/core/logic/10-switch.js b/nodes/core/logic/10-switch.js
index 919fe13ac..89593047e 100644
--- a/nodes/core/logic/10-switch.js
+++ b/nodes/core/logic/10-switch.js
@@ -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
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.error(err,nextMsg);
+ return processMessageQueue();
+ });
+ }
+
this.on('input', function(msg) {
- process_msg(msg, true);
+ processMessageQueue(msg);
});
this.on('close', function() {
- clear_pending();
+ clearPending();
});
}
diff --git a/nodes/core/logic/15-change.html b/nodes/core/logic/15-change.html
index 83bdecf9e..359b43fdd 100644
--- a/nodes/core/logic/15-change.html
+++ b/nodes/core/logic/15-change.html
@@ -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});
diff --git a/nodes/core/logic/15-change.js b/nodes/core/logic/15-change.js
index c7c05118e..2c1443846 100644
--- a/nodes/core/logic/15-change.js
+++ b/nodes/core/logic/15-change.js
@@ -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 { if (msg) { node.send(msg) }} )
+ .catch( err => node.error(err, msg))
});
}
}
diff --git a/nodes/core/logic/17-split.html b/nodes/core/logic/17-split.html
index 4c2b9313e..9c2866b9c 100644
--- a/nodes/core/logic/17-split.html
+++ b/nodes/core/logic/17-split.html
@@ -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]});
diff --git a/nodes/core/logic/17-split.js b/nodes/core/logic/17-split.js
index 92b527d01..3170ba584 100644
--- a/nodes/core/logic/17-split.js
+++ b/nodes/core/logic/17-split.js
@@ -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);
}
});
diff --git a/nodes/core/logic/18-sort.js b/nodes/core/logic/18-sort.js
index e30535dc1..124392014 100644
--- a/nodes/core/logic/18-sort.js
+++ b/nodes/core/logic/18-sort.js
@@ -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);
diff --git a/nodes/core/parsers/70-JSON.html b/nodes/core/parsers/70-JSON.html
index 565bae9a0..32b5a332a 100644
--- a/nodes/core/parsers/70-JSON.html
+++ b/nodes/core/parsers/70-JSON.html
@@ -31,6 +31,8 @@
- payloadobject | string
- A JavaScript object or JSON string.
+ - schemaobject
+ - An optional JSON Schema object to validate the payload against.
Outputs
@@ -41,6 +43,9 @@
If the input is a JavaScript object it creates a JSON string. The string can optionally be well-formatted.
+ schemaErrorarray
+ If JSON schema validation fails, the catch node will have a schemaError
property
+ containing an array of errors.
Details
By default, the node operates on msg.payload
, 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.
+ For more details about JSON Schema you can consult the specification
+ here.