Merge branch 'node-red:master' into master

This commit is contained in:
Ben Hardill 2023-05-12 21:02:47 +00:00 committed by GitHub
commit 11f74b3528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 2884 additions and 1845 deletions

View File

@ -11,7 +11,7 @@ Put an `x` in the boxes that apply
-->
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] New feature (non-breaking change which adds functionality)
<!--
If you want to raise a pull-request with a new feature, or a refactoring
@ -25,10 +25,13 @@ the [forum](https://discourse.nodered.org) or
<!-- Describe the nature of this change. What problem does it address? -->
Adds authentication option to the Email node (node-red-node-email) to use OAuth and XOAuth2
********** This version: IMAP ONLY **********
## Checklist
<!-- Put an `x` in the boxes that apply -->
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red-nodes/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [ ] I have run `grunt` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality
- [x] I have read the [contribution guidelines](https://github.com/node-red/node-red-nodes/blob/master/CONTRIBUTING.md)
- [x] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [x] I have run `grunt` to verify the unit tests pass
- [x] I have added suitable unit tests to cover the new/changed functionality

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ setenv.sh
package-lock.json
social/xmpp/92-xmpp.old
*.tgz
.DS_Store

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-random",
"version" : "0.4.0",
"version" : "0.4.1",
"description" : "A Node-RED node that when triggered generates a random number between two values.",
"dependencies" : {
},

View File

@ -18,9 +18,8 @@ module.exports = function(RED) {
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 = Number(msg.from);
if (isNaN(msg.from)) { // if it isn't a number setup NaN error
tmp.low = NaN;
tmp.low_e = " From: " + msg.from; // setup to show bad incoming msg.from
}
@ -31,9 +30,8 @@ module.exports = function(RED) {
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 = Number(msg.to);
if (isNaN(msg.to)) { // if it isn't a number setup NaN error
tmp.high = NaN
tmp.high_e = " To: " + msg.to // setup to show bad incoming msg.to
}

View File

@ -45,46 +45,59 @@ module.exports = function(RED) {
}
}
var startPin = function() {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true;
node.status({fill:"yellow",shape:"dot",text:"rpi-gpio.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:"gpio/"+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.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.removeAllListeners();
delete node.child;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (!node.finished && code === 1) {
setTimeout(function() {startPin()}, 250);
}
else if (node.finished) {
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"}); }
});
node.child.on('error', function (err) {
if (err.code === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")+err.path,err); }
else if (err.code === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")+err.path,err); }
else { node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err) }
});
node.child.stdin.on('error', function (err) {
if (!node.finished) {
node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err);
}
});
}
if (allOK === true) {
if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true;
node.status({fill:"yellow",shape:"dot",text:"rpi-gpio.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:"gpio/"+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.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.finished) {
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"}); }
});
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})) }
});
startPin();
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
@ -108,10 +121,13 @@ module.exports = function(RED) {
delete pinsInUse[node.pin];
if (node.child != null) {
node.finished = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
node.child.stdin.write("close "+node.pin, () => {
if (node.child) {
node.child.kill('SIGKILL');
}
});
}
else { done(); }
else { if (done) { done(); } }
});
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
@ -188,11 +204,16 @@ module.exports = function(RED) {
});
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); }
if (err.code === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")+err.path,err); }
else if (err.code === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")+err.path,err); }
else { node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err) }
});
node.child.stdin.on('error', function (err) {
if (!node.finished) {
node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err);
}
});
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
@ -210,10 +231,12 @@ module.exports = function(RED) {
delete pinsInUse[node.pin];
if (node.child != null) {
node.finished = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
node.child.stdin.write("close "+node.pin, () => {
node.child.kill('SIGKILL');
setTimeout(function() { if (done) { done(); } }, 50);
});
}
else { done(); }
else { if (done) { done(); } }
});
}

View File

@ -66,8 +66,8 @@
"invalidinput": "Ungültige Eingabe",
"needtobeexecutable": "__command__ muss ausführbar sein",
"mustbeexecutable": "nrgpio muss ausführbar sein",
"commandnotfound": "nrgpio-Befehl nicht gefunden",
"commandnotexecutable": "nrgpio-Befehl nicht ausführbar",
"commandnotfound": "nrgpio-Befehl nicht gefunden ",
"commandnotexecutable": "nrgpio-Befehl nicht ausführbar ",
"error": "Fehler: __error__",
"pythoncommandnotfound": "nrgpio-Python-Befehl nicht aktiv"
}

View File

@ -66,8 +66,8 @@
"invalidinput": "Invalid input",
"needtobeexecutable": "__command__ needs to be executable",
"mustbeexecutable": "nrgpio must to be executable",
"commandnotfound": "nrgpio command not found",
"commandnotexecutable": "nrgpio command not executable",
"commandnotfound": "nrgpio command not found ",
"commandnotexecutable": "nrgpio command not executable ",
"error": "error: __error__",
"pythoncommandnotfound": "nrgpio python command not running"
}

View File

@ -66,8 +66,8 @@
"invalidinput": "入力が不正です",
"needtobeexecutable": "__command__ は実行可能である必要があります",
"mustbeexecutable": "nrgpio は実行可能である必要があります",
"commandnotfound": "nrgpio コマンドが見つかりません",
"commandnotexecutable": "nrgpio コマンドが実行可能ではありません",
"commandnotfound": "nrgpio コマンドが見つかりません ",
"commandnotexecutable": "nrgpio コマンドが実行可能ではありません ",
"error": "エラー: __error__",
"pythoncommandnotfound": "nrgpio python コマンドが実行されていません"
}

View File

@ -65,8 +65,8 @@
"invalidinput": "입력이 올바르지 않습니다",
"needtobeexecutable": "__command__ 은 실행가능상태일 필요가 있습니다 ",
"mustbeexecutable": "nrgpio 은 실행가능상태일 필요가 있습니다 ",
"commandnotfound": "nrgpio 커맨드를 찾을수 없습니다",
"commandnotexecutable": "nrgpio 커맨드가 실행가능상태가 아닙니다",
"commandnotfound": "nrgpio 커맨드를 찾을수 없습니다 ",
"commandnotexecutable": "nrgpio 커맨드가 실행가능상태가 아닙니다 ",
"error": "에러: __error__",
"pythoncommandnotfound": "nrgpio python 커맨드가 실행되지 않았습니다"
}

View File

