mirror of
https://github.com/node-red/node-red-nodes.git
synced 2025-03-01 10:37:43 +00:00
Merge branch 'node-red:master' into master
This commit is contained in:
commit
11f74b3528
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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
1
.gitignore
vendored
@ -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
|
||||||
|
@ -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" : {
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(); } }
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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 コマンドが実行されていません"
|
||||||
}
|
}
|
||||||
|
@ -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 커맨드가 실행되지 않았습니다"
|
||||||
}
|
}
|
||||||
|
@ -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" : {
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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> </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>
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
85
hardware/intel/mraa-gpio-led.html
Normal file
85
hardware/intel/mraa-gpio-led.html
Normal 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>
|
86
hardware/intel/mraa-gpio-led.js
Normal file
86
hardware/intel/mraa-gpio-led.js
Normal 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());
|
||||||
|
});
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
BIN
hardware/mcp3008/.DS_Store
vendored
BIN
hardware/mcp3008/.DS_Store
vendored
Binary file not shown.
@ -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",
|
||||||
|
@ -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) { });
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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": {
|
||||||
|
@ -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); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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" : {
|
||||||
},
|
},
|
||||||
|
@ -117,22 +117,22 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </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> </td>
|
<td> </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">
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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;"> S
|
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> 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;"> S
|
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> 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. [ { "oid": "1.3.6.1.2.1.1.5.0","type": "OctetString","value": "host1"},{"oid": "1.3.6.1.2.1.1.6.0","type": "OctetString",value: "somewhere"}]"
|
<textarea rows="10" cols="60" id="node-input-varbinds" placeholder="e.g. [ { "oid": "1.3.6.1.2.1.1.5.0", "type": "OctetString", "value": "host1"}, { "oid": "1.3.6.1.2.1.1.6.0", "type": "OctetString", value: "somewhere" } ]"
|
||||||
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;"> S
|
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> 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;"> S
|
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> 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;"> S
|
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> 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">
|
||||||
@ -291,6 +601,9 @@
|
|||||||
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>
|
||||||
@ -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>
|
||||||
|
538
io/snmp/snmp.js
538
io/snmp/snmp.js
@ -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" }
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
package.json
16
package.json
@ -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
44
parsers/cbor/70-cbor.html
Normal 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
38
parsers/cbor/70-cbor.js
Normal 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
14
parsers/cbor/LICENSE
Normal 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
27
parsers/cbor/README.md
Normal 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.
|
BIN
parsers/cbor/icons/parser-cbor.png
Normal file
BIN
parsers/cbor/icons/parser-cbor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 274 B |
32
parsers/cbor/package.json
Normal file
32
parsers/cbor/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
@ -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);
|
||||||
|
|
||||||
|
@ -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`.
|
||||||
|
@ -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",
|
||||||
|
@ -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>
|
||||||
|
@ -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",
|
||||||
|
@ -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件のみ取得します。",
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
@ -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,
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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": {
|
||||||
|
@ -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",
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 |
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
|
@ -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"}
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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, {
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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"
|
||||||
|
@ -204,6 +204,46 @@ describe('random node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it ("Test i5a (integer) - msg From = '0' To = '2' node no entries", function(done) {
|
||||||
|
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||||
|
{id:"n2", type:"helper"} ];
|
||||||
|
helper.load(testNode, flow, function() {
|
||||||
|
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(0,2);
|
||||||
|
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
catch(err) { done(err); }
|
||||||
|
});
|
||||||
|
n1.emit("input", {"test":"Test i5", "from": '0', "to": '2'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it ("Test i5b (integer) - msg From = '-3' To = '0' node no entries", function(done) {
|
||||||
|
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||||
|
{id:"n2", type:"helper"} ];
|
||||||
|
helper.load(testNode, flow, function() {
|
||||||
|
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(-3,0);
|
||||||
|
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
catch(err) { done(err); }
|
||||||
|
});
|
||||||
|
n1.emit("input", {"test":"Test i5", "from": '-3', "to": '0'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it ("Test f5 (float) - msg From = '6' To = '9' node no entries", function(done) {
|
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"]] },
|
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||||
{id:"n2", type:"helper"} ];
|
{id:"n2", type:"helper"} ];
|
||||||
|
101
test/parsers/cbor/70-cbor_spec.js
Normal file
101
test/parsers/cbor/70-cbor_spec.js
Normal 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});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -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);
|
||||||
|
@ -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":"";
|
||||||
|
@ -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]);
|
||||||
|
32
time/suncalc/locales/en-US/79-suncalc.json
Normal file
32
time/suncalc/locales/en-US/79-suncalc.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
11
time/suncalc/locales/ru/79-suncalc.html
Normal file
11
time/suncalc/locales/ru/79-suncalc.html
Normal 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>
|
33
time/suncalc/locales/ru/79-suncalc.json
Normal file
33
time/suncalc/locales/ru/79-suncalc.json
Normal 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": "Рассвет/закат"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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.
|
||||||
|
@ -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")); }
|
||||||
|
@ -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>
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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" : {
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user