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) - [ ] 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 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? --> <!-- 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 ## Checklist
<!-- Put an `x` in the boxes that apply --> <!-- 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) - [x] 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. - [x] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [ ] I have run `grunt` to verify the unit tests pass - [x] 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 added suitable unit tests to cover the new/changed functionality

1
.gitignore vendored
View File

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

View File

@ -1,6 +1,6 @@
{ {
"name" : "node-red-node-random", "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.", "description" : "A Node-RED node that when triggered generates a random number between two values.",
"dependencies" : { "dependencies" : {
}, },

View File

@ -18,9 +18,8 @@ module.exports = function(RED) {
if (node.low) { // if the the node has a value use it if (node.low) { // if the the node has a value use it
tmp.low = Number(node.low); tmp.low = Number(node.low);
} else if ('from' in msg) { // else see if a 'from' is in the msg } 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);
tmp.low = Number(msg.from); if (isNaN(msg.from)) { // if it isn't a number setup NaN error
} else { // otherwise setup NaN error
tmp.low = NaN; tmp.low = NaN;
tmp.low_e = " From: " + msg.from; // setup to show bad incoming msg.from 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 if (node.high) { // if the the node has a value use it
tmp.high = Number(node.high); tmp.high = Number(node.high);
} else if ('to' in msg) { // else see if a 'to' is in the msg } 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);
tmp.high = Number(msg.to); if (isNaN(msg.to)) { // if it isn't a number setup NaN error
} else { // otherwise setup NaN error
tmp.high = NaN tmp.high = NaN
tmp.high_e = " To: " + msg.to // setup to show bad incoming msg.to 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 (allOK === true) {
if (node.pin !== undefined) { if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]); startPin();
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})) }
});
} }
else { else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
@ -108,10 +121,13 @@ module.exports = function(RED) {
delete pinsInUse[node.pin]; delete pinsInUse[node.pin];
if (node.child != null) { if (node.child != null) {
node.finished = done; node.finished = done;
node.child.stdin.write("close "+node.pin); node.child.stdin.write("close "+node.pin, () => {
node.child.kill('SIGKILL'); if (node.child) {
node.child.kill('SIGKILL');
}
});
} }
else { done(); } else { if (done) { done(); } }
}); });
} }
RED.nodes.registerType("rpi-gpio in",GPIOInNode); RED.nodes.registerType("rpi-gpio in",GPIOInNode);
@ -188,11 +204,16 @@ module.exports = function(RED) {
}); });
node.child.on('error', function (err) { node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } if (err.code === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")+err.path,err); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } else if (err.code === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")+err.path,err); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } 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 { else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
@ -210,10 +231,12 @@ module.exports = function(RED) {
delete pinsInUse[node.pin]; delete pinsInUse[node.pin];
if (node.child != null) { if (node.child != null) {
node.finished = done; node.finished = done;
node.child.stdin.write("close "+node.pin); node.child.stdin.write("close "+node.pin, () => {
node.child.kill('SIGKILL'); 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", "invalidinput": "Ungültige Eingabe",
"needtobeexecutable": "__command__ muss ausführbar sein", "needtobeexecutable": "__command__ muss ausführbar sein",
"mustbeexecutable": "nrgpio muss ausführbar sein", "mustbeexecutable": "nrgpio muss ausführbar sein",
"commandnotfound": "nrgpio-Befehl nicht gefunden", "commandnotfound": "nrgpio-Befehl nicht gefunden ",
"commandnotexecutable": "nrgpio-Befehl nicht ausführbar", "commandnotexecutable": "nrgpio-Befehl nicht ausführbar ",
"error": "Fehler: __error__", "error": "Fehler: __error__",
"pythoncommandnotfound": "nrgpio-Python-Befehl nicht aktiv" "pythoncommandnotfound": "nrgpio-Python-Befehl nicht aktiv"
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('mraa-gpio-ain',{ RED.nodes.registerType('mraa-gpio-ain',{
category: 'Intel gpio', category: 'GPIO',
color: '#a6bbcf', color: '#a6bbcf',
paletteLabel: 'analogue', paletteLabel: 'analogue',
defaults: { defaults: {
@ -27,6 +27,7 @@
if (data === 5) { t = "Raspberry Pi"; } if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; } if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; } if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#btype').text(t); $('#btype').text(t);
$('#node-input-pin').val(pinnow); $('#node-input-pin').val(pinnow);
}); });
@ -62,7 +63,7 @@
</script> </script>
<script type="text/x-red" data-help-name="mraa-gpio-ain"> <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> <p>The <code>msg.payload</code> will contain the value, and <code>msg.topic</code>
contains "{the_board_name}/A{the pin number}".</p> 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> <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 node = this;
var msg = { topic:node.board+"/A"+node.pin }; var msg = { topic:node.board+"/A"+node.pin };
var old = -99999; var old = -99999;
// ADC set to 12 for IOT2050
if (this.board === "SIMATIC IOT2050") {
node.x.setBit(12);
}
this.timer = setInterval(function() { this.timer = setInterval(function() {
msg.payload = node.x.read(); msg.payload = node.x.read();
if (msg.payload !== old) { if (msg.payload !== old) {
@ -21,6 +25,7 @@ module.exports = function(RED) {
this.on('close', function() { this.on('close', function() {
clearInterval(this.timer); clearInterval(this.timer);
node.x.close();
}); });
} }
RED.nodes.registerType("mraa-gpio-ain", gpioAin); RED.nodes.registerType("mraa-gpio-ain", gpioAin);

View File

@ -1,13 +1,15 @@
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('mraa-gpio-din',{ RED.nodes.registerType('mraa-gpio-din',{
category: 'Intel gpio', category: 'GPIO',
color: '#a6bbcf', color: '#a6bbcf',
paletteLabel: 'digital', paletteLabel: 'digital',
defaults: { defaults: {
name: {value:""}, name: {value:""},
pin: {value:"", required: true}, pin: {value:"", required: true},
interrupt: {value:"", required: true} interrupt: {value:"", required: true},
mode: {value:"", required: true},
initial: {value: false}
}, },
inputs:0, inputs:0,
outputs:1, outputs:1,
@ -30,6 +32,7 @@
if (data === 5) { t = "Raspberry Pi"; } if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; } if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; } if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#type-tip').text(t); $('#type-tip').text(t);
$('#node-input-pin').val(pinnow); $('#node-input-pin').val(pinnow);
}); });
@ -59,6 +62,23 @@
<option value="11">D11</option> <option value="11">D11</option>
<option value="12">D12</option> <option value="12">D12</option>
<option value="13">D13</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> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
@ -70,6 +90,11 @@
<option value="b">Both </option> <option value="b">Both </option>
</select> </select>
</div> </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"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <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;"> <input type="text" id="node-input-name" placeholder="Name" style="width: 250px;">
@ -78,7 +103,7 @@
</script> </script>
<script type="text/x-red" data-help-name="mraa-gpio-din"> <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> <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> contains "{the_board_name}/D{the pin number}".</p>
</script> </script>

View File

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

View File

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

View File

@ -29,6 +29,7 @@ module.exports = function(RED) {
}); });
this.on('close', function() { this.on('close', function() {
node.p.close();
}); });
} }
RED.nodes.registerType("mraa-gpio-dout", gpioDout); 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"> <script type="text/javascript">
RED.nodes.registerType('mraa-gpio-pwm',{ RED.nodes.registerType('mraa-gpio-pwm',{
category: 'Intel gpio', category: 'GPIO',
color: '#a6bbcf', color: '#a6bbcf',
paletteLabel: 'pwm', paletteLabel: 'pwm',
defaults: { defaults: {
@ -35,6 +35,7 @@
if (data === 5) { t = "Raspberry Pi"; } if (data === 5) { t = "Raspberry Pi"; }
if (data === 6) { t = "Beaglebone"; } if (data === 6) { t = "Beaglebone"; }
if (data === 7) { t = "Banana"; } if (data === 7) { t = "Banana"; }
if (data === 26) { t = "IOT2050"; }
$('#type-tip').text(t); $('#type-tip').text(t);
$('#node-input-pin').val(pinnow); $('#node-input-pin').val(pinnow);
}); });
@ -60,13 +61,23 @@
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label> <label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
<select type="text" id="node-input-pin" style="width: 250px;"> <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='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="3">D3</option> <optgroup label="Intel Galileo/Edison">
<option value="5">D5</option> <option value="3">D3</option>
<option value="6">D6</option> <option value="5">D5</option>
<option value="9">D9</option> <option value="6">D6</option>
<option value="10">D10</option> <option value="9">D9</option>
<option value="11">D11</option> <option value="10">D10</option>
</select> <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>
<div class="form-row"> <div class="form-row">
<label for="node-input-period"><i class="fa fa-clock-o"></i> Period</label> <label for="node-input-period"><i class="fa fa-clock-o"></i> Period</label>
@ -80,9 +91,10 @@
</script> </script>
<script type="text/x-red" data-help-name="mraa-gpio-pwm"> <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 <p>The <code>msg.payload</code> should contain a floating point number value
between 0 and 1, (or a string representation thereof.)</p> 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>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> </script>

View File

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

View File

@ -1,7 +1,7 @@
{ {
"name" : "node-red-node-intel-gpio", "name" : "node-red-node-intel-gpio",
"version" : "0.0.6", "version" : "0.3.0",
"description" : "A Node-RED node to talk to an Intel Galileo or Edison using mraa", "description" : "A Node-RED node to talk to an Intel Galileo, Edison or Siemens IOT2050 board using mraa",
"dependencies" : { "dependencies" : {
}, },
"repository" : { "repository" : {
@ -10,18 +10,27 @@
"directory" : "tree/master/hardware/intel" "directory" : "tree/master/hardware/intel"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"keywords": [ "node-red", "intel", "galileo", "edison" ], "keywords": [ "node-red", "intel", "galileo", "edison", "siemens", "iot2050" ],
"node-red" : { "node-red" : {
"nodes" : { "nodes" : {
"mraa-gpio-ain": "mraa-gpio-ain.js", "mraa-gpio-ain": "mraa-gpio-ain.js",
"mraa-gpio-din": "mraa-gpio-din.js", "mraa-gpio-din": "mraa-gpio-din.js",
"mraa-gpio-dout": "mraa-gpio-dout.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": { "author": {
"name": "Dave Conway-Jones", "name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com", "email": "ceejay@vnet.ibm.com",
"url": "http://nodered.org" "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", "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", "description" : "A Node-RED node to interact with a Raspberry Pi Sense HAT",
"repository" : { "repository" : {
"type":"git", "type":"git",

View File

@ -7,7 +7,7 @@ module.exports = function(RED) {
var hatCommand = __dirname+'/sensehat'; 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"; 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. // Any data on stderr means a bad thing has happened.
// Best to kill it and let it reconnect. // Best to kill it and let it reconnect.
if (RED.settings.verbose) { RED.log.error("err: "+data+" :"); } 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.stderr.on('error', function(err) { });
hat.stdin.on('error', function(err) { }); hat.stdin.on('error', function(err) { });

View File

@ -1,9 +1,9 @@
{ {
"name" : "node-red-node-pi-unicorn-hat", "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.", "description" : "A Node-RED node to output to a Raspberry Pi Unicorn HAT from Pimorini.",
"dependencies" : { "dependencies" : {
"pngjs": "2.2.*" "pngjs": "2.3.1"
}, },
"repository" : { "repository" : {
"type":"git", "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 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. from opening the config dialog and selecting the node you want.
The node accepts the following `msg.payload` as input The node accepts the following `msg.payload` as input

View File

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

View File

@ -4,7 +4,7 @@ module.exports = function(RED) {
var spawn = require("child_process").spawn; var spawn = require("child_process").spawn;
var plat = require("os").platform(); var plat = require("os").platform();
function doPing(node, host, arrayMode) { function doPing(node, host, msg, arrayMode) {
const defTimeout = 5000; const defTimeout = 5000;
var ex, ex6, hostOptions, commandLineOptions; var ex, ex6, hostOptions, commandLineOptions;
if (typeof host === "string") { if (typeof host === "string") {
@ -20,7 +20,8 @@ module.exports = function(RED) {
hostOptions.timeout = hostOptions.timeout < 1000 ? 1000 : hostOptions.timeout; hostOptions.timeout = hostOptions.timeout < 1000 ? 1000 : hostOptions.timeout;
hostOptions.timeout = hostOptions.timeout > 30000 ? 30000 : hostOptions.timeout; hostOptions.timeout = hostOptions.timeout > 30000 ? 30000 : hostOptions.timeout;
var timeoutS = Math.round(hostOptions.timeout / 1000); //whole numbers only 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. //only include the extra msg object if operating in advance/array mode.
if (arrayMode) { if (arrayMode) {
msg.ping = hostOptions msg.ping = hostOptions
@ -221,7 +222,7 @@ module.exports = function(RED) {
let pingables = generatePingList(node.host); let pingables = generatePingList(node.host);
for (let index = 0; index < pingables.length; index++) { for (let index = 0; index < pingables.length; index++) {
const element = pingables[index]; const element = pingables[index];
if (element) { doPing(node, element, false); } if (element) { doPing(node, element, {}, false); }
} }
}, node.timer); }, node.timer);
} }
@ -234,12 +235,12 @@ module.exports = function(RED) {
let pingables = generatePingList(payload) let pingables = generatePingList(payload)
for (let index = 0; index < pingables.length; index++) { for (let index = 0; index < pingables.length; index++) {
const element = pingables[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) ) { } else if (Array.isArray(payload) ) {
for (let index = 0; index < payload.length; index++) { for (let index = 0; index < payload.length; index++) {
const element = payload[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", "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.", "description" : "A Node-RED node to ping a remote server, for use as a keep-alive check.",
"dependencies" : { "dependencies" : {
}, },

View File

@ -117,22 +117,22 @@
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<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>
<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="8">8</option>
<option value="7">7</option> <option value="7">7</option>
<option value="6">6</option> <option value="6">6</option>
<option value="5">5</option> <option value="5">5</option>
</select></td> </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="none" data-i18n="serial.parity.none"></option>
<option value="even" data-i18n="serial.parity.even"></option> <option value="even" data-i18n="serial.parity.even"></option>
<option value="mark" data-i18n="serial.parity.mark"></option> <option value="mark" data-i18n="serial.parity.mark"></option>
<option value="odd" data-i18n="serial.parity.odd"></option> <option value="odd" data-i18n="serial.parity.odd"></option>
<option value="space" data-i18n="serial.parity.space"></option> <option value="space" data-i18n="serial.parity.space"></option>
</select></td> </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="2">2</option>
<option value="1">1</option> <option value="1">1</option>
</select></td> </select></td>
@ -149,22 +149,22 @@
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <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="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option> <option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option> <option value="low" data-i18n="serial.linestates.low"></option>
</select></td> </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="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option> <option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option> <option value="low" data-i18n="serial.linestates.low"></option>
</select></td> </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="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option> <option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option> <option value="low" data-i18n="serial.linestates.low"></option>
</select></td> </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="none" data-i18n="serial.linestates.none"></option>
<option value="high" data-i18n="serial.linestates.high"></option> <option value="high" data-i18n="serial.linestates.high"></option>
<option value="low" data-i18n="serial.linestates.low"></option> <option value="low" data-i18n="serial.linestates.low"></option>
@ -177,23 +177,23 @@
</div> </div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;"> <div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.start"></span> <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> <span data-i18n="serial.label.startor"></span>
</div> </div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;"> <div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.split"></span> <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="char" data-i18n="serial.split.character"></option>
<option value="time" data-i18n="serial.split.timeout"></option> <option value="time" data-i18n="serial.split.timeout"></option>
<option value="interbyte" data-i18n="serial.split.silent"></option> <option value="interbyte" data-i18n="serial.split.silent"></option>
<option value="count" data-i18n="serial.split.lengths"></option> <option value="count" data-i18n="serial.split.lengths"></option>
</select> </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> <span id="node-units"></span>
</div> </div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;"> <div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.deliver"></span> <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="false" data-i18n="serial.output.ascii"></option>
<option value="bin" data-i18n="serial.output.binary"></option> <option value="bin" data-i18n="serial.output.binary"></option>
</select> </select>
@ -204,7 +204,7 @@
</div> </div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;"> <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> <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> </div>
<div id="node-config-req"> <div id="node-config-req">

View File

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

View File

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

View File

@ -1,8 +1,16 @@
node-red-node-snmp node-red-node-snmp
================== ==================
A pair of <a href="http://nodered.org" target="_new">Node-RED</a> nodes that A set 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. 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 Install
------- -------
@ -18,9 +26,15 @@ Usage
SNMP oids fetcher. Can fetch a single or comma separated list of oids. Triggered by any input. 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) `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 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 The oids configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input. 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. 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.: `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 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. 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. 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 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 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 The oid configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input. 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. 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. `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 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 The oid configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input. 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. 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. `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 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 The oid configured in the edit config will override `msg.oid`. Leave blank if you
want to use `msg.oid` to provide input. want to use `msg.oid` to provide input.

View File

@ -1,9 +1,9 @@
{ {
"name" : "node-red-node-snmp", "name" : "node-red-node-snmp",
"version" : "0.0.25", "version" : "2.0.0",
"description" : "A Node-RED node that looks for SNMP oids.", "description" : "A Node-RED node that gets and sets SNMP oid values. Supports v1, v2c and v3",
"dependencies" : { "dependencies" : {
"net-snmp" : "1.2.4" "net-snmp" : "^3.9.0"
}, },
"repository" : { "repository" : {
"type":"git", "type":"git",
@ -11,7 +11,7 @@
"directory" : "tree/master/io/snmp" "directory" : "tree/master/io/snmp"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"keywords": [ "node-red", "snmp", "oid" ], "keywords": [ "node-red", "snmp", "oid", "snmpv3" ],
"node-red" : { "node-red" : {
"nodes" : { "nodes" : {
"snmp": "snmp.js" "snmp": "snmp.js"
@ -24,6 +24,9 @@
}, },
"contributors": [ "contributors": [
{ "name": "Mika Karaila" }, { "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"> <script type="text/html" data-template-name="snmp">
<div class="form-row"> <div class="form-row">
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label> <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)"> <input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div> </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"> <div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label> <label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;"> <select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option> <option value="1">v1</option>
<option value="2c">v2c</option> <option value="2c">v2c</option>
<option value="3">v3</option>
</select> </select>
<span style="margin-left:50px;">Timeout</span> <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>
<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"> <div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OIDs</label> <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> <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>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.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</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><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>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> <p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
@ -42,11 +136,19 @@
color: "YellowGreen", color: "YellowGreen",
defaults: { defaults: {
host: { value: "127.0.0.1" }, host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true }, version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 }, 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, inputs: 1,
outputs: 1, outputs: 1,
@ -56,6 +158,9 @@
}, },
labelStyle: function () { labelStyle: function () {
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
} }
}); });
</script> </script>
@ -65,22 +170,61 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label> <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)"> <input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div> </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"> <div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label> <label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;"> <select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option> <option value="1">v1</option>
<option value="2c">v2c</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> </select>
<span style="margin-left:50px;">Timeout</span> <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>
<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"> <div class="form-row">
<label for="node-input-varbinds"><i class="fa fa-tags"></i> Varbinds</label> <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> style="width:70%;"></textarea>
</div> </div>
<div class="form-row"> <div class="form-row">
@ -94,15 +238,18 @@
<p>Simple snmp Set node. Trigger by any input</p> <p>Simple snmp Set node. Trigger by any input</p>
<p><code>msg.host</code> may contain the host.</p> <p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</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. <p><code>msg.varbinds</code> may contain varbinds as an array of json objects containing multiple oids, types and values.
<pre>[ <code style="font-size: smaller;"><pre style="white-space: pre;">[
{ {
"oid": "1.3.6.1.2.1.1.5.0", "oid": "1.3.6.1.2.1.1.5.0",
"type": "OctetString", "type": "OctetString",
"value": "host1" "value": "host1"
}, },
{ "oid": ... } { "oid": ... }
]</pre> ]</pre></code>
<p>Any numeric inputs must be numbers, not strings, e.g. 1 not "1".</p> <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>OIDs must be numeric. iso. is the same a 1.</p>
</p> </p>
@ -114,12 +261,27 @@
color: "YellowGreen", color: "YellowGreen",
defaults: { defaults: {
host: { value: "127.0.0.1" }, host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true }, version: { value: "1", required: true },
varbinds: { value: "" },
timeout: { value: 5 }, 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: "" } name: { value: "" }
}, },
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1, inputs: 1,
outputs: 0, outputs: 0,
icon: "snmp.png", icon: "snmp.png",
@ -128,6 +290,9 @@
}, },
labelStyle: function () { labelStyle: function () {
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
} }
}); });
</script> </script>
@ -137,19 +302,58 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label> <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)"> <input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div> </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"> <div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label> <label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;"> <select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option> <option value="1">v1</option>
<option value="2c">v2c</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> </select>
<span style="margin-left:50px;">Timeout</span> <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>
<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"> <div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label> <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"> <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>Simple SNMP oid table fetcher. Triggered by any input.</p>
<p><code>msg.host</code> may contain the host.</p> <p><code>msg.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</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><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>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>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
@ -176,12 +383,20 @@
color: "YellowGreen", color: "YellowGreen",
defaults: { defaults: {
host: { value: "127.0.0.1" }, host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true }, version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 }, 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: "" } name: { value: "" }
}, },
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1, inputs: 1,
outputs: 1, outputs: 1,
icon: "snmp.png", icon: "snmp.png",
@ -190,6 +405,9 @@
}, },
labelStyle: function () { labelStyle: function () {
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
} }
}); });
</script> </script>
@ -199,19 +417,58 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label> <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)"> <input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div> </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"> <div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label> <label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;"> <select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option> <option value="1">v1</option>
<option value="2c">v2c</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> </select>
<span style="margin-left:50px;">Timeout</span> <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>
<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"> <div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label> <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"> <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>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.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</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><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>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>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
@ -238,12 +498,20 @@
color: "YellowGreen", color: "YellowGreen",
defaults: { defaults: {
host: { value: "127.0.0.1" }, host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true }, version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 }, 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: "" } name: { value: "" }
}, },
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1, inputs: 1,
outputs: 1, outputs: 1,
icon: "snmp.png", icon: "snmp.png",
@ -252,6 +520,9 @@
}, },
labelStyle: function () { labelStyle: function () {
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
} }
}); });
</script> </script>
@ -262,19 +533,58 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label> <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)"> <input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div> </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"> <div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label> <label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;"> <select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option> <option value="1">v1</option>
<option value="2c">v2c</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> </select>
<span style="margin-left:50px;">Timeout</span> <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>
<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"> <div class="form-row">
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label> <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"> <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>
<script type="text/html" data-help-name="snmp walker"> <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> 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.host</code> may contain the host.</p>
<p><code>msg.community</code> may contain the community.</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><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>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>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> the typical snmpwalk command line app.</p>
</script> </script>
@ -304,12 +617,20 @@
color: "YellowGreen", color: "YellowGreen",
defaults: { defaults: {
host: { value: "127.0.0.1" }, host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true }, version: { value: "1", required: true },
oids: { value: "" },
timeout: { value: 5 }, 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: "" } name: { value: "" }
}, },
credentials: {
username: { type: "text" },
authkey: { type: "password" },
privkey: { type: "password" }
},
inputs: 1, inputs: 1,
outputs: 1, outputs: 1,
icon: "snmp.png", icon: "snmp.png",
@ -318,6 +639,9 @@
}, },
labelStyle: function () { labelStyle: function () {
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
node_snmp_common.oneditprepare(this);
} }
}); });
</script> </script>