@ -1,6 +1,6 @@
{
"name": "node-red-node-pi-gpio",
"version": "2.0.3",
"version": "2.0.6",
"description": "The basic Node-RED node for Pi GPIO",
"dependencies" : {
},

View File

@ -1,7 +1,7 @@
<script type="text/javascript">
RED.nodes.registerType('mraa-gpio-ain',{
category: 'Intel gpio',
category: 'GPIO',
color: '#a6bbcf',
paletteLabel: 'analogue',
defaults: {
@ -27,6 +27,7 @@
if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#btype').text(t);
$('#node-input-pin').val(pinnow);
});
@ -62,7 +63,7 @@
</script>
<script type="text/x-red" data-help-name="mraa-gpio-ain">
<p>An analogue input pin for an Intel Galileo or Edison board that is read every <i>interval</i> milliseconds.</p>
<p>An analogue input pin for a board that is read every <i>interval</i> milliseconds.</p>
<p>The <code>msg.payload</code> will contain the value, and <code>msg.topic</code>
contains "{the_board_name}/A{the pin number}".</p>
<p>The value is only sent if it is different from the previously read value.</p>

View File

@ -11,6 +11,10 @@ module.exports = function(RED) {
var node = this;
var msg = { topic:node.board+"/A"+node.pin };
var old = -99999;
// ADC set to 12 for IOT2050
if (this.board === "SIMATIC IOT2050") {
node.x.setBit(12);
}
this.timer = setInterval(function() {
msg.payload = node.x.read();
if (msg.payload !== old) {
@ -21,6 +25,7 @@ module.exports = function(RED) {
this.on('close', function() {
clearInterval(this.timer);
node.x.close();
});
}
RED.nodes.registerType("mraa-gpio-ain", gpioAin);

View File

@ -1,13 +1,15 @@
<script type="text/javascript">
RED.nodes.registerType('mraa-gpio-din',{
category: 'Intel gpio',
category: 'GPIO',
color: '#a6bbcf',
paletteLabel: 'digital',
defaults: {
name: {value:""},
pin: {value:"", required: true},
interrupt: {value:"", required: true}
interrupt: {value:"", required: true},
mode: {value:"", required: true},
initial: {value: false}
},
inputs:0,
outputs:1,
@ -30,6 +32,7 @@
if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#type-tip').text(t);
$('#node-input-pin').val(pinnow);
});
@ -59,6 +62,23 @@
<option value="11">D11</option>
<option value="12">D12</option>
<option value="13">D13</option>
<option value="14">D14</option>
<option value="15">D15</option>
<option value="16">D16</option>
<option value="17">D17</option>
<option value="18">D18</option>
<option value="19">D19</option>
<option value="20">USER button</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa-level-up"></i> Mode</label>
<select type="text" id="node-input-mode" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select mode</option>
<option value="0">Strong </option>
<option value="1">Pull-up </option>
<option value="2">Pull-down </option>
<option value="3">Hiz </option>
</select>
</div>
<div class="form-row">
@ -70,6 +90,11 @@
<option value="b">Both </option>
</select>
</div>
<div class="form-row" id="node-initial-tick">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-initial" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-initial" style="width: 70%;">Send initial message with level of pin.</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name" style="width: 250px;">
@ -78,7 +103,7 @@
</script>
<script type="text/x-red" data-help-name="mraa-gpio-din">
<p>A digital input pin for an Intel Galileo or Edison board.</p>
<p>A digital input pin for an Intel Galileo/Edison/Siemens IOT2050 board.</p>
<p>The <code>msg.payload</code> contains the value (0 or 1), and <code>msg.topic</code>
contains "{the_board_name}/D{the pin number}".</p>
</script>

View File

@ -7,13 +7,16 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
this.pin = n.pin;
this.interrupt = n.interrupt;
this.mode = n.mode;
this.initialMsg = n.initial;
this.x = new m.Gpio(parseInt(this.pin));
this.board = m.getPlatformName();
this.defaultTimeout = 100;
var node = this;
node.x.mode(m.PIN_GPIO);
node.x.mode(parseInt(this.mode));
node.x.dir(m.DIR_IN);
node.x.isr(m.EDGE_BOTH, function() {
var g = node.x.read();
var eventHandler = function(g) {
var msg = { payload:g, topic:node.board+"/D"+node.pin };
switch (g) {
case 0: {
@ -34,8 +37,15 @@ module.exports = function(RED) {
node.status({fill:"grey",shape:"ring",text:"unknown"});
}
}
});
switch (node.x.read()) {
}
var isrCallback = function() {
eventHandler(node.x.read());
}
node.x.isr(m.EDGE_BOTH, isrCallback);
var initialState = node.x.read();
switch (initialState) {
case 0: {
node.status({fill:"green",shape:"ring",text:"low"});
break;
@ -48,8 +58,17 @@ module.exports = function(RED) {
node.status({});
}
}
if (this.initialMsg) {
setTimeout(() => {
node.send( { payload: node.x.read(), topic:node.board+"/D"+node.pin } );
}, this.defaultTimeout);
}
this.on('close', function() {
node.x.isr(m.EDGE_BOTH, null);
node.x.isrExit();
node.x.close();
});
}
RED.nodes.registerType("mraa-gpio-din", gpioDin);

View File

@ -1,7 +1,7 @@
<script type="text/javascript">
RED.nodes.registerType('mraa-gpio-dout',{
category: 'Intel gpio',
category: 'GPIO',
color: '#a6bbcf',
paletteLabel: 'digital',
defaults: {
@ -36,6 +36,7 @@
if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#btype').text(t);
if (data === 0) {
$('#node-input-pin').append($("<option></option>").attr("value",14).text("LED - Galileo v1"));
@ -100,6 +101,6 @@
</script>
<script type="text/x-red" data-help-name="mraa-gpio-dout">
<p>A digital output pin for an Intel Galileo or Edison board.</p>
<p>A digital output pin for a board.</p>
<p>The <code>msg.payload</code> should contain the value 0 or 1.</p>
</script>

View File

@ -29,6 +29,7 @@ module.exports = function(RED) {
});
this.on('close', function() {
node.p.close();
});
}
RED.nodes.registerType("mraa-gpio-dout", gpioDout);

View File

@ -0,0 +1,85 @@
<script type="text/javascript">
RED.nodes.registerType('mraa-gpio-led',{
category: 'GPIO',
color: '#a6bbcf',
paletteLabel: 'led',
defaults: {
name: {value:""},
pin: {value:"", required: true},
color: {value:"", required: true},
},
inputs:1,
outputs:0,
icon: "arrow.png",
align: "right",
label: function() {
return this.name || "led";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var pinnow = this.pin;
$.getJSON('mraa-gpio/'+this.id,function(data) {
var t = "unknown";
if (data === 0) { t = "Galileo v1"; }
if (data === 1) { t = "Galileo v2"; }
if (data === 2) { t = "Edison Fab C"; }
if (data === 3) { t = "DE3813 Baytrail"; }
if (data === 4) { t = "Minnow Max"; }
if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#btype').text(t);
$('#node-input-pin').val(pinnow);
});
$.getJSON('mraa-version/'+this.id,function(data) {
$('#ver-tip').text(data);
});
var setstate = function () {
if ($('#node-input-set').is(":checked")) {
$("#node-set-state").show();
} else {
$("#node-set-state").hide();
}
};
$("#node-input-set").change(function () { setstate(); });
setstate();
}
});
</script>
<script type="text/x-red" data-template-name="mraa-gpio-led">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> Led</label>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="0">USER1</option>
<option value="1">USER2</option>
</select>
</div>
<div class="form-row">
<label for="node-input-color"><i class="fa fa-circle"></i> Color</label>
<select type="text" id="node-input-color" style="width:250px;">
<option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="0">GREEN</option>
<option value="1">RED</option>
<option value="2">ORANGE</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name" style="width: 250px;">
</div>
<div class="form-tips">Board : <span id="btype">n/a</span><br/>mraa version : <span id="ver-tip">n/a</span></div>
</script>
<script type="text/x-red" data-help-name="mraa-gpio-led">
<p>Led Control for a board.</p>
<p>The <code>msg.payload</code> should contain the value 0 or 1.</p>
</script>

View File

@ -0,0 +1,86 @@
module.exports = function(RED) {
var m = require('mraa');
function LEDNode(n) {
RED.nodes.createNode(this, n);
this.pin = Number(n.pin);
this.color = Number(n.color);
if (this.pin == 0) {
this.user1_green = new m.Led(0); /*user-led1-green*/
this.user1_red = new m.Led(1); /*user-led1-red*/
} if(this.pin == 1) {
this.user2_green = new m.Led(2); /*user-led2-green*/
this.user2_red = new m.Led(3); /*user-led2-red*/
}
function set_led_green(led_green, led_red) {
led_green.setBrightness(1);
led_red.setBrightness(0);
}
function set_led_red(led_green, led_red) {
led_green.setBrightness(0);
led_red.setBrightness(1);
}
function set_led_orange(led_green, led_red) {
led_green.setBrightness(1);
led_red.setBrightness(1);
}
function turn_off_led(led_green, led_red) {
led_green.setBrightness(0);
led_red.setBrightness(0);
}
this.on("input", function(msg) {
if (this.pin == 0) {
this.led_green = this.user1_green;
this.led_red = this.user1_red;
}
else if (this.pin == 1) {
this.led_green = this.user2_green;
this.led_red = this.user2_red;
}
if (msg.payload == "1") {
switch(this.color) {
case 0:
set_led_green(this.led_green, this.led_red);
break;
case 1:
set_led_red(this.led_green, this.led_red);
break;
case 2:
set_led_orange(this.led_green, this.led_red);
break;
default:
console.log("unexpected");
break;
}
}
else {
turn_off_led(this.led_green, this.led_red);
}
});
this.on('close', function() {
if (this.pin == 0) {
this.user1_green.close();
this.user1_red.close();
} if(this.pin == 1) {
this.user2_green.close();
this.user2_red.close();
}
});
}
RED.nodes.registerType("mraa-gpio-led", LEDNode);
RED.httpAdmin.get('/mraa-gpio/:id', RED.auth.needsPermission('mraa-gpio.read'), function(req,res) {
res.json(m.getPlatformType());
});
RED.httpAdmin.get('/mraa-version/:id', RED.auth.needsPermission('mraa-version.read'), function(req,res) {
res.json(m.getVersion());
});
}

View File

@ -1,7 +1,7 @@
<script type="text/javascript">
RED.nodes.registerType('mraa-gpio-pwm',{
category: 'Intel gpio',
category: 'GPIO',
color: '#a6bbcf',
paletteLabel: 'pwm',
defaults: {
@ -35,6 +35,7 @@
if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#type-tip').text(t);
$('#node-input-pin').val(pinnow);
});
@ -60,13 +61,23 @@
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="3">D3</option>
<option value="5">D5</option>
<option value="6">D6</option>
<option value="9">D9</option>
<option value="10">D10</option>
<option value="11">D11</option>
</select>
<optgroup label="Intel Galileo/Edison">
<option value="3">D3</option>
<option value="5">D5</option>
<option value="6">D6</option>
<option value="9">D9</option>
<option value="10">D10</option>
<option value="11">D11</option>
</optgroup>
<optgroup label="Siemens IOT2050">
<option value="4">D4</option>
<option value="5">D5</option>
<option value="6">D6</option>
<option value="7">D7</option>
<option value="8">D8</option>
<option value="9">D9</option>
</optgroup>
</select>
</div>
<div class="form-row">
<label for="node-input-period"><i class="fa fa-clock-o"></i> Period</label>
@ -80,9 +91,10 @@
</script>
<script type="text/x-red" data-help-name="mraa-gpio-pwm">
<p>A pulse width modulation (PWM) output pin for an Intel Galileo or Edison board.</p>
<p>A pulse width modulation (PWM) output pin for a board.</p>
<p>The <code>msg.payload</code> should contain a floating point number value
between 0 and 1, (or a string representation thereof.)</p>
<p>For servo control set the period to 20mS and vary the input between 0.05 and 0.10</p>
<p><b>Note</b> : Only pins 3, 5, 6, 9, 10 & 11 support PWM output.</p>
<p><b>Note</b> : Only pins 4, 5, 6, 7, 8 & 9 support PWM output for Siemens IOT2050.</p>
<p>Only pins 3, 5, 6, 9, 10 & 11 support PWM output for Intel Galileo/Edison.</p>
</script>

View File

@ -21,6 +21,7 @@ module.exports = function(RED) {
this.on('close', function() {
node.p.enable(false);
node.p.close();
});
}
RED.nodes.registerType("mraa-gpio-pwm", gpioPWM);

View File

@ -1,7 +1,7 @@
{
"name" : "node-red-node-intel-gpio",
"version" : "0.0.6",
"description" : "A Node-RED node to talk to an Intel Galileo or Edison using mraa",
"version" : "0.3.0",
"description" : "A Node-RED node to talk to an Intel Galileo, Edison or Siemens IOT2050 board using mraa",
"dependencies" : {
},
"repository" : {
@ -10,18 +10,27 @@
"directory" : "tree/master/hardware/intel"
},
"license": "Apache-2.0",
"keywords": [ "node-red", "intel", "galileo", "edison" ],
"keywords": [ "node-red", "intel", "galileo", "edison", "siemens", "iot2050" ],
"node-red" : {
"nodes" : {
"mraa-gpio-ain": "mraa-gpio-ain.js",
"mraa-gpio-din": "mraa-gpio-din.js",
"mraa-gpio-dout": "mraa-gpio-dout.js",
"mraa-gpio-pwm": "mraa-gpio-pwm.js"
"mraa-gpio-pwm": "mraa-gpio-pwm.js",
"mraa-gpio-led": "mraa-gpio-led.js"
}
},
"author": {
"name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com",
"url": "http://nodered.org"
}
},
"contributors": [
{
"name": "@fr0st61te"
},
{
"name": "@jan-kiszka"
}
]
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-pi-sense-hat",
"version" : "0.1.2",
"version" : "0.1.4",
"description" : "A Node-RED node to interact with a Raspberry Pi Sense HAT",
"repository" : {
"type":"git",

View File

@ -7,7 +7,7 @@ module.exports = function(RED) {
var hatCommand = __dirname+'/sensehat';
if (!fs.existsSync('/usr/lib/python2.7/dist-packages/sense_hat')) {
if (!fs.existsSync('/usr/lib/python2.7/dist-packages/sense_hat') && !fs.existsSync('/usr/lib/python3/dist-packages/sense_hat')) {
throw "Error: Can't find Sense HAT python libraries. Run sudo apt-get install sense-hat";
}
@ -107,7 +107,13 @@ module.exports = function(RED) {
// Any data on stderr means a bad thing has happened.
// Best to kill it and let it reconnect.
if (RED.settings.verbose) { RED.log.error("err: "+data+" :"); }
hat.kill('SIGKILL');
if (data.indexOf("WARNING") === 0) {
if (data.indexOf("sensor not present") !== -1) { return; }
else { RED.log.warn(data); }
}
else {
hat.kill('SIGKILL');
}
});
hat.stderr.on('error', function(err) { });
hat.stdin.on('error', function(err) { });

View File

@ -1,9 +1,9 @@
{
"name" : "node-red-node-pi-unicorn-hat",
"version" : "0.1.1",
"version" : "0.1.2",
"description" : "A Node-RED node to output to a Raspberry Pi Unicorn HAT from Pimorini.",
"dependencies" : {
"pngjs": "2.2.*"
"pngjs": "2.3.1"
},
"repository" : {
"type":"git",

View File

@ -16,7 +16,7 @@ Run the following command in your Node-RED user directory - typically `~/.node-r
The output node switches a socket, a light or group of lights on or off
This should be backward compatible with the pervious version of this node but will benefit
This should be backward compatible with the previous version of this node but will benefit
from opening the config dialog and selecting the node you want.
The node accepts the following `msg.payload` as input

View File

@ -28,14 +28,14 @@
],
"license": "Apache-2.0",
"dependencies": {
"node-ssdp": "~3.2.5",
"request": "~2.74.0",
"node-ssdp": "~3.3.0",
"request": "~2.88.2",
"xml2js": "~0.4.13",
"util": "~0.10.3",
"util": "~0.12.4",
"url": "~0.11.0",
"ip": "~1.0.1",
"body-parser": "~1.14.1",
"q": "~1.4.1"
"ip": "~1.1.5",
"body-parser": "~1.20.0",
"q": "~1.5.1"
},
"node-red": {
"nodes": {

View File

@ -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, msg, arrayMode) {
const defTimeout = 5000;
var ex, ex6, hostOptions, commandLineOptions;
if (typeof host === "string") {
@ -20,7 +20,8 @@ module.exports = function(RED) {
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 };
msg.payload = false;
msg.topic = hostOptions.host;
//only include the extra msg object if operating in advance/array mode.
if (arrayMode) {
msg.ping = hostOptions
@ -221,7 +222,7 @@ module.exports = function(RED) {
let pingables = generatePingList(node.host);
for (let index = 0; index < pingables.length; index++) {
const element = pingables[index];
if (element) { doPing(node, element, false); }
if (element) { doPing(node, element, {}, false); }
}
}, node.timer);
}
@ -234,12 +235,12 @@ module.exports = function(RED) {
let pingables = generatePingList(payload)
for (let index = 0; index < pingables.length; index++) {
const element = pingables[index];
if (element) { doPing(node, element, false); }
if (element) { doPing(node, element, RED.util.cloneMessage(msg), false); }
}
} else if (Array.isArray(payload) ) {
for (let index = 0; index < payload.length; index++) {
const element = payload[index];
if (element) { doPing(node, element, true); }
if (element) { doPing(node, element, RED.util.cloneMessage(msg), true); }
}
}
});

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-ping",
"version" : "0.3.1",
"version" : "0.3.3",
"description" : "A Node-RED node to ping a remote server, for use as a keep-alive check.",
"dependencies" : {
},

View File

@ -117,22 +117,22 @@
<tr>
<td>&nbsp;</td>
<td>
<input type="text" id="node-config-input-serialbaud" style="width:92% height:28px;">
<input type="text" id="node-config-input-serialbaud" style="width:92%; height:30px;">
</td>
<td><select type="text" id="node-config-input-databits" style="width:90%; height:28px;">
<td><select type="text" id="node-config-input-databits" style="width:90%; height:30px;">
<option value="8">8</option>
<option value="7">7</option>
<option value="6">6</option>
<option value="5">5</option>
</select></td>
<td><select type="text" id="node-config-input-parity" style="width:90%; height:28px;">
<td><select type="text" id="node-config-input-parity" style="width:90%; height:30px;">
<option value="none" data-i18n="serial.parity.none"></option>
<option value="even" data-i18n="serial.parity.even"></option>
<option value="mark" data-i18n="serial.parity.mark"></option>
<option value="odd" data-i18n="serial.parity.odd"></option>
<option value="space" data-i18n="serial.parity.space"></option>
</select></td>
<td><select type="text" id="node-config-input-stopbits" style="width:60px; height:28px;">
<td><select type="text" id="node-config-input-stopbits" style="width:60px; height:30px;">
<option value="2">2</option>
<option value="1">1</option>
</select></td>
@ -149,22 +149,22 @@
</tr>
<tr>
<td>&nbsp;</td>
<td><select type="text" id="node-config-input-dtr" style="width:72px; height:28px;">
<td><select type="text" id="node-config-input-dtr" style="width:72px; height:30px;">
<option value="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option>
</select></td>
<td><select type="text" id="node-config-input-rts" style="width:72px; height:28px;">
<td><select type="text" id="node-config-input-rts" style="width:72px; height:30px;">
<option value="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option>
</select></td>
<td><select type="text" id="node-config-input-cts" style="width:72px; height:28px;">
<td><select type="text" id="node-config-input-cts" style="width:72px; height:30px;">
<option value="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option>
</select></td>
<td><select type="text" id="node-config-input-dsr" style="width:72px; height:28px;">
<td><select type="text" id="node-config-input-dsr" style="width:72px; height:30px;">
<option value="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option>
@ -177,23 +177,23 @@
</div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.start"></span>
<input type="text" id="node-config-input-waitfor" style="width:50px; height:28px;">
<input type="text" id="node-config-input-waitfor" style="width:50px; height:28px; text-decoration:grey dotted underline;">
<span data-i18n="serial.label.startor"></span>
</div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.split"></span>
<select type="text" id="node-config-input-out" style="margin-left:11px; width:200px; height:28px;">
<select type="text" id="node-config-input-out" style="margin-left:11px; width:200px;">
<option value="char" data-i18n="serial.split.character"></option>
<option value="time" data-i18n="serial.split.timeout"></option>
<option value="interbyte" data-i18n="serial.split.silent"></option>
<option value="count" data-i18n="serial.split.lengths"></option>
</select>
<input type="text" id="node-config-input-newline" style="width:50px; height:28px;">
<input type="text" id="node-config-input-newline" style="width:50px;">
<span id="node-units"></span>
</div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.deliver"></span>
<select type="text" id="node-config-input-bin" style="margin-left:5px; width:150px; height:28px;">
<select type="text" id="node-config-input-bin" style="margin-left:5px; width:150px;">
<option value="false" data-i18n="serial.output.ascii"></option>
<option value="bin" data-i18n="serial.output.binary"></option>
</select>
@ -204,7 +204,7 @@
</div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<label style="width:auto;" for="node-config-input-addchar"><span data-i18n="serial.addsplit"></span></label>
<input type="text" id="node-config-input-addchar" style="width:50px; height:28px;">
<input type="text" id="node-config-input-addchar" style="width:50px; height:28px; text-decoration:grey dotted underline;">
</div>
</div>
<div id="node-config-req">

View File

@ -271,7 +271,7 @@ module.exports = function(RED) {
if (addchar !== "") { payload += addchar; }
}
else if (addchar !== "") {
payload = Buffer.concat([payload,addchar]);
payload = Buffer.concat([payload,Buffer.from(addchar)]);
}
return payload;
},

View File

@ -1,9 +1,9 @@
{
"name" : "node-red-node-serialport",
"version" : "1.0.1",
"version" : "1.0.3",
"description" : "Node-RED nodes to talk to serial ports",
"dependencies" : {
"serialport" : "^10.3.0"
"serialport" : "^10.5.0"
},
"repository" : {
"type":"git",
@ -21,7 +21,7 @@
"engines" : { "node" : ">=12.0.0" },
"author": {
"name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com",
"email": "dceejay@gmail.com",
"url": "http://nodered.org"
}
}

View File

@ -1,8 +1,16 @@
node-red-node-snmp
==================
A pair of <a href="http://nodered.org" target="_new">Node-RED</a> nodes that
fetch either individual oids, or a table oid from a SNMP enabled host.
A set of <a href="http://nodered.org" target="_new">Node-RED</a> nodes that
fetch values from SNMP enabled hosts. Supports v1, v2c and v3.
* SNMP get - Simple SNMP oid or oid list fetcher
* SNMP set - Simple snmp Set node.
* SNMP subtree - Simple sub tree fetcher
* SNMP table - Simple SNMP oid table fetcher
* SNMP walker - Simple SNMP oid walker fetcher
## v2 Breaking Change
v2 has a breaking change in that the single snmp node no longer tries to stringify an octet string type (04). This makes it consistent with the other nodes in this bundle. this means the user now has to convert to a string if required but has better control of how they wish to do that.
Install
-------
@ -18,9 +26,15 @@ Usage
SNMP oids fetcher. Can fetch a single or comma separated list of oids. Triggered by any input.
`msg.host` may contain the host.
`msg.host` may contain the host including the port.
`msg.community` may contain the community.
`msg.community` may contain the community. (v1 and v2c only)
`msg.username` may contain the username. (v3 only)
`msg.authkey` may contain the digest security key. (v3 only)
`msg.privkey` may contain the encryption security key. (v3 only)
`msg.oid` may contain a comma separated list of oids to search for. (no spaces)
@ -28,6 +42,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
The oids configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input.
@ -38,9 +58,15 @@ Values depends on the oids being requested.
SNMP sets the value of one or more OIDs.
`msg.host` may contain the host.
`msg.host` may contain the host including the port.
`msg.community` may contain the community.
`msg.community` may contain the community. (v1 and v2c only)
`msg.username` may contain the username. (v3 only)
`msg.authkey` may contain the digest security key. (v3 only)
`msg.privkey` may contain the encryption security key. (v3 only)
`msg.varbinds` may contain an array of varbind JSON objects e.g.:
```
@ -81,6 +107,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
The varbinds configured in the edit config will override `msg.varbinds`. Leave blank if you want to use `msg.varbinds` to provide input.
@ -89,16 +121,28 @@ The varbinds configured in the edit config will override `msg.varbinds`. Leave b
Simple SNMP table oid fetcher. Triggered by any input.
`msg.host` may contain the host.
`msg.host` may contain the host including the port.
`msg.community` may contain the community.
`msg.community` may contain the community. (v1 and v2c only)
`msg.oid` may contain the oid of a single table to search for.
`msg.username` may contain the username. (v3 only)
`msg.authkey` may contain the digest security key. (v3 only)
`msg.privkey` may contain the encryption security key. (v3 only)
`msg.oid` may contain a comma separated list of oids to search for. (no spaces)
The host configured in the edit config will override `msg.host`. Leave blank if you want to use `msg.host` to provide input.
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
The oid configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input.
@ -109,9 +153,15 @@ Values depends on the oids being requested.
Simple SNMP oid subtree fetcher. Triggered by any input. Reads from OID specified and any below it.
`msg.host` may contain the host.
`msg.host` may contain the host including the port.
`msg.community` may contain the community.
`msg.community` may contain the community. (v1 and v2c only)
`msg.username` may contain the username. (v3 only)
`msg.authkey` may contain the digest security key. (v3 only)
`msg.privkey` may contain the encryption security key. (v3 only)
`msg.oid` may contain the oid of a single table to search for.
@ -119,6 +169,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
The oid configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input.
@ -129,9 +185,15 @@ Values depends on the oids being requested.
Simple SNMP oid walker fetcher. Triggered by any input. Reads from OID specified to the end of the table.
`msg.host` may contain the host.
`msg.host` may contain the host including the port.
`msg.community` may contain the community.
`msg.community` may contain the community. (v1 and v2c only)
`msg.username` may contain the username. (v3 only)
`msg.authkey` may contain the digest security key. (v3 only)
`msg.privkey` may contain the encryption security key. (v3 only)
`msg.oid` may contain the oid of a single table to search for.
@ -139,6 +201,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
The oid configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input.

View File

@ -1,9 +1,9 @@
{
"name" : "node-red-node-snmp",
"version" : "0.0.25",
"description" : "A Node-RED node that looks for SNMP oids.",
"version" : "2.0.0",
"description" : "A Node-RED node that gets and sets SNMP oid values. Supports v1, v2c and v3",
"dependencies" : {
"net-snmp" : "1.2.4"
"net-snmp" : "^3.9.0"
},
"repository" : {
"type":"git",
@ -11,7 +11,7 @@
"directory" : "tree/master/io/snmp"
},
"license": "Apache-2.0",
"keywords": [ "node-red", "snmp", "oid" ],
"keywords": [ "node-red", "snmp", "oid", "snmpv3" ],
"node-red" : {
"nodes" : {
"snmp": "snmp.js"
@ -24,6 +24,9 @@
},
"contributors": [
{ "name": "Mika Karaila" },
{ "name": "Bryan Malyn" }
{ "name": "Bryan Malyn" },
{ "name": "Steve-Mcl" },
{ "name": "Andres" },
{ "name": "@echobops" }
]
}

View File

@ -1,21 +1,112 @@
<style id="node-red-node-snmp-common-style">
.form-row.form-row-snmpv1v2.hidden {
display: none !important;
}
.form-row.form-row-snmpv3.hidden {
display: none !important;
}
.form-row.form-row-snmpv3-auth.hidden {
display: none;
}
</style>
<script type="text/javascript" id="node-red-node-snmp-common-script">
const node_snmp_common = {
oneditprepare: function (node) {
const compat = { "v1": "1", "v2": "2c", "v2c": "2c", "v3": "3" };
if(compat[node.version]) {
node.version = compat[node.version];
} else if(["1","2c","3"].indexOf(node.version) < 0) {
node.version = "1";
}
$("#node-input-version").on("change", function(evt) {
const isV3 = $("#node-input-version").val() === "3";
$(".form-row-snmpv1v2").toggleClass("hidden", isV3);
$(".form-row-snmpv3").toggleClass("hidden", !isV3);
$("#node-input-auth").trigger("change");
});
$("#node-input-auth").on("change", function(evt) {
const isV3 = $("#node-input-version").val() === "3";
const auth = $("#node-input-auth").val();
if(isV3) {
switch (auth) {
case "authNoPriv":
$(".form-row-snmpv3-auth").toggleClass("hidden", false);
$(".form-row-snmpv3-priv").toggleClass("hidden", true);
break;
case "authPriv":
$(".form-row-snmpv3-auth").toggleClass("hidden", false);
$(".form-row-snmpv3-priv").toggleClass("hidden", false);
break;
default: //"noAuthNoPriv":
$(".form-row-snmpv3-auth").toggleClass("hidden", true);
$(".form-row-snmpv3-priv").toggleClass("hidden", true);
break;
}
}
});
$("#node-input-version").val(node.version);
if(!$("#node-input-auth").val()) {
$("#node-input-auth").val("noAuthNoPriv");
}
$("#node-input-version").trigger("change");
}
}
</script>
<script type="text/html" data-template-name="snmp">
<div class="form-row">
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div>
<div class="form-row">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option>
<option value="2c">v2c</option>
<option value="3">v3</option>
</select>
<span style="margin-left:50px;">Timeout</span>
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;">&nbsp;S
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;">&nbsp;S
</div>
<div class="form-row form-row-snmpv1v2">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<!-- Following Data is used for V3 Only -->
<div class="form-row form-row-snmpv3">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username" placeholder="username">
</div>
<div class="form-row form-row-snmpv3">
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
<select type="text" id="node-input-auth" style="width:150px;">
<option value="noAuthNoPriv">noAuthNoPriv</option>
<option value="authNoPriv">authNoPriv</option>
<option value="authPriv">authPriv</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
<select type="text" id="node-input-authprot" style="width:150px;">
<option value="MD5">MD5</option>
<option value="SHA">SHA</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
<input type="password" id="node-input-authkey" placeholder="Authentication key">
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
<select type="text" id="node-input-privprot" style="width:150px;">
<option value="DES">DES</option>
<option value="AES">AES</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
<input type="password" id="node-input-privkey" placeholder="Encryption key">
</div>
<!-- End of unique data for V3 -->
<div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OIDs</label>
<textarea rows="4" cols="60" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0" style="width:70%;"></textarea>
@ -31,6 +122,9 @@
<p>Simple SNMP oid or oid list fetcher. Triggered by any input.</p>
<p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</p>
<p><code>msg.username</code> may contain the username. (V3 only)</p>
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
<p><code>msg.oid</code> may contain a comma separated list of oids to request. (no spaces)</p>
<p>OIDs must be numeric. iso. is the same a 1. </p>
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
@ -42,11 +136,19 @@
color: "YellowGreen",
defaults: {
host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 },
name: { value: "" }
community: { value: "public" },
auth: { value: "noAuthNoPriv", required: true },
authprot: { value: "MD5", required: true },
privprot: { value: "DES", required: true },
oids: { value: "" },
name: { value: "" },
},
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1,
outputs: 1,
@ -56,6 +158,9 @@
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
}
});
</script>
@ -65,22 +170,61 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div>
<div class="form-row">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option>
<option value="2c">v2c</option>
<!-- Following Data is used for V3 Only -->
<option value="3">v3</option>
<!-- End of unique data for V3 -->
</select>
<span style="margin-left:50px;">Timeout</span>
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;">&nbsp;S
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;">&nbsp;S
</div>
<div class="form-row form-row-snmpv1v2">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<!-- Following Data is used for V3 Only -->
<div class="form-row form-row-snmpv3">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username" placeholder="username">
</div>
<div class="form-row form-row-snmpv3">
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
<select type="text" id="node-input-auth" style="width:150px;">
<option value="noAuthNoPriv">noAuthNoPriv</option>
<option value="authNoPriv">authNoPriv</option>
<option value="authPriv">authPriv</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
<select type="text" id="node-input-authprot" style="width:150px;">
<option value="MD5">MD5</option>
<option value="SHA">SHA</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
<input type="password" id="node-input-authkey" placeholder="Authentication key">
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
<select type="text" id="node-input-privprot" style="width:150px;">
<option value="DES">DES</option>
<option value="AES">AES</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
<input type="password" id="node-input-privkey" placeholder="Encryption key">
</div>
<!-- End of unique data for V3 -->
<div class="form-row">
<label for="node-input-varbinds"><i class="fa fa-tags"></i> Varbinds</label>
<textarea rows="10" cols="60" id="node-input-varbinds" placeholder="e.g. [ { &quot;oid&quot;: &quot;1.3.6.1.2.1.1.5.0&quot;,&quot;type&quot;: &quot;OctetString&quot;,&quot;value&quot;: &quot;host1&quot;},{&quot;oid&quot;: &quot;1.3.6.1.2.1.1.6.0&quot;,&quot;type&quot;: &quot;OctetString&quot;,value: &quot;somewhere&quot;}]"
<textarea rows="10" cols="60" id="node-input-varbinds" placeholder="e.g. [ { &quot;oid&quot;: &quot;1.3.6.1.2.1.1.5.0&quot;, &quot;type&quot;: &quot;OctetString&quot;, &quot;value&quot;: &quot;host1&quot;}, { &quot;oid&quot;: &quot;1.3.6.1.2.1.1.6.0&quot;, &quot;type&quot;: &quot;OctetString&quot;, value: &quot;somewhere&quot; } ]"
style="width:70%;"></textarea>
</div>
<div class="form-row">
@ -94,15 +238,18 @@
<p>Simple snmp Set node. Trigger by any input</p>
<p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</p>
<p><code>msg.username</code> may contain the username. (V3 only)</p>
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
<p><code>msg.varbinds</code> may contain varbinds as an array of json objects containing multiple oids, types and values.
<pre>[
{
"oid": "1.3.6.1.2.1.1.5.0",
"type": "OctetString",
"value": "host1"
},
{ "oid": ... }
]</pre>
<code style="font-size: smaller;"><pre style="white-space: pre;">[
{
"oid": "1.3.6.1.2.1.1.5.0",
"type": "OctetString",
"value": "host1"
},
{ "oid": ... }
]</pre></code>
<p>Any numeric inputs must be numbers, not strings, e.g. 1 not "1".</p>
<p>OIDs must be numeric. iso. is the same a 1.</p>
</p>
@ -114,12 +261,27 @@
color: "YellowGreen",
defaults: {
host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true },
varbinds: { value: "" },
timeout: { value: 5 },
community: { value: "public" },
auth: { value: "noAuthNoPriv", required: true },
authprot: { value: "MD5", required: true },
privprot: { value: "DES", required: true },
oids: { value: "" },
varbinds: { value: "", validate:function(v) {
try {
return !v || !!JSON.parse(v);
} catch(e) {
return false;
}
}},
name: { value: "" }
},
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1,
outputs: 0,
icon: "snmp.png",
@ -128,6 +290,9 @@
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
}
});
</script>
@ -137,19 +302,58 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div>
<div class="form-row">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option>
<option value="2c">v2c</option>
<!-- Following Data is used for V3 Only -->
<option value="3">v3</option>
<!-- End of unique data for V3 -->
</select>
<span style="margin-left:50px;">Timeout</span>
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;">&nbsp;S
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;">&nbsp;S
</div>
<div class="form-row form-row-snmpv1v2">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<!-- Following Data is used for V3 Only -->
<div class="form-row form-row-snmpv3">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username" placeholder="username">
</div>
<div class="form-row form-row-snmpv3">
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
<select type="text" id="node-input-auth" style="width:150px;">
<option value="noAuthNoPriv">noAuthNoPriv</option>
<option value="authNoPriv">authNoPriv</option>
<option value="authPriv">authPriv</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
<select type="text" id="node-input-authprot" style="width:150px;">
<option value="MD5">MD5</option>
<option value="SHA">SHA</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
<input type="password" id="node-input-authkey" placeholder="Authentication key">
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
<select type="text" id="node-input-privprot" style="width:150px;">
<option value="DES">DES</option>
<option value="AES">AES</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
<input type="password" id="node-input-privkey" placeholder="Encryption key">
</div>
<!-- End of unique data for V3 -->
<div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label>
<input type="text" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0">
@ -165,6 +369,9 @@
<p>Simple SNMP oid table fetcher. Triggered by any input.</p>
<p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</p>
<p><code>msg.username</code> may contain the username. (V3 only)</p>
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
<p><code>msg.oid</code> may contain the oid of a table to request.</p>
<p>OID must be numeric. iso. is the same a 1.</p>
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
@ -176,12 +383,20 @@
color: "YellowGreen",
defaults: {
host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 },
community: { value: "public" },
auth: { value: "noAuthNoPriv", required: true },
authprot: { value: "MD5", required: true },
privprot: { value: "DES", required: true },
oids: { value: "" },
name: { value: "" }
},
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1,
outputs: 1,
icon: "snmp.png",
@ -190,6 +405,9 @@
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
}
});
</script>
@ -199,19 +417,58 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div>
<div class="form-row">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option>
<option value="2c">v2c</option>
<!-- Following Data is used for V3 Only -->
<option value="3">v3</option>
<!-- End of unique data for V3 -->
</select>
<span style="margin-left:50px;">Timeout</span>
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;">&nbsp;S
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;">&nbsp;S
</div>
<div class="form-row form-row-snmpv1v2">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<!-- Following Data is used for V3 Only -->
<div class="form-row form-row-snmpv3">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username" placeholder="username">
</div>
<div class="form-row form-row-snmpv3">
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
<select type="text" id="node-input-auth" style="width:150px;">
<option value="noAuthNoPriv">noAuthNoPriv</option>
<option value="authNoPriv">authNoPriv</option>
<option value="authPriv">authPriv</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
<select type="text" id="node-input-authprot" style="width:150px;">
<option value="MD5">MD5</option>
<option value="SHA">SHA</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
<input type="password" id="node-input-authkey" placeholder="Authentication key">
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
<select type="text" id="node-input-privprot" style="width:150px;">
<option value="DES">DES</option>
<option value="AES">AES</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
<input type="password" id="node-input-privkey" placeholder="Encryption key">
</div>
<!-- End of unique data for V3 -->
<div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label>
<input type="text" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0">
@ -227,6 +484,9 @@
<p>Simple SNMP oid subtree fetcher. Triggered by any input. Reads all OIDS at and below the current base OID.</p>
<p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</p>
<p><code>msg.username</code> may contain the username. (V3 only)</p>
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
<p><code>msg.oid</code> may contain the oid of a table to request.</p>
<p>OID must be numeric. iso. is the same a 1. </p>
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
@ -238,12 +498,20 @@
color: "YellowGreen",
defaults: {
host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 },
community: { value: "public" },
auth: { value: "noAuthNoPriv", required: true },
authprot: { value: "MD5", required: true },
privprot: { value: "DES", required: true },
oids: { value: "" },
name: { value: "" }
},
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1,
outputs: 1,
icon: "snmp.png",
@ -252,6 +520,9 @@
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
}
});
</script>
@ -262,19 +533,58 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div>
<div class="form-row">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option>
<option value="2c">v2c</option>
<!-- Following Data is used for V3 Only -->
<option value="3">v3</option>
<!-- End of unique data for V3 -->
</select>
<span style="margin-left:50px;">Timeout</span>
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;">&nbsp;S
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;">&nbsp;S
</div>
<div class="form-row form-row-snmpv1v2">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<!-- Following Data is used for V3 Only -->
<div class="form-row form-row-snmpv3">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username" placeholder="username">
</div>
<div class="form-row form-row-snmpv3">
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
<select type="text" id="node-input-auth" style="width:150px;">
<option value="noAuthNoPriv">noAuthNoPriv</option>
<option value="authNoPriv">authNoPriv</option>
<option value="authPriv">authPriv</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
<select type="text" id="node-input-authprot" style="width:150px;">
<option value="MD5">MD5</option>
<option value="SHA">SHA</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
<input type="password" id="node-input-authkey" placeholder="Authentication key">
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
<select type="text" id="node-input-privprot" style="width:150px;">
<option value="DES">DES</option>
<option value="AES">AES</option>
</select>
</div>
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
<input type="password" id="node-input-privkey" placeholder="Encryption key">
</div>
<!-- End of unique data for V3 -->
<div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label>
<input type="text" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0">
@ -287,14 +597,17 @@
</script>
<script type="text/html" data-help-name="snmp walker">
<p>Simple SNMP oid walker fetcher. Triggered by any input.
<p>Simple SNMP oid walker fetcher. Triggered by any input.
Fetches all nodes from this OID to the end of the table.</p>
<p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</p>
<p><code>msg.username</code> may contain the username. (V3 only)</p>
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
<p><code>msg.oid</code> may contain the oid of a table to request.</p>
<p>OID must be numeric. iso. is the same a 1. </p>
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
<p><b>Note</b>: This node does indeed "walk" down the tree. This is different behaviour to
<p><b>Note</b>: This node does indeed "walk" down the tree. This is different behaviour to
the typical snmpwalk command line app.</p>
</script>
@ -304,12 +617,20 @@
color: "YellowGreen",
defaults: {
host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 },
community: { value: "public" },
auth: { value: "noAuthNoPriv", required: true },
authprot: { value: "MD5", required: true },
privprot: { value: "DES", required: true },
oids: { value: "" },
name: { value: "" }
},
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1,
outputs: 1,
icon: "snmp.png",
@ -318,6 +639,9 @@
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
}
});
</script>

