diff --git a/.gitignore b/.gitignore index e6623f64..729dabf3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ puball.sh setenv.sh /.project package-lock.json +social/xmpp/92-xmpp.old +*.tgz diff --git a/function/random/README.md b/function/random/README.md index 0713d08a..91710a84 100644 --- a/function/random/README.md +++ b/function/random/README.md @@ -19,4 +19,6 @@ If set to return an integer it can include both the low and high values. If set to return a floating point value it will be from the low value, up to, but **not** including the high value. `min <= n < max` - so selecting 1 to 6 will return values 1 <= n < 6 . +You can dynamically pass in the 'From' and 'To' values to the node using msg.to and/or msg.from. **NOTE:** hard coded values in the node **always take precedence**. + **Note:** This returns numbers - objects of type **number**. diff --git a/function/random/locales/en-US/random.html b/function/random/locales/en-US/random.html index dbf239b4..9c18b0da 100644 --- a/function/random/locales/en-US/random.html +++ b/function/random/locales/en-US/random.html @@ -1,7 +1,16 @@ diff --git a/function/random/locales/ja/random.html b/function/random/locales/ja/random.html index ffb9b2c0..5b09fea8 100644 --- a/function/random/locales/ja/random.html +++ b/function/random/locales/ja/random.html @@ -1,7 +1,16 @@ diff --git a/function/random/package.json b/function/random/package.json index 43965ece..2f2f4dc0 100644 --- a/function/random/package.json +++ b/function/random/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-random", - "version" : "0.2.0", + "version" : "0.3.1", "description" : "A Node-RED node that when triggered generates a random number between two values.", "dependencies" : { }, @@ -19,5 +19,8 @@ "name": "Dave Conway-Jones", "email": "ceejay@vnet.ibm.com", "url": "http://nodered.org" - } + }, + "contributors": [ + {"name": "@zenofmud"} + ] } diff --git a/function/random/random.html b/function/random/random.html index bf1ae44a..7443615f 100644 --- a/function/random/random.html +++ b/function/random/random.html @@ -31,8 +31,8 @@ color:"#E2D96E", defaults: { name: {value:""}, - low: {value:"1"}, - high: {value:"10"}, + low: {value: 1,validate:function(v) { return !isNaN(v) || v.length === 0;} }, + high: {value: 10,validate:function(v) { return !isNaN(v) || v.length === 0;} }, inte: {value:"true"}, property: {value:"payload",required:true} }, diff --git a/function/random/random.js b/function/random/random.js index dac7b0b9..681cfefe 100644 --- a/function/random/random.js +++ b/function/random/random.js @@ -3,21 +3,73 @@ module.exports = function(RED) { "use strict"; function RandomNode(n) { RED.nodes.createNode(this,n); - this.low = Number(n.low || 1); - this.high = Number(n.high || 10); + + this.low = n.low + this.high = n.high this.inte = n.inte || false; this.property = n.property||"payload"; var node = this; + var tmp = {}; + this.on("input", function(msg) { - var value; - if (node.inte == "true" || node.inte === true) { - value = Math.round(Math.random() * (node.high - node.low + 1) + node.low - 0.5); + + tmp.low = 1 // set this as the default low value + tmp.low_e = "" + if (node.low) { // if the the node has a value use it + tmp.low = Number(node.low); + } else if ('from' in msg) { // else see if a 'from' is in the msg + if (Number(msg.from)) { // if it is, and is a number, use it + tmp.low = Number(msg.from); + } else { // otherwise setup NaN error + tmp.low = NaN; + tmp.low_e = " From: " + msg.from; // setup to show bad incoming msg.from + } } - else { - value = Math.random() * (node.high - node.low) + node.low; + + tmp.high = 10 // set this as the default high value + tmp.high_e = ""; + if (node.high) { // if the the node has a value use it + tmp.high = Number(node.high); + } else if ('to' in msg) { // else see if a 'to' is in the msg + if (Number(msg.to)) { // if it is, and is a number, use it + tmp.high = Number(msg.to); + } else { // otherwise setup NaN error + tmp.high = NaN + tmp.high_e = " To: " + msg.to // setup to show bad incoming msg.to + } + } + + // if tmp.low or high are not numbers, send an error msg with bad values + if ( (isNaN(tmp.low)) || (isNaN(tmp.high)) ) { + this.error("Random: one of the input values is not a number. " + tmp.low_e + tmp.high_e); + } else { + // at this point we have valid values so now to generate the random number! + + // flip the values if low > high so random will work + var value = 0; + if (tmp.low > tmp.high) { + value = tmp.low + tmp.low = tmp.high + tmp.high = value + } + + // if returning an integer, do a math.ceil() on the low value and a + // Math.floor()high value before generate the random number. This must be + // done to insure the rounding doesn't round up if using something like 4.7 + // which would end up with 5 + if ( (node.inte == "true") || (node.inte === true) ) { + tmp.low = Math.ceil(tmp.low); + tmp.high = Math.floor(tmp.high); + // use this to round integers + value = Math.round(Math.random() * (tmp.high - tmp.low + 1) + tmp.low - 0.5); + } else { + // use this to round floats + value = (Math.random() * (tmp.high - tmp.low)) + tmp.low; + } + + RED.util.setMessageProperty(msg,node.property,value); + node.send(msg); } - RED.util.setMessageProperty(msg,node.property,value); - node.send(msg); }); } RED.nodes.registerType("random",RandomNode); diff --git a/hardware/Arduino/package.json b/hardware/Arduino/package.json index b1c8e059..14a9d18f 100644 --- a/hardware/Arduino/package.json +++ b/hardware/Arduino/package.json @@ -3,7 +3,7 @@ "version" : "0.3.1", "description" : "A Node-RED node to talk to an Arduino running firmata", "dependencies" : { - "firmata" : "^2.0.0" + "firmata" : "^2.3.0" }, "repository" : { "type":"git", diff --git a/hardware/PiGpio/36-rpi-gpio.js b/hardware/PiGpio/36-rpi-gpio.js index c5b11dfc..1c263f30 100644 --- a/hardware/PiGpio/36-rpi-gpio.js +++ b/hardware/PiGpio/36-rpi-gpio.js @@ -4,7 +4,6 @@ module.exports = function(RED) { var execSync = require('child_process').execSync; var exec = require('child_process').exec; var spawn = require('child_process').spawn; - var fs = require('fs'); var testCommand = __dirname+'/testgpio.py' var gpioCommand = __dirname+'/nrgpio'; @@ -271,16 +270,21 @@ module.exports = function(RED) { RED.nodes.createNode(this,n); var node = this; - if (allOK === true) { + var doConnect = function() { node.child = spawn(gpioCommand+".py", ["kbd","0"]); node.status({fill:"green",shape:"dot",text:"rpi-gpio.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 }); + var d = data.toString().trim().split("\n"); + for (var i = 0; i < d.length; i++) { + if (d[i] !== '') { + var b = d[i].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) { @@ -295,7 +299,10 @@ module.exports = function(RED) { node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); node.finished(); } - else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); } + else { + node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); + setTimeout(function() { doConnect(); },2000) + } }); node.child.on('error', function (err) { @@ -303,6 +310,10 @@ module.exports = function(RED) { else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } }); + } + + if (allOK === true) { + doConnect(); node.on("close", function(done) { node.status({}); diff --git a/hardware/PiGpio/package.json b/hardware/PiGpio/package.json index e948b4ba..4331e228 100644 --- a/hardware/PiGpio/package.json +++ b/hardware/PiGpio/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-pi-gpio", - "version": "1.2.0", + "version": "1.2.3", "description": "The basic Node-RED node for Pi GPIO", "dependencies" : { }, diff --git a/hardware/blink1/package.json b/hardware/blink1/package.json index 32f29182..cbc9c384 100644 --- a/hardware/blink1/package.json +++ b/hardware/blink1/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-blink1", - "version" : "0.0.17", + "version" : "0.0.18", "description" : "A Node-RED node to control a Thingm Blink(1)", "dependencies" : { - "node-blink1" : "0.2.2" + "node-blink1" : "0.5.1" }, "repository" : { "type":"git", diff --git a/hardware/blinkstick/76-blinkstick.html b/hardware/blinkstick/76-blinkstick.html index 0f9ce707..f72f3203 100644 --- a/hardware/blinkstick/76-blinkstick.html +++ b/hardware/blinkstick/76-blinkstick.html @@ -1,5 +1,5 @@ - - + + + + diff --git a/hardware/blinkstick/blinkstick.js b/hardware/blinkstick/blinkstick.js new file mode 100644 index 00000000..389dd605 --- /dev/null +++ b/hardware/blinkstick/blinkstick.js @@ -0,0 +1,64 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var blinkstick = require("blinkstick"); + + Object.size = function(obj) { + var size = 0; + for (var key in obj) { if (obj.hasOwnProperty(key)) { size++; } } + return size; + }; + + function BlinkStick(n) { + RED.nodes.createNode(this,n); + var p1 = /^\#[A-Fa-f0-9]{6}$/ + var p2 = /[0-9]+,[0-9]+,[0-9]+/ + this.led = blinkstick.findFirst(); // maybe try findAll() (one day) + var node = this; + + this.on("input", function(msg) { + if (msg != null) { + if (Object.size(node.led) !== 0) { + try { + if (p2.test(msg.payload)) { + var rgb = msg.payload.split(","); + node.led.setColor(parseInt(rgb[0])&255, parseInt(rgb[1])&255, parseInt(rgb[2])&255); + } + else { + node.led.setColor(msg.payload.toLowerCase().replace(/\s+/g,'')); + } + } + catch (err) { + node.warn("BlinkStick missing ?"); + node.led = blinkstick.findFirst(); + } + } + else { + //node.warn("No BlinkStick found"); + node.led = blinkstick.findFirst(); + } + } + }); + if (Object.size(node.led) === 0) { + node.error("No BlinkStick found"); + } + + } + + RED.nodes.registerType("blinkstick",BlinkStick); +} diff --git a/hardware/blinkstick/package.json b/hardware/blinkstick/package.json index a7c2c344..6b43e404 100644 --- a/hardware/blinkstick/package.json +++ b/hardware/blinkstick/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-blinkstick", - "version" : "0.1.16", + "version" : "0.1.7", "description" : "A Node-RED node to control a Blinkstick", "dependencies" : { - "blinkstick" : "1.1.3" + "blinkstick" : "1.2.0" }, "repository" : { "type":"git", diff --git a/hardware/mcp3008/package.json b/hardware/mcp3008/package.json index 218ce52d..8b221b0d 100644 --- a/hardware/mcp3008/package.json +++ b/hardware/mcp3008/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-pi-mcp3008", - "version" : "0.2.1", + "version" : "0.3.0", "description" : "A Node-RED node to read from the MCP3008 Analogue to Digital Converter", "dependencies" : { - "mcp-spi-adc": "^2.0.6" + "mcp-spi-adc": "^3.1.0" }, "repository" : { "type":"git", diff --git a/hardware/mcp3008/pimcp3008.js b/hardware/mcp3008/pimcp3008.js index e765836f..61c74485 100644 --- a/hardware/mcp3008/pimcp3008.js +++ b/hardware/mcp3008/pimcp3008.js @@ -2,16 +2,23 @@ module.exports = function(RED) { "use strict"; var fs = require('fs'); + var allOK = false; + var mcpadc; // unlikely if not on a Pi try { var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); - if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); } + if (cpuinfo.indexOf(": BCM") === -1) { + RED.log.warn("Info : mcp3xxx : Not running on a Pi - Ignoring node"); + } + else { + mcpadc = require('mcp-spi-adc'); + allOK = true; + } } catch(err) { - throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); + RED.log.warn("Info : mcp3xxx : Not running on a Pi - Ignoring node"); } - var mcpadc = require('mcp-spi-adc'); var mcp3xxx = []; function PiMcpNode(n) { @@ -26,49 +33,54 @@ module.exports = function(RED) { var opt = { speedHz:20000, deviceNumber:node.dnum, busNumber:node.bus }; var chans = parseInt(this.dev.substr(3)); - try { - fs.statSync("/dev/spidev"+node.bus+"."+node.dnum); - if (mcp3xxx.length === 0) { - for (var i=0; i= 0) && (pay < chans)) { pin = pay; } + else { node.warn("Payload needs to select channel 0 to "+(chans-1)); } + } + else { pin = parseInt(node.pin); } + if (pin !== null) { + mcp3xxx[pin].read(function (err, reading) { + if (err) { node.warn("Read error: "+err); } + else { node.send({payload:reading.rawValue, topic:"adc/"+pin}); } + }); + } + }); } - node.on("input", function(msg) { - var pin = null; - if (node.pin === "M") { - var pay = parseInt(msg.payload.toString()); - if ((pay >= 0) && (pay < chans)) { pin = pay; } - else { node.warn("Payload needs to select channel 0 to "+(chans-1)); } - } - else { pin = parseInt(node.pin); } - if (pin !== null) { - mcp3xxx[pin].read(function (err, reading) { - if (err) { node.warn("Read error: "+err); } - else { node.send({payload:reading.rawValue, topic:"adc/"+pin}); } - }); + catch(err) { + node.error("Error : Can't find SPI device - is SPI enabled in raspi-config ?"); + } + + node.on("close", function(done) { + if (mcp3xxx.length !== 0) { + var j=0; + for (var i=0; i + - @@ -76,6 +87,7 @@ color:"#c6dbef", defaults: { name: { value:"" }, + gpio: { value:18 }, pixels: { value:"", required:true, validate:RED.validators.number() }, bgnd: { value:"" }, fgnd: { value:"" }, diff --git a/hardware/neopixel/neopixel.js b/hardware/neopixel/neopixel.js index 2d905647..12214b33 100644 --- a/hardware/neopixel/neopixel.js +++ b/hardware/neopixel/neopixel.js @@ -40,6 +40,9 @@ module.exports = function(RED) { this.rgb = n.rgb || "rgb"; this.gamma = n.gamma; if (this.gamma === undefined) { this.gamma = true; } + this.gpio = n.gpio || 18; + this.channel = 0; + if (this.gpio == 13 || this.gpio == 19) { this.channel = 1; } this.brightness = Number(n.brightness || 100); this.wipe = Number(n.wipe || 40); if (this.wipe < 0) { this.wipe = 0; } @@ -114,7 +117,7 @@ module.exports = function(RED) { } if (allOK === true) { - node.child = spawn(piCommand, [node.pixels, node.wipe, node.mode, node.brightness, node.gamma]); + node.child = spawn(piCommand, [node.pixels, node.wipe, node.mode, node.brightness, node.gamma, node.channel, node.gpio]); node.status({fill:"green",shape:"dot",text:"ok"}); node.on("input", inputlistener); diff --git a/hardware/neopixel/package.json b/hardware/neopixel/package.json index 841fbc78..6a1e65eb 100644 --- a/hardware/neopixel/package.json +++ b/hardware/neopixel/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-pi-neopixel", - "version" : "0.0.25", + "version" : "0.1.1", "description" : "A Node-RED node to output to a neopixel (ws2812) string of LEDS from a Raspberry Pi.", "dependencies" : { }, diff --git a/hardware/pigpiod/package.json b/hardware/pigpiod/package.json index 350f8067..ffa0a018 100644 --- a/hardware/pigpiod/package.json +++ b/hardware/pigpiod/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-pi-gpiod", - "version": "0.1.0", + "version": "0.2.0", "description": "A node-red node for PiGPIOd", "dependencies" : { "js-pigpio": "*" diff --git a/hardware/pigpiod/pi-gpiod.html b/hardware/pigpiod/pi-gpiod.html index 859070d8..86018771 100644 --- a/hardware/pigpiod/pi-gpiod.html +++ b/hardware/pigpiod/pi-gpiod.html @@ -1,5 +1,5 @@ - - - - diff --git a/io/ping/88-ping.js b/io/ping/88-ping.js index 36dd6b70..2c428630 100644 --- a/io/ping/88-ping.js +++ b/io/ping/88-ping.js @@ -4,7 +4,7 @@ module.exports = function(RED) { var spawn = require("child_process").spawn; var plat = require("os").platform(); - function doPing(node, host, arrayMode){ + function doPing(node, host, arrayMode) { const defTimeout = 5000; var ex, hostOptions, commandLineOptions; if (typeof host === "string") { @@ -25,30 +25,27 @@ module.exports = function(RED) { if (arrayMode) { msg.ping = hostOptions } - if (plat == "linux" || plat == "android") { + if (plat == "linux" || plat == "android") { commandLineOptions = ["-n", "-w", timeoutS, "-c", "1"] - } else if (plat.match(/^win/)) { + } else if (plat.match(/^win/)) { commandLineOptions = ["-n", "1", "-w", hostOptions.timeout] - } else if (plat == "darwin" || plat == "freebsd") { + } else if (plat == "darwin" || plat == "freebsd") { commandLineOptions = ["-n", "-t", timeoutS, "-c", "1"] - } else { - node.error("Sorry - your platform - "+plat+" - is not recognised.", msg); + } else { + node.error("Sorry - your platform - "+plat+" - is not recognised.", msg); return; //dont pass go - just return! } //spawn with timeout in case of os issue - ex = spawn("ping", [...commandLineOptions, hostOptions.host]); + ex = spawn("ping", [...commandLineOptions, hostOptions.host]); //monitor every spawned process & SIGINT if too long var spawnTout = setTimeout(() => { node.log(`ping - Host '${hostOptions.host}' process timeout - sending SIGINT`) try { - if (ex && ex.pid) { - ex.removeAllListeners(); - ex.kill("SIGINT"); - } - } - catch(e) { console.warn(e) } + if (ex && ex.pid) { ex.kill("SIGINT"); } + } + catch(e) {console.warn(e); } }, hostOptions.timeout+1000); //add 1s for grace var res = false; @@ -56,11 +53,9 @@ module.exports = function(RED) { var fail = false; //var regex = /from.*time.(.*)ms/; var regex = /=.*[<|=]([0-9]*).*TTL|ttl..*=([0-9\.]*)/; - if (ex && ex.hasOwnProperty("stdout")) { - ex.stdout.on("data", function (data) { - line += data.toString(); - }); - } + ex.stdout.on("data", function (data) { + line += data.toString(); + }); ex.on("exit", function (err) { clearTimeout(spawnTout); }); @@ -99,7 +94,7 @@ module.exports = function(RED) { function generatePingList(str) { return (str + "").split(",").map((e) => (e + "").trim()).filter((e) => e != ""); } - function clearPingInterval(){ + function clearPingInterval() { if (node.tout) { clearInterval(node.tout); } } diff --git a/io/ping/package.json b/io/ping/package.json index 64c399ec..cbf02369 100644 --- a/io/ping/package.json +++ b/io/ping/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-ping", - "version" : "0.2.1", + "version" : "0.2.2", "description" : "A Node-RED node to ping a remote server, for use as a keep-alive check.", "dependencies" : { }, diff --git a/io/serialport/25-serial.js b/io/serialport/25-serial.js index 2d9b4f2c..9612e943 100644 --- a/io/serialport/25-serial.js +++ b/io/serialport/25-serial.js @@ -5,6 +5,7 @@ module.exports = function(RED) { var events = require("events"); var serialp = require("serialport"); var bufMaxSize = 32768; // Max serial buffer size, for inputs... + const serialReconnectTime = settings.serialReconnectTime || 15000; // TODO: 'serialPool' should be encapsulated in SerialPortNode @@ -350,7 +351,7 @@ module.exports = function(RED) { } obj.tout = setTimeout(function() { setupSerial(); - }, settings.serialReconnectTime); + }, serialReconnectTime); } }); obj.serial.on('error', function(err) { @@ -359,7 +360,7 @@ module.exports = function(RED) { if (obj.tout) { clearTimeout(obj.tout); } obj.tout = setTimeout(function() { setupSerial(); - }, settings.serialReconnectTime); + }, serialReconnectTime); }); obj.serial.on('close', function() { if (!obj._closing) { @@ -371,7 +372,7 @@ module.exports = function(RED) { if (obj.tout) { clearTimeout(obj.tout); } obj.tout = setTimeout(function() { setupSerial(); - }, settings.serialReconnectTime); + }, serialReconnectTime); } }); obj.serial.on('open',function() { diff --git a/io/serialport/locales/ja/25-serial.json b/io/serialport/locales/ja/25-serial.json index 647ed53c..fb570e87 100644 --- a/io/serialport/locales/ja/25-serial.json +++ b/io/serialport/locales/ja/25-serial.json @@ -1,5 +1,9 @@ { "serial": { + "status": { + "waiting": "waiting", + "timeout": "timeout" + }, "label": { "serialport": "シリアルポート", "settings": "設定", @@ -11,8 +15,13 @@ "split": "入力の分割方法", "deliver": "分割後の配信データ", "output": "出力", + "request": "リクエスト", + "responsetimeout": "デフォルトの応答タイムアウト", + "ms": "ミリ秒", "serial": "serial", - "none": "なし" + "none": "なし", + "start": "オプションで開始文字", + "startor": "を待ちます。" }, "placeholder": { "serialport": "例: /dev/ttyUSB0/" @@ -25,13 +34,14 @@ "space": "スペース" }, "linestates": { - "none": "auto", - "high": "High", - "low": "Low" + "none": "自動", + "high": "高", + "low": "低" }, "split": { "character": "文字列で区切る", "timeout": "タイムアウト後で区切る", + "silent": "一定の待ち時間後に区切る", "lengths": "一定の文字数で区切る" }, "output": { @@ -40,19 +50,24 @@ }, "addsplit": "出力メッセージに分割文字を追加する", "tip": { + "responsetimeout": "Tip: デフォルトの応答タイムアウトは msg.timeout の設定で上書きすることができます。", "split": "Tip: \"区切り\" 文字は、入力を別々のメッセージに分割するために使用され、シリアルポートに送信されるすべてのメッセージに追加することもできます。", - "timeout": "Tip: タイムアウトモードでのタイムアウトは最初の文字が到着したときから始まります。" + "silent": "Tip: In line-silent mode timeout is restarted upon arrival of any character (i.e. inter-byte timeout).", + "timeout": "Tip: タイムアウトモードでのタイムアウトは最初の文字が到着したときから始まります。", + "count": "Tip: カウントモードでは msg.count 設定は、構成された値よりも小さいときに限り、構成されたカウントを上書きすることができます。", + "waitfor": "Tip: オプションです。すべてのデータを受信するには、空白のままにします。文字($)・エスケープコード(\\n)・16進コード(0x02)を受け入れることができます。" , + "addchar": "Tip: この文字は、シリアルポートに送信されるすべてのメッセージに追加されます。通常は \\r や \\n です。" }, - "onopen": "serial port __port__ opened at __baud__ baud __config__", + "onopen": "シリアルポート __port__ が __baud__ ボー __config__ で開かれました", "errors": { - "missing-conf": "missing serial config", - "serial-port": "serial port", - "error": "serial port __port__ error: __error__", - "unexpected-close": "serial port __port__ closed unexpectedly", - "disconnected": "serial port __port__ disconnected", - "closed": "serial port __port__ closed", + "missing-conf": "シリアル設定がありません。", + "serial-port": "シリアルポート", + "error": "シリアルポート __port__ エラー: __error__", + "unexpected-close": "シリアルポート __port__ が予期せず閉じられました", + "disconnected": "シリアルポート __port__ 切断", + "closed": "シリアルポート __port__ 閉じられました", "list": "ポートのリスト化に失敗しました。手動で入力してください。", - "badbaudrate": "ボーレートが不正です" + "badbaudrate": "ボーレートが不正です。" } } } diff --git a/io/serialport/package.json b/io/serialport/package.json index d8f237ed..aefb7123 100644 --- a/io/serialport/package.json +++ b/io/serialport/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-serialport", - "version" : "0.11.0", + "version" : "0.11.1", "description" : "Node-RED nodes to talk to serial ports", "dependencies" : { - "serialport" : "^9.0.1" + "serialport" : "^9.0.2" }, "repository" : { "type":"git", diff --git a/package.json b/package.json index 08c1e6af..bf551b0a 100644 --- a/package.json +++ b/package.json @@ -40,22 +40,22 @@ "grunt-lint-inline": "^1.0.0", "grunt-simple-mocha": "^0.4.1", "imap": "^0.8.19", - "mailparser": "^3.0.0", + "mailparser": "~3.0.1", "markdown-it": "^11.0.0", "mocha": "~6.2.3", "msgpack-lite": "^0.1.26", "multilang-sentiment": "^1.2.0", "ngeohash": "^0.6.3", - "node-red": "^1.1.3", + "node-red": "~1.2.6", "node-red-node-test-helper": "~0.2.5", - "nodemailer": "^6.4.10", + "nodemailer": "~6.4.16", "poplib": "^0.1.7", "proxyquire": "^2.1.3", "pushbullet": "^2.4.0", "sentiment": "^2.1.0", "should": "^13.2.3", "sinon": "~7.5.0", - "smtp-server": "^3.7.0", + "smtp-server": "~3.8.0", "supertest": "^4.0.2", "when": "^3.7.8" }, diff --git a/social/email/61-email.js b/social/email/61-email.js index 272c7390..0c7b3b5a 100644 --- a/social/email/61-email.js +++ b/social/email/61-email.js @@ -94,6 +94,8 @@ module.exports = function(RED) { sendopts.inReplyTo = msg.inReplyTo; sendopts.replyTo = msg.replyTo; sendopts.references = msg.references; + sendopts.headers = msg.headers; + sendopts.priority = msg.priority; } sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line if (msg.hasOwnProperty("envelope")) { sendopts.envelope = msg.envelope; } @@ -118,7 +120,18 @@ module.exports = function(RED) { var payload = RED.util.ensureString(msg.payload); sendopts.text = payload; // plaintext body if (/<[a-z][\s\S]*>/i.test(payload)) { sendopts.html = payload; } // html body - if (msg.attachments) { sendopts.attachments = msg.attachments; } // add attachments + if (msg.attachments && Array.isArray(msg.attachments)) { + sendopts.attachments = msg.attachments; + for (var a=0; a < sendopts.attachments.length; a++) { + if (sendopts.attachments[a].hasOwnProperty("content")) { + if (typeof sendopts.attachments[a].content !== "string" && !Buffer.isBuffer(sendopts.attachments[a].content)) { + node.error(RED._("email.errors.invalidattachment"),msg); + node.status({fill:"red",shape:"ring",text:"email.status.sendfail"}); + return; + } + } + } + } } smtpTransport.sendMail(sendopts, function(error, info) { if (error) { diff --git a/social/email/locales/en-US/61-email.html b/social/email/locales/en-US/61-email.html index 282ff3b2..3806b8de 100644 --- a/social/email/locales/en-US/61-email.html +++ b/social/email/locales/en-US/61-email.html @@ -2,7 +2,8 @@