View File

@ -1,262 +1,480 @@
module.exports = function (RED) { module.exports = function (RED) {
"use strict"; "use strict";
var snmp = require("net-snmp"); const SNMP = require("net-snmp");
const sessions = {};
var sessions = {}; function generateUUID() {
let d = Date.now();
function getSession(host, community, version, timeout) { let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || (Date.now() * Math.random() * 100000);//Time in microseconds since load
var sessionKey = host + ":" + community + ":" + version; return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var port = 161; let r = Math.random() * 16;//random number between 0 and 16
if (host.indexOf(":") !== -1) { if (d > 0) {//Use timestamp until depleted
port = host.split(":")[1]; r = (d + r) % 16 | 0;
host = host.split(":")[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)) { // SNMPv1 or SNMPv2c call
sessions[sessionKey] = snmp.createSession(host, community, { port:port, version:version, timeout:(timeout || 5000) }); else {
sessions[sessionid] = SNMP.createSession(host, user.community, options);
} }
return sessions[sessionKey]; return sessions[sessionid];
} }
function SnmpNode(n) { // Any session needs to be closed after completion
RED.nodes.createNode(this, n); function closeSession(sessionid) {
this.community = n.community; try {
this.host = n.host; sessions[sessionid].removeAllListeners();
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1; } catch (e) { }
this.oids = n.oids.replace(/\s/g, ""); try {
this.timeout = Number(n.timeout || 5) * 1000; sessions[sessionid].close();
var node = this; } catch (e) { }
delete sessions[sessionid];
}
this.on("input", function (msg) { function initSnmpNode(node, config) {
var host = node.host || msg.host; node.community = config.community;
var community = node.community || msg.community; node.host = config.host;
var oids = node.oids || msg.oid; 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) { 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) { if (error) {
node.error(error.toString(), msg); node.error(error.toString(), msg);
} } else {
else { for (let i = 0; i < varbinds.length; i++) {
for (var i = 0; i < varbinds.length; i++) { let vb = varbinds[i];
if (snmp.isVarbindError(varbinds[i])) { if (SNMP.isVarbindError(vb)) {
node.error(snmp.varbindError(varbinds[i]), msg); 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 (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 {
// if (vb.type == 4) { vb.value = vb.value.toString(); }
// }
vb.tstr = SNMP.ObjectType[vb.type];
} }
msg.oid = oids;
msg.payload = varbinds; msg.payload = varbinds;
msg.oid = oids;
node.send(msg); 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"); 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) { function SnmpSNode(n) {
RED.nodes.createNode(this, n); const node = this;
this.community = n.community; RED.nodes.createNode(node, n);
this.host = n.host; initSnmpNode(node, n);
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1; node.varbinds = n.varbinds;
this.varbinds = n.varbinds; if (node.varbinds && node.varbinds.trim().length === 0) { delete node.varbinds; }
this.timeout = Number(n.timeout || 5) * 1000; node.on("input", function (msg) {
if (this.varbinds && this.varbinds.trim().length === 0) { delete this.varbinds; } const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
var node = this; const varbinds = (node.varbinds) ? JSON.parse(node.varbinds) : msg.varbinds;
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;
if (varbinds) { if (varbinds) {
for (var i = 0; i < varbinds.length; i++) { for (let i = 0; i < varbinds.length; i++) {
varbinds[i].type = snmp.ObjectType[varbinds[i].type]; 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) { if (error) {
node.error(error.toString(), msg); node.error(error.toString(), msg);
} } else {
else { for (let i = 0; i < varbinds.length; i++) {
for (var i = 0; i < varbinds.length; i++) {
// for version 2c we must check each OID for an error condition // for version 2c we must check each OID for an error condition
if (snmp.isVarbindError(varbinds[i])) { if (SNMP.isVarbindError(varbinds[i])) {
node.error(snmp.varbindError(varbinds[i]), msg); node.error(SNMP.varbindError(varbinds[i]), msg);
} }
} }
} }
closeSession(sessionid);
}); });
} } else {
else {
node.warn("No varbinds to set"); 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) { function SnmpTNode(n) {
RED.nodes.createNode(this, n); const node = this;
this.community = n.community; RED.nodes.createNode(node, n);
this.host = n.host; initSnmpNode(node, n);
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1; node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
this.oids = n.oids.replace(/\s/g, ""); const maxRepetitions = 20;
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
var maxRepetitions = 20;
function sortInt(a, b) { function sortInt(a, b) {
if (a > b) { return 1; } if (a > b) { return 1; }
else if (b > a) { return -1; } else if (b > a) { return -1; } else { return 0; }
else { return 0; }
} }
this.on("input", function (msg) { node.on("input", function (msg) {
var host = node.host || msg.host; const oids = node.oids || msg.oid;
var community = node.community || msg.community; const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
var oids = node.oids || msg.oid;
if (oids) { if (oids) {
msg.oid = 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) { if (error) {
node.error(error.toString(), msg); node.error(error.toString(), msg);
} } else {
else { const indexes = [];
var indexes = []; for (let index in table) {
for (var index in table) {
if (table.hasOwnProperty(index)) { if (table.hasOwnProperty(index)) {
indexes.push(parseInt(index)); indexes.push(parseInt(index));
} }
} }
indexes.sort(sortInt); indexes.sort(sortInt);
for (var i = 0; i < indexes.length; i++) { for (let i = 0; i < indexes.length; i++) {
var columns = []; const columns = [];
for (var column in table[indexes[i]]) { for (let column in table[indexes[i]]) {
if (table[indexes[i]].hasOwnProperty(column)) { if (table[indexes[i]].hasOwnProperty(column)) {
columns.push(parseInt(column)); columns.push(parseInt(column));
} }
} }
columns.sort(sortInt); 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; msg.payload = table;
node.send(msg); node.send(msg);
} }
closeSession(sessionid);
}); });
} } else {
else {
node.warn("No oid to search for"); 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) { function SnmpSubtreeNode(n) {
RED.nodes.createNode(this, n); const node = this;
this.community = n.community; RED.nodes.createNode(node, n);
this.host = n.host; initSnmpNode(node, n);
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1; node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
this.oids = n.oids.replace(/\s/g, ""); const maxRepetitions = 20;
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
var maxRepetitions = 20;
var response = [];
function feedCb(varbinds) { node.on("input", function (msg) {
for (var i = 0; i < varbinds.length; i++) { const oids = node.oids || msg.oid;
if (snmp.isVarbindError(varbinds[i])) { const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
node.error(snmp.varbindError(varbinds[i]), msg); const response = [];
} function feedCb(varbinds) {
else { for (let i = 0; i < varbinds.length; i++) {
//console.log(varbinds[i].oid + "|" + varbinds[i].value); if (SNMP.isVarbindError(varbinds[i])) {
response.push({ oid: varbinds[i].oid, value: varbinds[i].value }); 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) { if (oids) {
msg.oid = 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) { if (error) {
node.error(error.toString(), msg); node.error(error.toString(), msg);
} } else {
else { msg.payload = response;
// Clone the array
msg.payload = response.slice(0);
node.send(msg); node.send(msg);
//Clears response
response.length = 0;
} }
closeSession(sessionid);
}); });
} } else {
else {
node.warn("No oid to search for"); 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) { function SnmpWalkerNode(n) {
RED.nodes.createNode(this, n); const node = this;
this.community = n.community; RED.nodes.createNode(node, n);
this.host = n.host; initSnmpNode(node, n);
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1; node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
this.oids = n.oids.replace(/\s/g, ""); const maxRepetitions = 20;
this.timeout = Number(n.timeout || 5) * 1000;
var node = this;
var maxRepetitions = 20;
var response = [];
function feedCb(varbinds) { node.on("input", function (msg) {
for (var i = 0; i < varbinds.length; i++) { const oids = node.oids || msg.oid;
if (snmp.isVarbindError(varbinds[i])) { const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
node.error(snmp.varbindError(varbinds[i]), msg); const response = [];
} function feedCb(varbinds) {
else { for (let i = 0; i < varbinds.length; i++) {
//console.log(varbinds[i].oid + "|" + varbinds[i].value); if (SNMP.isVarbindError(varbinds[i])) {
response.push({ oid: varbinds[i].oid, value: varbinds[i].value }); 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) { if (oids) {
msg.oid = 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) { if (error) {
node.error(error.toString(), msg); node.error(error.toString(), msg);
} } else {
else { msg.payload = response;
// Clone the array
msg.payload = response.slice(0);
node.send(msg); node.send(msg);
//Clears response
response.length = 0;
} }
closeSession(sessionid);
}); });
} } else {
else {
node.warn("No oid to search for"); 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.warn("reconnecting");
}); });
node.client.on("reconnect", function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
node.client.on("error", function(error) { node.client.on("error", function(error) {
node.status({fill:"grey",shape:"dot",text:"error"}); node.status({fill:"grey",shape:"dot",text:"error"});
node.warn(error); node.warn(error);
@ -124,6 +128,10 @@ module.exports = function(RED) {
node.warn("reconnecting"); node.warn("reconnecting");
}); });
node.client.on("reconnect", function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
node.client.on("error", function(error) { node.client.on("error", function(error) {
node.status({fill:"grey",shape:"dot",text:"error"}); node.status({fill:"grey",shape:"dot",text:"error"});
node.warn(error); node.warn(error);

View File

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

View File

@ -33,29 +33,29 @@
"devDependencies": { "devDependencies": {
"exif": "^0.6.0", "exif": "^0.6.0",
"feedparser": "^2.2.10", "feedparser": "^2.2.10",
"grunt": "^1.4.1", "grunt": "^1.6.1",
"grunt-cli": "^1.4.3", "grunt-cli": "^1.4.3",
"grunt-contrib-jshint": "^2.1.0", "grunt-contrib-jshint": "^2.1.0",
"grunt-jscs": "^3.0.1", "grunt-jscs": "^3.0.1",
"grunt-lint-inline": "^1.0.0", "grunt-lint-inline": "^1.0.0",
"grunt-simple-mocha": "^0.4.1", "grunt-simple-mocha": "^0.4.1",
"imap": "^0.8.19", "imap": "^0.8.19",
"mailparser": "^3.4.0", "mailparser": "^3.6.4",
"markdown-it": "^12.3.0", "markdown-it": "^12.3.2",
"mocha": "~6.2.3", "mocha": "~6.2.3",
"msgpack-lite": "^0.1.26", "msgpack-lite": "^0.1.26",
"multilang-sentiment": "^1.2.0", "multilang-sentiment": "^1.2.0",
"ngeohash": "^0.6.3", "ngeohash": "^0.6.3",
"node-red": "^2.1.4", "node-red": "^3.0.2",
"node-red-node-test-helper": "^0.2.7", "node-red-node-test-helper": "^0.3.0",
"nodemailer": "^6.7.2", "nodemailer": "^6.9.1",
"poplib": "^0.1.7", "node-pop3": "^0.8.0",
"proxyquire": "^2.1.3", "proxyquire": "^2.1.3",
"pushbullet": "^2.4.0", "pushbullet": "^2.4.0",
"sentiment": "^2.1.0", "sentiment": "^2.1.0",
"should": "^13.2.3", "should": "^13.2.3",
"sinon": "~7.5.0", "sinon": "~7.5.0",
"smtp-server": "^3.10.0", "smtp-server": "^3.11.0",
"supertest": "^4.0.2", "supertest": "^4.0.2",
"when": "^3.7.8" "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", "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.", "description": "A Node-RED node to convert a markdown string to html.",
"dependencies": { "dependencies": {
"markdown-it": "^12.3.2" "markdown-it": "^13.0.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -16,6 +16,7 @@
"markdown" "markdown"
], ],
"node-red": { "node-red": {
"version": ">=1.0.0",
"nodes": { "nodes": {
"markdown": "70-markdown.js" "markdown": "70-markdown.js"
} }
@ -24,5 +25,8 @@
"name": "Dave Conway-Jones", "name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com", "email": "ceejay@vnet.ibm.com",
"url": "http://nodered.org" "url": "http://nodered.org"
},
"engines": {
"node": ">=14"
} }
} }

View File

@ -40,15 +40,32 @@
<span data-i18n="email.label.useSecureConnection"></span> <span data-i18n="email.label.useSecureConnection"></span>
</div> </div>
<div class="form-row"> <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> <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"> <input type="text" id="node-input-userid">
</div> </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> <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"> <input type="password" id="node-input-password">
</div> </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/> <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> <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;"> <input type="checkbox" id="node-input-tls" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="email.label.rejectUnauthorised"></span> <span data-i18n="email.label.rejectUnauthorised"></span>
@ -68,6 +85,9 @@
defaults: { defaults: {
server: {value:"smtp.gmail.com",required:true}, server: {value:"smtp.gmail.com",required:true},
port: {value:"465",required:true}, port: {value:"465",required:true},
authtype: {value: "BASIC"},
saslformat: {value: true},
token: {value: "oauth2Response.access_token"},
secure: {value: true}, secure: {value: true},
tls: {value: true}, tls: {value: true},
name: {value:""}, name: {value:""},
@ -92,11 +112,43 @@
return (this.dname)?"node_label_italic":""; return (this.dname)?"node_label_italic":"";
}, },
oneditprepare: function() { 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) { if (this.credentials.global) {
$('#node-tip').show(); $('#node-tip').show();
} else { } else {
$('#node-tip').hide(); $('#node-tip').hide();
} }
$("#node-input-token").typedInput({
type:'msg',
types:['msg']
});
} }
}); });
})(); })();
@ -143,13 +195,29 @@
<input type="text" id="node-input-port" placeholder="993"> <input type="text" id="node-input-port" placeholder="993">
</div> </div>
<div class="form-row"> <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> <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"> <input type="text" id="node-input-userid">
</div> </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> <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"> <input type="password" id="node-input-password">
</div> </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"> <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> <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"> <input type="text" id="node-input-box">
@ -222,6 +290,29 @@
} }
checkPorts(); 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>
</script> </script>
@ -237,6 +328,9 @@
useSSL: {value: true}, useSSL: {value: true},
autotls: {value: "never"}, autotls: {value: "never"},
port: {value:"993",required:true}, 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 box: {value:"INBOX"}, // For IMAP, The mailbox to process
disposition: { value: "Read" }, // For IMAP, the disposition of the read email disposition: { value: "Read" }, // For IMAP, the disposition of the read email
criteria: {value: "UNSEEN"}, criteria: {value: "UNSEEN"},
@ -263,6 +357,10 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var that = this; var that = this;
if (this.authtype === undefined) {
this.authtype = "BASIC";
$("#node-input-authtype").val('BASIC');
}
if (this.credentials.global) { if (this.credentials.global) {
$('#node-tip').show(); $('#node-tip').show();
} else { } else {
@ -289,6 +387,8 @@
else { else {
$('#node-repeatTime').show(); $('#node-repeatTime').show();
that.inputs = 0; that.inputs = 0;
$("#node-input-authtype").val("BASIC");
$("#node-input-authtype").change();
} }
}); });
$("#node-input-criteria").change(function() { $("#node-input-criteria").change(function() {
@ -297,6 +397,10 @@
$("#node-input-fetch").change(); $("#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"> <script type="text/html" data-template-name="e-mail mta">
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-random"></i> <span data-i18n="email.label.port"></span></label> <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>
<div class="form-row"> <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> <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>
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('e-mail mta',{ RED.nodes.registerType('e-mail mta', {
category: 'social', category: 'social',
color:"#c7e9c0", color: "#c7e9c0",
defaults: { defaults: {
name: {value:""}, name: { value: "" },
port: {value:"1025",required:true}, 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, inputs: 0,
outputs:1, outputs: 1,
icon: "envelope.png", icon: "envelope.png",
paletteLabel: function() { return this._("email.email") + " MTA" }, paletteLabel: function () { return this._("email.email") + " MTA" },
label: function() { label: function () {
return this.name||this._("email.email") + " MTA"; return this.name || this._("email.email") + " MTA";
}, },
labelStyle: function() { labelStyle: function () {
return this.name?"node_label_italic":""; 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 */ /* eslint-disable indent */
const { domainToUnicode } = require("url");
/** /**
* POP3 protocol - RFC1939 - https://www.ietf.org/rfc/rfc1939.txt * POP3 protocol - RFC1939 - https://www.ietf.org/rfc/rfc1939.txt
* *
* Dependencies: * Dependencies:
* * poplib - https://www.npmjs.com/package/poplib * * node-pop3 - https://www.npmjs.com/package/node-pop3
* * nodemailer - https://www.npmjs.com/package/nodemailer * * nodemailer - https://www.npmjs.com/package/nodemailer
* * imap - https://www.npmjs.com/package/imap * * imap - https://www.npmjs.com/package/imap
* * mailparser - https://www.npmjs.com/package/mailparser * * mailparser - https://www.npmjs.com/package/mailparser
@ -14,22 +16,17 @@ module.exports = function(RED) {
"use strict"; "use strict";
var util = require("util"); var util = require("util");
var Imap = require('imap'); var Imap = require('imap');
var POP3Client = require("./poplib.js"); var Pop3Command = require("node-pop3");
var nodemailer = require("nodemailer"); var nodemailer = require("nodemailer");
var simpleParser = require("mailparser").simpleParser; var simpleParser = require("mailparser").simpleParser;
var SMTPServer = require("smtp-server").SMTPServer; var SMTPServer = require("smtp-server").SMTPServer;
//var microMTA = require("micromta").microMTA; //var microMTA = require("micromta").microMTA;
var fs = require('fs');
if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) { if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) {
throw "Error : Requires nodejs version >= 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) { function EmailNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.topic = n.topic; this.topic = n.topic;
@ -38,26 +35,33 @@ module.exports = function(RED) {
this.outport = n.port; this.outport = n.port;
this.secure = n.secure; this.secure = n.secure;
this.tls = true; 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")) { if (this.credentials && this.credentials.hasOwnProperty("userid")) {
this.userid = this.credentials.userid; this.userid = this.credentials.userid;
} else { }
if (globalkeys) { else if (this.authtype !== "NONE") {
this.userid = globalkeys.user; this.error(RED._("email.errors.nouserid"));
flag = true; }
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")) { else if (this.authtype === "XOAUTH2") {
this.password = this.credentials.password; this.saslformat = n.saslformat;
} else { if (n.token !== "") {
if (globalkeys) { this.token = n.token;
this.password = globalkeys.pass; } else {
flag = true; 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; } if (n.tls === false) { this.tls = false; }
var node = this; var node = this;
@ -68,12 +72,28 @@ module.exports = function(RED) {
tls: {rejectUnauthorized: node.tls} tls: {rejectUnauthorized: node.tls}
} }
if (this.userid && this.password) { if (node.authtype === "BASIC" ) {
smtpOptions.auth = { smtpOptions.auth = {
user: node.userid, user: node.userid,
pass: node.password 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); var smtpTransport = nodemailer.createTransport(smtpOptions);
this.on("input", function(msg, send, done) { this.on("input", function(msg, send, done) {
@ -95,7 +115,8 @@ module.exports = function(RED) {
sendopts.headers = msg.headers; sendopts.headers = msg.headers;
sendopts.priority = msg.priority; 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")) { if (msg.hasOwnProperty("header") && msg.header.hasOwnProperty("message-id")) {
sendopts.inReplyTo = msg.header["message-id"]; sendopts.inReplyTo = msg.header["message-id"];
sendopts.subject = "Re: " + sendopts.subject; sendopts.subject = "Re: " + sendopts.subject;
@ -116,7 +137,8 @@ module.exports = function(RED) {
sendopts.attachments[0].contentType = msg.headers["content-type"]; sendopts.attachments[0].contentType = msg.headers["content-type"];
} }
// Create some body text.. // 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 { else {
var payload = RED.util.ensureString(msg.payload); var payload = RED.util.ensureString(msg.payload);
@ -173,6 +195,7 @@ module.exports = function(RED) {
// Setup the EmailInNode // Setup the EmailInNode
function EmailInNode(n) { function EmailInNode(n) {
var imap; var imap;
var pop3;
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.name = n.name; this.name = n.name;
@ -187,43 +210,45 @@ module.exports = function(RED) {
this.repeat = 1500; this.repeat = 1500;
} }
if (this.inputs === 1) { this.repeat = 0; } if (this.inputs === 1) { this.repeat = 0; }
this.inserver = n.server || (globalkeys && globalkeys.server) || "imap.gmail.com"; this.inserver = n.server || "imap.gmail.com";
this.inport = n.port || (globalkeys && globalkeys.port) || "993"; this.inport = n.port || "993";
this.box = n.box || "INBOX"; this.box = n.box || "INBOX";
this.useSSL= n.useSSL; this.useSSL= n.useSSL;
this.autotls= n.autotls; this.autotls= n.autotls;
this.protocol = n.protocol || "IMAP"; this.protocol = n.protocol || "IMAP";
this.disposition = n.disposition || "None"; // "None", "Delete", "Read" this.disposition = n.disposition || "None"; // "None", "Delete", "Read"
this.criteria = n.criteria || "UNSEEN"; // "ALL", "ANSWERED", "FLAGGED", "SEEN", "UNANSWERED", "UNFLAGGED", "UNSEEN" this.criteria = n.criteria || "UNSEEN"; // "ALL", "ANSWERED", "FLAGGED", "SEEN", "UNANSWERED", "UNFLAGGED", "UNSEEN"
this.authtype = n.authtype || "BASIC";
var flag = false; if (this.authtype !== "BASIC") {
this.inputs = 1;
this.repeat = 0;
}
if (this.credentials && this.credentials.hasOwnProperty("userid")) { if (this.credentials && this.credentials.hasOwnProperty("userid")) {
this.userid = this.credentials.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")) { else if (this.authtype !== "NONE") {
this.password = this.credentials.password; this.error(RED._("email.errors.nouserid"));
} else { }
if (globalkeys) { if (this.authtype === "BASIC" ) {
this.password = globalkeys.pass; if (this.credentials && this.credentials.hasOwnProperty("password")) {
flag = true; this.password = this.credentials.password;
} else { }
else {
this.error(RED._("email.errors.nopassword")); this.error(RED._("email.errors.nopassword"));
} }
} }
if (flag) { else if (this.authtype === "XOAUTH2") {
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true}); this.saslformat = n.saslformat;
if (n.token !== "") {
this.token = n.token;
} else {
this.error(RED._("email.errors.notoken"));
}
} }
var node = this; 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 // 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 // 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, // 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 // retrieve each message, call processNewMessage to process it and then delete
// the messages from the server. // the messages from the server.
function checkPOP3(msg) { async function checkPOP3(msg,send,done) {
var currentMessage; var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
var maxMessage; var saslxoauth2 = "";
//node.log("Checking POP3 for new messages"); var currentMessage = 1;
var maxMessage = 0;
var nextMessage;
// Form a new connection to our email server using POP3. pop3 = new Pop3Command({
var pop3Client = new POP3Client( "host": node.inserver,
node.inport, node.inserver, "tls": node.useSSL,
{enabletls: node.useSSL} // Should we use SSL to connect to our email server? "timeout": tout,
); "port": node.inport
// 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));
}
}); });
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(); setInputRepeatTimeout();
node.log("error: " + JSON.stringify(err)); done();
}); return;
}
pop3Client.on("connect", function() { maxMessage = (await pop3.STAT()).split(" ")[0];
//node.log("We are now connected"); if (maxMessage>0) {
pop3Client.login(node.userid, node.password); node.status({fill:"blue", shape:"dot", text:"email.status.fetching"});
}); while(currentMessage<=maxMessage) {
try {
pop3Client.on("login", function(status, rawData) { nextMessage = await pop3.RETR(currentMessage);
//node.log("login: " + status + ", " + rawData); } catch(err) {
if (status) { node.error(RED._("email.errors.fetchfail", err.message),err);
pop3Client.stat(); node.status({fill:"red",shape:"ring",text:"email.status.fetcherror"});
} else { setInputRepeatTimeout();
node.log(util.format("login error: %s %j", status, rawData)); done();
pop3Client.quit(); return;
setInputRepeatTimeout(); }
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 } // End of checkPOP3
@ -358,7 +370,50 @@ module.exports = function(RED) {
// Check the email sever using the IMAP protocol for new messages. // Check the email sever using the IMAP protocol for new messages.
var s = false; var s = false;
var ss = 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"); //console.log("Checking IMAP for new messages");
// We get back a 'ready' event once we have connected to imap // We get back a 'ready' event once we have connected to imap
s = true; s = true;
@ -391,6 +446,7 @@ module.exports = function(RED) {
imap.end(); imap.end();
s = false; s = false;
setInputRepeatTimeout(); setInputRepeatTimeout();
done(err);
return; return;
} }
else { else {
@ -406,6 +462,7 @@ module.exports = function(RED) {
imap.end(); imap.end();
s = false; s = false;
setInputRepeatTimeout(); setInputRepeatTimeout();
done(err);
return; return;
} }
else { else {
@ -416,6 +473,8 @@ module.exports = function(RED) {
imap.end(); imap.end();
s = false; s = false;
setInputRepeatTimeout(); setInputRepeatTimeout();
msg.payload = 0;
done();
return; return;
} }
@ -453,11 +512,13 @@ module.exports = function(RED) {
imap.end(); imap.end();
s = false; s = false;
setInputRepeatTimeout(); setInputRepeatTimeout();
msg.payload = results.length;
done();
}; };
if (node.disposition === "Delete") { if (node.disposition === "Delete") {
imap.addFlags(results, "\Deleted", cleanup); imap.addFlags(results, '\\Deleted', imap.expunge(cleanup) );
} else if (node.disposition === "Read") { } else if (node.disposition === "Read") {
imap.addFlags(results, "\Seen", cleanup); imap.addFlags(results, '\\Seen', cleanup);
} else { } else {
cleanup(); cleanup();
} }
@ -468,6 +529,7 @@ module.exports = function(RED) {
imap.end(); imap.end();
s = false; s = false;
setInputRepeatTimeout(); setInputRepeatTimeout();
done(err);
}); });
} }
}); // End of imap->search }); // End of imap->search
@ -477,6 +539,7 @@ module.exports = function(RED) {
node.error(e.toString(),e); node.error(e.toString(),e);
s = ss = false; s = ss = false;
imap.end(); imap.end();
done(e);
return; return;
} }
} }
@ -485,6 +548,7 @@ module.exports = function(RED) {
node.error(RED._("email.errors.bad_criteria"),msg); node.error(RED._("email.errors.bad_criteria"),msg);
s = ss = false; s = ss = false;
imap.end(); imap.end();
done();
return; return;
} }
} }
@ -496,42 +560,20 @@ module.exports = function(RED) {
// Perform a check of the email inboxes using either POP3 or IMAP // Perform a check of the email inboxes using either POP3 or IMAP
function checkEmail(msg) { function checkEmail(msg,send,done) {
if (node.protocol === "POP3") { if (node.protocol === "POP3") {
checkPOP3(msg); checkPOP3(msg,send,done);
} else if (node.protocol === "IMAP") { } else if (node.protocol === "IMAP") {
if (s === false && ss == false) { checkIMAP(msg); } if (s === false && ss == false) { checkIMAP(msg,send,done); }
} }
} // End of checkEmail } // End of checkEmail
if (node.protocol === "IMAP") { node.on("input", function(msg, send, done) {
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000; send = send || function() { node.send.apply(node,arguments) };
imap = new Imap({ checkEmail(msg,send,done);
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);
}); });
this.on("close", function() { node.on("close", function() {
if (this.interval_id != null) { if (this.interval_id != null) {
clearTimeout(this.interval_id); clearTimeout(this.interval_id);
} }
@ -542,6 +584,10 @@ module.exports = function(RED) {
} }
}); });
function status_clear() {
node.status({});
}
function setInputRepeatTimeout() { function setInputRepeatTimeout() {
// Set the repetition timer as needed // Set the repetition timer as needed
if (!isNaN(node.repeat) && node.repeat > 0) { if (!isNaN(node.repeat) && node.repeat > 0) {
@ -565,47 +611,81 @@ module.exports = function(RED) {
function EmailMtaNode(n) { function EmailMtaNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this, n);
this.port = n.port; 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; 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({ node.options.onData = function (stream, session, callback) {
secure: false, simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
logger: false, if (err) { node.error(RED._("email.errors.parsefail"),err); }
disabledCommands: ['AUTH', 'STARTTLS'], else {
node.status({fill:"green", shape:"dot", text:""});
onData: function (stream, session, callback) { var msg = {}
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => { msg.payload = parsed.text;
if (err) { node.error(RED._("email.errors.parsefail"),err); } msg.topic = parsed.subject;
else { msg.date = parsed.date;
node.status({fill:"green", shape:"dot", text:""}); msg.header = {};
var msg = {} parsed.headers.forEach((v, k) => {msg.header[k] = v;});
msg.payload = parsed.text; if (parsed.html) { msg.html = parsed.html; }
msg.topic = parsed.subject; if (parsed.to) {
msg.date = parsed.date; if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; }
msg.header = {}; else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; }
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);
} }
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); 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 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. 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 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>, 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> 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 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 Additionally `msg.header` contains the complete header object including
**to**, **cc** and other potentially useful properties. **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 ### Output node
Sends the `msg.payload` as an email, with a subject of `msg.topic`. Sends the `msg.payload` as an email, with a subject of `msg.topic`.

View File

@ -36,7 +36,7 @@
"always": "immer", "always": "immer",
"rejectUnauthorised": "Überprüfen sie, ob das serverzertifikat gültig ist" "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": { "tip": {
"cred": "<b>Hinweis</b>: Berechtigungen von globaler emailkeys.js-Datei kopiert", "cred": "<b>Hinweis</b>: Berechtigungen von globaler emailkeys.js-Datei kopiert",
"recent": "Tipp: Es wird nur die letzte E-Mail abgerufen", "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> <p>You may optionally set <code>msg.from</code> in the payload which will override the <code>userid</code>
default value.</p> default value.</p>
<h3>Gmail users</h3> <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>, <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>
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> <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> <h3>Details</h3>
<p>The payload can be html format. You may supply a separate plaintext version using <code>msg.plaintext</code>. <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. 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>
<script type="text/html" data-help-name="e-mail in"> <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> <h3>Overview</h3>
<p>The subject is loaded into <code>msg.topic</code> and <code>msg.payload</code> is the plain text body. <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>
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 <h3>Message Properties</h3>
<i>to</i>, <i>cc</i> and other potentially useful properties.</p> <p>The following properties are set on the message object:</p>
<p>It can optionally mark the message as Read (default), Delete it, or leave unmarked (None).</p> <ul>
<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 <li><code>msg.topic</code> - the subject of the email</li>
information on the <code>msg.criteria</code> format if needed.</p> <li><code>msg.payload</code> - the plain text body of the email</li>
<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 <li><code>msg.html</code> - the HTML body of the email (if present)</li>
each object represents a specific attachments. The format of the object is:</p> <li><code>msg.from</code> - the sender of the email</li>
<pre> <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>
contentType: // The MIME content description <li><code>msg.attachments</code> - an array of objects representing any attachments included in the email</li>
fileName: // A suggested file name associated with this attachment </ul>
transferEncoding: // How was the original email attachment encodded?
contentDisposition: // Unknown <h3>Module Used</h3>
generatedFileName: // A suggested file name associated with this attachment <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>
contentId: // A unique generated ID for this attachment <p>It also makes use of <a href="https://github.com/node-pop3/node-pop3#readme" target="_new">node-pop3 module</a></p>
checksum: // A checksum against the data
length: // Size of data in bytes <h3>Attachment Format</h3>
content: // The actual content of the data contained in a Node.js Buffer object <p>Each object in the <code>msg.attachments</code> array is formatted as follows:</p>
// We can turn this into a base64 data string with content.toString('base64') <pre>
} {
</pre> contentType: // The MIME content description
<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> fileName: // A suggested file name associated with this attachment
<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> transferEncoding: // How was the original email attachment encoded?
<p><b>Note</b>: The maximum refresh interval is 2147483 seconds (24.8 days).</p> 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>
<script type="text/html" data-help-name="e-mail mta"> <script type="text/html" data-help-name="e-mail mta">
<p>Mail Transfer Agent - listens on a port for incoming SMTP mails.</p> <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 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> 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. <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 On linux systems this can be done by running
<pre>sudo setcap 'cap_net_bind_service=+eip' $(which node)</pre> <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> 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> </script>

View File

@ -31,12 +31,24 @@
"unflagged": "Unflagged", "unflagged": "Unflagged",
"unseen": "Unseen", "unseen": "Unseen",
"autotls": "Start TLS?", "autotls": "Start TLS?",
"authtype": "Auth type",
"saslformat": "Format to SASL",
"token": "Token",
"never": "never", "never": "never",
"required": "if required", "required": "if required",
"always": "always", "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": { "tip": {
"cred": "<b>Note:</b> Copied credentials from global emailkeys.js file.", "cred": "<b>Note:</b> Copied credentials from global emailkeys.js file.",
"recent": "Tip: Only retrieves the single most recent email.", "recent": "Tip: Only retrieves the single most recent email.",
@ -60,6 +72,7 @@
"errors": { "errors": {
"nouserid": "No e-mail userid set", "nouserid": "No e-mail userid set",
"nopassword": "No e-mail password set", "nopassword": "No e-mail password set",
"notoken": "No token property set",
"nocredentials": "No Email credentials found. See info panel.", "nocredentials": "No Email credentials found. See info panel.",
"nosmtptransport": "No SMTP transport. See info panel.", "nosmtptransport": "No SMTP transport. See info panel.",
"nopayload": "No payload to send", "nopayload": "No payload to send",

View File

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

View File

@ -1,15 +1,17 @@
{ {
"name": "node-red-node-email", "name": "node-red-node-email",
"version": "1.15.1", "version": "2.0.0",
"description": "Node-RED nodes to send and receive simple emails.", "description": "Node-RED nodes to send and receive simple emails.",
"dependencies": { "dependencies": {
"imap": "^0.8.19", "imap": "^0.8.19",
"mailparser": "^3.4.0", "node-pop3": "^0.8.0",
"nodemailer": "^6.7.3", "mailparser": "^3.6.4",
"smtp-server": "^3.10.0" "nodemailer": "^6.9.1",
"smtp-server": "^3.11.0"
}, },
"bundledDependencies": [ "bundledDependencies": [
"imap", "imap",
"node-pop3",
"mailparser", "mailparser",
"nodemailer", "nodemailer",
"smtp-server" "smtp-server"
@ -26,9 +28,12 @@
"gmail", "gmail",
"imap", "imap",
"pop", "pop",
"smtp",
"smtp-server",
"mta" "mta"
], ],
"node-red": { "node-red": {
"version": ">=1.0.0",
"nodes": { "nodes": {
"email": "61-email.js" "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> <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> <input type="text" id="node-input-interval" style="width:60px"> <span data-i18n="feedparse.label.minutes"></span>
</div> </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"> <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> <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"> <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
@ -20,7 +25,8 @@
defaults: { defaults: {
name: {value:""}, name: {value:""},
url: {value:"", required:true}, 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, inputs:0,
outputs:1, outputs:1,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name" : "node-red-node-pushbullet", "name" : "node-red-node-pushbullet",
"version" : "0.0.17", "version" : "0.0.19",
"description" : "A Node-RED node to send alerts via Pushbullet", "description" : "A Node-RED node to send alerts via Pushbullet",
"dependencies" : { "dependencies" : {
"pushbullet": "^2.4.0", "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", "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", "description": "A Node-RED node to send and receive messages using Pusher.com",
"dependencies": { "dependencies": {
"pusher": "^1.5.1", "pusher": "^5.1.2",
"pusher-client": "^1.1.0" "pusher-js": "^8.0.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -25,5 +25,11 @@
"name": "Dave Conway-Jones", "name": "Dave Conway-Jones",
"email": "ceejay@vnet.ibm.com", "email": "ceejay@vnet.ibm.com",
"url": "http://nodered.org" "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"> <script type="text/x-red" data-template-name="pusher in">
<div class="form-row"> <div class="form-row">
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label> <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>
<div class="form-row"> <div class="form-row">
<label for="node-input-eventname"><i class="fa fa-tasks"></i> Event</label> <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>
<div class="form-row"> <div class="form-row">
<label for="node-input-pusherappkeysub"><i class="fa fa-lock"></i> App Key</label> <label for="node-input-pusherappkeysub"><i class="fa fa-lock"></i> App Key</label>
@ -15,10 +15,15 @@
<div class="form-row"> <div class="form-row">
<label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label> <label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label>
<select type="text" id="node-input-cluster"> <select type="text" id="node-input-cluster">
<option value="mt1">us-east-1 (US - default)</option> <option value="mt1">N. Virginia (US - default)</option>
<option value="eu">eu-west-1 (Europe)</option> <option value="us2">Ohio (US)</option>
<option value="ap1">ap-southeast-1 (Singapore)</option> <option value="us3">Oregon (US)</option>
<option value="ap2">ap-south-1 (Mumbai)</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> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
@ -87,10 +92,15 @@
<div class="form-row"> <div class="form-row">
<label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label> <label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label>
<select type="text" id="node-input-cluster"> <select type="text" id="node-input-cluster">
<option value="mt1">us-east-1 (US - default)</option> <option value="mt1">N. Virginia (US - default)</option>
<option value="eu">eu-west-1 (Europe)</option> <option value="us2">Ohio (US)</option>
<option value="ap1">ap-southeast-1 (Singapore)</option> <option value="us3">Oregon (US)</option>
<option value="ap2">ap-south-1 (Mumbai)</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> </select>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

@ -65,19 +65,26 @@ module.exports = function(RED) {
}); });
} }
this.connect = function() { node.connect = function() {
if (!this.connected && !this.connecting) { if (!node.connected && !node.connecting) {
doConnect(); doConnect();
} }
} }
this.on('close', function(done) { node.on('close', function(done) {
if (this.tick) { clearTimeout(this.tick); } if (node.tick) { clearTimeout(node.tick); }
if (this.check) { clearInterval(this.check); } if (node.check) { clearInterval(node.check); }
node.connected = false;
// node.connection.release(); // node.connection.release();
node.emit("state"," "); 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, { RED.nodes.registerType("MySQLdatabase",MySQLNode, {

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red-node-mysql", "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", "description": "A Node-RED node to read and write to a MySQL database",
"dependencies": { "dependencies": {
"mysql2": "^2.3.3" "mysql2": "^2.3.3"

View File

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

View File

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

View File

@ -15,257 +15,297 @@ describe('random node', function() {
helper.stopServer(done); helper.stopServer(done);
}); });
}); });
// ============================================================ // ============================================================
it ("Test i1 (integer) - DEFAULT no overrides defaults to 1 and 10", function(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"]] }, var flow = [{id:"n1", type:"random", low: "" , high:"" , inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(1,10); 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... 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) { 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"]] }, var flow = [{id:"n1", type:"random", low:"" , high:"" , inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(1.0,9.999); msg.payload.should.be.within(1.0,9.999);
//msg.payload.toString().indexOf(".").should.not.equal(-1); //msg.payload.toString().indexOf(".").should.not.equal(-1);
done(); 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(-3,3); 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... 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(-3.0,3.0); msg.payload.should.be.within(-3.0,3.0);
done(); 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(2,5); 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... 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(2.0,5.0); msg.payload.should.be.within(2.0,5.0);
done(); 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: 5, high:"", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(5,10); msg.payload.should.be.within(5,10);
msg.payload.toString().indexOf(".").should.equal(-1); msg.payload.toString().indexOf(".").should.equal(-1);
done(); 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: 5, high:"", inte:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(5.0,10.0); msg.payload.should.be.within(5.0,10.0);
done(); 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) { 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"]] }, var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(6,9); 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... 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) { it ("Test i5a (integer) - msg From = '0' To = '2' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] }, var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(6.0,9.0); msg.payload.should.be.within(0,2);
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... 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) { it ("Test i5b (integer) - msg From = '-3' To = '0' node no entries", function(done) {
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] }, var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
//console.log(msg); //console.log(msg);
msg.should.have.a.property("payload"); msg.should.have.a.property("payload");
msg.payload.should.be.within(2.4,7.3); msg.payload.should.be.within(-3,0);
done(); 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("repeat", 300000);
n1.should.have.property("inserver", "imap.gmail.com"); n1.should.have.property("inserver", "imap.gmail.com");
n1.should.have.property("inport", "993"); 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(); done();
}); });
}); });
@ -51,6 +70,7 @@ describe('email Node', function () {
helper.load(emailNode, flow, function () { helper.load(emailNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.should.have.property('name', "emailout"); n1.should.have.property('name', "emailout");
n1.should.have.property("authtype", "BASIC");
done(); done();
}); });
}); });
@ -83,7 +103,7 @@ describe('email Node', function () {
//console.log(helper.log()); //console.log(helper.log());
//logEvents.should.have.length(3); //logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg'); 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(); done();
} catch (e) { } catch (e) {
done(e); done(e);
@ -134,7 +154,7 @@ describe('email Node', function () {
// console.log(logEvents[0][0].msg.toString()); // console.log(logEvents[0][0].msg.toString());
//logEvents.should.have.length(3); //logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg'); 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(); done();
} catch (e) { } catch (e) {
done(e); done(e);
@ -179,7 +199,7 @@ describe('email Node', function () {
//console.log(helper.log().args); //console.log(helper.log().args);
//logEvents.should.have.length(3); //logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg'); 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(); done();
} catch (e) { } catch (e) {
done(e); done(e);

View File

@ -1,44 +1,44 @@
<script type="text/html" data-template-name="sunrise"> <script type="text/html" data-template-name="sunrise">
<div class="form-row"> <div class="form-row">
<label for="node-input-lat"><i class="fa fa-globe"></i> Latitude</label> <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"> <input type="text" id="node-input-lat" placeholder="51.025">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-lon"><i class="fa fa-globe"></i> Longitude</label> <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"> <input type="text" id="node-input-lon" placeholder="-1.4">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-start"><i class="fa fa-clock-o"></i> Start</label> <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%'> <select id="node-input-start" style='width:70%'>
<option value="nightEnd">Morning astronomical twilight starts</option> <option value="nightEnd" data-i18n="sunrise.nightEnd"></option>
<option value="nauticalDawn">Morning nautical twilight starts</option> <option value="nauticalDawn" data-i18n="sunrise.nauticalDawn"></option>
<option value="dawn">Dawn, morning civil twilight starts</option> <option value="dawn" data-i18n="sunrise.dawn"></option>
<option value="sunrise">Sunrise</option> <option value="sunrise" data-i18n="sunrise.sunrise"></option>
<option value="sunriseEnd">Sunrise end</option> <option value="sunriseEnd" data-i18n="sunrise.sunriseEnd"></option>
<option value="goldenHourEnd">End of morning golden hour</option> <option value="goldenHourEnd" data-i18n="sunrise.goldenHourEnd"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-end"><i class="fa fa-clock-o"></i> End</label> <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%'> <select id="node-input-end" style='width:70%'>
<option value="goldenHour">Start of evening golden hour</option> <option value="goldenHour" data-i18n="sunrise.goldenHour"></option>
<option value="sunsetStart">Sunset start</option> <option value="sunsetStart" data-i18n="sunrise.sunsetStart"></option>
<option value="sunset">Sunset, civil twilight starts</option> <option value="sunset" data-i18n="sunrise.sunset"></option>
<option value="dusk">Dusk, Evening astronomical twilight starts</option> <option value="dusk" data-i18n="sunrise.dusk"></option>
<option value="nauticalDusk">Evening nautical twilight starts</option> <option value="nauticalDusk" data-i18n="sunrise.nauticalDusk"></option>
<option value="night">Dark enough for astronomy</option> <option value="night" data-i18n="sunrise.night"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label><i class="fa fa-arrows-h"></i> Offset</label> <label><i class="fa fa-arrows-h"></i><span data-i18n="sunrise.label.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-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">end</span> <input type="text" id="node-input-eoff" placeholder="minutes" style='width:60px;'> mins <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>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <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" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]sunrise.label.name">
</div> </div>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -56,10 +56,15 @@
}, },
inputs:0, inputs:0,
outputs:2, outputs:2,
outputLabels: ["once per minute","only on change"], outputLabels: function(i) {
return [
this._("sunrise.onePerMin"),
this._("sunrise.onse")
][i];
},
icon: "sun.png", icon: "sun.png",
label: function() { label: function() {
return this.name||"Sun rise/set"; return this.name||this._("sunrise.sunName");
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; 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}; 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 ((e1 > 0) & (e2 < 0)) { msg.payload = 1; }
if (oldval == null) { oldval = msg.payload; } if (oldval == null) { oldval = msg.payload; }
if (msg.payload == 1) { node.status({fill:"yellow",shape:"dot",text:"day"}); } if (msg.payload == 1) { node.status({fill:"yellow",shape:"dot",text:"sunrise.dayState"}); }
else { node.status({fill:"blue",shape:"dot",text:"night"}); } else { node.status({fill:"blue",shape:"dot",text:"sunrise.nightState"}); }
if (msg.payload != oldval) { if (msg.payload != oldval) {
oldval = msg.payload; oldval = msg.payload;
node.send([msg,msg]); 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", "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", "description" : "A Node-RED node to provide a signal at sunrise and sunset",
"dependencies" : { "dependencies" : {
"suncalc" : "^1.8.0" "suncalc" : "^1.8.0"

View File

@ -7,9 +7,11 @@ simple timeswitch node to schedule daily on/off events.
Install 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 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 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. 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. 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 **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. [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> 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 <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> 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> <p>You may also optionally specify a <code>msg.topic</code> if required.</p>
</script> </script>

View File

@ -1,9 +1,9 @@
{ {
"name" : "node-red-node-timeswitch", "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.", "description" : "A Node-RED node to provide a simple timeswitch to schedule daily on/off events.",
"dependencies" : { "dependencies" : {
"spacetime": "^6.12.5", "spacetime": "^7.4.0",
"suncalc": "^1.8.0" "suncalc": "^1.8.0"
}, },
"repository" : { "repository" : {
@ -25,7 +25,19 @@
}, },
"contributors": [ "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"; "use strict";
var SunCalc = require('suncalc'); var SunCalc = require('suncalc');
const spacetime = require("spacetime") const spacetime = require("spacetime")
const SUNRISE_KEY = "sunrise";
const SUNSET_KEY = "sunset";
function TimeswitchNode(n) { function TimeswitchNode(n) {
RED.nodes.createNode(this, n); RED.nodes.createNode(this, n);
this.lat = n.lat; this.lat = n.lat;
this.lon = n.lon; this.lon = n.lon;
this.start = n.start || "sunrise";
this.end = n.end || "sunset";
this.startt = n.starttime; this.startt = n.starttime;
this.endt = n.endtime; this.endt = n.endtime;
this.duskoff = n.duskoff; this.sunriseOffset = n.dawnoff;
this.dawnoff = n.dawnoff; this.sunsetOffset = n.duskoff;
this.mytopic = n.mytopic; this.mytopic = n.mytopic;
this.timezone = n.timezone || "UTC"; this.timezone = n.timezone || "UTC";
@ -24,6 +23,7 @@ module.exports = function(RED) {
this.thu = n.thu; this.thu = n.thu;
this.fri = n.fri; this.fri = n.fri;
this.sat = n.sat; this.sat = n.sat;
this.jan = n.jan; this.jan = n.jan;
this.feb = n.feb; this.feb = n.feb;
this.mar = n.mar; this.mar = n.mar;
@ -38,118 +38,195 @@ module.exports = function(RED) {
this.dec = n.dec; this.dec = n.dec;
var node = this; var node = this;
var ison = 0;
var newendtime = 0;
this.on("input", function(msg2) { this.on("input", function () {
if (msg2.payload === "reset") { ison = 0; } // 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; // all sun events for the given lat/long
var now = new Date(Date.now() + timeOffset); const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon);
var nowMillis = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), 0); let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute");
var midnightMillis = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0); let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute");
var today = Math.round((nowMillis - midnightMillis) / 60000) % 1440;
var starttime = Number(node.startt);
var endtime = Number(node.endt);
if ((starttime >= 5000) || (endtime == 5000) || (endtime == 6000)) { // add optional sun event offset, if specified
var times = SunCalc.getTimes(now, node.lat, node.lon); sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
var startMillis = Date.UTC(times[node.start].getUTCFullYear(), times[node.start].getUTCMonth(), times[node.start].getUTCDate(), times[node.start].getUTCHours(), times[node.start].getUTCMinutes()); sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes");
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); // check if sun event has already occurred today
var dusk = ((endMillis - midnightMillis) / 60000) + Number(node.duskoff); if (now.isAfter(sunriseDateTime)) {
if (starttime == 5000) { starttime = dawn; } // get tomorrow's sunrise, since it'll be different
if (starttime == 6000) { starttime = dusk; } sunriseDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNRISE_KEY]).nearest("minute");
if (endtime == 5000) { endtime = dawn; } // add optional sun event offset, if specified (again)
if (endtime == 6000) { endtime = dusk; } sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
if (RED.settings.verbose) { node.log("Dawn " + parseInt(dawn / 60) + ":" + dawn % 60 + " - Dusk " + parseInt(dusk / 60) + ":" + dusk % 60); } }
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; // log sun events
switch (now.getDay()) { if (RED.settings.verbose) {
case 0 : { if (node.sun) { proceed++; } break; } node.log(`Sunrise ${sunriseDateTime.format("time")} - Sunset ${sunsetDateTime.format("time")} `);
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; }
} }
if (proceed) { // apply selected timezone to selected times (not to sunrise/sunset-- those are based on lat/long)
switch (now.getMonth()) { const currentTimeZone = now.timezone();
case 0 : { if (node.jan) { proceed++; } break; } const selectedTimeZone = spacetime(now.epoch, this.timezone.toLowerCase()).timezone();
case 1 : { if (node.feb) { proceed++; } break; }
case 2 : { if (node.mar) { proceed++; } break; } // handler function to convert minute strings (from <option> tags) to spacetime objects, called below
case 3 : { if (node.apr) { proceed++; } break; } let getSelectedTimeFromMinuteString = minuteString => {
case 4 : { if (node.may) { proceed++; } break; } const selectedTimeInMinutesAfterMidnight = Number(minuteString);
case 5 : { if (node.jun) { proceed++; } break; } let selectedTime = spacetime.now();
case 6 : { if (node.jul) { proceed++; } break; } // if less than 1440, what are the time values for the next start and stop time?
case 7 : { if (node.aug) { proceed++; } break; } if (selectedTimeInMinutesAfterMidnight < 1440) {
case 8 : { if (node.sep) { proceed++; } break; } // determine offset to get from selected time zone to current timezone
case 9 : { if (node.oct) { proceed++; } break; } // e.g. current (EDT) is -4, selected (PDT) is -7
case 10: { if (node.nov) { proceed++; } break; } // in order to get from PDT to EDT, you must add 3
case 11: { if (node.dec) { proceed++; } break; } // (-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; } // handler function for the node payload, called below
else { proceed = 0; } let sendPayload = (payload, nextTime) => {
// var o = nextTime.goto(selectedTimeZone.name).offset()/60;
newendtime = endtime; // if (o > 0) { o = "+" + o; }
if (endtime > 10000) { newendtime = starttime + (endtime - 10000); } // else {o = "-" + o; }
if (payload == 1) {
if (proceed) { // have to handle midnight wrap node.status({
if (starttime <= newendtime) { fill: "yellow",
if ((today >= starttime) && (today <= newendtime)) { proceed++; } 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 { var msg = {};
if ((today >= starttime) || (today <= newendtime)) { proceed++; } 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) { if (!proceed) {
var duration = newendtime - today; sendPayload(0, selectedOnTime);
if (today > newendtime) { duration += 1440; } return;
//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)});
} }
//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 this month is not among the selected months, stop here
if (node.mytopic) { msg.topic = node.mytopic; } switch (nowNative.getMonth()) {
msg.payload = (proceed >= 2) ? 1 : 0; case 0 : { if (!node.jan) { proceed &= false; } break; }
node.send(msg); 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", {}); node.emit("input", {});
}, 2000); // wait 2 secs before starting to let things settle down e.g. UI connect }, 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", {}); node.emit("input", {});
}, 60000); // trigger every 60 secs }, 60000); // trigger every 60 secs
this.on("close", function() { this.on("close", function () {
if (tock) { clearTimeout(tock); } if (tock) { clearTimeout(tock); }
if (tick) { clearInterval(tick); } 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); var node = RED.nodes.getNode(req.params.id);
if (node != null) { if (node != null) {
try { try {
node.emit("input", {payload:"reset"}); node.emit("input", { payload: "reset" });
res.sendStatus(200); res.sendStatus(200);
} } catch (err) {
catch (err) {
res.sendStatus(500); res.sendStatus(500);
node.error("Inject failed:" + err); node.error("Inject failed:" + err);
} }
} } else {
else {
res.sendStatus(404); 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. 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. **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. 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.closer = n.closer || "SIGKILL";
this.autorun = true; this.autorun = true;
if (n.autorun === false) { this.autorun = false; } if (n.autorun === false) { this.autorun = false; }
if (this.args.match(/^\[.*\]$/)) { this.args = parseArgs(this.args);
try { this.args = JSON.parse(this.args); }
catch(e) {
node.warn(RED._("daemon.errors.badparams"))
}
}
else { this.args = this.args.match(/("[^"]*")|[^ ]+/g); }
var node = this; var node = this;
var lastmsg = {}; 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) { function inputlistener(msg) {
if (msg != null) { if (msg != null) {
if (msg.hasOwnProperty("kill") && node.running) { if (msg.hasOwnProperty("kill") && node.running) {
@ -32,7 +37,11 @@ module.exports = function(RED) {
node.child.kill(msg.kill.toUpperCase()); node.child.kill(msg.kill.toUpperCase());
} }
else if (msg.hasOwnProperty("start") && !node.running) { 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 { else {
if (!Buffer.isBuffer(msg.payload)) { if (!Buffer.isBuffer(msg.payload)) {
@ -41,22 +50,27 @@ module.exports = function(RED) {
if (node.cr === true) { msg.payload += "\n"; } if (node.cr === true) { msg.payload += "\n"; }
} }
node.debug("inp: "+msg.payload); node.debug("inp: "+msg.payload);
lastmsg = msg;
if (node.child !== null && node.running) { node.child.stdin.write(msg.payload); } if (node.child !== null && node.running) { node.child.stdin.write(msg.payload); }
else { node.warn(RED._("daemon.errors.notrunning")); } else { node.warn(RED._("daemon.errors.notrunning")); }
lastmsg = msg;
} }
} }
} }
function runit() { function runit(appendArgs) {
var line = ""; var line = "";
if (!node.cmd || (typeof node.cmd !== "string") || (node.cmd.length < 1)) { if (!node.cmd || (typeof node.cmd !== "string") || (node.cmd.length < 1)) {
node.status({fill:"grey",shape:"ring",text:RED._("daemon.status.nocommand")}); node.status({fill:"grey",shape:"ring",text:RED._("daemon.status.nocommand")});
return; return;
} }
let args = node.args;
if (appendArgs !== undefined && appendArgs.length > 0) {
args = args.concat(appendArgs);
}
try { try {
node.child = spawn(node.cmd, node.args); node.child = spawn(node.cmd, args);
node.debug(node.cmd+" "+JSON.stringify(node.args)); node.debug(node.cmd+" "+JSON.stringify(args));
node.status({fill:"green",shape:"dot",text:RED._("daemon.status.running")}); node.status({fill:"green",shape:"dot",text:RED._("daemon.status.running")});
node.running = true; node.running = true;
@ -106,6 +120,12 @@ module.exports = function(RED) {
else { node.log('error: ' + err); } else { node.log('error: ' + err); }
node.status({fill:"red",shape:"ring",text:RED._("daemon.status.error")}); 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) { catch(e) {
if (e.errno === "ENOENT") { node.warn(RED._("daemon.errors.notfound")); } 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>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>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 <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. <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> For example, if running a Python app, the <code>-u</code> parameter will stop the output being buffered.</p>
</script> </script>

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name" : "node-red-node-daemon", "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.", "description" : "A Node-RED node that runs and monitors a long running system command.",
"dependencies" : { "dependencies" : {
}, },