View File

@ -1,262 +1,480 @@
module.exports = function (RED) {
"use strict";
var snmp = require("net-snmp");
var sessions = {};
function getSession(host, community, version, timeout) {
var sessionKey = host + ":" + community + ":" + version;
var port = 161;
if (host.indexOf(":") !== -1) {
port = host.split(":")[1];
host = host.split(":")[0];
const SNMP = require("net-snmp");
const sessions = {};
function generateUUID() {
let d = Date.now();
let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || (Date.now() * Math.random() * 100000);//Time in microseconds since load
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16;//random number between 0 and 16
if (d > 0) {//Use timestamp until depleted
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {//Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
function openSession(sessionid, host, user, options) {
// SNMPv3 call
if (options.version === SNMP.Version3) {
sessions[sessionid] = SNMP.createV3Session(host, user, options);
}
if (!(sessionKey in sessions)) {
sessions[sessionKey] = snmp.createSession(host, community, { port:port, version:version, timeout:(timeout || 5000) });
// SNMPv1 or SNMPv2c call
else {
sessions[sessionid] = SNMP.createSession(host, user.community, options);
}
return sessions[sessionKey];
return sessions[sessionid];
}
function SnmpNode(n) {
RED.nodes.createNode(this, n);
this.community = n.community;
this.host = n.host;
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
this.oids = n.oids.replace(/\s/g, "");
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
// Any session needs to be closed after completion
function closeSession(sessionid) {
try {
sessions[sessionid].removeAllListeners();
} catch (e) { }
try {
sessions[sessionid].close();
} catch (e) { }
delete sessions[sessionid];
}
this.on("input", function (msg) {
var host = node.host || msg.host;
var community = node.community || msg.community;
var oids = node.oids || msg.oid;
function initSnmpNode(node, config) {
node.community = config.community;
node.host = config.host;
node.version = config.version;
node.auth = config.auth;
node.authprot = config.authprot;
node.privprot = config.privprot;
if (node.credentials) {
node.username = node.credentials.username;
node.authkey = node.credentials.authkey;
node.privkey = node.credentials.privkey;
}
node.timeout = Number(config.timeout || 5) * 1000;
}
function prepareSnmpOptions(node, msg) {
let host = node.host || msg.host;
const sessionid = generateUUID();
const user = {}
const options = {};
const compat = { "v1": "1", "v2": "2c", "v2c": "2c", "v3": "3" };
if(compat[node.version]) {
node.version = compat[node.version];
} else if(["1","2c","3"].indexOf(node.version) < 0) {
node.version = "1";
}
options.version = node.version;
if (node.version === "1") {
options.version = SNMP.Version1;
user.community = node.community || msg.community;
} else if (node.version === "2c") {
options.version = SNMP.Version2c;
user.community = node.community || msg.community;
} else if (node.version === "3") {
user.name = node.username || msg.username || "";
user.level = SNMP.SecurityLevel.noAuthNoPriv;
user.authProtocol = SNMP.AuthProtocols.none;
user.authKey = "";
user.privProtocol = SNMP.PrivProtocols.none;
user.privKey = "";
options.version = SNMP.Version3;
if (node.auth === "authNoPriv" || node.auth === "authPriv") {
user.level = SNMP.SecurityLevel.authNoPriv;
user.authProtocol = (node.authprot === "SHA") ? SNMP.AuthProtocols.sha : SNMP.AuthProtocols.md5;
user.authKey = node.authkey || msg.authkey || "";
if (node.auth === "authPriv") {
user.level = SNMP.SecurityLevel.authPriv;
if (node.privprot === "DES" || node.privprot === "AES") {
user.privProtocol = (node.privprot === "AES") ? SNMP.PrivProtocols.aes : SNMP.PrivProtocols.des;
user.privKey = node.privkey || msg.privkey || "";
}
}
}
}
options.timeout = node.timeout;
options.debug = msg.debug || undefined;
options.port = options.port || 161;
options.retries = options.retries || 1;
if (msg.engineID) {
options.engineID = msg.engineID;//The engineID used for SNMPv3 communications, given as a hex string - defaults to a system-generated engineID containing elements of random
}
if (msg.backoff) {
options.backoff = msg.backoff;//The factor by which to increase the timeout for every retry, defaults to 1 for no increase
}
if (msg.backwardsGetNexts) {
options.backwardsGetNexts = msg.backwardsGetNexts;//boolean to allow GetNext operations to retrieve lexicographically preceding OIDs
}
if (msg.idBitsSize === 16 || msg.idBitsSize === 32) {
options.idBitsSize = msg.idBitsSize;//Either 16 or 32, defaults to 32. Used to reduce the size of the generated id for compatibility with some older devices.
}
const ipv = parseIP(host);
if (ipv.version === 4) {
host = ipv.ip;
options.port = ipv.port || options.port;
options.transport = 'udp4';
} else if (ipv.version === 6) {
host = ipv.ip;
options.port = ipv.port || options.port;
options.transport = 'udp6';
} else {
//probably a host name
if (host.indexOf(":") > 0) {
host = host.split(":")[0];
options.port = host.split(":")[1];
}
}
return {
host: host,
sessionid: sessionid,
user: user,
options: options,
}
}
function parseIP(ip) {
const IPV4_PAT = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::(\d+)){0,1}$/g;
const IPV6_DOUBLE_COL_PAT = /^\[{0,1}([0-9a-f:]*)::([0-9a-f:]*)(?:\]:(\d+)){0,1}$/g;
const ipv4Matcher = IPV4_PAT.exec(ip);
let hex = "";
let port;
let ipOnly = [];
try {
if (ipv4Matcher && ipv4Matcher.length) {
for (let i = 1; i <= 4; i++) {
ipOnly.push(ipv4Matcher[i]);
hex += toHex4(ipv4Matcher[i]);
}
if (ipv4Matcher[5]) {
port = parseInt(ipv4Matcher[5]);
}
return { ip: ipOnly.join("."), hex, port, version: 4 };
}
// IPV6 Must be colons format (a:b:c:d:e:A.B.C.D not currently supported)
let ipv6Pattern = "^\\[{0,1}";
for (let i = 1; i <= 7; i++) {
ipv6Pattern += "([0-9a-f]+):";
}
ipv6Pattern += "([0-9a-f]+)(?:\\]:(\\d+)){0,1}$";
const IPV6_PAT = new RegExp(ipv6Pattern);
// IPV6, double colon
const ipv6DoubleColonMatcher = IPV6_DOUBLE_COL_PAT.exec(ip);
if (ipv6DoubleColonMatcher && ipv6DoubleColonMatcher.length) {
let p1 = ipv6DoubleColonMatcher[1];
if (!p1) {
p1 = "0";
}
let p2 = ipv6DoubleColonMatcher[2];
if (!p2) {
p2 = "0";
}
p1 = p1.padStart(4, "0");
p2 = p2.padStart(4, "0");
ip = p1 + getZeros(8 - numCount(p1) - numCount(p2)) + p2;
if (ipv6DoubleColonMatcher[3]) {
ip = "[" + ip + "]:" + ipv6DoubleColonMatcher[3];
}
}
// IPV6
const ipv6Matcher = IPV6_PAT.exec(ip);
if (ipv6Matcher && ipv6Matcher.length) {
for (let i = 1; i <= 8; i++) {
const p = toHex6(ipv6Matcher[i]).padStart(4, "0");
ipOnly.push(p);
hex += p;
}
if (ipv6Matcher[9]) {
port = parseInt(ipv6Matcher[9]);
}
return { ip: ipOnly.join(":"), hex, port, version: 6 };
}
throw new Error("Unknown address: " + ip);
} catch (error) {
return { ip, hex, port, version: null, error: error };
}
function numCount(/** @type {string} */s) {
return s.split(":").length;
}
function getZeros(/** @type {number} */ count) {
const sb = [":"];
while (count > 0) {
sb.push("0000:");
count--;
}
return sb.join("");
}
function toHex4(/** @type {string} */ s) {
const val = parseInt(s);
if (val < 0 || val > 255) {
throw new Error("Invalid value : " + s);
}
return val.toString(16).padStart(2, "0");
}
function toHex6(/** @type {string} */ s) {
const val = parseInt(s, 16);
if (val < 0 || val > 65536) {
throw new Error("Invalid hex value : " + s);
}
return s;
}
}
function SnmpNode(n) {
const node = this;
RED.nodes.createNode(node, n);
initSnmpNode(node, n);
node.oids = n.oids ? n.oids.replace(/\s/g, "") : "";
node.on("input", function (msg) {
const oids = node.oids || msg.oid;
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
if (oids) {
getSession(host, community, node.version, node.timeout).get(oids.split(","), function (error, varbinds) {
let sess = openSession(sessionid, host, user, options);
sess.on("error", function (err) {
node.error(err, msg);
})
sess.get(oids.split(","), function (error, varbinds) {
if (error) {
node.error(error.toString(), msg);
}
else {
for (var i = 0; i < varbinds.length; i++) {
if (snmp.isVarbindError(varbinds[i])) {
node.error(snmp.varbindError(varbinds[i]), msg);
}
else {
if (varbinds[i].type == 4) { varbinds[i].value = varbinds[i].value.toString(); }
varbinds[i].tstr = snmp.ObjectType[varbinds[i].type];
//node.log(varbinds[i].oid + "|" + varbinds[i].tstr + "|" + varbinds[i].value);
} else {
for (let i = 0; i < varbinds.length; i++) {
let vb = varbinds[i];
if (SNMP.isVarbindError(vb)) {
node.error(SNMP.varbindError(vb), msg);
vb._error = SNMP.varbindError(vb); //add _error to msg so users can determine the varbind is not valid
}
// else {
// if (vb.type == 4) { vb.value = vb.value.toString(); }
// }
vb.tstr = SNMP.ObjectType[vb.type];
}
msg.oid = oids;
msg.payload = varbinds;
msg.oid = oids;
node.send(msg);
}
closeSession(sessionid); // Needed to close the session else a bad or good read could affect future readings
});
}
else {
} else {
node.warn("No oid(s) to search for");
}
});
}
RED.nodes.registerType("snmp", SnmpNode);
RED.nodes.registerType("snmp", SnmpNode, {
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
}
});
function SnmpSNode(n) {
RED.nodes.createNode(this, n);
this.community = n.community;
this.host = n.host;
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
this.varbinds = n.varbinds;
this.timeout = Number(n.timeout || 5) * 1000;
if (this.varbinds && this.varbinds.trim().length === 0) { delete this.varbinds; }
var node = this;
this.on("input", function (msg) {
var host = node.host || msg.host;
var community = node.community || msg.community;
var varbinds = (node.varbinds) ? JSON.parse(node.varbinds) : msg.varbinds;
const node = this;
RED.nodes.createNode(node, n);
initSnmpNode(node, n);
node.varbinds = n.varbinds;
if (node.varbinds && node.varbinds.trim().length === 0) { delete node.varbinds; }
node.on("input", function (msg) {
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
const varbinds = (node.varbinds) ? JSON.parse(node.varbinds) : msg.varbinds;
if (varbinds) {
for (var i = 0; i < varbinds.length; i++) {
varbinds[i].type = snmp.ObjectType[varbinds[i].type];
for (let i = 0; i < varbinds.length; i++) {
varbinds[i].type = SNMP.ObjectType[varbinds[i].type];
}
getSession(host, community, node.version, node.timeout).set(varbinds, function (error, varbinds) {
let sess = openSession(sessionid, host, user, options);
sess.on("error", function (err) {
node.error(err, msg);
})
sess.set(varbinds, function (error, varbinds) {
if (error) {
node.error(error.toString(), msg);
}
else {
for (var i = 0; i < varbinds.length; i++) {
} else {
for (let i = 0; i < varbinds.length; i++) {
// for version 2c we must check each OID for an error condition
if (snmp.isVarbindError(varbinds[i])) {
node.error(snmp.varbindError(varbinds[i]), msg);
if (SNMP.isVarbindError(varbinds[i])) {
node.error(SNMP.varbindError(varbinds[i]), msg);
}
}
}
closeSession(sessionid);
});
}
else {
} else {
node.warn("No varbinds to set");
}
});
}
RED.nodes.registerType("snmp set", SnmpSNode);
RED.nodes.registerType("snmp set", SnmpSNode, {
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
}
});
function SnmpTNode(n) {
RED.nodes.createNode(this, n);
this.community = n.community;
this.host = n.host;
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
this.oids = n.oids.replace(/\s/g, "");
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
var maxRepetitions = 20;
const node = this;
RED.nodes.createNode(node, n);
initSnmpNode(node, n);
node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
const maxRepetitions = 20;
function sortInt(a, b) {
if (a > b) { return 1; }
else if (b > a) { return -1; }
else { return 0; }
else if (b > a) { return -1; } else { return 0; }
}
this.on("input", function (msg) {
var host = node.host || msg.host;
var community = node.community || msg.community;
var oids = node.oids || msg.oid;
node.on("input", function (msg) {
const oids = node.oids || msg.oid;
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
if (oids) {
msg.oid = oids;
getSession(host, community, node.version, node.timeout).table(oids, maxRepetitions, function (error, table) {
let sess = openSession(sessionid, host, user, options);
sess.on("error", function (err) {
node.error(err, msg);
})
sess.table(oids, maxRepetitions, function (error, table) {
if (error) {
node.error(error.toString(), msg);
}
else {
var indexes = [];
for (var index in table) {
} else {
const indexes = [];
for (let index in table) {
if (table.hasOwnProperty(index)) {
indexes.push(parseInt(index));
}
}
indexes.sort(sortInt);
for (var i = 0; i < indexes.length; i++) {
var columns = [];
for (var column in table[indexes[i]]) {
for (let i = 0; i < indexes.length; i++) {
const columns = [];
for (let column in table[indexes[i]]) {
if (table[indexes[i]].hasOwnProperty(column)) {
columns.push(parseInt(column));
}
}
columns.sort(sortInt);
// console.log("row index = " + indexes[i]);
// for (var j = 0; j < columns.length; j++) {
// console.log(" column " + columns[j] + " = " + table[indexes[i]][columns[j]]);
// }
}
msg.payload = table;
node.send(msg);
}
closeSession(sessionid);
});
}
else {
} else {
node.warn("No oid to search for");
}
});
}
RED.nodes.registerType("snmp table", SnmpTNode);
RED.nodes.registerType("snmp table", SnmpTNode, {
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
}
});
function SnmpSubtreeNode(n) {
RED.nodes.createNode(this, n);
this.community = n.community;
this.host = n.host;
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
this.oids = n.oids.replace(/\s/g, "");
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
var maxRepetitions = 20;
var response = [];
const node = this;
RED.nodes.createNode(node, n);
initSnmpNode(node, n);
node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
const maxRepetitions = 20;
function feedCb(varbinds) {
for (var i = 0; i < varbinds.length; i++) {
if (snmp.isVarbindError(varbinds[i])) {
node.error(snmp.varbindError(varbinds[i]), msg);
}
else {
//console.log(varbinds[i].oid + "|" + varbinds[i].value);
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
node.on("input", function (msg) {
const oids = node.oids || msg.oid;
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
const response = [];
function feedCb(varbinds) {
for (let i = 0; i < varbinds.length; i++) {
if (SNMP.isVarbindError(varbinds[i])) {
node.error(SNMP.varbindError(varbinds[i]), msg);
} else {
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
}
}
}
}
this.on("input", function (msg) {
var host = node.host || msg.host;
var community = node.community || msg.community;
var oids = node.oids || msg.oid;
if (oids) {
msg.oid = oids;
getSession(host, community, node.version, node.timeout).subtree(msg.oid, maxRepetitions, feedCb, function (error) {
let sess = openSession(sessionid, host, user, options);
sess.on("error", function (err) {
node.error(err, msg);
})
sess.subtree(msg.oid, maxRepetitions, feedCb, function (error) {
if (error) {
node.error(error.toString(), msg);
}
else {
// Clone the array
msg.payload = response.slice(0);
} else {
msg.payload = response;
node.send(msg);
//Clears response
response.length = 0;
}
closeSession(sessionid);
});
}
else {
} else {
node.warn("No oid to search for");
}
});
}
RED.nodes.registerType("snmp subtree", SnmpSubtreeNode);
RED.nodes.registerType("snmp subtree", SnmpSubtreeNode, {
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
}
});
function SnmpWalkerNode(n) {
RED.nodes.createNode(this, n);
this.community = n.community;
this.host = n.host;
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
this.oids = n.oids.replace(/\s/g, "");
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
var maxRepetitions = 20;
var response = [];
const node = this;
RED.nodes.createNode(node, n);
initSnmpNode(node, n);
node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
const maxRepetitions = 20;
function feedCb(varbinds) {
for (var i = 0; i < varbinds.length; i++) {
if (snmp.isVarbindError(varbinds[i])) {
node.error(snmp.varbindError(varbinds[i]), msg);
}
else {
//console.log(varbinds[i].oid + "|" + varbinds[i].value);
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
node.on("input", function (msg) {
const oids = node.oids || msg.oid;
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
const response = [];
function feedCb(varbinds) {
for (let i = 0; i < varbinds.length; i++) {
if (SNMP.isVarbindError(varbinds[i])) {
node.error(SNMP.varbindError(varbinds[i]), msg);
} else {
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
}
}
}
}
this.on("input", function (msg) {
node.msg = msg;
var oids = node.oids || msg.oid;
var host = node.host || msg.host;
var community = node.community || msg.community;
if (oids) {
msg.oid = oids;
getSession(host, community, node.version, node.timeout).walk(msg.oid, maxRepetitions, feedCb, function (error) {
let sess = openSession(sessionid, host, user, options);
sess.on("error", function (err) {
node.error(err, msg);
})
sess.walk(msg.oid, maxRepetitions, feedCb, function (error) {
if (error) {
node.error(error.toString(), msg);
}
else {
// Clone the array
msg.payload = response.slice(0);
} else {
msg.payload = response;
node.send(msg);
//Clears response
response.length = 0;
}
closeSession(sessionid);
});
}
else {
} else {
node.warn("No oid to search for");
}
});
}
RED.nodes.registerType("snmp walker", SnmpWalkerNode);
RED.nodes.registerType("snmp walker", SnmpWalkerNode, {
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
}
});
};

View File

@ -57,6 +57,10 @@ module.exports = function(RED) {
node.warn("reconnecting");
});
node.client.on("reconnect", function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
node.client.on("error", function(error) {
node.status({fill:"grey",shape:"dot",text:"error"});
node.warn(error);
@ -124,6 +128,10 @@ module.exports = function(RED) {
node.warn("reconnecting");
});
node.client.on("reconnect", function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
node.client.on("error", function(error) {
node.status({fill:"grey",shape:"dot",text:"error"});
node.warn(error);

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-stomp",
"version" : "0.0.12",
"version" : "0.0.14",
"description" : "A Node-RED node to publish and subscribe to/from a Stomp server",
"dependencies" : {
"stomp-client" : "^0.9.0"
@ -19,7 +19,7 @@
},
"author": {
"name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com",
"email": "dceejay@gmail.com",
"url": "http://nodered.org"
}
}

View File

@ -33,29 +33,29 @@
"devDependencies": {
"exif": "^0.6.0",
"feedparser": "^2.2.10",
"grunt": "^1.4.1",
"grunt": "^1.6.1",
"grunt-cli": "^1.4.3",
"grunt-contrib-jshint": "^2.1.0",
"grunt-jscs": "^3.0.1",
"grunt-lint-inline": "^1.0.0",
"grunt-simple-mocha": "^0.4.1",
"imap": "^0.8.19",
"mailparser": "^3.4.0",
"markdown-it": "^12.3.0",
"mailparser": "^3.6.4",
"markdown-it": "^12.3.2",
"mocha": "~6.2.3",
"msgpack-lite": "^0.1.26",
"multilang-sentiment": "^1.2.0",
"ngeohash": "^0.6.3",
"node-red": "^2.1.4",
"node-red-node-test-helper": "^0.2.7",
"nodemailer": "^6.7.2",
"poplib": "^0.1.7",
"node-red": "^3.0.2",
"node-red-node-test-helper": "^0.3.0",
"nodemailer": "^6.9.1",
"node-pop3": "^0.8.0",
"proxyquire": "^2.1.3",
"pushbullet": "^2.4.0",
"sentiment": "^2.1.0",
"should": "^13.2.3",
"sinon": "~7.5.0",
"smtp-server": "^3.10.0",
"smtp-server": "^3.11.0",
"supertest": "^4.0.2",
"when": "^3.7.8"
},

44
parsers/cbor/70-cbor.html Normal file
View File

@ -0,0 +1,44 @@
<script type="text/html" data-template-name="cbor">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="cbor">
<p>A function that converts the <code>msg.payload</code> to and from <a href = "https://cbor.io"><i>cbor</i></a> format.</p>
<p>If the input is NOT a buffer it tries to convert it to a cbor buffer.</p>
<p>If the input is a cbor buffer it tries to decode it back.</p>
<p><b>Note</b>: this node does not currently encode raw <code>buffer</code> types.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('cbor',{
category: 'parser',
color:"#DEBD5C",
defaults: {
name: {value:""},
property: {value:"payload",required:true}
},
inputs:1,
outputs:1,
icon: "parser-cbor.png",
label: function() {
return this.name||"cbor";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
}
});
</script>

38
parsers/cbor/70-cbor.js Normal file
View File

@ -0,0 +1,38 @@
module.exports = function(RED) {
"use strict";
var cbor = require('cbor-x');
function CborNode(n) {
RED.nodes.createNode(this,n);
this.property = n.property||"payload";
var node = this;
this.on("input", function(msg) {
var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
if (Buffer.isBuffer(value)) {
var l = value.length;
try {
value = cbor.decode(value);
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg);
node.status({text:l +" b->o "+ JSON.stringify(value).length});
}
catch (e) {
node.error("Bad decode",msg);
node.status({text:"not a cbor buffer"});
}
}
else {
var le = JSON.stringify(value).length;
value = cbor.encode(value);
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg);
node.status({text:le +" o->b "+ value.length});
}
}
else { node.warn("No payload found to process"); }
});
}
RED.nodes.registerType("cbor",CborNode);
}

14
parsers/cbor/LICENSE Normal file
View File

@ -0,0 +1,14 @@
Copyright 2016 JS Foundation and other contributors, https://js.foundation/
Copyright 2013-2016 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.

27
parsers/cbor/README.md Normal file
View File

@ -0,0 +1,27 @@
node-red-node-cbor
==================
A <a href="http://nodered.org" target="_new">Node-RED</a> node to pack and unpack objects to cbor format buffers.
Install
-------
Run the following command in your Node-RED user directory - typically `~/.node-red`
npm install node-red-node-cbor
Changes
-------
Version 1.0.0 - move to cbor-x library (more supported and faster).
Usage
-----
Uses the <a href="https://www.npmjs.org/package/cbor-x">cbor-x npm</a> to pack and unpack msg.payload objects to <a href="https://cbor.io/">cbor</a> format buffers.
**Note**: this node does not currently encode raw <code>buffer</code> types.
It will automatically try to *decode* any buffer received, and may not cause an error.
If the input is NOT a buffer it converts it into a msgpack buffer.
If the input is a msgpack buffer it converts it back to the original type.

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

32
parsers/cbor/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name" : "node-red-node-cbor",
"version" : "1.0.1",
"description" : "A Node-RED node to pack and unpack objects to cbor format",
"dependencies" : {
"cbor-x" : "^1.3.2"
},
"bundledDependencies": [
"cbor-x"
],
"repository" : {
"type":"git",
"url":"https://github.com/node-red/node-red-nodes.git",
"directory": "tree/master/parsers/cbor"
},
"license": "Apache-2.0",
"keywords": [ "node-red", "cbor" ],
"node-red" : {
"version": ">=1.0.0",
"nodes" : {
"cbor": "70-cbor.js"
}
},
"author": {
"name": "Dave Conway-Jones",
"email": "dceejay@gmail.com",
"url": "http://nodered.org"
},
"engines": {
"node": ">=14"
}
}

View File

@ -1,9 +1,9 @@
{
"name": "node-red-node-markdown",
"version": "0.3.0",
"version": "0.4.0",
"description": "A Node-RED node to convert a markdown string to html.",
"dependencies": {
"markdown-it": "^12.3.2"
"markdown-it": "^13.0.1"
},
"repository": {
"type": "git",
@ -16,6 +16,7 @@
"markdown"
],
"node-red": {
"version": ">=1.0.0",
"nodes": {
"markdown": "70-markdown.js"
}
@ -24,5 +25,8 @@
"name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com",
"url": "http://nodered.org"
},
"engines": {
"node": ">=14"
}
}

View File

@ -40,15 +40,32 @@
<span data-i18n="email.label.useSecureConnection"></span>
</div>
<div class="form-row">
<label for="node-input-authtype"><i class="fa fa-tasks"></i> <span data-i18n="email.label.authtype"></span></label>
<select type="text" id="node-input-authtype">
<option value="BASIC">Basic</option>
<option value="XOAUTH2">XOAuth2</option>
<option value="NONE">None</option>
</select>
</div>
<div class="form-row node-input-userid">
<label for="node-input-userid"><i class="fa fa-user"></i> <span data-i18n="email.label.userid"></span></label>
<input type="text" id="node-input-userid">
</div>
<div class="form-row">
<div class="form-row node-input-password">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="email.label.password"></span></label>
<input type="password" id="node-input-password">
</div>
<div class="form-row node-input-saslformat" style="display: none;">
<label for="node-input-saslformat"><i class="fa fa-code"></i> <span data-i18n="email.label.saslformat"></span></label>
<input type="checkbox" id="node-input-saslformat" style="width: auto;">
</div>
<div class="form-row node-input-token" style="display: none;">
<label for="node-input-token"><i class="fa fa-lock"></i> <span data-i18n="email.label.token"></span></label>
<input type="text" id="node-input-token" placeholder="oauth2Response.access_token">
</div>
<br/>
<div class="form-row">
<div class="form-row node-input-useTLS">
<label for="node-input-useTLS"><i class="fa fa-lock"></i> <span data-i18n="email.label.useTLS"></label>
<input type="checkbox" id="node-input-tls" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="email.label.rejectUnauthorised"></span>
@ -68,6 +85,9 @@
defaults: {
server: {value:"smtp.gmail.com",required:true},
port: {value:"465",required:true},
authtype: {value: "BASIC"},
saslformat: {value: true},
token: {value: "oauth2Response.access_token"},
secure: {value: true},
tls: {value: true},
name: {value:""},
@ -92,11 +112,43 @@
return (this.dname)?"node_label_italic":"";
},
oneditprepare: function() {
if (this.authtype === undefined) {
this.authtype = "BASIC";
$("#node-input-authtype").val('BASIC');
}
$("#node-input-authtype").change(function() {
var protocol = $("#node-input-authtype").val();
if (protocol === "BASIC") {
$(".node-input-userid").show();
$(".node-input-password").show();
$(".node-input-saslformat").hide();
$(".node-input-token").hide();
$(".node-input-useTLS").show();
} else if (protocol === "NONE") {
$(".node-input-userid").hide();
$(".node-input-password").hide();
$(".node-input-saslformat").hide();
$(".node-input-token").hide();
$(".node-input-useTLS").hide();
} else {
$(".node-input-userid").show();
$(".node-input-password").hide();
$(".node-input-saslformat").show();
$(".node-input-token").show();
$("#node-input-fetch").val("trigger");
$("#node-input-fetch").change();
$(".node-input-useTLS").show();
}
});
if (this.credentials.global) {
$('#node-tip').show();
} else {
$('#node-tip').hide();
}
$("#node-input-token").typedInput({
type:'msg',
types:['msg']
});
}
});
})();
@ -143,13 +195,29 @@
<input type="text" id="node-input-port" placeholder="993">
</div>
<div class="form-row">
<label for="node-input-authtype"><i class="fa fa-tasks"></i> <span data-i18n="email.label.authtype"></span></label>
<select type="text" id="node-input-authtype">
<option value="BASIC">Basic</option>
<option value="XOAUTH2">XOAuth2</option>
<option value="NONE">None</option>
</select>
</div>
<div class="form-row node-input-userid" id="node-userid">
<label for="node-input-userid"><i class="fa fa-user"></i> <span data-i18n="email.label.userid"></span></label>
<input type="text" id="node-input-userid">
</div>
<div class="form-row">
<div class="form-row node-input-password" id="node-password">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="email.label.password"></span></label>
<input type="password" id="node-input-password">
</div>
<div class="form-row node-input-saslformat" style="display: none;">
<label for="node-input-saslformat"><i class="fa fa-code"></i> <span data-i18n="email.label.saslformat"></span></label>
<input type="checkbox" id="node-input-saslformat" style="width: auto;">
</div>
<div class="form-row node-input-token" style="display: none;">
<label for="node-input-token"><i class="fa fa-lock"></i> <span data-i18n="email.label.token"></span></label>
<input type="text" id="node-input-token" placeholder="oauth2Response.access_token">
</div>
<div class="form-row node-input-box">
<label for="node-input-box"><i class="fa fa-inbox"></i> <span data-i18n="email.label.folder"></span></label>
<input type="text" id="node-input-box">
@ -222,6 +290,29 @@
}
checkPorts();
});
$("#node-input-authtype").change(function() {
var protocol = $("#node-input-authtype").val();
if (protocol === "BASIC") {
$(".node-input-userid").show();
$(".node-input-password").show();
$(".node-input-saslformat").hide();
$(".node-input-token").hide();
} else if (protocol === "NONE") {
$(".node-input-userid").hide();
$(".node-input-password").hide();
$(".node-input-saslformat").hide();
$(".node-input-token").hide();
}
else {
$(".node-input-userid").show();
$(".node-input-password").hide();
$(".node-input-saslformat").show();
$(".node-input-token").show();
$("#node-input-fetch").val("trigger");
$("#node-input-fetch").change();
}
});
</script>
</script>
@ -237,6 +328,9 @@
useSSL: {value: true},
autotls: {value: "never"},
port: {value:"993",required:true},
authtype: {value: "BASIC"},
saslformat: {value: true},
token: {value: "oauth2Response.access_token"},
box: {value:"INBOX"}, // For IMAP, The mailbox to process
disposition: { value: "Read" }, // For IMAP, the disposition of the read email
criteria: {value: "UNSEEN"},
@ -263,6 +357,10 @@
},
oneditprepare: function() {
var that = this;
if (this.authtype === undefined) {
this.authtype = "BASIC";
$("#node-input-authtype").val('BASIC');
}
if (this.credentials.global) {
$('#node-tip').show();
} else {
@ -289,6 +387,8 @@
else {
$('#node-repeatTime').show();
that.inputs = 0;
$("#node-input-authtype").val("BASIC");
$("#node-input-authtype").change();
}
});
$("#node-input-criteria").change(function() {
@ -297,6 +397,10 @@
$("#node-input-fetch").change();
}
});
$("#node-input-token").typedInput({
type:'msg',
types:['msg']
});
}
});
})();
@ -306,7 +410,46 @@
<script type="text/html" data-template-name="e-mail mta">
<div class="form-row">
<label for="node-input-port"><i class="fa fa-random"></i> <span data-i18n="email.label.port"></span></label>
<input type="text" id="node-input-port" style="width:70%;"/>
<input type="text" id="node-input-port" style="width:100px">
<label style="width:40px"> </label>
<input type="checkbox" id="node-input-secure" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="email.label.enableSecure"></span>
</div>
<div class="form-row">
<label for="node-input-starttls"><i class="fa fa-lock"></i> <span data-i18n="email.label.enableStarttls"></span></label>
<input type="checkbox" id="node-input-starttls" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="email.label.starttlsUpgrade"></span>
</div>
<div class="form-row" id="certRow">
<label for="node-input-certFile"><i class="fa fa-file"></i>
<span data-i18n="email.label.certFile"></span></label>
<input type="text" id="node-input-certFile" placeholder="server.crt" style="width:100%">
</div>
<div class="form-row" id="keyRow">
<label for="node-input-keyFile"><i class="fa fa-key"></i>
<span data-i18n="email.label.keyFile"></span></label>
<input type="text" id="node-input-keyFile" placeholder="private.key" style="width:100%">
</div>
<div class="form-row">
<label for="node-input-auth"><i class="fa fa-user"></i> <span data-i18n="email.label.users"></span></label>
<input type="checkbox" id="node-input-auth" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="email.label.auth"></span>
</div>
<div class="form-row node-input-email-users-container-row" style="margin-bottom: 0px;">
<div id="node-input-email-users-container-div" style="box-sizing: border-box; border-radius: 5px;
height: 200px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-input-email-users-container" style="list-style-type:none; margin: 0;"></ol>
</div>
</div>
<div class="form-row">
<a href="#" class="editor-button editor-button-small" id="node-input-email-users-add" style="margin-top: 4px;">
<i class="fa fa-plus"></i>
<span data-i18n="email.label.addButton"></span>
</a>
</div>
<div class="form-row">
<label for="node-input-expert"><i class="fa fa-cog"></i> <span data-i18n="email.label.expert"></span></label>
<input type="text" id="node-input-expert">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
@ -316,22 +459,148 @@
</script>
<script type="text/javascript">
RED.nodes.registerType('e-mail mta',{
RED.nodes.registerType('e-mail mta', {
category: 'social',
color:"#c7e9c0",
color: "#c7e9c0",
defaults: {
name: {value:""},
port: {value:"1025",required:true},
name: { value: "" },
port: { value: "1025", required: true, validate: RED.validators.number() },
secure: { value: false },
starttls: { value: false },
certFile: { value: "" },
keyFile: { value: "" },
users: { value: [] },
auth: { value: false },
expert: { value: '{"logger":false}' }
},
inputs:0,
outputs:1,
inputs: 0,
outputs: 1,
icon: "envelope.png",
paletteLabel: function() { return this._("email.email") + " MTA" },
label: function() {
return this.name||this._("email.email") + " MTA";
paletteLabel: function () { return this._("email.email") + " MTA" },
label: function () {
return this.name || this._("email.email") + " MTA";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
let node = this;
// Certificate settings
$("#node-input-secure").change(secVisibility);
$("#node-input-starttls").change(secVisibility);
function secVisibility() {
if ($("#node-input-secure").is(":checked") || $("#node-input-starttls").is(":checked")) {
$("#certRow").show();
$("#keyRow").show();
} else {
$("#certRow").hide();
$("#keyRow").hide();
}
}
// User Management
let cacheItemCount = 0;
if (node.users && node.users.length > 0) {
cacheItemCount = node.users.length;
node.users.forEach(function (element, index, array) {
generateUserEntry(element, index);
});
}
function generateUserEntry(user, id) {
let container = $("<li/>", {
style: "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"
});
let row = $('<div id="row' + id + '"/>').appendTo(container);
$('<i style="color: #eee; cursor: move;" class="node-input-email-users-handle fa fa-bars"></i>').appendTo(row);
let userField = $("<input/>", {
id: "node-input-email-users-name" + id,
class: "userName",
type: "text",
style: "margin-left:5px;width:100px;",
placeholder: "name"
}).appendTo(row);
let passwordField = $("<input/>", {
id: "node-input-email-users-password" + id,
class: "userPassword",
type: "password",
style: "margin: 0 auto;width:50%;min-width:20px;margin-left:5px",
placeholder: "password"
}).appendTo(row);
userField.val(user.name);
passwordField.val(user.password);
let finalspan = $("<span/>", {
style: "float: right;margin-right: 10px;"
}).appendTo(row);
let removeUserButton = $("<a/>", {
href: "#",
id: "node-button-user-remove" + id,
class: "editor-button editor-button-small",
style: "margin-top: 7px; margin-left: 5px;"
}).appendTo(finalspan);
$("<i/>", { class: "fa fa-remove" }).appendTo(removeUserButton);
removeUserButton.click(function () {
container.css({ background: "#fee" });
container.fadeOut(300, function () {
$(this).remove();
});
});
$("#node-input-email-users-container").append(container);
}
$("#node-input-email-users-container").sortable({
axis: "y",
handle: ".node-input-email-users-handle",
cursor: "move"
});
$("#node-input-email-users-container .node-input-email-users-handle").disableSelection();
$("#node-input-email-users-add").click(function () {
if (!cacheItemCount || cacheItemCount < 0) {
cacheItemCount = 0;
}
generateUserEntry({ name: "", password: "" }, cacheItemCount++);
$("#node-input-email-users-container-div").scrollTop(
$("#node-input-email-users-container-div").get(0).scrollHeight
);
});
$("#node-input-auth").change(function () {
if ($("#node-input-auth").is(":checked")) {
$("#node-input-email-users-add").show();
$("#node-input-email-users-container-div").show();
} else {
$("#node-input-email-users-add").hide();
$("#node-input-email-users-container-div").hide();
}
});
// Expert settings
$("#node-input-expert").typedInput({
type: "json",
types: ["json"]
})
},
oneditsave: function () {
let node = this;
let cacheUsers = $("#node-input-email-users-container").children();
node.users = [];
cacheUsers.each(function () {
node.users.push({
name: $(this)
.find(".userName")
.val(),
password: $(this)
.find(".userPassword")
.val()
});
});
}
});
</script>
</script>

View File

@ -1,10 +1,12 @@
/* eslint-disable indent */
const { domainToUnicode } = require("url");
/**
* POP3 protocol - RFC1939 - https://www.ietf.org/rfc/rfc1939.txt
*
* Dependencies:
* * poplib - https://www.npmjs.com/package/poplib
* * node-pop3 - https://www.npmjs.com/package/node-pop3
* * nodemailer - https://www.npmjs.com/package/nodemailer
* * imap - https://www.npmjs.com/package/imap
* * mailparser - https://www.npmjs.com/package/mailparser
@ -14,22 +16,17 @@ module.exports = function(RED) {
"use strict";
var util = require("util");
var Imap = require('imap');
var POP3Client = require("./poplib.js");
var Pop3Command = require("node-pop3");
var nodemailer = require("nodemailer");
var simpleParser = require("mailparser").simpleParser;
var SMTPServer = require("smtp-server").SMTPServer;
//var microMTA = require("micromta").microMTA;
var fs = require('fs');
if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) {
throw "Error : Requires nodejs version >= 8.";
}
try {
var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
}
catch(err) {
}
function EmailNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
@ -38,26 +35,33 @@ module.exports = function(RED) {
this.outport = n.port;
this.secure = n.secure;
this.tls = true;
var flag = false;
this.authtype = n.authtype || "BASIC";
if (this.authtype !== "BASIC") {
this.inputs = 1;
this.repeat = 0;
}
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
this.userid = this.credentials.userid;
} else {
if (globalkeys) {
this.userid = globalkeys.user;
flag = true;
}
else if (this.authtype !== "NONE") {
this.error(RED._("email.errors.nouserid"));
}
if (this.authtype === "BASIC" ) {
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.password = this.credentials.password;
}
else {
this.error(RED._("email.errors.nopassword"));
}
}
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.password = this.credentials.password;
} else {
if (globalkeys) {
this.password = globalkeys.pass;
flag = true;
else if (this.authtype === "XOAUTH2") {
this.saslformat = n.saslformat;
if (n.token !== "") {
this.token = n.token;
} else {
this.error(RED._("email.errors.notoken"));
}
}
if (flag) {
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
}
if (n.tls === false) { this.tls = false; }
var node = this;
@ -68,12 +72,28 @@ module.exports = function(RED) {
tls: {rejectUnauthorized: node.tls}
}
if (this.userid && this.password) {
if (node.authtype === "BASIC" ) {
smtpOptions.auth = {
user: node.userid,
pass: node.password
};
}
else if (node.authtype === "XOAUTH2") {
var value = RED.util.getMessageProperty(msg,node.token);
if (value !== undefined) {
if (node.saslformat) {
//Make base64 string for access - compatible with outlook365 and gmail
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
} else {
saslxoauth2 = value;
}
}
smtpOptions.auth = {
type: "OAuth2",
user: node.userid,
accessToken: saslxoauth2
};
}
var smtpTransport = nodemailer.createTransport(smtpOptions);
this.on("input", function(msg, send, done) {
@ -95,7 +115,8 @@ module.exports = function(RED) {
sendopts.headers = msg.headers;
sendopts.priority = msg.priority;
}
sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line
if (msg.hasOwnProperty("topic") && msg.topic === '') { sendopts.subject = ""; }
else { sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; } // subject line
if (msg.hasOwnProperty("header") && msg.header.hasOwnProperty("message-id")) {
sendopts.inReplyTo = msg.header["message-id"];
sendopts.subject = "Re: " + sendopts.subject;
@ -116,7 +137,8 @@ module.exports = function(RED) {
sendopts.attachments[0].contentType = msg.headers["content-type"];
}
// Create some body text..
sendopts.text = RED._("email.default-message",{filename:fname, description:(msg.description||"")});
if (msg.hasOwnProperty("description")) { sendopts.text = msg.description; }
else { sendopts.text = RED._("email.default-message",{filename:fname}); }
}
else {
var payload = RED.util.ensureString(msg.payload);
@ -173,6 +195,7 @@ module.exports = function(RED) {
// Setup the EmailInNode
function EmailInNode(n) {
var imap;
var pop3;
RED.nodes.createNode(this,n);
this.name = n.name;
@ -187,43 +210,45 @@ module.exports = function(RED) {
this.repeat = 1500;
}
if (this.inputs === 1) { this.repeat = 0; }
this.inserver = n.server || (globalkeys && globalkeys.server) || "imap.gmail.com";
this.inport = n.port || (globalkeys && globalkeys.port) || "993";
this.inserver = n.server || "imap.gmail.com";
this.inport = n.port || "993";
this.box = n.box || "INBOX";
this.useSSL= n.useSSL;
this.autotls= n.autotls;
this.protocol = n.protocol || "IMAP";
this.disposition = n.disposition || "None"; // "None", "Delete", "Read"
this.criteria = n.criteria || "UNSEEN"; // "ALL", "ANSWERED", "FLAGGED", "SEEN", "UNANSWERED", "UNFLAGGED", "UNSEEN"
var flag = false;
this.authtype = n.authtype || "BASIC";
if (this.authtype !== "BASIC") {
this.inputs = 1;
this.repeat = 0;
}
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
this.userid = this.credentials.userid;
} else {
if (globalkeys) {
this.userid = globalkeys.user;
flag = true;
} else {
this.error(RED._("email.errors.nouserid"));
}
}
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.password = this.credentials.password;
} else {
if (globalkeys) {
this.password = globalkeys.pass;
flag = true;
} else {
else if (this.authtype !== "NONE") {
this.error(RED._("email.errors.nouserid"));
}
if (this.authtype === "BASIC" ) {
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.password = this.credentials.password;
}
else {
this.error(RED._("email.errors.nopassword"));
}
}
if (flag) {
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
else if (this.authtype === "XOAUTH2") {
this.saslformat = n.saslformat;
if (n.token !== "") {
this.token = n.token;
} else {
this.error(RED._("email.errors.notoken"));
}
}
var node = this;
this.interval_id = null;
node.interval_id = null;
// Process a new email message by building a Node-RED message to be passed onwards
// in the message flow. The parameter called `msg` is the template message we
@ -252,103 +277,90 @@ module.exports = function(RED) {
// Check the POP3 email mailbox for any new messages. For any that are found,
// retrieve each message, call processNewMessage to process it and then delete
// the messages from the server.
function checkPOP3(msg) {
var currentMessage;
var maxMessage;
//node.log("Checking POP3 for new messages");
async function checkPOP3(msg,send,done) {
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
var saslxoauth2 = "";
var currentMessage = 1;
var maxMessage = 0;
var nextMessage;
// Form a new connection to our email server using POP3.
var pop3Client = new POP3Client(
node.inport, node.inserver,
{enabletls: node.useSSL} // Should we use SSL to connect to our email server?
);
// If we have a next message to retrieve, ask to retrieve it otherwise issue a
// quit request.
function nextMessage() {
if (currentMessage > maxMessage) {
pop3Client.quit();
setInputRepeatTimeout();
return;
}
pop3Client.retr(currentMessage);
currentMessage++;
} // End of nextMessage
pop3Client.on("stat", function(status, data) {
// Data contains:
// {
// count: <Number of messages to be read>
// octect: <size of messages to be read>
// }
if (status) {
currentMessage = 1;
maxMessage = data.count;
nextMessage();
} else {
node.log(util.format("stat error: %s %j", status, data));
}
pop3 = new Pop3Command({
"host": node.inserver,
"tls": node.useSSL,
"timeout": tout,
"port": node.inport
});
try {
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"});
await pop3.connect();
if (node.authtype === "XOAUTH2") {
var value = RED.util.getMessageProperty(msg,node.token);
if (value !== undefined) {
if (node.saslformat) {
//Make base64 string for access - compatible with outlook365 and gmail
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
} else {
saslxoauth2 = value;
}
}
await pop3.command('AUTH', "XOAUTH2");
await pop3.command(saslxoauth2);
pop3Client.on("error", function(err) {
} else if (node.authtype === "BASIC") {
await pop3.command('USER', node.userid);
await pop3.command('PASS', node.password);
}
} catch(err) {
node.error(err.message,err);
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
setInputRepeatTimeout();
node.log("error: " + JSON.stringify(err));
});
pop3Client.on("connect", function() {
//node.log("We are now connected");
pop3Client.login(node.userid, node.password);
});
pop3Client.on("login", function(status, rawData) {
//node.log("login: " + status + ", " + rawData);
if (status) {
pop3Client.stat();
} else {
node.log(util.format("login error: %s %j", status, rawData));
pop3Client.quit();
setInputRepeatTimeout();
done();
return;
}
maxMessage = (await pop3.STAT()).split(" ")[0];
if (maxMessage>0) {
node.status({fill:"blue", shape:"dot", text:"email.status.fetching"});
while(currentMessage<=maxMessage) {
try {
nextMessage = await pop3.RETR(currentMessage);
} catch(err) {
node.error(RED._("email.errors.fetchfail", err.message),err);
node.status({fill:"red",shape:"ring",text:"email.status.fetcherror"});
setInputRepeatTimeout();
done();
return;
}
try {
// We have now received a new email message. Create an instance of a mail parser
// and pass in the email message. The parser will signal when it has parsed the message.
simpleParser(nextMessage, {}, function(err, parsed) {
//node.log(util.format("simpleParser: on(end): %j", mailObject));
if (err) {
node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
}
else {
processNewMessage(msg, parsed);
}
});
//processNewMessage(msg, nextMessage);
} catch(err) {
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
node.status({fill:"red",shape:"ring",text:"email.status.parseerror"});
setInputRepeatTimeout();
done();
return;
}
await pop3.DELE(currentMessage);
currentMessage++;
}
});
await pop3.QUIT();
node.status({fill:"green",shape:"dot",text:"finished"});
setTimeout(status_clear, 5000);
setInputRepeatTimeout();
done();
}
pop3Client.on("retr", function(status, msgNumber, data, rawData) {
// node.log(util.format("retr: status=%s, msgNumber=%d, data=%j", status, msgNumber, data));
if (status) {
// We have now received a new email message. Create an instance of a mail parser
// and pass in the email message. The parser will signal when it has parsed the message.
simpleParser(data, {}, function(err, parsed) {
//node.log(util.format("simpleParser: on(end): %j", mailObject));
if (err) {
node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
}
else {
processNewMessage(msg, parsed);
}
});
pop3Client.dele(msgNumber);
}
else {
node.log(util.format("retr error: %s %j", status, rawData));
pop3Client.quit();
setInputRepeatTimeout();
}
});
pop3Client.on("invalid-state", function(cmd) {
node.log("Invalid state: " + cmd);
});
pop3Client.on("locked", function(cmd) {
node.log("We were locked: " + cmd);
});
// When we have deleted the last processed message, we can move on to
// processing the next message.
pop3Client.on("dele", function(status, msgNumber) {
nextMessage();
});
} // End of checkPOP3
@ -358,7 +370,50 @@ module.exports = function(RED) {
// Check the email sever using the IMAP protocol for new messages.
var s = false;
var ss = false;
function checkIMAP(msg) {
function checkIMAP(msg,send,done) {
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
var saslxoauth2 = "";
if (node.authtype === "XOAUTH2") {
var value = RED.util.getMessageProperty(msg,node.token);
if (value !== undefined) {
if (node.saslformat) {
//Make base64 string for access - compatible with outlook365 and gmail
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
} else {
saslxoauth2 = value;
}
}
imap = new Imap({
xoauth2: saslxoauth2,
host: node.inserver,
port: node.inport,
tls: node.useSSL,
autotls: node.autotls,
tlsOptions: { rejectUnauthorized: false },
connTimeout: tout,
authTimeout: tout
});
} else {
imap = new Imap({
user: node.userid,
password: node.password,
host: node.inserver,
port: node.inport,
tls: node.useSSL,
autotls: node.autotls,
tlsOptions: { rejectUnauthorized: false },
connTimeout: tout,
authTimeout: tout
});
}
imap.on('error', function(err) {
if (err.errno !== "ECONNRESET") {
s = false;
node.error(err.message,err);
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
}
setInputRepeatTimeout();
});
//console.log("Checking IMAP for new messages");
// We get back a 'ready' event once we have connected to imap
s = true;
@ -391,6 +446,7 @@ module.exports = function(RED) {
imap.end();
s = false;
setInputRepeatTimeout();
done(err);
return;
}
else {
@ -406,6 +462,7 @@ module.exports = function(RED) {
imap.end();
s = false;
setInputRepeatTimeout();
done(err);
return;
}
else {
@ -416,6 +473,8 @@ module.exports = function(RED) {
imap.end();
s = false;
setInputRepeatTimeout();
msg.payload = 0;
done();
return;
}
@ -453,11 +512,13 @@ module.exports = function(RED) {
imap.end();
s = false;
setInputRepeatTimeout();
msg.payload = results.length;
done();
};
if (node.disposition === "Delete") {
imap.addFlags(results, "\Deleted", cleanup);
imap.addFlags(results, '\\Deleted', imap.expunge(cleanup) );
} else if (node.disposition === "Read") {
imap.addFlags(results, "\Seen", cleanup);
imap.addFlags(results, '\\Seen', cleanup);
} else {
cleanup();
}
@ -468,6 +529,7 @@ module.exports = function(RED) {
imap.end();
s = false;
setInputRepeatTimeout();
done(err);
});
}
}); // End of imap->search
@ -477,6 +539,7 @@ module.exports = function(RED) {
node.error(e.toString(),e);
s = ss = false;
imap.end();
done(e);
return;
}
}
@ -485,6 +548,7 @@ module.exports = function(RED) {
node.error(RED._("email.errors.bad_criteria"),msg);
s = ss = false;
imap.end();
done();
return;
}
}
@ -496,42 +560,20 @@ module.exports = function(RED) {
// Perform a check of the email inboxes using either POP3 or IMAP
function checkEmail(msg) {
function checkEmail(msg,send,done) {
if (node.protocol === "POP3") {
checkPOP3(msg);
checkPOP3(msg,send,done);
} else if (node.protocol === "IMAP") {
if (s === false && ss == false) { checkIMAP(msg); }
if (s === false && ss == false) { checkIMAP(msg,send,done); }
}
} // End of checkEmail
if (node.protocol === "IMAP") {
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
imap = new Imap({
user: node.userid,
password: node.password,
host: node.inserver,
port: node.inport,
tls: node.useSSL,
autotls: node.autotls,
tlsOptions: { rejectUnauthorized: false },
connTimeout: tout,
authTimeout: tout
});
imap.on('error', function(err) {
if (err.errno !== "ECONNRESET") {
node.log(err);
s = false;
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
}
setInputRepeatTimeout();
});
}
this.on("input", function(msg) {
checkEmail(msg);
node.on("input", function(msg, send, done) {
send = send || function() { node.send.apply(node,arguments) };
checkEmail(msg,send,done);
});
this.on("close", function() {
node.on("close", function() {
if (this.interval_id != null) {
clearTimeout(this.interval_id);
}
@ -542,6 +584,10 @@ module.exports = function(RED) {
}
});
function status_clear() {
node.status({});
}
function setInputRepeatTimeout() {
// Set the repetition timer as needed
if (!isNaN(node.repeat) && node.repeat > 0) {
@ -565,47 +611,81 @@ module.exports = function(RED) {
function EmailMtaNode(n) {
RED.nodes.createNode(this,n);
RED.nodes.createNode(this, n);
this.port = n.port;
this.secure = n.secure;
this.starttls = n.starttls;
this.certFile = n.certFile;
this.keyFile = n.keyFile;
this.users = n.users;
this.auth = n.auth;
try {
this.options = JSON.parse(n.expert);
} catch (error) {
this.options = {};
}
var node = this;
if (!Array.isArray(node.options.disabledCommands)) {
node.options.disabledCommands = [];
}
node.options.secure = node.secure;
if (node.certFile) {
node.options.cert = fs.readFileSync(node.certFile);
}
if (node.keyFile) {
node.options.key = fs.readFileSync(node.keyFile);
}
if (!node.starttls) {
node.options.disabledCommands.push("STARTTLS");
}
if (!node.auth) {
node.options.disabledCommands.push("AUTH");
}
node.mta = new SMTPServer({
secure: false,
logger: false,
disabledCommands: ['AUTH', 'STARTTLS'],
onData: function (stream, session, callback) {
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
if (err) { node.error(RED._("email.errors.parsefail"),err); }
else {
node.status({fill:"green", shape:"dot", text:""});
var msg = {}
msg.payload = parsed.text;
msg.topic = parsed.subject;
msg.date = parsed.date;
msg.header = {};
parsed.headers.forEach((v, k) => {msg.header[k] = v;});
if (parsed.html) { msg.html = parsed.html; }
if (parsed.to) {
if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; }
else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; }
}
if (parsed.cc) {
if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; }
else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; }
}
if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; }
if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; }
if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; }
if (parsed.attachments) { msg.attachments = parsed.attachments; }
else { msg.attachments = []; }
node.send(msg); // Propagate the message down the flow
setTimeout(function() { node.status({})}, 500);
node.options.onData = function (stream, session, callback) {
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
if (err) { node.error(RED._("email.errors.parsefail"),err); }
else {
node.status({fill:"green", shape:"dot", text:""});
var msg = {}
msg.payload = parsed.text;
msg.topic = parsed.subject;
msg.date = parsed.date;
msg.header = {};
parsed.headers.forEach((v, k) => {msg.header[k] = v;});
if (parsed.html) { msg.html = parsed.html; }
if (parsed.to) {
if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; }
else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; }
}
callback();
});
if (parsed.cc) {
if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; }
else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; }
}
if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; }
if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; }
if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; }
if (parsed.attachments) { msg.attachments = parsed.attachments; }
else { msg.attachments = []; }
node.send(msg); // Propagate the message down the flow
setTimeout(function() { node.status({})}, 500);
}
callback();
});
}
node.options.onAuth = function (auth, session, callback) {
let id = node.users.findIndex(function (item) {
return item.name === auth.username;
});
if (id >= 0 && node.users[id].password === auth.password) {
callback(null, { user: id + 1 });
} else {
callback(new Error("Invalid username or password"));
}
});
}
node.mta = new SMTPServer(node.options);
node.mta.listen(node.port);

View File

@ -9,7 +9,11 @@ Pre-requisite
You will need valid email credentials for your email server. For GMail this may mean
getting an application password if you have two-factor authentication enabled.
**Note :** Version 1.x of this node requires **Node.js v8** or newer.
For Exchange and Outlook 365 you must use OAuth2.0.
**Notes **:
Version 2.x of this node required **Node.js v12** or newer.
Version 1.x of this node requires **Node.js v8** or newer.
Install
-------
@ -26,6 +30,12 @@ GMail users
If you are accessing GMail you may need to either enable <a target="_new" href="https://support.google.com/mail/answer/185833?hl=en">an application password</a>,
or enable <a target="_new" href="https://support.google.com/accounts/answer/6010255?hl=en">less secure access</a> via your Google account settings.</p>
Office 365 users
----------------
If you are accessing Exchnage you will need to register an application through their platform and use OAuth2.0.
<a target="_new" href="https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#get-an-access-token">Details on how to do this can be found here.</a>
Usage
-----
@ -42,6 +52,9 @@ If there is text/html then that is returned in `msg.html`. `msg.from` and
Additionally `msg.header` contains the complete header object including
**to**, **cc** and other potentially useful properties.
Modern authentication through OAuth2.0 is supported, but must be triggered by an incoming access token and
can only be automatically triggered upstream.
### Output node
Sends the `msg.payload` as an email, with a subject of `msg.topic`.

View File

@ -36,7 +36,7 @@
"always": "immer",
"rejectUnauthorised": "Überprüfen sie, ob das serverzertifikat gültig ist"
},
"default-message": "__description__\n\nDatei von Node-RED ist angehängt: __filename__",
"default-message": "\nDatei von Node-RED ist angehängt: __filename__",
"tip": {
"cred": "<b>Hinweis</b>: Berechtigungen von globaler emailkeys.js-Datei kopiert",
"recent": "Tipp: Es wird nur die letzte E-Mail abgerufen",

View File

@ -7,8 +7,17 @@
<p>You may optionally set <code>msg.from</code> in the payload which will override the <code>userid</code>
default value.</p>
<h3>Gmail users</h3>
<p>If you are accessing Gmail you may need to either enable <a target="_new" href="https://support.google.com/mail/answer/185833?hl=en">an application password</a>,
or enable <a target="_new" href="https://support.google.com/accounts/answer/6010255?hl=en">less secure access</a> via your Google account settings.</p>
<p>If you are accessing Gmail you may need to either enable <a target="_new" href="https://support.google.com/mail/answer/185833?hl=en">an application password</a>.</p>
<h3>Authentication</h3>
<p>When connecting to a SMTP server, two authentication types are available: Basic and XOAuth2.</p>
<ul>
<li><b>Basic:</b> requires a username and password to be entered</li>
<li><b>XOAuth2:</b> requires a username and a <code>msg</code> property to extract the access token</li>
</ul>
<h3>SASL Formatting:</h3>
<p>SASL XOAuth2 tokens are created by combining the username and token, encoding it in base64, and passing it to the mail server in the following format:</p>
<pre>base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")</pre>
<p>If the checkbox is unticked, flow creators can format the token themselves before passing it to the node.</p>
<h3>Details</h3>
<p>The payload can be html format. You may supply a separate plaintext version using <code>msg.plaintext</code>.
If you don't and <code>msg.payload</code> contains html, it will also be used for the plaintext.
@ -26,43 +35,78 @@
</script>
<script type="text/html" data-help-name="e-mail in">
<p>Repeatedly gets emails from a POP3 or IMAP server and forwards on as a msg if not already seen.</p>
<p>The subject is loaded into <code>msg.topic</code> and <code>msg.payload</code> is the plain text body.
If there is text/html then that is returned in <code>msg.html</code>. <code>msg.from</code> and <code>msg.date</code> are also set if you need them.</p>
<p>Additionally <code>msg.header</code> contains the complete header object including
<i>to</i>, <i>cc</i> and other potentially useful properties.</p>
<p>It can optionally mark the message as Read (default), Delete it, or leave unmarked (None).</p>
<p>Uses the <a href="https://github.com/mscdex/node-imap/blob/master/README.md" target="_new">node-imap module</a> - see that page for
information on the <code>msg.criteria</code> format if needed.</p>
<p>Any attachments supplied in the incoming email can be found in the <code>msg.attachments</code> property. This will be an array of objects where
each object represents a specific attachments. The format of the object is:</p>
<pre>
{
contentType: // The MIME content description
fileName: // A suggested file name associated with this attachment
transferEncoding: // How was the original email attachment encodded?
contentDisposition: // Unknown
generatedFileName: // A suggested file name associated with this attachment
contentId: // A unique generated ID for this attachment
checksum: // A checksum against the data
length: // Size of data in bytes
content: // The actual content of the data contained in a Node.js Buffer object
// We can turn this into a base64 data string with content.toString('base64')
}
</pre>
<p><b>Note</b>: For POP3, the default port numbers are 110 for plain TCP and 995 for SSL. For IMAP the port numbers are 143 for plain TCP and 993 for SSL.</p>
<p><b>Note</b>: With option 'STARTTLS' an established plain connection is upgraded to an encrypted one. Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading.</p>
<p><b>Note</b>: The maximum refresh interval is 2147483 seconds (24.8 days).</p>
<h3>Overview</h3>
<p>The e-mail in node retrieves emails from a POP3 or IMAP server and forwards the email data as a message if it has not already been seen.</p>
<h3>Message Properties</h3>
<p>The following properties are set on the message object:</p>
<ul>
<li><code>msg.topic</code> - the subject of the email</li>
<li><code>msg.payload</code> - the plain text body of the email</li>
<li><code>msg.html</code> - the HTML body of the email (if present)</li>
<li><code>msg.from</code> - the sender of the email</li>
<li><code>msg.date</code> - the date the email was sent</li>
<li><code>msg.header</code> - the complete header object including information such as the "to" and "cc" recipients</li>
<li><code>msg.attachments</code> - an array of objects representing any attachments included in the email</li>
</ul>
<h3>Module Used</h3>
<p>The e-mail in node uses the <a href="https://github.com/mscdex/node-imap/blob/master/README.md" target="_new">node-imap module</a>, see that page for information on the <code>msg.criteria</code> format if needed.</p>
<p>It also makes use of <a href="https://github.com/node-pop3/node-pop3#readme" target="_new">node-pop3 module</a></p>
<h3>Attachment Format</h3>
<p>Each object in the <code>msg.attachments</code> array is formatted as follows:</p>
<pre>
{
contentType: // The MIME content description
fileName: // A suggested file name associated with this attachment
transferEncoding: // How was the original email attachment encoded?
contentDisposition: // Unknown
generatedFileName: // A suggested file name associated with this attachment
contentId: // A unique generated ID for this attachment
checksum: // A checksum against the data
length: // Size of data in bytes
content: // The actual content of the data contained in a Node.js Buffer object
// We can turn this into a base64 data string with content.toString('base64')
}
</pre>
<h3>Authentication</h3>
<p>When connecting to a POP3 or IMAP server, two authentication types are available: Basic and XOAuth2.</p>
<ul>
<li><b>Basic:</b> requires a username and password to be entered</li>
<li><b>XOAuth2:</b> requires a username and a <code>msg</code> property to extract the access token</li>
</ul>
<p>With XOAuth2 authentication, periodic fetching is not available. The node will only attemp to login when a new token is receieved.</p>
<h3>SASL Formatting:</h3>
<p>SASL XOAuth2 tokens are created by combining the username and token, encoding it in base64, and passing it to the mail server in the following format:</p>
<pre>base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")</pre>
<p>If the checkbox is unticked, flow creators can format the token themselves before passing it to the node.</p>
<h3>Notes</h3>
<ul>
<li>For POP3, the default port numbers are 110 for plain TCP and 995 for SSL. For IMAP the port numbers are 143 for plain TCP and 993 for SSL.</li>
<li>With option 'STARTTLS' an established plain connection is upgraded to an encrypted one. Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading.</li>
<li>The maximum refresh interval is 2147483 seconds (24.8 days).</li>
</ul>
</script>
<script type="text/html" data-help-name="e-mail mta">
<p>Mail Transfer Agent - listens on a port for incoming SMTP mails.</p>
<p><b>Note</b>: "NOT for production use" as there is no security built in.
<p><b>Note</b>: Default configuration is "NOT for production use" as security is not enabled.
This is primarily for local testing of outbound mail sending, but could be used
as a mail forwarder to a real email service if required.</p>
<p>To use ports below 1024, for example 25 or 465, you may need to get privileged access.
On linux systems this can be done by running
<pre>sudo setcap 'cap_net_bind_service=+eip' $(which node)</pre>
and restarting Node-RED. Be aware - this gives all node applications access to all ports.</p>
<h3>Security</h3>
<p>When <i>Secure connection</i> is checked, the connection will use TLS.
If not it is still possible to upgrade clear text socket to TLS socket by checking <i>Start TLS</i>.
In most cases when using port 465, check <i>Secure connection</i>. For port 587 or 25 keep it disabled, use <i>Start TLS</i> instead.</p>
<p>If you do no specify your own certificate (path to file) then a pregenerated self-signed certificate is used. Any respectful client refuses to accept such certificate.</p>
<h3>Authentication</h3>
<p>Authentication can be enabled (PLAIN or LOGIN). Add at least one user.</p>
<h3>Expert</h3>
<p>All options as described in <a href="https://nodemailer.com/extras/smtp-server/" target="_new">nodemailer SMTP server</a> can be made here.</p>
</script>

View File

@ -31,12 +31,24 @@
"unflagged": "Unflagged",
"unseen": "Unseen",
"autotls": "Start TLS?",
"authtype": "Auth type",
"saslformat": "Format to SASL",
"token": "Token",
"never": "never",
"required": "if required",
"always": "always",
"rejectUnauthorised": "Check server certificate is valid"
"rejectUnauthorised": "Check server certificate is valid",
"enableSecure": "Secure connection",
"enableStarttls": "Start TLS",
"starttlsUpgrade": "Upgrade cleartext connection with STARTTLS",
"certFile": "Certificate",
"keyFile":"Private key",
"users": "Users",
"auth": "Authenticate users",
"addButton": "Add",
"expert": "Expert"
},
"default-message": "__description__\n\nFile from Node-RED is attached: __filename__",
"default-message": "\nFile from Node-RED is attached: __filename__",
"tip": {
"cred": "<b>Note:</b> Copied credentials from global emailkeys.js file.",
"recent": "Tip: Only retrieves the single most recent email.",
@ -60,6 +72,7 @@
"errors": {
"nouserid": "No e-mail userid set",
"nopassword": "No e-mail password set",
"notoken": "No token property set",
"nocredentials": "No Email credentials found. See info panel.",
"nosmtptransport": "No SMTP transport. See info panel.",
"nopayload": "No payload to send",

View File

@ -36,7 +36,7 @@
"always": "常時",
"rejectUnauthorised": "チェックサーバ証明書は有効です"
},
"default-message": "__description__\n\nNode-REDからファイルが添付されました: __filename__",
"default-message": "\nNode-REDからファイルが添付されました: __filename__",
"tip": {
"cred": "<b>注釈:</b> emailkeys.jsファイルから認証情報をコピーしました。",
"recent": "注釈: 最新のメールを1件のみ取得します。",

View File

@ -1,15 +1,17 @@
{
"name": "node-red-node-email",
"version": "1.15.1",
"version": "2.0.0",
"description": "Node-RED nodes to send and receive simple emails.",
"dependencies": {
"imap": "^0.8.19",
"mailparser": "^3.4.0",
"nodemailer": "^6.7.3",
"smtp-server": "^3.10.0"
"node-pop3": "^0.8.0",
"mailparser": "^3.6.4",
"nodemailer": "^6.9.1",
"smtp-server": "^3.11.0"
},
"bundledDependencies": [
"imap",
"node-pop3",
"mailparser",
"nodemailer",
"smtp-server"
@ -26,9 +28,12 @@
"gmail",
"imap",
"pop",
"smtp",
"smtp-server",
"mta"
],
"node-red": {
"version": ">=1.0.0",
"nodes": {
"email": "61-email.js"
}

View File

@ -1,859 +0,0 @@
/*
Node.js POP3 client library
Copyright (C) 2011-2013 by Ditesh Shashikant Gathani <ditesh@gathani.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var net = require("net"),
tls = require("tls"),
util = require("util"),
crypto = require("crypto"),
events = require("events");
// Constructor
function POP3Client(port, host, options) {
if (options === undefined) { options = {}; }
// Optional constructor arguments
var enabletls = options.enabletls !== undefined ? options.enabletls: false;
var ignoretlserrs = options.ignoretlserrs !== undefined ? options.ignoretlserrs: false;
var debug = options.debug || false;
var tlsDirectOpts = options.tlsopts !== undefined ? options.tlsopts: {};
// Private variables follow
var self = this;
var response = null;
var checkResp = true;
var bufferedData = "";
var state = 0;
var locked = false;
var multiline = false;
var socket = null;
var tlssock = null;
var callback = function(resp, data) {
if (resp === false) {
locked = false;
callback = function() {};
self.emit("connect", false, data);
} else {
// Checking for APOP support
var banner = data.trim();
var bannerComponents = banner.split(" ");
for(var i=0; i < bannerComponents.length; i++) {
if (bannerComponents[i].indexOf("@") > 0) {
self.data["apop"] = true;
self.data["apop-timestamp"] = bannerComponents[i];
break;
}
}
state = 1;
self.data["banner"] = banner;
self.emit("connect", true, data);
}
};
// Public variables follow
this.data = {
host: host,
port: port,
banner: "",
stls: false,
apop: false,
username: "",
tls: enabletls,
ignoretlserrs: ignoretlserrs
};
// Privileged methods follow
this.setCallback = function(cb) { callback = cb; };
this.getCallback = function() { return callback };
this.setState = function(val) { state = val; };
this.getState = function() { return state; };
this.setLocked = function(val) { locked = val; };
this.getLocked = function() { return locked; };
this.setMultiline = function(val) { multiline = val; };
this.getMultiline = function() { return multiline; };
// Writes to remote server socket
this.write = function(command, argument) {
var text = command;
if (argument !== undefined) { text = text + " " + argument + "\r\n"; }
else { text = text + "\r\n"; }
if (debug) { console.log("Client: " + util.inspect(text)); }
socket.write(text);
};
// Kills the socket connection
this.end = function() {
socket.end();
};
// Upgrades a standard unencrypted TCP connection to use TLS
// Liberally copied and modified from https://gist.github.com/848444
// starttls() should be a private function, but I can't figure out
// how to get a public prototypal method (stls) to talk to private method (starttls)
// which references private variables without going through a privileged method
this.starttls = function(options) {
var s = socket;
s.removeAllListeners("end");
s.removeAllListeners("data");
s.removeAllListeners("error");
socket=null;
var sslcontext = require('crypto').createCredentials(options);
var pair = tls.createSecurePair(sslcontext, false);
var cleartext = pipe(pair);
pair.on('secure', function() {
var verifyError = pair.ssl.verifyError();
cleartext.authorized = true;
if (verifyError) {
cleartext.authorized = false;
cleartext.authorizationError = verifyError;
}
cleartext.on("data", onData);
cleartext.on("error", onError);
cleartext.on("end", onEnd);
socket=cleartext;
(self.getCallback())(cleartext.authorized, cleartext.authorizationError);
});
cleartext._controlReleased = true;
function pipe(pair) {
pair.encrypted.pipe(s);
s.pipe(pair.encrypted);
pair.fd = s.fd;
var cleartext = pair.cleartext;
cleartext.socket = s;
cleartext.encrypted = pair.encrypted;
cleartext.authorized = false;
function onerror(e) {
if (cleartext._controlReleased) { cleartext.emit('error', e); }
}
function onclose() {
s.removeListener('error', onerror);
s.removeListener('close', onclose);
}
s.on('error', onerror);
s.on('close', onclose);
return cleartext;
}
};
// Private methods follow
// Event handlers follow
function onData(data) {
data = data.toString("ascii");
bufferedData += data;
if (debug) { console.log("Server: " + util.inspect(data)); }
if (checkResp === true) {
if (bufferedData.substr(0, 3) === "+OK") {
checkResp = false;
response = true;
} else if (bufferedData.substr(0, 4) === "-ERR") {
checkResp = false;
response = false;
// The following is only used for SASL
} else if (multiline === false) {
checkResp = false;
response = true;
}
}
if (checkResp === false) {
if (multiline === true && (response === false || bufferedData.substr(bufferedData.length-5) === "\r\n.\r\n")) {
// Make a copy to avoid race conditions
var responseCopy = response;
var bufferedDataCopy = bufferedData;
response = null;
checkResp = true;
multiline = false;
bufferedData = "";
callback(responseCopy, bufferedDataCopy);
} else if (multiline === false) {
// Make a copy to avoid race conditions
var responseCopy = response;
var bufferedDataCopy = bufferedData;
response = null;
checkResp = true;
multiline = false;
bufferedData = "";
callback(responseCopy, bufferedDataCopy);
}
}
}
function onError(err) {
if (err.errno === 111) { self.emit("connect", false, err); }
else { self.emit("error", err); }
}
function onEnd(data) {
self.setState(0);
socket = null;
}
function onClose() {
self.emit("close");
}
// Constructor code follows
// Set up EventEmitter constructor function
events.EventEmitter.call(this);
// Remote end socket
if (enabletls === true) {
tlssock = tls.connect({
host: host,
port: port,
rejectUnauthorized: !self.data.ignoretlserrs
}, function() {
if (tlssock.authorized === false && self.data["ignoretlserrs"] === false) {
self.emit("tls-error", tlssock.authorizationError);
}
}
);
socket = tlssock;
} else { socket = new net.createConnection(port, host); }
// Set up event handlers
socket.on("data", onData);
socket.on("error", onError);
socket.on("end", onEnd);
socket.on("close", onClose);
}
util.inherits(POP3Client, events.EventEmitter);
POP3Client.prototype.login = function (username, password) {
var self = this;
if (self.getState() !== 1) { self.emit("invalid-state", "login"); }
else if (self.getLocked() === true) { self.emit("locked", "login"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
if (resp === false) {
self.setLocked(false);
self.setCallback(function() {});
self.emit("login", false, data);
} else {
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
if (resp !== false) { self.setState(2); }
self.emit("login", resp, data);
});
self.setMultiline(false);
self.write("PASS", password);
}
});
self.setMultiline(false);
self.write("USER", username);
}
};
// SASL AUTH implementation
// Currently supports SASL PLAIN and CRAM-MD5
POP3Client.prototype.auth = function (type, username, password) {
type = type.toUpperCase();
var self = this;
var types = {"PLAIN": 1, "CRAM-MD5": 1};
var initialresp = "";
if (self.getState() !== 1) { self.emit("invalid-state", "auth"); }
else if (self.getLocked() === true) { self.emit("locked", "auth"); }
if ((type in types) === false) {
self.emit("auth", false, "Invalid auth type", null);
return;
}
function tlsok() {
if (type === "PLAIN") {
initialresp = " " + new Buffer(username + "\u0000" + username + "\u0000" + password).toString("base64") + "=";
self.setCallback(function(resp, data) {
if (resp !== false) { self.setState(2); }
self.emit("auth", resp, data, data);
});
} else if (type === "CRAM-MD5") {
self.setCallback(function(resp, data) {
if (resp === false) { self.emit("auth", resp, "Server responded -ERR to AUTH CRAM-MD5", data); }
else {
var challenge = new Buffer(data.trim().substr(2), "base64").toString();
var hmac = crypto.createHmac("md5", password);
var response = new Buffer(username + " " + hmac.update(challenge).digest("hex")).toString("base64");
self.setCallback(function(resp, data) {
var errmsg = null;
if (resp !== false) { self.setState(2); }
else {errmsg = "Server responded -ERR to response"; }
self.emit("auth", resp, null, data);
});
self.write(response);
}
});
}
self.write("AUTH " + type + initialresp);
}
if (self.data["tls"] === false && self.data["stls"] === false) {
// Remove all existing STLS listeners
self.removeAllListeners("stls");
self.on("stls", function(resp, rawdata) {
if (resp === false) {
// We (optionally) ignore self signed cert errors,
// in blatant violation of RFC 2595, Section 2.4
if (self.data["ignoretlserrs"] === true && rawdata === "DEPTH_ZERO_SELF_SIGNED_CERT"){ tlsok(); }
else { self.emit("auth", false, "Unable to upgrade connection to STLS", rawdata); }
} else { tlsok(); }
});
self.stls();
} else { tlsok(); }
};
POP3Client.prototype.apop = function (username, password) {
var self = this;
if (self.getState() !== 1) { self.emit("invalid-state", "apop"); }
else if (self.getLocked() === true) { self.emit("locked", "apop"); }
else if (self.data["apop"] === false) { self.emit("apop", false, "APOP support not detected on remote server"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
if (resp === true) { self.setState(2); }
self.emit("apop", resp, data);
});
self.setMultiline(false);
self.write("APOP", username + " " + crypto.createHash("md5").update(self.data["apop-timestamp"] + password).digest("hex"));
}
};
POP3Client.prototype.stls = function() {
var self = this;
if (self.getState() !== 1) { self.emit("invalid-state", "stls"); }
else if (self.getLocked() === true) { self.emit("locked", "stls"); }
else if (self.data["tls"] === true) { self.emit("stls", false, "Unable to execute STLS as TLS connection already established"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
if (resp === true) {
self.setCallback(function(resp, data) {
if (resp === false && self.data["ignoretlserrs"] === true && data === "DEPTH_ZERO_SELF_SIGNED_CERT") {resp = true; }
self.data["stls"] = true;
self.emit("stls", resp, data);
});
self.starttls();
} else { self.emit("stls", false, data); }
});
self.setMultiline(false);
self.write("STLS");
}
};
POP3Client.prototype.top = function(msgnumber, lines) {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "top"); }
else if (self.getLocked() === true) { self.emit("locked", "top"); }
else {
self.setCallback(function(resp, data) {
var returnValue = null;
self.setLocked(false);
self.setCallback(function() {});
if (resp !== false) {
returnValue = "";
var startOffset = data.indexOf("\r\n", 0) + 2;
var endOffset = data.indexOf("\r\n.\r\n", 0) + 2;
if (endOffset > startOffset) {returnValue = data.substr(startOffset, endOffset-startOffset); }
}
self.emit("top", resp, msgnumber, returnValue, data);
});
self.setMultiline(true);
self.write("TOP", msgnumber + " " + lines);
}
};
POP3Client.prototype.list = function(msgnumber) {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "list"); }
else if (self.getLocked() === true) { self.emit("locked", "list"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
var returnValue = null;
var msgcount = 0;
self.setLocked(false);
self.setCallback(function() {});
if (resp !== false) {
returnValue = [];
var listitem = "";
if (msgnumber !== undefined) {
msgcount = 1
listitem = data.split(" ");
returnValue[listitem[1]] = listitem[2];
} else {
var offset = 0;
var newoffset = 0;
var returnValue = [];
var startOffset = data.indexOf("\r\n", 0) + 2;
var endOffset = data.indexOf("\r\n.\r\n", 0) + 2;
if (endOffset > startOffset) {
data = data.substr(startOffset, endOffset-startOffset);
while(true) {
if (offset > endOffset) { break; }
newoffset = data.indexOf("\r\n", offset);
if (newoffset < 0) { break; }
msgcount++;
listitem = data.substr(offset, newoffset-offset);
listitem = listitem.split(" ");
returnValue[listitem[0]] = listitem[1];
offset = newoffset + 2;
}
}
}
}
self.emit("list", resp, msgcount, msgnumber, returnValue, data);
});
if (msgnumber !== undefined) { self.setMultiline(false); }
else { self.setMultiline(true); }
self.write("LIST", msgnumber);
}
};
POP3Client.prototype.stat = function() {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "stat"); }
else if (self.getLocked() === true) { self.emit("locked", "stat"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
var returnValue = null;
self.setLocked(false);
self.setCallback(function() {});
if (resp !== false) {
var listitem = data.split(" ");
returnValue = {
"count": listitem[1].trim(),
"octets": listitem[2].trim(),
};
}
self.emit("stat", resp, returnValue, data);
});
self.setMultiline(false);
self.write("STAT", undefined);
}
};
POP3Client.prototype.uidl = function(msgnumber) {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "uidl"); }
else if (self.getLocked() === true) { self.emit("locked", "uidl"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
var returnValue = null;
self.setLocked(false);
self.setCallback(function() {});
if (resp !== false) {
returnValue = [];
var listitem = "";
if (msgnumber !== undefined) {
listitem = data.split(" ");
returnValue[listitem[1]] = listitem[2].trim();
} else {
var offset = 0;
var newoffset = 0;
var returnValue = [];
var startOffset = data.indexOf("\r\n", 0) + 2;
var endOffset = data.indexOf("\r\n.\r\n", 0) + 2;
if (endOffset > startOffset) {
data = data.substr(startOffset, endOffset-startOffset);
endOffset -= startOffset;
while (offset < endOffset) {
newoffset = data.indexOf("\r\n", offset);
listitem = data.substr(offset, newoffset-offset);
listitem = listitem.split(" ");
returnValue[listitem[0]] = listitem[1];
offset = newoffset + 2;
}
}
}
}
self.emit("uidl", resp, msgnumber, returnValue, data);
});
if (msgnumber !== undefined) { self.setMultiline(false); }
else { self.setMultiline(true); }
self.write("UIDL", msgnumber);
}
};
POP3Client.prototype.retr = function(msgnumber) {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "retr"); }
else if (self.getLocked() === true) { self.emit("locked", "retr"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
var returnValue = null;
self.setLocked(false);
self.setCallback(function() {});
if (resp !== false) {
var startOffset = data.indexOf("\r\n", 0) + 2;
var endOffset = data.indexOf("\r\n.\r\n", 0);
returnValue = data.substr(startOffset, endOffset-startOffset);
}
self.emit("retr", resp, msgnumber, returnValue, data);
});
self.setMultiline(true);
self.write("RETR", msgnumber);
}
};
POP3Client.prototype.dele = function(msgnumber) {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "dele"); }
else if (self.getLocked() === true) { self.emit("locked", "dele"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
self.emit("dele", resp, msgnumber, data);
});
self.setMultiline(false);
self.write("DELE", msgnumber);
}
};
POP3Client.prototype.noop = function() {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "noop"); }
else if (self.getLocked() === true) { self.emit("locked", "noop"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
self.emit("noop", resp, data);
});
self.setMultiline(false);
self.write("NOOP", undefined);
}
};
POP3Client.prototype.rset = function() {
var self = this;
if (self.getState() !== 2) { self.emit("invalid-state", "rset"); }
else if (self.getLocked() === true) { self.emit("locked", "rset"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
self.emit("rset", resp, data);
});
self.setMultiline(false);
self.write("RSET", undefined);
}
};
POP3Client.prototype.capa = function() {
var self = this;
if (self.getState() === 0) { self.emit("invalid-state", "quit"); }
else if (self.getLocked() === true) { self.emit("locked", "capa"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
var returnValue = null;
self.setLocked(false);
self.setCallback(function() {});
if (resp === true) {
var startOffset = data.indexOf("\r\n", 0) + 2;
var endOffset = data.indexOf("\r\n.\r\n", 0);
returnValue = data.substr(startOffset, endOffset-startOffset);
returnValue = returnValue.split("\r\n");
}
self.emit("capa", resp, returnValue, data);
});
self.setMultiline(true);
self.write("CAPA", undefined);
}
};
POP3Client.prototype.quit = function() {
var self = this;
if (self.getState() === 0) { self.emit("invalid-state", "quit"); }
else if (self.getLocked() === true) { self.emit("locked", "quit"); }
else {
self.setLocked(true);
self.setCallback(function(resp, data) {
self.setLocked(false);
self.setCallback(function() {});
self.end();
self.emit("quit", resp, data);
});
self.setMultiline(false);
self.write("QUIT", undefined);
}
};
module.exports = POP3Client;

View File

@ -7,6 +7,11 @@
<label for="node-input-interval"><i class="fa fa-repeat"></i> <span data-i18n="feedparse.label.refresh"></span></label>
<input type="text" id="node-input-interval" style="width:60px"> <span data-i18n="feedparse.label.minutes"></span>
</div>
<div class="form-row">
<label> </label>
<input type="checkbox" id="node-input-ignorefirst" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="feedparse.label.ignorefirst"></span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
@ -20,7 +25,8 @@
defaults: {
name: {value:""},
url: {value:"", required:true},
interval: { value:15, required:true, validate:function(v) {return (!isNaN(parseInt(v)) && (parseInt(v) <= 35790))} }
interval: { value:15, required:true, validate:function(v) {return (!isNaN(parseInt(v)) && (parseInt(v) <= 35790))} },
ignorefirst: { value:false }
},
inputs:0,
outputs:1,

View File

@ -11,11 +11,13 @@ module.exports = function(RED) {
if (n.interval > 35790) { this.warn(RED._("feedparse.errors.invalidinterval")) }
this.interval = (parseInt(n.interval)||15) * 60000;
this.interval_id = null;
this.ignorefirst = n.ignorefirst || false;
this.seen = {};
this.donefirst = false;
var node = this;
var parsedUrl = url.parse(this.url);
if (!(parsedUrl.host || (parsedUrl.hostname && parsedUrl.port)) && !parsedUrl.isUnix) {
node.error(RED._("feedparse.errors.invalidurl"));
node.error(RED._("feedparse.errors.invalidurl"),RED._("feedparse.errors.invalidurl"));
}
else {
var getFeed = function() {
@ -33,19 +35,24 @@ module.exports = function(RED) {
else { res.pipe(feedparser); }
});
feedparser.on('error', function(error) { node.error(error); });
feedparser.on('error', function(error) { node.error(error,error); });
feedparser.on('readable', function () {
var stream = this, article;
while (article = stream.read()) { // jshint ignore:line
if (!(article.guid in node.seen) || ( node.seen[article.guid] !== 0 && node.seen[article.guid] != article.date.getTime())) {
node.seen[article.guid] = article.date?article.date.getTime():0;
node.seen[article.guid] = article.date ? article.date.getTime() : 0;
var msg = {
topic: article.origlink || article.link,
payload: article.description,
article: article
};
node.send(msg);
if (node.ignorefirst === true && node.donefirst === false) {
// do nothing
}
else {
node.send(msg);
}
}
}
});
@ -53,7 +60,7 @@ module.exports = function(RED) {
feedparser.on('meta', function (meta) {});
feedparser.on('end', function () {});
};
node.interval_id = setInterval(function() { getFeed(); }, node.interval);
node.interval_id = setInterval(function() { node.donefirst = true; getFeed(); }, node.interval);
getFeed();
}

View File

@ -4,7 +4,8 @@
"label": {
"feedurl": "Feed url",
"refresh": "Refresh",
"minutes": "minutes"
"minutes": "minutes",
"ignorefirst": "Ignore any stories older than restart"
},
"errors": {
"badstatuscode": "error - Bad status code",

View File

@ -1,6 +1,6 @@
{
"name": "node-red-node-feedparser",
"version": "0.2.2",
"version": "0.3.0",
"description": "A Node-RED node to get RSS Atom feeds.",
"dependencies": {
"feedparser": "^2.2.10",
@ -15,7 +15,8 @@
"keywords": [
"node-red",
"atom",
"rss"
"rss",
"feed"
],
"node-red": {
"nodes": {

View File

@ -1,9 +1,9 @@
{
"name": "node-red-node-notify",
"version": "0.2.0",
"version": "0.3.0",
"description": "A Node-RED node to send local popup Notify alerts",
"dependencies": {
"node-notifier": "^5.4.5"
"node-notifier": "^10.0.1"
},
"repository": {
"type": "git",

View File

@ -221,8 +221,14 @@ module.exports = function(RED) {
msg.payload = incoming.iden;
}
else if (incoming.type === 'sms_changed') {
msg.topic = "SMS: "+ incoming.notifications[0].title;
msg.payload = incoming.notifications[0].body;
if (incoming.notifications && incoming.notifications.length > 0) {
msg.topic = "SMS: "+ incoming.notifications[0].title;
msg.payload = incoming.notifications[0].body;
}
else {
msg.topic = "SMS: ";
msg.payload = "";
}
msg.message = incoming;
}
else {
@ -244,12 +250,11 @@ module.exports = function(RED) {
function PushbulletOut(n) {
RED.nodes.createNode(this, n);
var self = this;
this.title = n.title;
this.chan = n.chan;
this.pushtype = n.pushtype;
this.pusher = null;
var self = this;
var configNode;

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-pushbullet",
"version" : "0.0.17",
"version" : "0.0.19",
"description" : "A Node-RED node to send alerts via Pushbullet",
"dependencies" : {
"pushbullet": "^2.4.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,10 +1,10 @@
{
"name": "node-red-node-pusher",
"version": "0.1.0",
"version": "1.0.0",
"description": "A Node-RED node to send and receive messages using Pusher.com",
"dependencies": {
"pusher": "^1.5.1",
"pusher-client": "^1.1.0"
"pusher": "^5.1.2",
"pusher-js": "^8.0.1"
},
"repository": {
"type": "git",
@ -25,5 +25,11 @@
"name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com",
"url": "http://nodered.org"
}
},
"contributors": [
{
"name": "Shaqaruden",
"email": "shaqaruden@gmail.com"
}
]
}

View File

@ -2,11 +2,11 @@
<script type="text/x-red" data-template-name="pusher in">
<div class="form-row">
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
<input type="text" id="node-input-channel" placeholder="my_channel">
<input type="text" id="node-input-channel" placeholder="channel">
</div>
<div class="form-row">
<label for="node-input-eventname"><i class="fa fa-tasks"></i> Event</label>
<input type="text" id="node-input-eventname" placeholder="test_event_name">
<input type="text" id="node-input-eventname" placeholder="event name">
</div>
<div class="form-row">
<label for="node-input-pusherappkeysub"><i class="fa fa-lock"></i> App Key</label>
@ -15,10 +15,15 @@
<div class="form-row">
<label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label>
<select type="text" id="node-input-cluster">
<option value="mt1">us-east-1 (US - default)</option>
<option value="eu">eu-west-1 (Europe)</option>
<option value="ap1">ap-southeast-1 (Singapore)</option>
<option value="ap2">ap-south-1 (Mumbai)</option>
<option value="mt1">N. Virginia (US - default)</option>
<option value="us2">Ohio (US)</option>
<option value="us3">Oregon (US)</option>
<option value="eu">Ireland (Europe)</option>
<option value="ap1">Singapore (Asia)</option>
<option value="ap2">Mumbai (India)</option>
<option value="ap3">Tokyo (Asia)</option>
<option value="ap4">Sydney (Australia)</option>
<option value="sa1">São Paulo (Brazil)</option>
</select>
</div>
<div class="form-row">
@ -87,10 +92,15 @@
<div class="form-row">
<label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label>
<select type="text" id="node-input-cluster">
<option value="mt1">us-east-1 (US - default)</option>
<option value="eu">eu-west-1 (Europe)</option>
<option value="ap1">ap-southeast-1 (Singapore)</option>
<option value="ap2">ap-south-1 (Mumbai)</option>
<option value="mt1">N. Virginia (US - default)</option>
<option value="us2">Ohio (US)</option>
<option value="us3">Oregon (US)</option>
<option value="eu">Ireland (Europe)</option>
<option value="ap1">Singapore (Asia)</option>
<option value="ap2">Mumbai (India)</option>
<option value="ap3">Tokyo (Asia)</option>
<option value="ap4">Sydney (Australia)</option>
<option value="sa1">São Paulo (Brazil)</option>
</select>
</div>

View File

@ -1,12 +1,12 @@
module.exports = function(RED) {
module.exports = function (RED) {
"use strict";
var Pusher = require('pusher');
var PusherClient = require('pusher-client');
const Pusher = require("pusher")
const PusherClient = require('pusher-js');
//node for subscribing to an event/channel
function PusherNode(n) {
RED.nodes.createNode(this,n);
RED.nodes.createNode(this, n);
this.channel = n.channel;
this.eventname = n.eventname;
this.cluster = n.cluster || "mt1";
@ -19,19 +19,21 @@ module.exports = function(RED) {
else { this.error("No Pusher app key set for input node"); }
//create a subscription to the channel and event defined by user
var socket = new PusherClient(''+node.appkey, {cluster:node.cluster, encrypted:true});
var chan = socket.subscribe(''+node.channel);
chan.bind(''+node.eventname,
function(data) {
var msg = {topic:node.eventname, channel:node.channel};
if (data.hasOwnProperty("payload")) { msg.payload = data.payload; }
else { msg.payload = data; }
node.send(msg);
}
);
// var socket = new PusherClient(''+node.appkey, {cluster:node.cluster, encrypted:true});
const pusher = new PusherClient('' + node.appkey, {
cluster: node.cluster
});
const channel = pusher.subscribe('' + node.channel);
node.on("close", function() {
socket.disconnect();
channel.bind('' + node.eventname, function (data) {
var msg = { topic: node.eventname, channel: node.channel };
if (data.hasOwnProperty("payload")) { msg.payload = data.payload; }
else { msg.payload = data; }
node.send(msg);
});
node.on("close", function () {
pusher.disconnect();
});
}
@ -39,7 +41,7 @@ module.exports = function(RED) {
//Node for sending Pusher events
function PusherNodeSend(n) {
// Create a RED node
RED.nodes.createNode(this,n);
RED.nodes.createNode(this, n);
var node = this;
var credentials = this.credentials;
@ -56,33 +58,35 @@ module.exports = function(RED) {
this.eventname = n.eventname;
this.cluster = n.cluster || "mt1";
var pusherd = new Pusher({
var pusher = new Pusher({
appId: this.appid,
key: this.appkey,
secret: this.appsecret,
cluster: this.cluster
});
node.on("input", function(msg) {
pusherd.trigger(this.channel, this.eventname, {
node.on("input", function (msg) {
pusher.trigger(this.channel, this.eventname, {
"payload": msg.payload
});
});
node.on("close", function() {
node.on("close", function () {
});
}
RED.nodes.registerType("pusher in",PusherNode,{
RED.nodes.registerType("pusher in", PusherNode, {
credentials: {
pusherappkeysub: "text"
}
});
RED.nodes.registerType("pusher out",PusherNodeSend,{
RED.nodes.registerType("pusher out", PusherNodeSend, {
credentials: {
pusherappid: {type:"text"},
pusherappkey: {type:"text"},
pusherappsecret: {type:"password"}
pusherappid: { type: "text" },
pusherappkey: { type: "text" },
pusherappsecret: { type: "password" }
},
encrypted: true
});

View File

@ -157,13 +157,17 @@
<input type="text" id="node-config-input-user" placeholder="joe@blah.im">
</div>
<div class="form-row">
<label for="node-config-input-nickname"><i class="fa fa-user"></i> Nickname</label>
<label for="node-config-input-nickname"><i class="fa fa-heart"></i> Nickname</label>
<input type="text" id="node-config-input-nickname" placeholder="Joe (optional)">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-config-input-password">
</div>
<div class="form-row">
<label for="node-config-input-resource"><i class="fa fa-globe"></i> Resource</label>
<input type="text" id="node-config-input-resource" placeholder="optional resource id">
</div>
</script>
<script type="text/javascript">
@ -173,7 +177,8 @@
server: {value:"", required:false},
port: {value:5222, required:false, validate:RED.validators.number()},
user: {value:""},
nickname: {value:""}
nickname: {value:""},
resource: {value:""}
},
credentials: {
password: {type:"password"}

View File

@ -30,6 +30,8 @@ module.exports = function(RED) {
else {
this.port = parseInt(n.port);
}
this.domain = this.jid.split('@')[1] || this.server;
this.resource = n.resource || "";
// The password is obfuscated and stored in a separate location
var credentials = this.credentials;
@ -45,12 +47,15 @@ module.exports = function(RED) {
if (RED.settings.verbose || LOGITALL) {
this.log("Setting up connection xmpp: {service: "+proto+"://"+this.server+":"+this.port+", username: "+this.username+", password: "+this.password+"}");
}
this.client = client({
var opts = {
service: proto+'://' + this.server + ':' + this.port,
domain: this.domain,
username: this.username,
password: this.password,
timeout: 60000
});
}
if (this.resource !== "") { opts.resource = this.resource; }
this.client = client(opts);
this.client.timeout = 60000;
// helper variable for checking against later, maybe we should be using the client

View File

@ -1,6 +1,6 @@
{
"name": "node-red-node-xmpp",
"version": "0.5.10",
"version": "0.6.0",
"description": "A Node-RED node to talk to an XMPP server",
"dependencies": {
"@xmpp/client": "^0.13.1"

View File

@ -65,19 +65,26 @@ module.exports = function(RED) {
});
}
this.connect = function() {
if (!this.connected && !this.connecting) {
node.connect = function() {
if (!node.connected && !node.connecting) {
doConnect();
}
}
this.on('close', function(done) {
if (this.tick) { clearTimeout(this.tick); }
if (this.check) { clearInterval(this.check); }
node.connected = false;
node.on('close', function(done) {
if (node.tick) { clearTimeout(node.tick); }
if (node.check) { clearInterval(node.check); }
// node.connection.release();
node.emit("state"," ");
node.pool.end(function(err) { done(); });
if (node.connected) {
node.connected = false;
node.pool.end(function(err) { done(); });
}
else {
delete node.pool;
done();
}
});
}
RED.nodes.registerType("MySQLdatabase",MySQLNode, {

View File

@ -1,6 +1,6 @@
{
"name": "node-red-node-mysql",
"version": "1.0.1",
"version": "1.0.3",
"description": "A Node-RED node to read and write to a MySQL database",
"dependencies": {
"mysql2": "^2.3.3"

View File

@ -16,6 +16,7 @@ module.exports = function(RED) {
var fileTail = function() {
if (fs.existsSync(node.filename)) {
node.status({ });
if (node.filetype === "text") {
node.tail = new Tail(node.filename,{separator:node.split, flushAtEOF:true});
}
@ -40,6 +41,7 @@ module.exports = function(RED) {
node.tail.on("error", function(err) {
node.status({ fill: "red",shape:"ring", text: "node-red:common.status.error" });
node.error(err.toString());
if (err.code ==="ENOENT") { scheduleRestart(); }
});
}
else {
@ -51,6 +53,8 @@ module.exports = function(RED) {
var scheduleRestart = function() {
node.tout = setTimeout(function() {
node.tout = null;
if (node.tail) { node.tail.unwatch(); }
delete node.tail;
fileTail();
}, 10000);
};

View File

@ -1,6 +1,6 @@
{
"name": "node-red-node-tail",
"version": "0.3.2",
"version": "0.4.0",
"description": "A node to tail files for Node-RED",
"dependencies": {
"tail": "^2.2.4"

View File

@ -15,257 +15,297 @@ describe('random node', function() {
helper.stopServer(done);
});
});
// ============================================================
it ("Test i1 (integer) - DEFAULT no overrides defaults to 1 and 10", function(done) {
var flow = [{id:"n1", type:"random", low: "" , high:"" , inte:true, wires:[["n2"]] },
it ("Test i1 (integer) - DEFAULT no overrides defaults to 1 and 10", function(done) {
var flow = [{id:"n1", type:"random", low: "" , high:"" , inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(1,10);
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(1,10);
msg.payload.toString().indexOf(".").should.equal(-1); // see if it's really an integer and not a float...
done();
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i1"});
n1.emit("input", {"test":"Test i1"});
});
});
it ("Test f1 (float) - DEFAULT no overrides defaults to 1 and 10", function(done) {
var flow = [{id:"n1", type:"random", low:"" , high:"" , inte:false, wires:[["n2"]] },
it ("Test f1 (float) - DEFAULT no overrides defaults to 1 and 10", function(done) {
var flow = [{id:"n1", type:"random", low:"" , high:"" , inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(1.0,9.999);
//msg.payload.toString().indexOf(".").should.not.equal(-1);
done();
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(1.0,9.999);
//msg.payload.toString().indexOf(".").should.not.equal(-1);
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f-1"});
n1.emit("input", {"test":"Test f-1"});
});
});
// ============================================================
it ("Test i2 (integer) - FLIP node From = 3 To = -3", function(done) {
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:true, wires:[["n2"]] },
it ("Test i2 (integer) - FLIP node From = 3 To = -3", function(done) {
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(-3,3);
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(-3,3);
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
done();
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i2"});
n1.emit("input", {"test":"Test i2"});
});
});
it ("Test f2 (float) - FLIP node From = 3 To = -3", function(done) {
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:false, wires:[["n2"]] },
it ("Test f2 (float) - FLIP node From = 3 To = -3", function(done) {
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(-3.0,3.0);
done();
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(-3.0,3.0);
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f2"});
n1.emit("input", {"test":"Test f2"});
});
});
// ============================================================
it ("Test i3 (integer) - values in msg From = 2 To = '5', node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
it ("Test i3 (integer) - values in msg From = 2 To = '5', node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2,5);
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2,5);
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
done();
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i3", "from":2, "to":'5'});
n1.emit("input", {"test":"Test i3", "from":2, "to":'5'});
});
});
it ("Test f3 (float) - values in msg From = 2 To = '5', node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
it ("Test f3 (float) - values in msg From = 2 To = '5', node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2.0,5.0);
done();
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2.0,5.0);
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f3", "from":2, "to":'5'});
n1.emit("input", {"test":"Test f3", "from":2, "to":'5'});
});
});
// ============================================================
it ("Test i4 (integer) - value in msg From = 2, node From = 5 To = '' - node overides From = 5 To defaults to 10", function(done) {
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:true, wires:[["n2"]] },
it ("Test i4 (integer) - value in msg From = 2, node From = 5 To = '' - node overides From = 5 To defaults to 10", function(done) {
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(5,10);
msg.payload.toString().indexOf(".").should.equal(-1);
done();
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(5,10);
msg.payload.toString().indexOf(".").should.equal(-1);
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i4", "from": 2});
n1.emit("input", {"test":"Test i4", "from": 2});
});
});
it ("Test f4 (float) - value in msg From = 2, node From = 5 To = '' - node wins 'To' defaults to 10", function(done) {
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:false, wires:[["n2"]] },
it ("Test f4 (float) - value in msg From = 2, node From = 5 To = '' - node wins 'To' defaults to 10", function(done) {
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(5.0,10.0);
done();
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(5.0,10.0);
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f4", "from": 2});
n1.emit("input", {"test":"Test f4", "from": 2});
});
});
// ============================================================
it ("Test i5 (integer) - msg From = '6' To = '9' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
it ("Test i5 (integer) - msg From = '6' To = '9' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(6,9);
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(6,9);
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
done();
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i5", "from": '6', "to": '9'});
n1.emit("input", {"test":"Test i5", "from": '6', "to": '9'});
});
});
it ("Test f5 (float) - msg From = '6' To = '9' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
it ("Test i5a (integer) - msg From = '0' To = '2' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(6.0,9.0);
done();
}
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f5", "from": '6', "to": '9'});
});
});
// ============================================================
it ("Test i6 (integer) - msg From = 2.4 To = '7.3' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2,7);
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(0,2);
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
done();
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i6", "from": 2.4, "to": '7.3'});
n1.emit("input", {"test":"Test i5", "from": '0', "to": '2'});
});
});
it ("Test f6 (float) - msg From = 2.4 To = '7.3' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
it ("Test i5b (integer) - msg From = '-3' To = '0' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2.4,7.3);
done();
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(-3,0);
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
done();
}
catch(err) { done(err); }
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f6", "from": 2.4, "to": '7.3'});
n1.emit("input", {"test":"Test i5", "from": '-3', "to": '0'});
});
});
it ("Test f5 (float) - msg From = '6' To = '9' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(6.0,9.0);
done();
}
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f5", "from": '6', "to": '9'});
});
});
// ============================================================
it ("Test i6 (integer) - msg From = 2.4 To = '7.3' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2,7);
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
done();
}
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test i6", "from": 2.4, "to": '7.3'});
});
});
it ("Test f6 (float) - msg From = 2.4 To = '7.3' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
//console.log(msg);
msg.should.have.a.property("payload");
msg.payload.should.be.within(2.4,7.3);
done();
}
catch(err) { done(err); }
});
n1.emit("input", {"test":"Test f6", "from": 2.4, "to": '7.3'});
});
});
// ============================================================
});

View File

@ -0,0 +1,101 @@
var should = require("should");
var helper = require("node-red-node-test-helper");
var testNode = require('../../../parsers/cbor/70-cbor.js');
describe('cbor node', function() {
"use strict";
beforeEach(function(done) {
helper.startServer(done);
});
afterEach(function(done) {
helper.unload().then(function() {
helper.stopServer(done);
});
});
it("should be loaded with correct defaults", function(done) {
var flow = [{"id":"n1", "type":"cbor", "name":"cbor1", "wires":[[]]}];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property("name", "cbor1");
done();
});
});
var buf;
it('should pack an object', function(done) {
var flow = [{"id":"n1", "type":"cbor", wires:[["n2"]] },
{id:"n2", type:"helper"}];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.a.property("payload");
msg.payload.should.be.a.Object;
msg.payload.should.have.length(47);
buf = msg.payload;
done();
});
n1.emit("input", {payload:{A:1, B:"string", C:true, D:[1,true,"string"], E:{Y:9,Z:"string"}}});
});
});
it('should unpack a Buffer', function(done) {
var flow = [{"id":"n1", "type":"cbor", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.a.property("payload");
msg.payload.should.have.a.property("A",1);
msg.payload.should.have.a.property("B",'string');
msg.payload.should.have.a.property("C",true);
msg.payload.should.have.a.property("D",[1,true,"string"]);
msg.payload.should.have.a.property("E");
msg.payload.E.should.have.a.property("Y",9);
msg.payload.E.should.have.a.property("Z","string");
done();
});
n1.emit("input", {payload:buf});
});
});
it('should error if the buffer fails to decode', function(done) {
buf[0] = 0x87;
var flow = [{"id":"n1", "type":"cbor", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
done("should not get here if there is an error.");
});
setTimeout(function() {
done();
}, 25);
n1.emit("input", {payload:buf});
});
});
it('ignore msg with no payload', function(done) {
var flow = [{"id":"n1", "type":"cbor", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
done("should not get here with no payload.");
});
setTimeout(function() {
done();
}, 25);
n1.emit("input", {topic:1});
});
});
});

View File

@ -31,6 +31,25 @@ describe('email Node', function () {
n1.should.have.property("repeat", 300000);
n1.should.have.property("inserver", "imap.gmail.com");
n1.should.have.property("inport", "993");
n1.should.have.property("authtype", "BASIC");
done();
});
});
it('should force input on XOAuth2', function (done) {
var flow = [{
id: "n1",
type: "e-mail in",
name: "emailin",
authtype: "XOAUTH2",
wires: [
[]
]
}];
helper.load(emailNode, flow, function () {
var n1 = helper.getNode("n1");
n1.should.have.property("repeat", 0);
n1.should.have.property("inputs", 1);
done();
});
});
@ -51,6 +70,7 @@ describe('email Node', function () {
helper.load(emailNode, flow, function () {
var n1 = helper.getNode("n1");
n1.should.have.property('name', "emailout");
n1.should.have.property("authtype", "BASIC");
done();
});
});
@ -83,7 +103,7 @@ describe('email Node', function () {
//console.log(helper.log());
//logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("email.errors.nopayload");
logEvents[2][0].msg.toString().should.startWith("email.errors.nopayload");
done();
} catch (e) {
done(e);
@ -134,7 +154,7 @@ describe('email Node', function () {
// console.log(logEvents[0][0].msg.toString());
//logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Error:");
logEvents[2][0].msg.toString().should.startWith("Error:");
done();
} catch (e) {
done(e);
@ -179,7 +199,7 @@ describe('email Node', function () {
//console.log(helper.log().args);
//logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Error:");
logEvents[2][0].msg.toString().should.startWith("Error:");
done();
} catch (e) {
done(e);

View File

@ -1,44 +1,44 @@
<script type="text/html" data-template-name="sunrise">
<div class="form-row">
<label for="node-input-lat"><i class="fa fa-globe"></i> Latitude</label>
<input type="text" id="node-input-lat" placeholder="51.025">
</div>
<div class="form-row">
<label for="node-input-lon"><i class="fa fa-globe"></i> Longitude</label>
<input type="text" id="node-input-lon" placeholder="-1.4">
</div>
<div class="form-row">
<label for="node-input-start"><i class="fa fa-clock-o"></i> Start</label>
<select id="node-input-start" style='width:70%'>
<option value="nightEnd">Morning astronomical twilight starts</option>
<option value="nauticalDawn">Morning nautical twilight starts</option>
<option value="dawn">Dawn, morning civil twilight starts</option>
<option value="sunrise">Sunrise</option>
<option value="sunriseEnd">Sunrise end</option>
<option value="goldenHourEnd">End of morning golden hour</option>
</select>
</div>
<div class="form-row">
<label for="node-input-end"><i class="fa fa-clock-o"></i> End</label>
<select id="node-input-end" style='width:70%'>
<option value="goldenHour">Start of evening golden hour</option>
<option value="sunsetStart">Sunset start</option>
<option value="sunset">Sunset, civil twilight starts</option>
<option value="dusk">Dusk, Evening astronomical twilight starts</option>
<option value="nauticalDusk">Evening nautical twilight starts</option>
<option value="night">Dark enough for astronomy</option>
</select>
</div>
<div class="form-row">
<label><i class="fa fa-arrows-h"></i> Offset</label>
<span style="margin-right:4px">start</span> <input type="text" id="node-input-soff" placeholder="minutes" style='width:60px;'> mins
<span style="margin-left:14px; margin-right:4px">end</span> <input type="text" id="node-input-eoff" placeholder="minutes" style='width:60px;'> mins
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-lat"><i class="fa fa-globe"></i><span data-i18n="sunrise.label.latitude"></span></label>
<input type="text" id="node-input-lat" placeholder="51.025">
</div>
<div class="form-row">
<label for="node-input-lon"><i class="fa fa-globe"></i><span data-i18n="sunrise.label.longitude"></span></label>
<input type="text" id="node-input-lon" placeholder="-1.4">
</div>
<div class="form-row">
<label for="node-input-start"><i class="fa fa-clock-o"></i><span data-i18n="sunrise.label.start"></label>
<select id="node-input-start" style='width:70%'>
<option value="nightEnd" data-i18n="sunrise.nightEnd"></option>
<option value="nauticalDawn" data-i18n="sunrise.nauticalDawn"></option>
<option value="dawn" data-i18n="sunrise.dawn"></option>
<option value="sunrise" data-i18n="sunrise.sunrise"></option>
<option value="sunriseEnd" data-i18n="sunrise.sunriseEnd"></option>
<option value="goldenHourEnd" data-i18n="sunrise.goldenHourEnd"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-end"><i class="fa fa-clock-o"></i><span data-i18n="sunrise.label.end"></label>
<select id="node-input-end" style='width:70%'>
<option value="goldenHour" data-i18n="sunrise.goldenHour"></option>
<option value="sunsetStart" data-i18n="sunrise.sunsetStart"></option>
<option value="sunset" data-i18n="sunrise.sunset"></option>
<option value="dusk" data-i18n="sunrise.dusk"></option>
<option value="nauticalDusk" data-i18n="sunrise.nauticalDusk"></option>
<option value="night" data-i18n="sunrise.night"></option>
</select>
</div>
<div class="form-row">
<label><i class="fa fa-arrows-h"></i><span data-i18n="sunrise.label.offset"></label>
<span style="margin-right:4px" data-i18n="sunrise.start"></span> <input type="text" id="node-input-soff" placeholder="minutes" style='width:60px;' > <span data-i18n="sunrise.mins"></span>
<span style="margin-left:14px; margin-right:4px" data-i18n="sunrise.end"></span> <input type="text" id="node-input-eoff" placeholder="minutes" style='width:60px;'><span data-i18n="sunrise.mins"></span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i><span data-i18n="sunrise.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]sunrise.label.name">
</div>
</script>
<script type="text/javascript">
@ -56,10 +56,15 @@
},
inputs:0,
outputs:2,
outputLabels: ["once per minute","only on change"],
outputLabels: function(i) {
return [
this._("sunrise.onePerMin"),
this._("sunrise.onse")
][i];
},
icon: "sun.png",
label: function() {
return this.name||"Sun rise/set";
return this.name||this._("sunrise.sunName");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@ -49,8 +49,8 @@ module.exports = function(RED) {
var msg = {payload:0, topic:"sun", sun:sun, moon:moon, start:s1, end:s2, now:now};
if ((e1 > 0) & (e2 < 0)) { msg.payload = 1; }
if (oldval == null) { oldval = msg.payload; }
if (msg.payload == 1) { node.status({fill:"yellow",shape:"dot",text:"day"}); }
else { node.status({fill:"blue",shape:"dot",text:"night"}); }
if (msg.payload == 1) { node.status({fill:"yellow",shape:"dot",text:"sunrise.dayState"}); }
else { node.status({fill:"blue",shape:"dot",text:"sunrise.nightState"}); }
if (msg.payload != oldval) {
oldval = msg.payload;
node.send([msg,msg]);

View File

@ -0,0 +1,32 @@
{
"sunrise": {
"label": {
"latitude": " Latitude",
"longitude": " Longitude",
"start": " Start",
"end": " End",
"offset": " Offset",
"name": " Name"
},
"nightEnd": "Morning astronomical twilight starts",
"nauticalDawn": "Morning nautical twilight starts",
"dawn": "Dawn, morning civil twilight starts",
"sunrise": "Sunrise",
"sunriseEnd": "Sunrise end",
"goldenHourEnd": "End of morning golden hour",
"goldenHour": "Start of evening golden hour",
"sunsetStart": "Sunset start",
"sunset": "Sunset, civil twilight starts",
"dusk": "Dusk, Evening astronomical twilight starts",
"nauticalDusk": "Evening nautical twilight starts",
"night": "Dark enough for astronomy",
"start": " start",
"mins": " mins",
"end": " end",
"dayState": "day",
"nightState": "night",
"onePerMin": "once per minute",
"onse": "only on change",
"sunName": "Sun rise/set"
}
}

View File

@ -0,0 +1,11 @@
<script type="text/html" data-help-name="sunrise">
<p>Использует модуль suncalc для вывода данных о восходе и закате солнца в зависимости от указанного местоположения.</p>
<p>Доступно несколько вариантов определения восхода и захода солнца, подробности смотрите в модуле <i><a href = "https://github.com/mourner/suncalc" target="_new">suncalc</a></i>.</p>
<p> Время начала и окончания может быть смещено на несколько минут до (минус) или после (плюс) выбранного события.</p>
<p>Первый выход отправляет каждую минуту <code>msg.payload</code> <i>1</i> или <i>0</i> в зависимости от того, находится ли он между выбранными событиями или нет.
Второй выход отправляет только при переходе от ночи к дню (<i>-> 1</i>) или от дня к ночи (<i>-> 0</i>).</p>
<p>Так же выводит <code>msg.start</code>, <code>msg.end</code> и <code>msg.now</code> в формате ISO. Которые содержат время начала, окончания события на сегоднящний день с поправкой на указанное смещение, а так же текущее время.</p>
<p><code>msg.sun</code> — это содержит азимут и высоту в градусах текущего положения солнца.</p>
<p><code>msg.moon</code> — это содержит <thead></thead> положение, фазу, освещение и значок луны.</p>
</script>

View File

@ -0,0 +1,33 @@
{
"sunrise": {
"label": {
"latitude": " Широта",
"longitude": " Долгота",
"start": " Начало",
"end": " Окончание",
"offset": " Смещение",
"name": " Имя"
},
"nightEnd": "Утренние астрономические сумерки",
"nauticalDawn": "Утренние навигационные сумерки",
"dawn": "Рассвет, утренние гражданские сумерки",
"sunrise": "Восход",
"sunriseEnd": "Восход закончился",
"goldenHourEnd": "Окончание утреннего золотого часа",
"goldenHour": "Начало вечернего золотого часа",
"sunsetStart": "Начало заката",
"sunset": "Закат, начинаются вечерние гражданские сумерки",
"dusk": "Смеркается, начинаются вечерние астрономические сумерки",
"nauticalDusk": "Начинаются вечерние морские сумерки",
"night": "Достаточно темно для астрономии",
"start": " начало",
"mins": " мин",
"end": " конец",
"dayState": "день",
"nightState": "ночь",
"onePerMin": "раз в минуту",
"onse": "по событию",
"sunName": "Рассвет/закат"
}
}

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-suncalc",
"version" : "1.0.1",
"version" : "1.1.0",
"description" : "A Node-RED node to provide a signal at sunrise and sunset",
"dependencies" : {
"suncalc" : "^1.8.0"

View File

@ -7,9 +7,11 @@ simple timeswitch node to schedule daily on/off events.
Install
-------
Run the following command in your Node-RED user directory - typically `~/.node-red`
You can install by using the `Menu - Manage Palette` option, or running the following command in your
Node-RED user directory - typically `~/.node-red`
npm install node-red-node-timeswitch
cd ~/.node-red
npm i node-red-node-timeswitch
Usage
-----
@ -24,9 +26,11 @@ or dusk, and negatively (-ve) for minutes before dawn or dusk..
The output emits a `msg.payload` of *1* or *0* every minute depending on
whether the current time is during the selected on time or off time.
If you just need the transitions from 0->1 or 1->0 then follow this node with an RBE node.
If you just need the transitions from 0->1 or 1->0 then follow this node with a `filter (RBE)` node.
You may also optionally specify a `msg.topic` if required.
**Note**: For a more complex version with more built-in options see Pete Scargill's
[node-red-contrib-bigtimer](http://flows.nodered.org/node/node-red-contrib-bigtimer) node.
**Note**: For a more complex version with more built-in options see
[node-red-contrib-bigtimer](http://flows.nodered.org/node/node-red-contrib-bigtimer) node, or
for multiple schedules and a nice visual interface to cron then use Steve's
[node-red-contrib-cron-plus](https://flows.nodered.org/node/node-red-contrib-cron-plus) node.

View File

@ -7,6 +7,6 @@
or negatively (-ve) for minutes earlier.</p>
<p>The output emits a <code>msg.payload</code> of <i>1</i> or <i>0</i> every minute depending on
whether the current time is during the selected on time or off time.</p>
<p>If you just need the transitions from 0->1 or 1->0 then follow this node with an RBE node.</p>
<p>If you just need the transitions from 0->1 or 1->0 then follow this node with a filter (RBE) node.</p>
<p>You may also optionally specify a <code>msg.topic</code> if required.</p>
</script>

View File

@ -1,9 +1,9 @@
{
"name" : "node-red-node-timeswitch",
"version" : "0.1.0",
"version" : "1.0.0",
"description" : "A Node-RED node to provide a simple timeswitch to schedule daily on/off events.",
"dependencies" : {
"spacetime": "^6.12.5",
"spacetime": "^7.4.0",
"suncalc": "^1.8.0"
},
"repository" : {
@ -25,7 +25,19 @@
},
"contributors": [
{
"name": "@pmacostapdi"
"name": "@dceejay"
},
{
"name": "@pmacostapdi"
},
{
"name": "@heikokue"
},
{
"name": "@sammachin"
},
{
"name": "@jdmallen"
}
]
}

View File

@ -1,19 +1,18 @@
module.exports = function(RED) {
module.exports = function (RED) {
"use strict";
var SunCalc = require('suncalc');
const spacetime = require("spacetime")
const SUNRISE_KEY = "sunrise";
const SUNSET_KEY = "sunset";
function TimeswitchNode(n) {
RED.nodes.createNode(this, n);
this.lat = n.lat;
this.lon = n.lon;
this.start = n.start || "sunrise";
this.end = n.end || "sunset";
this.startt = n.starttime;
this.endt = n.endtime;
this.duskoff = n.duskoff;
this.dawnoff = n.dawnoff;
this.sunriseOffset = n.dawnoff;
this.sunsetOffset = n.duskoff;
this.mytopic = n.mytopic;
this.timezone = n.timezone || "UTC";
@ -24,6 +23,7 @@ module.exports = function(RED) {
this.thu = n.thu;
this.fri = n.fri;
this.sat = n.sat;
this.jan = n.jan;
this.feb = n.feb;
this.mar = n.mar;
@ -38,118 +38,195 @@ module.exports = function(RED) {
this.dec = n.dec;
var node = this;
var ison = 0;
var newendtime = 0;
this.on("input", function(msg2) {
if (msg2.payload === "reset") { ison = 0; }
this.on("input", function () {
// current global time
const now = spacetime.now();
const nowNative = now.toNativeDate();
var timeOffset = spacetime(Date.now()).goto(this.timezone.toLowerCase()).timezone().current.offset * 60 * 60 * 1000;
var now = new Date(Date.now() + timeOffset);
var nowMillis = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), 0);
var midnightMillis = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0);
var today = Math.round((nowMillis - midnightMillis) / 60000) % 1440;
var starttime = Number(node.startt);
var endtime = Number(node.endt);
// all sun events for the given lat/long
const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon);
let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute");
let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute");
if ((starttime >= 5000) || (endtime == 5000) || (endtime == 6000)) {
var times = SunCalc.getTimes(now, node.lat, node.lon);
var startMillis = Date.UTC(times[node.start].getUTCFullYear(), times[node.start].getUTCMonth(), times[node.start].getUTCDate(), times[node.start].getUTCHours(), times[node.start].getUTCMinutes());
var endMillis = Date.UTC(times[node.end].getUTCFullYear(), times[node.end].getUTCMonth(), times[node.end].getUTCDate(), times[node.end].getUTCHours(), times[node.end].getUTCMinutes());
var dawn = ((startMillis - midnightMillis) / 60000) + Number(node.dawnoff);
var dusk = ((endMillis - midnightMillis) / 60000) + Number(node.duskoff);
if (starttime == 5000) { starttime = dawn; }
if (starttime == 6000) { starttime = dusk; }
if (endtime == 5000) { endtime = dawn; }
if (endtime == 6000) { endtime = dusk; }
if (RED.settings.verbose) { node.log("Dawn " + parseInt(dawn / 60) + ":" + dawn % 60 + " - Dusk " + parseInt(dusk / 60) + ":" + dusk % 60); }
// add optional sun event offset, if specified
sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes");
// check if sun event has already occurred today
if (now.isAfter(sunriseDateTime)) {
// get tomorrow's sunrise, since it'll be different
sunriseDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNRISE_KEY]).nearest("minute");
// add optional sun event offset, if specified (again)
sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
}
if (now.isAfter(sunsetDateTime)) {
// get tomorrow's sunset, since it'll be different
sunsetDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNSET_KEY]).nearest("minute");
// add optional sun event offset, if specified (again)
sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes");
}
var proceed = 0;
switch (now.getDay()) {
case 0 : { if (node.sun) { proceed++; } break; }
case 1 : { if (node.mon) { proceed++; } break; }
case 2 : { if (node.tue) { proceed++; } break; }
case 3 : { if (node.wed) { proceed++; } break; }
case 4 : { if (node.thu) { proceed++; } break; }
case 5 : { if (node.fri) { proceed++; } break; }
case 6 : { if (node.sat) { proceed++; } break; }
// log sun events
if (RED.settings.verbose) {
node.log(`Sunrise ${sunriseDateTime.format("time")} - Sunset ${sunsetDateTime.format("time")} `);
}
if (proceed) {
switch (now.getMonth()) {
case 0 : { if (node.jan) { proceed++; } break; }
case 1 : { if (node.feb) { proceed++; } break; }
case 2 : { if (node.mar) { proceed++; } break; }
case 3 : { if (node.apr) { proceed++; } break; }
case 4 : { if (node.may) { proceed++; } break; }
case 5 : { if (node.jun) { proceed++; } break; }
case 6 : { if (node.jul) { proceed++; } break; }
case 7 : { if (node.aug) { proceed++; } break; }
case 8 : { if (node.sep) { proceed++; } break; }
case 9 : { if (node.oct) { proceed++; } break; }
case 10: { if (node.nov) { proceed++; } break; }
case 11: { if (node.dec) { proceed++; } break; }
// apply selected timezone to selected times (not to sunrise/sunset-- those are based on lat/long)
const currentTimeZone = now.timezone();
const selectedTimeZone = spacetime(now.epoch, this.timezone.toLowerCase()).timezone();
// handler function to convert minute strings (from <option> tags) to spacetime objects, called below
let getSelectedTimeFromMinuteString = minuteString => {
const selectedTimeInMinutesAfterMidnight = Number(minuteString);
let selectedTime = spacetime.now();
// if less than 1440, what are the time values for the next start and stop time?
if (selectedTimeInMinutesAfterMidnight < 1440) {
// determine offset to get from selected time zone to current timezone
// e.g. current (EDT) is -4, selected (PDT) is -7
// in order to get from PDT to EDT, you must add 3
// (-4) - (-7) = +3
const offset = currentTimeZone.current.offset - selectedTimeZone.current.offset;
const selectedHourValue = Math.floor(selectedTimeInMinutesAfterMidnight / 60);
const selectedMinuteValue = Math.floor(selectedTimeInMinutesAfterMidnight % 60);
selectedTime = selectedTime.hour(selectedHourValue).minute(selectedMinuteValue).second(0).millisecond(0);
selectedTime = selectedTime.add(offset, "hours");
// select the next time if it's in the past
if (now.isAfter(selectedTime)) {
selectedTime = selectedTime.add(1, "day");
}
} else if (selectedTimeInMinutesAfterMidnight == 5000) { // sunrise
selectedTime = sunriseDateTime;
} else if (selectedTimeInMinutesAfterMidnight == 6000) { // sunset
selectedTime = sunsetDateTime;
}
return selectedTime;
};
// our definitive next ON time
let selectedOnTime = getSelectedTimeFromMinuteString(node.startt);
// our definitive next OFF time
let selectedOffTime = getSelectedTimeFromMinuteString(node.endt);
// handle the "Start + X Minutes" cases
if (node.endt >= 10000) {
// even though the next start time might be tomorrow,
// the start time + X minutes might still be coming today,
// so we need to go back a day first
const selectedOnTimeMinus1Day = selectedOnTime.subtract(1, "day");
selectedOffTime = selectedOnTimeMinus1Day.add(node.endt - 10000, "minutes");
// _now_ we can check if the off time is in the past
if (now.isAfter(selectedOffTime)) {
selectedOffTime = selectedOffTime.add(1, "day");
}
}
if (proceed >= 2) { proceed = 1; }
else { proceed = 0; }
newendtime = endtime;
if (endtime > 10000) { newendtime = starttime + (endtime - 10000); }
if (proceed) { // have to handle midnight wrap
if (starttime <= newendtime) {
if ((today >= starttime) && (today <= newendtime)) { proceed++; }
// handler function for the node payload, called below
let sendPayload = (payload, nextTime) => {
// var o = nextTime.goto(selectedTimeZone.name).offset()/60;
// if (o > 0) { o = "+" + o; }
// else {o = "-" + o; }
if (payload == 1) {
node.status({
fill: "yellow",
shape: "dot",
text: `on until ${nextTime.goto(selectedTimeZone.name).format("time-24")}`
});
} else {
node.status({
fill: "blue",
shape: "dot",
text: `off until ${nextTime.goto(selectedTimeZone.name).format("time-24")}`
});
}
else {
if ((today >= starttime) || (today <= newendtime)) { proceed++; }
var msg = {};
if (node.mytopic) {
msg.topic = node.mytopic;
}
msg.payload = payload;
node.send(msg);
};
var proceed = true;
// if today is not among the selected days of the week, stop here
switch (nowNative.getDay()) {
case 0 : { if (!node.sun) { proceed &= false; } break; }
case 1 : { if (!node.mon) { proceed &= false; } break; }
case 2 : { if (!node.tue) { proceed &= false; } break; }
case 3 : { if (!node.wed) { proceed &= false; } break; }
case 4 : { if (!node.thu) { proceed &= false; } break; }
case 5 : { if (!node.fri) { proceed &= false; } break; }
case 6 : { if (!node.sat) { proceed &= false; } break; }
}
if (proceed >= 2) {
var duration = newendtime - today;
if (today > newendtime) { duration += 1440; }
//node.status({fill:"yellow",shape:"dot",text:"on for " + duration + " mins"});
node.status({fill:"yellow", shape:"dot", text:"on until " + parseInt(newendtime / 60) + ":" + ("0" + newendtime % 60).substr(-2)});
if (!proceed) {
sendPayload(0, selectedOnTime);
return;
}
//else { node.status({fill:"blue",shape:"dot",text:"off"}); }
else { node.status({fill:"blue", shape:"dot", text:"off until " + parseInt(starttime / 60) + ":" + ("0" + starttime % 60).substr(-2)}); }
var msg = {};
if (node.mytopic) { msg.topic = node.mytopic; }
msg.payload = (proceed >= 2) ? 1 : 0;
node.send(msg);
// if this month is not among the selected months, stop here
switch (nowNative.getMonth()) {
case 0 : { if (!node.jan) { proceed &= false; } break; }
case 1 : { if (!node.feb) { proceed &= false; } break; }
case 2 : { if (!node.mar) { proceed &= false; } break; }
case 3 : { if (!node.apr) { proceed &= false; } break; }
case 4 : { if (!node.may) { proceed &= false; } break; }
case 5 : { if (!node.jun) { proceed &= false; } break; }
case 6 : { if (!node.jul) { proceed &= false; } break; }
case 7 : { if (!node.aug) { proceed &= false; } break; }
case 8 : { if (!node.sep) { proceed &= false; } break; }
case 9 : { if (!node.oct) { proceed &= false; } break; }
case 10: { if (!node.nov) { proceed &= false; } break; }
case 11: { if (!node.dec) { proceed &= false; } break; }
}
if (!proceed) {
sendPayload(0, selectedOnTime);
return;
}
// if the chronological order is NOW --> ON --> OFF, then now should be OFF
if (proceed && selectedOffTime.isAfter(selectedOnTime)) {
sendPayload(0, selectedOnTime);
return;
}
// if the chronological order is NOW --> OFF --> ON, then now should be ON
if (proceed && selectedOffTime.isBefore(selectedOnTime)) {
sendPayload(1, selectedOffTime);
return;
}
// Note: we already ensured that all ON or OFF times would be in the future,
// so there is no midnight wrapping issue.
});
var tock = setTimeout(function() {
var tock = setTimeout(function () {
node.emit("input", {});
}, 2000); // wait 2 secs before starting to let things settle down e.g. UI connect
var tick = setInterval(function() {
var tick = setInterval(function () {
node.emit("input", {});
}, 60000); // trigger every 60 secs
this.on("close", function() {
this.on("close", function () {
if (tock) { clearTimeout(tock); }
if (tick) { clearInterval(tick); }
});
}
RED.httpAdmin.post("/timeswitch/:id", RED.auth.needsPermission("timeswitch.write"), function(req, res) {
RED.httpAdmin.post("/timeswitch/:id", RED.auth.needsPermission("timeswitch.write"), function (req, res) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {
try {
node.emit("input", {payload:"reset"});
node.emit("input", { payload: "reset" });
res.sendStatus(200);
}
catch (err) {
} catch (err) {
res.sendStatus(500);
node.error("Inject failed:" + err);
}
}
else {
} else {
res.sendStatus(404);
}
});

View File

@ -34,7 +34,7 @@ to restart the command automatically.
Setting `msg.kill` to a signal name (e.g. SIGINT, SIGHUP) will stop the process - but if the restart flag is set it will then auto restart.
Sending `msg.start` will also re-start the process.
Sending `msg.start` will also re-start the process. Additional arguments can be specified in `msg.args`.
**Note:** Some applications will automatically buffer lines of output. It is advisable to turn off this behaviour.
For example, if running a Python app, the `-u` parameter will stop the output being buffered.

View File

@ -15,16 +15,21 @@ module.exports = function(RED) {
this.closer = n.closer || "SIGKILL";
this.autorun = true;
if (n.autorun === false) { this.autorun = false; }
if (this.args.match(/^\[.*\]$/)) {
try { this.args = JSON.parse(this.args); }
catch(e) {
node.warn(RED._("daemon.errors.badparams"))
}
}
else { this.args = this.args.match(/("[^"]*")|[^ ]+/g); }
this.args = parseArgs(this.args);
var node = this;
var lastmsg = {};
function parseArgs(args) {
if (args.match(/^\[.*\]$/)) {
try { args = JSON.parse(args); }
catch(e) {
node.warn(RED._("daemon.errors.badparams"))
}
}
else { args = args.match(/("[^"]*")|[^ ]+/g); }
return args;
}
function inputlistener(msg) {
if (msg != null) {
if (msg.hasOwnProperty("kill") && node.running) {
@ -32,7 +37,11 @@ module.exports = function(RED) {
node.child.kill(msg.kill.toUpperCase());
}
else if (msg.hasOwnProperty("start") && !node.running) {
runit();
let args = "";
if (msg.hasOwnProperty("args") && msg.args.length > 0) {
args = parseArgs(msg.args.trim());
}
runit(args);
}
else {
if (!Buffer.isBuffer(msg.payload)) {
@ -41,22 +50,27 @@ module.exports = function(RED) {
if (node.cr === true) { msg.payload += "\n"; }
}
node.debug("inp: "+msg.payload);
lastmsg = msg;
if (node.child !== null && node.running) { node.child.stdin.write(msg.payload); }
else { node.warn(RED._("daemon.errors.notrunning")); }
lastmsg = msg;
}
}
}
function runit() {
function runit(appendArgs) {
var line = "";
if (!node.cmd || (typeof node.cmd !== "string") || (node.cmd.length < 1)) {
node.status({fill:"grey",shape:"ring",text:RED._("daemon.status.nocommand")});
return;
}
let args = node.args;
if (appendArgs !== undefined && appendArgs.length > 0) {
args = args.concat(appendArgs);
}
try {
node.child = spawn(node.cmd, node.args);
node.debug(node.cmd+" "+JSON.stringify(node.args));
node.child = spawn(node.cmd, args);
node.debug(node.cmd+" "+JSON.stringify(args));
node.status({fill:"green",shape:"dot",text:RED._("daemon.status.running")});
node.running = true;
@ -106,6 +120,12 @@ module.exports = function(RED) {
else { node.log('error: ' + err); }
node.status({fill:"red",shape:"ring",text:RED._("daemon.status.error")});
});
node.child.stdin.on('error', function (err) {
if (err.errno === "EPIPE") { node.error(RED._("daemon.errors.pipeclosed"),lastmsg); }
else { node.log('error: ' + err); }
node.status({fill:"red",shape:"ring",text:RED._("daemon.status.error")});
});
}
catch(e) {
if (e.errno === "ENOENT") { node.warn(RED._("daemon.errors.notfound")); }

View File

@ -5,7 +5,7 @@
<p>Parameters can be space separated, space separated with quotes, or a javascript array. For example `aa bb` or `"cc dd"` or `["aa","bb cc""]`.</p>
<p>If the called program stops (i.e. a return code is produced), this node can attempt to restart the command.</p>
<p>Setting <code>msg.kill</code> to a signal name (e.g. SIGINT, SIGHUP) will stop the process - but if the
restart flag is set it will then auto restart. Sending <code>msg.start</code> will also re-start the process.</p>
restart flag is set it will then auto restart. Sending <code>msg.start</code> will also re-start the process. Additional arguments can be specified in <code>msg.args</code>.</p>
<p><b>Note:</b> Some applications will automatically buffer lines of output. It is advisable to turn off this behaviour.
For example, if running a Python app, the <code>-u</code> parameter will stop the output being buffered.</p>
</script>

View File

@ -37,7 +37,8 @@
"notrunning": "Command not running",
"notfound": "Command not found",
"notexecutable": "Command not executable",
"restarting": "Restarting"
"restarting": "Restarting",
"pipeclosed": "Process closed"
}
}
}

View File

@ -1,6 +1,6 @@
{
"name" : "node-red-node-daemon",
"version" : "0.4.0",
"version" : "0.5.1",
"description" : "A Node-RED node that runs and monitors a long running system command.",
"dependencies" : {
},