From 437c29525d1945de0f31efa2198dd9d185484518 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Fri, 3 Apr 2020 16:07:41 +0100 Subject: [PATCH] new features: Add Triggered Mode to Ping Node (#643) --- io/ping/88-ping.html | 85 +++++++++++++-- io/ping/88-ping.js | 159 +++++++++++++++++++++-------- io/ping/README.md | 9 +- io/ping/locales/en-US/88-ping.html | 51 ++++++++- io/ping/locales/en-US/88-ping.json | 7 +- io/ping/locales/ja/88-ping.html | 78 +++++++++++++- io/ping/locales/ja/88-ping.json | 7 +- 7 files changed, 336 insertions(+), 60 deletions(-) diff --git a/io/ping/88-ping.html b/io/ping/88-ping.html index 8c76afa6..7826c500 100644 --- a/io/ping/88-ping.html +++ b/io/ping/88-ping.html @@ -1,10 +1,17 @@ - diff --git a/io/ping/88-ping.js b/io/ping/88-ping.js index 1db1f59e..2b3ed337 100644 --- a/io/ping/88-ping.js +++ b/io/ping/88-ping.js @@ -1,61 +1,134 @@ module.exports = function(RED) { "use strict"; - var spawn = require('child_process').spawn; - var plat = require('os').platform(); + var spawn = require("child_process").spawn; + var plat = require("os").platform(); + + function doPing(node, host, arrayMode){ + const defTimeout = 5000; + var ex, hostOptions, commandLineOptions; + if(typeof host === "string"){ + hostOptions = { + host: host, + timeout: defTimeout + } + } else { + hostOptions = host; + hostOptions.timeout = isNaN(parseInt(hostOptions.timeout)) ? defTimeout : parseInt(hostOptions.timeout); + } + //clamp timeout between 1 and 30 sec + hostOptions.timeout = hostOptions.timeout < 1000 ? 1000 : hostOptions.timeout; + hostOptions.timeout = hostOptions.timeout > 30000 ? 30000 : hostOptions.timeout; + var timeoutS = Math.round(hostOptions.timeout / 1000); //whole numbers only + var msg = { payload:false, topic:hostOptions.host }; + //only include the extra msg object if operating in advance/array mode. + if(arrayMode){ + msg.ping = hostOptions + } + if (plat == "linux" || plat == "android") { + commandLineOptions = ["-n", "-w", timeoutS, "-c", "1"] + } else if (plat.match(/^win/)) { + commandLineOptions = ["-n", "1", "-w", hostOptions.timeout] + } else if (plat == "darwin" || plat == "freebsd") { + commandLineOptions = ["-n", "-t", timeoutS, "-c", "1"] + } 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]); + + //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.kill("SIGINT"); }} catch(e){console.warn(e)} + }, hostOptions.timeout+1000); //add 1s for grace + + var res = false; + var line = ""; + var fail = false; + //var regex = /from.*time.(.*)ms/; + var regex = /=.*[<|=]([0-9]*).*TTL|ttl..*=([0-9\.]*)/; + ex.stdout.on("data", function (data) { + line += data.toString(); + }); + ex.on("exit", function (err) { + clearTimeout(spawnTout); + }); + ex.on("error", function (err) { + fail = true; + if (err.code === "ENOENT") { + node.error(err.code + " ping command not found", msg); + } + else if (err.code === "EACCES") { + node.error(err.code + " can't run ping command", msg); + } + else { + node.error(err.code, msg); + } + }); + ex.on("close", function (code) { + if (fail) { fail = false; return; } + var m = regex.exec(line)||""; + if (m !== "") { + if (m[1]) { res = Number(m[1]); } + if (m[2]) { res = Number(m[2]); } + } + if (code === 0) { msg.payload = res } + try { node.send(msg); } + catch(e) {console.warn(e)} + }); + } function PingNode(n) { RED.nodes.createNode(this,n); + this.mode = n.mode; this.host = n.host; this.timer = n.timer * 1000; var node = this; - node.tout = setInterval(function() { - var ex; - if (plat == "linux" || plat == "android") { ex = spawn('ping', ['-n', '-w', '5', '-c', '1', node.host]); } - else if (plat.match(/^win/)) { ex = spawn('ping', ['-n', '1', '-w', '5000', node.host]); } - else if (plat == "darwin" || plat == "freebsd") { ex = spawn('ping', ['-n', '-t', '5', '-c', '1', node.host]); } - else { node.error("Sorry - your platform - "+plat+" - is not recognised."); } - var res = false; - var line = ""; - var fail = false; - //var regex = /from.*time.(.*)ms/; - var regex = /=.*[<|=]([0-9]*).*TTL|ttl..*=([0-9\.]*)/; - ex.stdout.on('data', function (data) { - line += data.toString(); - }); - //ex.stderr.on('data', function (data) { - //console.log('[ping] stderr: ' + data); - //}); - ex.on('error', function (err) { - fail = true; - if (err.code === "ENOENT") { - node.error(err.code + " ping command not found"); + function generatePingList(str) { + return (str + "").split(",").map((e) => (e + "").trim()).filter((e) => e != ""); + } + function clearPingInterval(){ + if (node.tout) { clearInterval(node.tout); } + } + + if(node.mode === "triggered"){ + clearPingInterval(); + } else if(node.timer){ + node.tout = setInterval(function() { + let pingables = generatePingList(node.host); + for (let index = 0; index < pingables.length; index++) { + const element = pingables[index]; + if(element){ doPing(node, element, false); } } - else if (err.code === "EACCES") { - node.error(err.code + " can't run ping command"); + }, node.timer); + } + + this.on("input", function (msg) { + let node = this; + let payload = node.host || msg.payload; + if(typeof payload == "string"){ + let pingables = generatePingList(payload) + for (let index = 0; index < pingables.length; index++) { + const element = pingables[index]; + if(element){ doPing(node, element, false); } } - else { - node.error(err.code); + } else if (Array.isArray(payload) ) { + for (let index = 0; index < payload.length; index++) { + const element = payload[index]; + if(element){ doPing(node, element, true); } } - }); - ex.on('close', function (code) { - if (fail) { fail = false; return; } - var m = regex.exec(line)||""; - if (m !== '') { - if (m[1]) { res = Number(m[1]); } - if (m[2]) { res = Number(m[2]); } - } - var msg = { payload:false, topic:node.host }; - if (code === 0) { msg = { payload:res, topic:node.host }; } - try { node.send(msg); } - catch(e) {} - }); - }, node.timer); + } + }); this.on("close", function() { - if (this.tout) { clearInterval(this.tout); } + clearPingInterval(); }); + + } RED.nodes.registerType("ping",PingNode); -} +} \ No newline at end of file diff --git a/io/ping/README.md b/io/ping/README.md index 36127c73..44cb27d0 100644 --- a/io/ping/README.md +++ b/io/ping/README.md @@ -27,7 +27,7 @@ The fix is to allow it as follows Usage ----- -Pings a machine and returns the trip time in mS as `msg.payload`. +Pings 1 or more devices and returns the trip time in mS as `msg.payload`. Returns boolean `false` if no response received, or if the host is unresolveable. @@ -35,4 +35,9 @@ Returns boolean `false` if no response received, or if the host is unresolveable `msg.topic` contains the ip address of the target host. -Default ping is every 20 seconds but can be configured. +There are 2 modes - `Timed` and `Triggered`. + +* Timed mode - this is the default mode that pings your devices on a timed basis. Default ping is every 20 seconds but can be configured. +* Triggered mode - this mode permits you to trigger the ping by an input message. If the `Target` is left blank and `msg.payload` is a string or array, you can ping 1 or more devices on demand. + +Refer to the built in help on the side-bar info panel for more details. diff --git a/io/ping/locales/en-US/88-ping.html b/io/ping/locales/en-US/88-ping.html index 4ba4dde9..cd81ec09 100644 --- a/io/ping/locales/en-US/88-ping.html +++ b/io/ping/locales/en-US/88-ping.html @@ -1,8 +1,53 @@ - diff --git a/io/ping/locales/en-US/88-ping.json b/io/ping/locales/en-US/88-ping.json index bcff8820..f1a9093e 100644 --- a/io/ping/locales/en-US/88-ping.json +++ b/io/ping/locales/en-US/88-ping.json @@ -3,7 +3,12 @@ "ping": "ping", "label": { "target": "Target", - "ping": "Ping (S)" + "ping": "Ping (S)", + "mode": "Mode", + "mode_option": { + "timed": "Timed", + "triggered": "Triggered" + } } } } diff --git a/io/ping/locales/ja/88-ping.html b/io/ping/locales/ja/88-ping.html index 922747bf..ead433ad 100644 --- a/io/ping/locales/ja/88-ping.html +++ b/io/ping/locales/ja/88-ping.html @@ -1,8 +1,82 @@ - diff --git a/io/ping/locales/ja/88-ping.json b/io/ping/locales/ja/88-ping.json index 7b39cf3d..5eefe0e2 100644 --- a/io/ping/locales/ja/88-ping.json +++ b/io/ping/locales/ja/88-ping.json @@ -3,7 +3,12 @@ "ping": "ping", "label": { "target": "対象", - "ping": "Ping (秒)" + "ping": "Ping (秒)", + "mode": "モード", + "mode_option": { + "timed": "時限", + "triggered": "引き金になった" + } } } }