Sends the msg.payload as an email, with a subject of msg.topic.

The default message recipient can be configured in the node, if it is left blank it should be set using the msg.to property of the incoming message. If left blank - you can also specify any or all of: msg.cc, msg.bcc, msg.replyTo, msg.inReplyTo, msg.references properties.

+ you can also specify any or all of: msg.cc, msg.bcc, msg.replyTo, + msg.inReplyTo, msg.references, msg.headers, or msg.priority properties.

You may optionally set msg.from in the payload which will override the userid default value.

GMail users

diff --git a/social/email/locales/en-US/61-email.json b/social/email/locales/en-US/61-email.json index cca6fa1d..606580b7 100644 --- a/social/email/locales/en-US/61-email.json +++ b/social/email/locales/en-US/61-email.json @@ -60,7 +60,8 @@ "fetchfail": "Failed to fetch folder: __folder__", "parsefail": "Failed to parse message", "messageerror": "Fetch message error: __error__", - "refreshtoolarge": "Refresh interval too large. Limiting to 2147483 seconds" + "refreshtoolarge": "Refresh interval too large. Limiting to 2147483 seconds", + "invalidattachment": "Invalid attachment content. Must be String or buffer" } } } diff --git a/social/email/locales/ja/61-email.html b/social/email/locales/ja/61-email.html index d8dbfac9..7e7f64c5 100644 --- a/social/email/locales/ja/61-email.html +++ b/social/email/locales/ja/61-email.html @@ -2,7 +2,7 @@ - - - + + diff --git a/storage/sqlite/locales/en-US/sqlite.json b/storage/sqlite/locales/en-US/sqlite.json new file mode 100644 index 00000000..059f1194 --- /dev/null +++ b/storage/sqlite/locales/en-US/sqlite.json @@ -0,0 +1,20 @@ +{ + "sqlite": { + "label": { + "database": "Database", + "sqlQuery": "SQL Query", + "viaMsgTopic": "Via msg.topic", + "fixedStatement": "Fixed Statement", + "preparedStatement": "Prepared Statement", + "batchWithoutResponse": "Batch without response", + "sqlStatement": "SQL Statement", + "mode": "Mode", + "readWriteCreate": "Read-Write-Create", + "readWrite": "Read-Write", + "readOnly": "Read-Only" + }, + "tips": { + "memoryDb": "Note: Setting the database name to :memory: will create a non-persistant in memory database." + } + } +} diff --git a/storage/sqlite/locales/ja/sqlite.html b/storage/sqlite/locales/ja/sqlite.html new file mode 100644 index 00000000..5c6e8da1 --- /dev/null +++ b/storage/sqlite/locales/ja/sqlite.html @@ -0,0 +1,26 @@ + + + diff --git a/storage/sqlite/locales/ja/sqlite.json b/storage/sqlite/locales/ja/sqlite.json new file mode 100644 index 00000000..dda91077 --- /dev/null +++ b/storage/sqlite/locales/ja/sqlite.json @@ -0,0 +1,20 @@ +{ + "sqlite": { + "label": { + "database": "データベース", + "sqlQuery": "SQLクエリ", + "viaMsgTopic": "msg.topic経由", + "fixedStatement": "固定文", + "preparedStatement": "事前定義文", + "batchWithoutResponse": "一括(応答なし)", + "sqlStatement": "SQL文", + "mode": "モード", + "readWriteCreate": "読み取り-書き込み-作成", + "readWrite": "読み取り-書き込み", + "readOnly": "読み取りのみ" + }, + "tips": { + "memoryDb": "注釈: データベース名に :memory: を設定すると、非永続的なメモリデータベースを作成します。" + } + } +} diff --git a/storage/sqlite/sqlite.html b/storage/sqlite/sqlite.html index ceeca265..faca8ab7 100644 --- a/storage/sqlite/sqlite.html +++ b/storage/sqlite/sqlite.html @@ -1,19 +1,17 @@ - - - - - - - + + + + diff --git a/utility/annotate-image/annotate.js b/utility/annotate-image/annotate.js new file mode 100644 index 00000000..bf12fc72 --- /dev/null +++ b/utility/annotate-image/annotate.js @@ -0,0 +1,192 @@ +module.exports = function(RED) { + "use strict"; + const pureimage = require("pureimage"); + const Readable = require("stream").Readable; + const Writable = require("stream").Writable; + const path = require("path"); + + let fontLoaded = false; + function loadFont() { + if (!fontLoaded) { + const fnt = pureimage.registerFont(path.join(__dirname,'./SourceSansPro-Regular.ttf'),'Source Sans Pro'); + fnt.load(); + fontLoaded = true; + } + } + + function AnnotateNode(n) { + RED.nodes.createNode(this,n); + var node = this; + const defaultFill = n.fill || ""; + const defaultStroke = n.stroke || "#ffC000"; + const defaultLineWidth = parseInt(n.lineWidth) || 5; + const defaultFontSize = n.fontSize || 24; + const defaultFontColor = n.fontColor || "#ffC000"; + loadFont(); + + this.on("input", function(msg) { + if (Buffer.isBuffer(msg.payload)) { + if (msg.payload[0] !== 0xFF || msg.payload[1] !== 0xD8) { + node.error("Not a JPEG image",msg); + } else if (Array.isArray(msg.annotations) && msg.annotations.length > 0) { + const stream = new Readable(); + stream.push(msg.payload); + stream.push(null); + pureimage.decodeJPEGFromStream(stream).then(img => { + const c = pureimage.make(img.width, img.height); + const ctx = c.getContext('2d'); + ctx.drawImage(img,0,0,img.width,img.height); + + ctx.lineJoin = 'bevel'; + + msg.annotations.forEach(function(annotation) { + ctx.fillStyle = annotation.fill || defaultFill; + ctx.strokeStyle = annotation.stroke || defaultStroke; + ctx.lineWidth = annotation.lineWidth || defaultLineWidth; + ctx.lineJoin = 'bevel'; + let x,y,r,w,h; + + if (!annotation.type && annotation.bbox) { + annotation.type = 'rect'; + } + + switch(annotation.type) { + case 'rect': + if (annotation.bbox) { + x = annotation.bbox[0] + y = annotation.bbox[1] + w = annotation.bbox[2] + h = annotation.bbox[3] + } else { + x = annotation.x + y = annotation.y + w = annotation.w + h = annotation.h + } + + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + ctx.beginPath(); + ctx.lineWidth = annotation.lineWidth || defaultLineWidth; + ctx.rect(x,y,w,h); + ctx.closePath(); + ctx.stroke(); + + if (annotation.label) { + ctx.font = `${annotation.fontSize || defaultFontSize}pt 'Source Sans Pro'`; + ctx.fillStyle = annotation.fontColor || defaultFontColor; + ctx.textBaseline = "top"; + ctx.textAlign = "left"; + //set offset value so txt is above or below image + if (annotation.labelLocation) { + if (annotation.labelLocation === "top") { + y = y - (20+((defaultLineWidth*0.5)+(Number(defaultFontSize)))); + if (y < 0) + { + y = 0; + } + } + else if (annotation.labelLocation === "bottom") { + y = y + (10+h+(((defaultLineWidth*0.5)+(Number(defaultFontSize))))); + ctx.textBaseline = "bottom"; + + } + } + //if not user defined make best guess for top or bottom based on location + else { + //not enought room above imagebox, put label on the bottom + if (y < 0 + (20+((defaultLineWidth*0.5)+(Number(defaultFontSize))))) { + y = y + (10+h+(((defaultLineWidth*0.5)+(Number(defaultFontSize))))); + ctx.textBaseline = "bottom"; + } + //else put the label on the top + else { + y = y - (20+((defaultLineWidth*0.5)+(Number(defaultFontSize)))); + if (y < 0) { + y = 0; + } + } + } + + + ctx.fillText(annotation.label, x,y); + } + break; + case 'circle': + if (annotation.bbox) { + x = annotation.bbox[0] + annotation.bbox[2]/2 + y = annotation.bbox[1] + annotation.bbox[3]/2 + r = Math.min(annotation.bbox[2],annotation.bbox[3])/2; + } else { + x = annotation.x + y = annotation.y + r = annotation.r; + } + ctx.beginPath(); + ctx.lineWidth = annotation.lineWidth || defaultLineWidth; + ctx.arc(x,y,r,0,Math.PI*2); + ctx.closePath(); + ctx.stroke(); + if (annotation.label) { + ctx.font = `${annotation.fontSize || defaultFontSize}pt 'Source Sans Pro'`; + ctx.fillStyle = annotation.fontColor || defaultFontColor; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + ctx.fillText(annotation.label, x+2,y) + } + break; + } + }); + const bufferOutput = getWritableBuffer(); + pureimage.encodeJPEGToStream(c,bufferOutput.stream,90).then(() => { + msg.payload = bufferOutput.getBuffer(); + node.send(msg); + }) + }).catch(err => { + node.error(err,msg); + }) + } else { + // No annotations to make - send the message on + node.send(msg); + } + } else { + node.error("Payload not a Buffer",msg) + } + return msg; + }); + } + RED.nodes.registerType("annotate-image",AnnotateNode); + + + + function getWritableBuffer() { + var currentSize = 0; + var buffer = null; + const stream = new Writable({ + write(chunk, encoding, callback) { + if (!buffer) { + buffer = Buffer.from(chunk); + } else { + var newBuffer = Buffer.allocUnsafe(currentSize + chunk.length); + buffer.copy(newBuffer); + chunk.copy(newBuffer,currentSize); + buffer = newBuffer; + } + currentSize += chunk.length + callback(); + } + }); + return { + stream: stream, + getBuffer: function() { + return buffer; + } + } + } +} diff --git a/utility/annotate-image/package.json b/utility/annotate-image/package.json new file mode 100644 index 00000000..8b3564e4 --- /dev/null +++ b/utility/annotate-image/package.json @@ -0,0 +1,26 @@ +{ + "name": "node-red-node-annotate-image", + "version": "0.1.1", + "description": "A Node-RED node that can annotate an image", + "dependencies": { + "pureimage": "^0.2.5" + }, + "repository": { + "type": "git", + "url": "https://github.com/node-red/node-red-nodes/tree/master/utility/iamge-annotate" + }, + "license": "Apache-2.0", + "keywords": [ + "node-red" + ], + "node-red": { + "nodes": { + "annotate": "annotate.js" + } + }, + "contributors": [ + { + "name": "Nick O'Leary" + } + ] +} \ No newline at end of file diff --git a/utility/exif/94-exif.html b/utility/exif/94-exif.html index 3b019849..3f7d99ac 100644 --- a/utility/exif/94-exif.html +++ b/utility/exif/94-exif.html @@ -1,17 +1,31 @@ - - diff --git a/utility/exif/94-exif.js b/utility/exif/94-exif.js index 93ba0b3b..ff7e07e1 100644 --- a/utility/exif/94-exif.js +++ b/utility/exif/94-exif.js @@ -1,7 +1,6 @@ module.exports = function(RED) { "use strict"; - var ExifImage = require('exif').ExifImage; function convertDegreesMinutesSecondsToDecimals(degrees, minutes, seconds) { var result; @@ -11,7 +10,11 @@ module.exports = function(RED) { function ExifNode(n) { RED.nodes.createNode(this,n); + this.mode = n.mode || "normal"; + if (this.mode === "worldmap") { this.property = "payload.content"; } + else { this.property = n.property || "payload"; } var node = this; + var ExifImage = require('exif').ExifImage; /*** * Extracts GPS location information from Exif data. If enough information is @@ -20,7 +23,7 @@ module.exports = function(RED) { * Assumes that the msg object will always have exifData available as msg.exif. * Assume that the GPS data saved into Exif provides a valid value */ - function addMsgLocationDataFromExifGPSData(msg) { + function addMsgLocationDataFromExifGPSData(msg,val) { var gpsData = msg.exif.gps; // declaring variable purely to make checks more readable if (gpsData.GPSAltitude) { /* istanbul ignore else */ @@ -31,14 +34,12 @@ module.exports = function(RED) { // The data provided in Exif is in degrees, minutes, seconds, this is to be converted into a single floating point degree if (gpsData.GPSLatitude.length === 3) { // OK to convert latitude if (gpsData.GPSLongitude.length === 3) { // OK to convert longitude - var latitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLatitude[0], gpsData.GPSLatitude[1], gpsData.GPSLatitude[2]); latitude = Math.round(latitude * 100000)/100000; // 5dp is approx 1m resolution... // (N)orth means positive latitude, (S)outh means negative latitude if (gpsData.GPSLatitudeRef.toString() === 'S' || gpsData.GPSLatitudeRef.toString() === 's') { latitude = latitude * -1; } - var longitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLongitude[0], gpsData.GPSLongitude[1], gpsData.GPSLongitude[2]); longitude = Math.round(longitude * 100000)/100000; // 5dp is approx 1m resolution... // (E)ast means positive longitude, (W)est means negative longitude @@ -49,7 +50,6 @@ module.exports = function(RED) { if (!msg.location) { msg.location = {}; } msg.location.lat = latitude; msg.location.lon = longitude; - return; } else { node.log("Invalid longitude data, no location information has been added to the message."); @@ -62,21 +62,49 @@ module.exports = function(RED) { else { node.log("The location of this image cannot be determined safely so no location information has been added to the message."); } + msg.location.arc = { + ranges: [500,1000,2000], + pan: gpsData.GPSImgDirection, + fov: (2 * Math.atan(36 / (2 * msg.exif.exif.FocalLengthIn35mmFormat)) * 180 / Math.PI), + color: '#910000' + } + msg.location.icon = "fa-camera"; + var na; + if (val.hasOwnProperty("name")) { na = val.name; } + else if (msg.hasOwnProperty("filename")) { na = msg.filename.split('/').pop(); } + else { na = msg.exif.image.Make+"_"+msg.exif.image.ModifyDate; } + msg.location.name = na; + msg.location.layer = "Images"; + msg.location.popup = '' } this.on("input", function(msg) { + if (node.mode === "worldmap" && Buffer.isBuffer(msg.payload)) { node.property = "payload"; } + else if (node.mode === "worldmap" && (msg.payload.action !== "file" || msg.payload.type.indexOf("image") === -1)) { return; } // in case worldmap-in not filtered. try { - if (msg.payload) { - if (Buffer.isBuffer(msg.payload)) { - new ExifImage({ image : msg.payload }, function (error, exifData) { + var value = RED.util.getMessageProperty(msg,node.property); + if (value !== undefined) { + if (typeof value === "string") { // it must be a base64 encoded inline image type + if (value.indexOf('data:image') !== -1) { + value = new Buffer.from(value.replace(/^data:image\/[a-z]+;base64,/, ""), 'base64'); + } + } + if (Buffer.isBuffer(value)) { // or a proper jpg buffer + new ExifImage({ image:value }, function (error, exifData) { if (error) { - node.log(error.toString()); + if (node.mode !== "worldmap") { + node.log(error.toString()); + } + else { + msg.location = {name:msg.payload.name, lat:msg.payload.lat, lon:msg.payload.lon, layer:"Images", icon:"fa-camera", draggable:true}; + msg.location.popup = '
'; + } } else { if (exifData) { msg.exif = exifData; if ((exifData.hasOwnProperty("gps")) && (Object.keys(exifData.gps).length !== 0)) { - addMsgLocationDataFromExifGPSData(msg); + addMsgLocationDataFromExifGPSData(msg,value); } //else { node.log("The incoming image did not contain Exif GPS data."); } } @@ -84,6 +112,10 @@ module.exports = function(RED) { node.warn("The incoming image did not contain any Exif data, nothing to do."); } } + if (node.mode === "worldmap") { + msg.payload = msg.location; + delete msg.location; + } node.send(msg); }); } @@ -93,7 +125,7 @@ module.exports = function(RED) { } } else { - node.error("No payload received, the Exif node cannot proceed, no messages sent.",msg); + node.warn("No input received, the Exif node cannot proceed, no messages sent.",msg); return; } } diff --git a/utility/exif/package.json b/utility/exif/package.json index fec3db6c..89c587d5 100644 --- a/utility/exif/package.json +++ b/utility/exif/package.json @@ -1,23 +1,30 @@ { - "name" : "node-red-node-exif", - "version" : "0.0.7", - "description" : "A Node-RED node that extracts Exif information from JPEG image buffers.", - "dependencies" : { - "exif": "0.4.0" + "name": "node-red-node-exif", + "version": "0.2.1", + "description": "A Node-RED node that extracts Exif information from JPEG image buffers.", + "dependencies": { + "exif": "^0.6.0" }, - "repository" : { - "type":"git", - "url":"https://github.com/node-red/node-red-nodes/tree/master/utility/exif" + "repository": { + "type": "git", + "url": "https://github.com/node-red/node-red-nodes/tree/master/utility/exif" }, "license": "Apache-2.0", - "keywords": [ "node-red", "exif"], - "node-red" : { - "nodes" : { + "keywords": [ + "node-red", + "exif" + ], + "node-red": { + "nodes": { "exif": "94-exif.js" } }, "contributors": [ - {"name": "Dave Conway-Jones"}, - {"name": "Zoltan Balogh"} + { + "name": "Dave Conway-Jones" + }, + { + "name": "Zoltan Balogh" + } ] }