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)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [x] New feature (non-breaking change which adds functionality)
|
||||
|
||||
<!--
|
||||
If you want to raise a pull-request with a new feature, or a refactoring
|
||||
@ -25,10 +25,13 @@ the [forum](https://discourse.nodered.org) or
|
||||
|
||||
<!-- Describe the nature of this change. What problem does it address? -->
|
||||
|
||||
Adds authentication option to the Email node (node-red-node-email) to use OAuth and XOAuth2
|
||||
********** This version: IMAP ONLY **********
|
||||
|
||||
## Checklist
|
||||
<!-- Put an `x` in the boxes that apply -->
|
||||
|
||||
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red-nodes/blob/master/CONTRIBUTING.md)
|
||||
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
|
||||
- [ ] I have run `grunt` to verify the unit tests pass
|
||||
- [ ] I have added suitable unit tests to cover the new/changed functionality
|
||||
- [x] I have read the [contribution guidelines](https://github.com/node-red/node-red-nodes/blob/master/CONTRIBUTING.md)
|
||||
- [x] For non-bugfix PRs, I have discussed this change on the forum/slack team.
|
||||
- [x] I have run `grunt` to verify the unit tests pass
|
||||
- [x] I have added suitable unit tests to cover the new/changed functionality
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ setenv.sh
|
||||
package-lock.json
|
||||
social/xmpp/92-xmpp.old
|
||||
*.tgz
|
||||
.DS_Store
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-random",
|
||||
"version" : "0.4.0",
|
||||
"version" : "0.4.1",
|
||||
"description" : "A Node-RED node that when triggered generates a random number between two values.",
|
||||
"dependencies" : {
|
||||
},
|
||||
|
@ -18,9 +18,8 @@ module.exports = function(RED) {
|
||||
if (node.low) { // if the the node has a value use it
|
||||
tmp.low = Number(node.low);
|
||||
} else if ('from' in msg) { // else see if a 'from' is in the msg
|
||||
if (Number(msg.from)) { // if it is, and is a number, use it
|
||||
tmp.low = Number(msg.from);
|
||||
} else { // otherwise setup NaN error
|
||||
tmp.low = Number(msg.from);
|
||||
if (isNaN(msg.from)) { // if it isn't a number setup NaN error
|
||||
tmp.low = NaN;
|
||||
tmp.low_e = " From: " + msg.from; // setup to show bad incoming msg.from
|
||||
}
|
||||
@ -31,9 +30,8 @@ module.exports = function(RED) {
|
||||
if (node.high) { // if the the node has a value use it
|
||||
tmp.high = Number(node.high);
|
||||
} else if ('to' in msg) { // else see if a 'to' is in the msg
|
||||
if (Number(msg.to)) { // if it is, and is a number, use it
|
||||
tmp.high = Number(msg.to);
|
||||
} else { // otherwise setup NaN error
|
||||
tmp.high = Number(msg.to);
|
||||
if (isNaN(msg.to)) { // if it isn't a number setup NaN error
|
||||
tmp.high = NaN
|
||||
tmp.high_e = " To: " + msg.to // setup to show bad incoming msg.to
|
||||
}
|
||||
|
@ -45,46 +45,59 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
var startPin = function() {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
|
||||
node.running = true;
|
||||
node.status({fill:"yellow",shape:"dot",text:"rpi-gpio.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var d = data.toString().trim().split("\n");
|
||||
for (var i = 0; i < d.length; i++) {
|
||||
if (d[i] === '') { return; }
|
||||
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
|
||||
node.send({ topic:"gpio/"+node.pin, payload:Number(d[i]) });
|
||||
}
|
||||
node.buttonState = d[i];
|
||||
node.status({fill:"green",shape:"dot",text:d[i]});
|
||||
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
|
||||
}
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child.removeAllListeners();
|
||||
delete node.child;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (!node.finished && code === 1) {
|
||||
setTimeout(function() {startPin()}, 250);
|
||||
}
|
||||
else if (node.finished) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.finished();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.code === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")+err.path,err); }
|
||||
else if (err.code === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")+err.path,err); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err) }
|
||||
});
|
||||
|
||||
node.child.stdin.on('error', function (err) {
|
||||
if (!node.finished) {
|
||||
node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (allOK === true) {
|
||||
if (node.pin !== undefined) {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
|
||||
node.running = true;
|
||||
node.status({fill:"yellow",shape:"dot",text:"rpi-gpio.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var d = data.toString().trim().split("\n");
|
||||
for (var i = 0; i < d.length; i++) {
|
||||
if (d[i] === '') { return; }
|
||||
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
|
||||
node.send({ topic:"gpio/"+node.pin, payload:Number(d[i]) });
|
||||
}
|
||||
node.buttonState = d[i];
|
||||
node.status({fill:"green",shape:"dot",text:d[i]});
|
||||
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
|
||||
}
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.finished) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.finished();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
|
||||
});
|
||||
|
||||
startPin();
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
@ -108,10 +121,13 @@ module.exports = function(RED) {
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.finished = done;
|
||||
node.child.stdin.write("close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
node.child.stdin.write("close "+node.pin, () => {
|
||||
if (node.child) {
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
});
|
||||
}
|
||||
else { done(); }
|
||||
else { if (done) { done(); } }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
|
||||
@ -188,11 +204,16 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
if (err.code === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")+err.path,err); }
|
||||
else if (err.code === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")+err.path,err); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err) }
|
||||
});
|
||||
|
||||
node.child.stdin.on('error', function (err) {
|
||||
if (!node.finished) {
|
||||
node.error(RED._("rpi-gpio.errors.error",{error:err.code}),err);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
@ -210,10 +231,12 @@ module.exports = function(RED) {
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.finished = done;
|
||||
node.child.stdin.write("close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
node.child.stdin.write("close "+node.pin, () => {
|
||||
node.child.kill('SIGKILL');
|
||||
setTimeout(function() { if (done) { done(); } }, 50);
|
||||
});
|
||||
}
|
||||
else { done(); }
|
||||
else { if (done) { done(); } }
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -66,8 +66,8 @@
|
||||
"invalidinput": "Ungültige Eingabe",
|
||||
"needtobeexecutable": "__command__ muss ausführbar sein",
|
||||
"mustbeexecutable": "nrgpio muss ausführbar sein",
|
||||
"commandnotfound": "nrgpio-Befehl nicht gefunden",
|
||||
"commandnotexecutable": "nrgpio-Befehl nicht ausführbar",
|
||||
"commandnotfound": "nrgpio-Befehl nicht gefunden ",
|
||||
"commandnotexecutable": "nrgpio-Befehl nicht ausführbar ",
|
||||
"error": "Fehler: __error__",
|
||||
"pythoncommandnotfound": "nrgpio-Python-Befehl nicht aktiv"
|
||||
}
|
||||
|
@ -66,8 +66,8 @@
|
||||
"invalidinput": "Invalid input",
|
||||
"needtobeexecutable": "__command__ needs to be executable",
|
||||
"mustbeexecutable": "nrgpio must to be executable",
|
||||
"commandnotfound": "nrgpio command not found",
|
||||
"commandnotexecutable": "nrgpio command not executable",
|
||||
"commandnotfound": "nrgpio command not found ",
|
||||
"commandnotexecutable": "nrgpio command not executable ",
|
||||
"error": "error: __error__",
|
||||
"pythoncommandnotfound": "nrgpio python command not running"
|
||||
}
|
||||
|
@ -66,8 +66,8 @@
|
||||
"invalidinput": "入力が不正です",
|
||||
"needtobeexecutable": "__command__ は実行可能である必要があります",
|
||||
"mustbeexecutable": "nrgpio は実行可能である必要があります",
|
||||
"commandnotfound": "nrgpio コマンドが見つかりません",
|
||||
"commandnotexecutable": "nrgpio コマンドが実行可能ではありません",
|
||||
"commandnotfound": "nrgpio コマンドが見つかりません ",
|
||||
"commandnotexecutable": "nrgpio コマンドが実行可能ではありません ",
|
||||
"error": "エラー: __error__",
|
||||
"pythoncommandnotfound": "nrgpio python コマンドが実行されていません"
|
||||
}
|
||||
|
@ -65,8 +65,8 @@
|
||||
"invalidinput": "입력이 올바르지 않습니다",
|
||||
"needtobeexecutable": "__command__ 은 실행가능상태일 필요가 있습니다 ",
|
||||
"mustbeexecutable": "nrgpio 은 실행가능상태일 필요가 있습니다 ",
|
||||
"commandnotfound": "nrgpio 커맨드를 찾을수 없습니다",
|
||||
"commandnotexecutable": "nrgpio 커맨드가 실행가능상태가 아닙니다",
|
||||
"commandnotfound": "nrgpio 커맨드를 찾을수 없습니다 ",
|
||||
"commandnotexecutable": "nrgpio 커맨드가 실행가능상태가 아닙니다 ",
|
||||
"error": "에러: __error__",
|
||||
"pythoncommandnotfound": "nrgpio python 커맨드가 실행되지 않았습니다"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-node-pi-gpio",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.6",
|
||||
"description": "The basic Node-RED node for Pi GPIO",
|
||||
"dependencies" : {
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mraa-gpio-ain',{
|
||||
category: 'Intel gpio',
|
||||
category: 'GPIO',
|
||||
color: '#a6bbcf',
|
||||
paletteLabel: 'analogue',
|
||||
defaults: {
|
||||
@ -27,6 +27,7 @@
|
||||
if (data === 5) { t = "Raspberry Pi"; }
|
||||
if (data === 6) { t = "Beaglebone"; }
|
||||
if (data === 7) { t = "Banana"; }
|
||||
if (data === 26) { t = "IOT2050"; }
|
||||
$('#btype').text(t);
|
||||
$('#node-input-pin').val(pinnow);
|
||||
});
|
||||
@ -62,7 +63,7 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mraa-gpio-ain">
|
||||
<p>An analogue input pin for an Intel Galileo or Edison board that is read every <i>interval</i> milliseconds.</p>
|
||||
<p>An analogue input pin for a board that is read every <i>interval</i> milliseconds.</p>
|
||||
<p>The <code>msg.payload</code> will contain the value, and <code>msg.topic</code>
|
||||
contains "{the_board_name}/A{the pin number}".</p>
|
||||
<p>The value is only sent if it is different from the previously read value.</p>
|
||||
|
@ -11,6 +11,10 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
var msg = { topic:node.board+"/A"+node.pin };
|
||||
var old = -99999;
|
||||
// ADC set to 12 for IOT2050
|
||||
if (this.board === "SIMATIC IOT2050") {
|
||||
node.x.setBit(12);
|
||||
}
|
||||
this.timer = setInterval(function() {
|
||||
msg.payload = node.x.read();
|
||||
if (msg.payload !== old) {
|
||||
@ -21,6 +25,7 @@ module.exports = function(RED) {
|
||||
|
||||
this.on('close', function() {
|
||||
clearInterval(this.timer);
|
||||
node.x.close();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mraa-gpio-ain", gpioAin);
|
||||
|
@ -1,13 +1,15 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mraa-gpio-din',{
|
||||
category: 'Intel gpio',
|
||||
category: 'GPIO',
|
||||
color: '#a6bbcf',
|
||||
paletteLabel: 'digital',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pin: {value:"", required: true},
|
||||
interrupt: {value:"", required: true}
|
||||
interrupt: {value:"", required: true},
|
||||
mode: {value:"", required: true},
|
||||
initial: {value: false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
@ -30,6 +32,7 @@
|
||||
if (data === 5) { t = "Raspberry Pi"; }
|
||||
if (data === 6) { t = "Beaglebone"; }
|
||||
if (data === 7) { t = "Banana"; }
|
||||
if (data === 26) { t = "IOT2050"; }
|
||||
$('#type-tip').text(t);
|
||||
$('#node-input-pin').val(pinnow);
|
||||
});
|
||||
@ -59,6 +62,23 @@
|
||||
<option value="11">D11</option>
|
||||
<option value="12">D12</option>
|
||||
<option value="13">D13</option>
|
||||
<option value="14">D14</option>
|
||||
<option value="15">D15</option>
|
||||
<option value="16">D16</option>
|
||||
<option value="17">D17</option>
|
||||
<option value="18">D18</option>
|
||||
<option value="19">D19</option>
|
||||
<option value="20">USER button</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa-level-up"></i> Mode</label>
|
||||
<select type="text" id="node-input-mode" style="width: 250px;">
|
||||
<option value='' disabled selected style='display:none;'>select mode</option>
|
||||
<option value="0">Strong </option>
|
||||
<option value="1">Pull-up </option>
|
||||
<option value="2">Pull-down </option>
|
||||
<option value="3">Hiz </option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@ -70,6 +90,11 @@
|
||||
<option value="b">Both </option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-initial-tick">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-initial" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-initial" style="width: 70%;">Send initial message with level of pin.</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name" style="width: 250px;">
|
||||
@ -78,7 +103,7 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mraa-gpio-din">
|
||||
<p>A digital input pin for an Intel Galileo or Edison board.</p>
|
||||
<p>A digital input pin for an Intel Galileo/Edison/Siemens IOT2050 board.</p>
|
||||
<p>The <code>msg.payload</code> contains the value (0 or 1), and <code>msg.topic</code>
|
||||
contains "{the_board_name}/D{the pin number}".</p>
|
||||
</script>
|
||||
|
@ -7,13 +7,16 @@ module.exports = function(RED) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.pin = n.pin;
|
||||
this.interrupt = n.interrupt;
|
||||
this.mode = n.mode;
|
||||
this.initialMsg = n.initial;
|
||||
this.x = new m.Gpio(parseInt(this.pin));
|
||||
this.board = m.getPlatformName();
|
||||
this.defaultTimeout = 100;
|
||||
var node = this;
|
||||
node.x.mode(m.PIN_GPIO);
|
||||
node.x.mode(parseInt(this.mode));
|
||||
node.x.dir(m.DIR_IN);
|
||||
node.x.isr(m.EDGE_BOTH, function() {
|
||||
var g = node.x.read();
|
||||
|
||||
var eventHandler = function(g) {
|
||||
var msg = { payload:g, topic:node.board+"/D"+node.pin };
|
||||
switch (g) {
|
||||
case 0: {
|
||||
@ -34,8 +37,15 @@ module.exports = function(RED) {
|
||||
node.status({fill:"grey",shape:"ring",text:"unknown"});
|
||||
}
|
||||
}
|
||||
});
|
||||
switch (node.x.read()) {
|
||||
}
|
||||
|
||||
var isrCallback = function() {
|
||||
eventHandler(node.x.read());
|
||||
}
|
||||
|
||||
node.x.isr(m.EDGE_BOTH, isrCallback);
|
||||
var initialState = node.x.read();
|
||||
switch (initialState) {
|
||||
case 0: {
|
||||
node.status({fill:"green",shape:"ring",text:"low"});
|
||||
break;
|
||||
@ -48,8 +58,17 @@ module.exports = function(RED) {
|
||||
node.status({});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.initialMsg) {
|
||||
setTimeout(() => {
|
||||
node.send( { payload: node.x.read(), topic:node.board+"/D"+node.pin } );
|
||||
}, this.defaultTimeout);
|
||||
}
|
||||
|
||||
this.on('close', function() {
|
||||
node.x.isr(m.EDGE_BOTH, null);
|
||||
node.x.isrExit();
|
||||
node.x.close();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mraa-gpio-din", gpioDin);
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mraa-gpio-dout',{
|
||||
category: 'Intel gpio',
|
||||
category: 'GPIO',
|
||||
color: '#a6bbcf',
|
||||
paletteLabel: 'digital',
|
||||
defaults: {
|
||||
@ -36,6 +36,7 @@
|
||||
if (data === 5) { t = "Raspberry Pi"; }
|
||||
if (data === 6) { t = "Beaglebone"; }
|
||||
if (data === 7) { t = "Banana"; }
|
||||
if (data === 26) { t = "IOT2050"; }
|
||||
$('#btype').text(t);
|
||||
if (data === 0) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",14).text("LED - Galileo v1"));
|
||||
@ -100,6 +101,6 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mraa-gpio-dout">
|
||||
<p>A digital output pin for an Intel Galileo or Edison board.</p>
|
||||
<p>A digital output pin for a board.</p>
|
||||
<p>The <code>msg.payload</code> should contain the value 0 or 1.</p>
|
||||
</script>
|
||||
|
@ -29,6 +29,7 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
this.on('close', function() {
|
||||
node.p.close();
|
||||
});
|
||||
}
|
||||
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">
|
||||
RED.nodes.registerType('mraa-gpio-pwm',{
|
||||
category: 'Intel gpio',
|
||||
category: 'GPIO',
|
||||
color: '#a6bbcf',
|
||||
paletteLabel: 'pwm',
|
||||
defaults: {
|
||||
@ -35,6 +35,7 @@
|
||||
if (data === 5) { t = "Raspberry Pi"; }
|
||||
if (data === 6) { t = "Beaglebone"; }
|
||||
if (data === 7) { t = "Banana"; }
|
||||
if (data === 26) { t = "IOT2050"; }
|
||||
$('#type-tip').text(t);
|
||||
$('#node-input-pin').val(pinnow);
|
||||
});
|
||||
@ -60,13 +61,23 @@
|
||||
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
|
||||
<select type="text" id="node-input-pin" style="width: 250px;">
|
||||
<option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
|
||||
<option value="3">D3</option>
|
||||
<option value="5">D5</option>
|
||||
<option value="6">D6</option>
|
||||
<option value="9">D9</option>
|
||||
<option value="10">D10</option>
|
||||
<option value="11">D11</option>
|
||||
</select>
|
||||
<optgroup label="Intel Galileo/Edison">
|
||||
<option value="3">D3</option>
|
||||
<option value="5">D5</option>
|
||||
<option value="6">D6</option>
|
||||
<option value="9">D9</option>
|
||||
<option value="10">D10</option>
|
||||
<option value="11">D11</option>
|
||||
</optgroup>
|
||||
<optgroup label="Siemens IOT2050">
|
||||
<option value="4">D4</option>
|
||||
<option value="5">D5</option>
|
||||
<option value="6">D6</option>
|
||||
<option value="7">D7</option>
|
||||
<option value="8">D8</option>
|
||||
<option value="9">D9</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-period"><i class="fa fa-clock-o"></i> Period</label>
|
||||
@ -80,9 +91,10 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mraa-gpio-pwm">
|
||||
<p>A pulse width modulation (PWM) output pin for an Intel Galileo or Edison board.</p>
|
||||
<p>A pulse width modulation (PWM) output pin for a board.</p>
|
||||
<p>The <code>msg.payload</code> should contain a floating point number value
|
||||
between 0 and 1, (or a string representation thereof.)</p>
|
||||
<p>For servo control set the period to 20mS and vary the input between 0.05 and 0.10</p>
|
||||
<p><b>Note</b> : Only pins 3, 5, 6, 9, 10 & 11 support PWM output.</p>
|
||||
<p><b>Note</b> : Only pins 4, 5, 6, 7, 8 & 9 support PWM output for Siemens IOT2050.</p>
|
||||
<p>Only pins 3, 5, 6, 9, 10 & 11 support PWM output for Intel Galileo/Edison.</p>
|
||||
</script>
|
||||
|
@ -21,6 +21,7 @@ module.exports = function(RED) {
|
||||
|
||||
this.on('close', function() {
|
||||
node.p.enable(false);
|
||||
node.p.close();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mraa-gpio-pwm", gpioPWM);
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name" : "node-red-node-intel-gpio",
|
||||
"version" : "0.0.6",
|
||||
"description" : "A Node-RED node to talk to an Intel Galileo or Edison using mraa",
|
||||
"version" : "0.3.0",
|
||||
"description" : "A Node-RED node to talk to an Intel Galileo, Edison or Siemens IOT2050 board using mraa",
|
||||
"dependencies" : {
|
||||
},
|
||||
"repository" : {
|
||||
@ -10,18 +10,27 @@
|
||||
"directory" : "tree/master/hardware/intel"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [ "node-red", "intel", "galileo", "edison" ],
|
||||
"keywords": [ "node-red", "intel", "galileo", "edison", "siemens", "iot2050" ],
|
||||
"node-red" : {
|
||||
"nodes" : {
|
||||
"mraa-gpio-ain": "mraa-gpio-ain.js",
|
||||
"mraa-gpio-din": "mraa-gpio-din.js",
|
||||
"mraa-gpio-dout": "mraa-gpio-dout.js",
|
||||
"mraa-gpio-pwm": "mraa-gpio-pwm.js"
|
||||
"mraa-gpio-pwm": "mraa-gpio-pwm.js",
|
||||
"mraa-gpio-led": "mraa-gpio-led.js"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"name": "Dave Conway-Jones",
|
||||
"email": "ceejay@vnet.ibm.com",
|
||||
"url": "http://nodered.org"
|
||||
}
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "@fr0st61te"
|
||||
},
|
||||
{
|
||||
"name": "@jan-kiszka"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
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",
|
||||
"version" : "0.1.2",
|
||||
"version" : "0.1.4",
|
||||
"description" : "A Node-RED node to interact with a Raspberry Pi Sense HAT",
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
|
@ -7,7 +7,7 @@ module.exports = function(RED) {
|
||||
|
||||
var hatCommand = __dirname+'/sensehat';
|
||||
|
||||
if (!fs.existsSync('/usr/lib/python2.7/dist-packages/sense_hat')) {
|
||||
if (!fs.existsSync('/usr/lib/python2.7/dist-packages/sense_hat') && !fs.existsSync('/usr/lib/python3/dist-packages/sense_hat')) {
|
||||
throw "Error: Can't find Sense HAT python libraries. Run sudo apt-get install sense-hat";
|
||||
}
|
||||
|
||||
@ -107,7 +107,13 @@ module.exports = function(RED) {
|
||||
// Any data on stderr means a bad thing has happened.
|
||||
// Best to kill it and let it reconnect.
|
||||
if (RED.settings.verbose) { RED.log.error("err: "+data+" :"); }
|
||||
hat.kill('SIGKILL');
|
||||
if (data.indexOf("WARNING") === 0) {
|
||||
if (data.indexOf("sensor not present") !== -1) { return; }
|
||||
else { RED.log.warn(data); }
|
||||
}
|
||||
else {
|
||||
hat.kill('SIGKILL');
|
||||
}
|
||||
});
|
||||
hat.stderr.on('error', function(err) { });
|
||||
hat.stdin.on('error', function(err) { });
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name" : "node-red-node-pi-unicorn-hat",
|
||||
"version" : "0.1.1",
|
||||
"version" : "0.1.2",
|
||||
"description" : "A Node-RED node to output to a Raspberry Pi Unicorn HAT from Pimorini.",
|
||||
"dependencies" : {
|
||||
"pngjs": "2.2.*"
|
||||
"pngjs": "2.3.1"
|
||||
},
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
|
@ -16,7 +16,7 @@ Run the following command in your Node-RED user directory - typically `~/.node-r
|
||||
|
||||
The output node switches a socket, a light or group of lights on or off
|
||||
|
||||
This should be backward compatible with the pervious version of this node but will benefit
|
||||
This should be backward compatible with the previous version of this node but will benefit
|
||||
from opening the config dialog and selecting the node you want.
|
||||
|
||||
The node accepts the following `msg.payload` as input
|
||||
|
@ -28,14 +28,14 @@
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"node-ssdp": "~3.2.5",
|
||||
"request": "~2.74.0",
|
||||
"node-ssdp": "~3.3.0",
|
||||
"request": "~2.88.2",
|
||||
"xml2js": "~0.4.13",
|
||||
"util": "~0.10.3",
|
||||
"util": "~0.12.4",
|
||||
"url": "~0.11.0",
|
||||
"ip": "~1.0.1",
|
||||
"body-parser": "~1.14.1",
|
||||
"q": "~1.4.1"
|
||||
"ip": "~1.1.5",
|
||||
"body-parser": "~1.20.0",
|
||||
"q": "~1.5.1"
|
||||
},
|
||||
"node-red": {
|
||||
"nodes": {
|
||||
|
@ -4,7 +4,7 @@ module.exports = function(RED) {
|
||||
var spawn = require("child_process").spawn;
|
||||
var plat = require("os").platform();
|
||||
|
||||
function doPing(node, host, arrayMode) {
|
||||
function doPing(node, host, msg, arrayMode) {
|
||||
const defTimeout = 5000;
|
||||
var ex, ex6, hostOptions, commandLineOptions;
|
||||
if (typeof host === "string") {
|
||||
@ -20,7 +20,8 @@ module.exports = function(RED) {
|
||||
hostOptions.timeout = hostOptions.timeout < 1000 ? 1000 : hostOptions.timeout;
|
||||
hostOptions.timeout = hostOptions.timeout > 30000 ? 30000 : hostOptions.timeout;
|
||||
var timeoutS = Math.round(hostOptions.timeout / 1000); //whole numbers only
|
||||
var msg = { payload:false, topic:hostOptions.host };
|
||||
msg.payload = false;
|
||||
msg.topic = hostOptions.host;
|
||||
//only include the extra msg object if operating in advance/array mode.
|
||||
if (arrayMode) {
|
||||
msg.ping = hostOptions
|
||||
@ -221,7 +222,7 @@ module.exports = function(RED) {
|
||||
let pingables = generatePingList(node.host);
|
||||
for (let index = 0; index < pingables.length; index++) {
|
||||
const element = pingables[index];
|
||||
if (element) { doPing(node, element, false); }
|
||||
if (element) { doPing(node, element, {}, false); }
|
||||
}
|
||||
}, node.timer);
|
||||
}
|
||||
@ -234,12 +235,12 @@ module.exports = function(RED) {
|
||||
let pingables = generatePingList(payload)
|
||||
for (let index = 0; index < pingables.length; index++) {
|
||||
const element = pingables[index];
|
||||
if (element) { doPing(node, element, false); }
|
||||
if (element) { doPing(node, element, RED.util.cloneMessage(msg), false); }
|
||||
}
|
||||
} else if (Array.isArray(payload) ) {
|
||||
for (let index = 0; index < payload.length; index++) {
|
||||
const element = payload[index];
|
||||
if (element) { doPing(node, element, true); }
|
||||
if (element) { doPing(node, element, RED.util.cloneMessage(msg), true); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-ping",
|
||||
"version" : "0.3.1",
|
||||
"version" : "0.3.3",
|
||||
"description" : "A Node-RED node to ping a remote server, for use as a keep-alive check.",
|
||||
"dependencies" : {
|
||||
},
|
||||
|
@ -117,22 +117,22 @@
|
||||
<tr>
|
||||
<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><select type="text" id="node-config-input-databits" style="width:90%; height:28px;">
|
||||
<td><select type="text" id="node-config-input-databits" style="width:90%; height:30px;">
|
||||
<option value="8">8</option>
|
||||
<option value="7">7</option>
|
||||
<option value="6">6</option>
|
||||
<option value="5">5</option>
|
||||
</select></td>
|
||||
<td><select type="text" id="node-config-input-parity" style="width:90%; height:28px;">
|
||||
<td><select type="text" id="node-config-input-parity" style="width:90%; height:30px;">
|
||||
<option value="none" data-i18n="serial.parity.none"></option>
|
||||
<option value="even" data-i18n="serial.parity.even"></option>
|
||||
<option value="mark" data-i18n="serial.parity.mark"></option>
|
||||
<option value="odd" data-i18n="serial.parity.odd"></option>
|
||||
<option value="space" data-i18n="serial.parity.space"></option>
|
||||
</select></td>
|
||||
<td><select type="text" id="node-config-input-stopbits" style="width:60px; height:28px;">
|
||||
<td><select type="text" id="node-config-input-stopbits" style="width:60px; height:30px;">
|
||||
<option value="2">2</option>
|
||||
<option value="1">1</option>
|
||||
</select></td>
|
||||
@ -149,22 +149,22 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><select type="text" id="node-config-input-dtr" style="width:72px; height:28px;">
|
||||
<td><select type="text" id="node-config-input-dtr" style="width:72px; height:30px;">
|
||||
<option value="none" data-i18n="serial.linestates.none"></option>
|
||||
<option value="high" data-i18n="serial.linestates.high"></option>
|
||||
<option value="low" data-i18n="serial.linestates.low"></option>
|
||||
</select></td>
|
||||
<td><select type="text" id="node-config-input-rts" style="width:72px; height:28px;">
|
||||
<td><select type="text" id="node-config-input-rts" style="width:72px; height:30px;">
|
||||
<option value="none" data-i18n="serial.linestates.none"></option>
|
||||
<option value="high" data-i18n="serial.linestates.high"></option>
|
||||
<option value="low" data-i18n="serial.linestates.low"></option>
|
||||
</select></td>
|
||||
<td><select type="text" id="node-config-input-cts" style="width:72px; height:28px;">
|
||||
<td><select type="text" id="node-config-input-cts" style="width:72px; height:30px;">
|
||||
<option value="none" data-i18n="serial.linestates.none"></option>
|
||||
<option value="high" data-i18n="serial.linestates.high"></option>
|
||||
<option value="low" data-i18n="serial.linestates.low"></option>
|
||||
</select></td>
|
||||
<td><select type="text" id="node-config-input-dsr" style="width:72px; height:28px;">
|
||||
<td><select type="text" id="node-config-input-dsr" style="width:72px; height:30px;">
|
||||
<option value="none" data-i18n="serial.linestates.none"></option>
|
||||
<option value="high" data-i18n="serial.linestates.high"></option>
|
||||
<option value="low" data-i18n="serial.linestates.low"></option>
|
||||
@ -177,23 +177,23 @@
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
|
||||
<span data-i18n="serial.label.start"></span>
|
||||
<input type="text" id="node-config-input-waitfor" style="width:50px; height:28px;">
|
||||
<input type="text" id="node-config-input-waitfor" style="width:50px; height:28px; text-decoration:grey dotted underline;">
|
||||
<span data-i18n="serial.label.startor"></span>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
|
||||
<span data-i18n="serial.label.split"></span>
|
||||
<select type="text" id="node-config-input-out" style="margin-left:11px; width:200px; height:28px;">
|
||||
<select type="text" id="node-config-input-out" style="margin-left:11px; width:200px;">
|
||||
<option value="char" data-i18n="serial.split.character"></option>
|
||||
<option value="time" data-i18n="serial.split.timeout"></option>
|
||||
<option value="interbyte" data-i18n="serial.split.silent"></option>
|
||||
<option value="count" data-i18n="serial.split.lengths"></option>
|
||||
</select>
|
||||
<input type="text" id="node-config-input-newline" style="width:50px; height:28px;">
|
||||
<input type="text" id="node-config-input-newline" style="width:50px;">
|
||||
<span id="node-units"></span>
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
|
||||
<span data-i18n="serial.label.deliver"></span>
|
||||
<select type="text" id="node-config-input-bin" style="margin-left:5px; width:150px; height:28px;">
|
||||
<select type="text" id="node-config-input-bin" style="margin-left:5px; width:150px;">
|
||||
<option value="false" data-i18n="serial.output.ascii"></option>
|
||||
<option value="bin" data-i18n="serial.output.binary"></option>
|
||||
</select>
|
||||
@ -204,7 +204,7 @@
|
||||
</div>
|
||||
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
|
||||
<label style="width:auto;" for="node-config-input-addchar"><span data-i18n="serial.addsplit"></span></label>
|
||||
<input type="text" id="node-config-input-addchar" style="width:50px; height:28px;">
|
||||
<input type="text" id="node-config-input-addchar" style="width:50px; height:28px; text-decoration:grey dotted underline;">
|
||||
</div>
|
||||
</div>
|
||||
<div id="node-config-req">
|
||||
|
@ -271,7 +271,7 @@ module.exports = function(RED) {
|
||||
if (addchar !== "") { payload += addchar; }
|
||||
}
|
||||
else if (addchar !== "") {
|
||||
payload = Buffer.concat([payload,addchar]);
|
||||
payload = Buffer.concat([payload,Buffer.from(addchar)]);
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name" : "node-red-node-serialport",
|
||||
"version" : "1.0.1",
|
||||
"version" : "1.0.3",
|
||||
"description" : "Node-RED nodes to talk to serial ports",
|
||||
"dependencies" : {
|
||||
"serialport" : "^10.3.0"
|
||||
"serialport" : "^10.5.0"
|
||||
},
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
@ -21,7 +21,7 @@
|
||||
"engines" : { "node" : ">=12.0.0" },
|
||||
"author": {
|
||||
"name": "Dave Conway-Jones",
|
||||
"email": "ceejay@vnet.ibm.com",
|
||||
"email": "dceejay@gmail.com",
|
||||
"url": "http://nodered.org"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,16 @@
|
||||
node-red-node-snmp
|
||||
==================
|
||||
|
||||
A pair of <a href="http://nodered.org" target="_new">Node-RED</a> nodes that
|
||||
fetch either individual oids, or a table oid from a SNMP enabled host.
|
||||
A set of <a href="http://nodered.org" target="_new">Node-RED</a> nodes that
|
||||
fetch values from SNMP enabled hosts. Supports v1, v2c and v3.
|
||||
* SNMP get - Simple SNMP oid or oid list fetcher
|
||||
* SNMP set - Simple snmp Set node.
|
||||
* SNMP subtree - Simple sub tree fetcher
|
||||
* SNMP table - Simple SNMP oid table fetcher
|
||||
* SNMP walker - Simple SNMP oid walker fetcher
|
||||
|
||||
## v2 Breaking Change
|
||||
v2 has a breaking change in that the single snmp node no longer tries to stringify an octet string type (04). This makes it consistent with the other nodes in this bundle. this means the user now has to convert to a string if required but has better control of how they wish to do that.
|
||||
|
||||
Install
|
||||
-------
|
||||
@ -18,9 +26,15 @@ Usage
|
||||
|
||||
SNMP oids fetcher. Can fetch a single or comma separated list of oids. Triggered by any input.
|
||||
|
||||
`msg.host` may contain the host.
|
||||
`msg.host` may contain the host including the port.
|
||||
|
||||
`msg.community` may contain the community.
|
||||
`msg.community` may contain the community. (v1 and v2c only)
|
||||
|
||||
`msg.username` may contain the username. (v3 only)
|
||||
|
||||
`msg.authkey` may contain the digest security key. (v3 only)
|
||||
|
||||
`msg.privkey` may contain the encryption security key. (v3 only)
|
||||
|
||||
`msg.oid` may contain a comma separated list of oids to search for. (no spaces)
|
||||
|
||||
@ -28,6 +42,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
|
||||
|
||||
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
|
||||
|
||||
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
|
||||
|
||||
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
|
||||
|
||||
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
|
||||
|
||||
The oids configured in the edit config will override `msg.oid`. Leave blank if you
|
||||
want to use `msg.oid` to provide input.
|
||||
|
||||
@ -38,9 +58,15 @@ Values depends on the oids being requested.
|
||||
|
||||
SNMP sets the value of one or more OIDs.
|
||||
|
||||
`msg.host` may contain the host.
|
||||
`msg.host` may contain the host including the port.
|
||||
|
||||
`msg.community` may contain the community.
|
||||
`msg.community` may contain the community. (v1 and v2c only)
|
||||
|
||||
`msg.username` may contain the username. (v3 only)
|
||||
|
||||
`msg.authkey` may contain the digest security key. (v3 only)
|
||||
|
||||
`msg.privkey` may contain the encryption security key. (v3 only)
|
||||
|
||||
`msg.varbinds` may contain an array of varbind JSON objects e.g.:
|
||||
```
|
||||
@ -81,6 +107,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
|
||||
|
||||
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
|
||||
|
||||
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
|
||||
|
||||
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
|
||||
|
||||
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
|
||||
|
||||
The varbinds configured in the edit config will override `msg.varbinds`. Leave blank if you want to use `msg.varbinds` to provide input.
|
||||
|
||||
|
||||
@ -89,16 +121,28 @@ The varbinds configured in the edit config will override `msg.varbinds`. Leave b
|
||||
|
||||
Simple SNMP table oid fetcher. Triggered by any input.
|
||||
|
||||
`msg.host` may contain the host.
|
||||
`msg.host` may contain the host including the port.
|
||||
|
||||
`msg.community` may contain the community.
|
||||
`msg.community` may contain the community. (v1 and v2c only)
|
||||
|
||||
`msg.oid` may contain the oid of a single table to search for.
|
||||
`msg.username` may contain the username. (v3 only)
|
||||
|
||||
`msg.authkey` may contain the digest security key. (v3 only)
|
||||
|
||||
`msg.privkey` may contain the encryption security key. (v3 only)
|
||||
|
||||
`msg.oid` may contain a comma separated list of oids to search for. (no spaces)
|
||||
|
||||
The host configured in the edit config will override `msg.host`. Leave blank if you want to use `msg.host` to provide input.
|
||||
|
||||
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
|
||||
|
||||
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
|
||||
|
||||
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
|
||||
|
||||
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
|
||||
|
||||
The oid configured in the edit config will override `msg.oid`. Leave blank if you
|
||||
want to use `msg.oid` to provide input.
|
||||
|
||||
@ -109,9 +153,15 @@ Values depends on the oids being requested.
|
||||
|
||||
Simple SNMP oid subtree fetcher. Triggered by any input. Reads from OID specified and any below it.
|
||||
|
||||
`msg.host` may contain the host.
|
||||
`msg.host` may contain the host including the port.
|
||||
|
||||
`msg.community` may contain the community.
|
||||
`msg.community` may contain the community. (v1 and v2c only)
|
||||
|
||||
`msg.username` may contain the username. (v3 only)
|
||||
|
||||
`msg.authkey` may contain the digest security key. (v3 only)
|
||||
|
||||
`msg.privkey` may contain the encryption security key. (v3 only)
|
||||
|
||||
`msg.oid` may contain the oid of a single table to search for.
|
||||
|
||||
@ -119,6 +169,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
|
||||
|
||||
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
|
||||
|
||||
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
|
||||
|
||||
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
|
||||
|
||||
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
|
||||
|
||||
The oid configured in the edit config will override `msg.oid`. Leave blank if you
|
||||
want to use `msg.oid` to provide input.
|
||||
|
||||
@ -129,9 +185,15 @@ Values depends on the oids being requested.
|
||||
|
||||
Simple SNMP oid walker fetcher. Triggered by any input. Reads from OID specified to the end of the table.
|
||||
|
||||
`msg.host` may contain the host.
|
||||
`msg.host` may contain the host including the port.
|
||||
|
||||
`msg.community` may contain the community.
|
||||
`msg.community` may contain the community. (v1 and v2c only)
|
||||
|
||||
`msg.username` may contain the username. (v3 only)
|
||||
|
||||
`msg.authkey` may contain the digest security key. (v3 only)
|
||||
|
||||
`msg.privkey` may contain the encryption security key. (v3 only)
|
||||
|
||||
`msg.oid` may contain the oid of a single table to search for.
|
||||
|
||||
@ -139,6 +201,12 @@ The host configured in the edit config will override `msg.host`. Leave blank if
|
||||
|
||||
The community configured in the edit config will override `msg.community`. Leave blank if you want to use `msg.community` to provide input.
|
||||
|
||||
The username configured in the edit config will override `msg.username`. Leave blank if you want to use `msg.username` to provide input.
|
||||
|
||||
The digest security key configured in the edit config will override `msg.authkey`. Leave blank if you want to use `msg.authkey` to provide input.
|
||||
|
||||
The encryption security key configured in the edit config will override `msg.privkey`. Leave blank if you want to use `msg.privkey` to provide input.
|
||||
|
||||
The oid configured in the edit config will override `msg.oid`. Leave blank if you
|
||||
want to use `msg.oid` to provide input.
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name" : "node-red-node-snmp",
|
||||
"version" : "0.0.25",
|
||||
"description" : "A Node-RED node that looks for SNMP oids.",
|
||||
"version" : "2.0.0",
|
||||
"description" : "A Node-RED node that gets and sets SNMP oid values. Supports v1, v2c and v3",
|
||||
"dependencies" : {
|
||||
"net-snmp" : "1.2.4"
|
||||
"net-snmp" : "^3.9.0"
|
||||
},
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
@ -11,7 +11,7 @@
|
||||
"directory" : "tree/master/io/snmp"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [ "node-red", "snmp", "oid" ],
|
||||
"keywords": [ "node-red", "snmp", "oid", "snmpv3" ],
|
||||
"node-red" : {
|
||||
"nodes" : {
|
||||
"snmp": "snmp.js"
|
||||
@ -24,6 +24,9 @@
|
||||
},
|
||||
"contributors": [
|
||||
{ "name": "Mika Karaila" },
|
||||
{ "name": "Bryan Malyn" }
|
||||
{ "name": "Bryan Malyn" },
|
||||
{ "name": "Steve-Mcl" },
|
||||
{ "name": "Andres" },
|
||||
{ "name": "@echobops" }
|
||||
]
|
||||
}
|
||||
|
@ -1,21 +1,112 @@
|
||||
<style id="node-red-node-snmp-common-style">
|
||||
.form-row.form-row-snmpv1v2.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.form-row.form-row-snmpv3.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.form-row.form-row-snmpv3-auth.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" id="node-red-node-snmp-common-script">
|
||||
const node_snmp_common = {
|
||||
oneditprepare: function (node) {
|
||||
const compat = { "v1": "1", "v2": "2c", "v2c": "2c", "v3": "3" };
|
||||
if(compat[node.version]) {
|
||||
node.version = compat[node.version];
|
||||
} else if(["1","2c","3"].indexOf(node.version) < 0) {
|
||||
node.version = "1";
|
||||
}
|
||||
$("#node-input-version").on("change", function(evt) {
|
||||
const isV3 = $("#node-input-version").val() === "3";
|
||||
$(".form-row-snmpv1v2").toggleClass("hidden", isV3);
|
||||
$(".form-row-snmpv3").toggleClass("hidden", !isV3);
|
||||
$("#node-input-auth").trigger("change");
|
||||
});
|
||||
$("#node-input-auth").on("change", function(evt) {
|
||||
const isV3 = $("#node-input-version").val() === "3";
|
||||
const auth = $("#node-input-auth").val();
|
||||
if(isV3) {
|
||||
switch (auth) {
|
||||
case "authNoPriv":
|
||||
$(".form-row-snmpv3-auth").toggleClass("hidden", false);
|
||||
$(".form-row-snmpv3-priv").toggleClass("hidden", true);
|
||||
break;
|
||||
case "authPriv":
|
||||
$(".form-row-snmpv3-auth").toggleClass("hidden", false);
|
||||
$(".form-row-snmpv3-priv").toggleClass("hidden", false);
|
||||
break;
|
||||
default: //"noAuthNoPriv":
|
||||
$(".form-row-snmpv3-auth").toggleClass("hidden", true);
|
||||
$(".form-row-snmpv3-priv").toggleClass("hidden", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#node-input-version").val(node.version);
|
||||
if(!$("#node-input-auth").val()) {
|
||||
$("#node-input-auth").val("noAuthNoPriv");
|
||||
}
|
||||
$("#node-input-version").trigger("change");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="text/html" data-template-name="snmp">
|
||||
<div class="form-row">
|
||||
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
|
||||
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
|
||||
<select type="text" id="node-input-version" style="width:150px;">
|
||||
<option value="1">v1</option>
|
||||
<option value="2c">v2c</option>
|
||||
<option value="3">v3</option>
|
||||
</select>
|
||||
<span style="margin-left:50px;">Timeout</span>
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;"> S
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> S
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv1v2">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-input-username" placeholder="username">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
|
||||
<select type="text" id="node-input-auth" style="width:150px;">
|
||||
<option value="noAuthNoPriv">noAuthNoPriv</option>
|
||||
<option value="authNoPriv">authNoPriv</option>
|
||||
<option value="authPriv">authPriv</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
|
||||
<select type="text" id="node-input-authprot" style="width:150px;">
|
||||
<option value="MD5">MD5</option>
|
||||
<option value="SHA">SHA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
|
||||
<input type="password" id="node-input-authkey" placeholder="Authentication key">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
|
||||
<select type="text" id="node-input-privprot" style="width:150px;">
|
||||
<option value="DES">DES</option>
|
||||
<option value="AES">AES</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
|
||||
<input type="password" id="node-input-privkey" placeholder="Encryption key">
|
||||
</div>
|
||||
<!-- End of unique data for V3 -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-oids"><i class="fa fa-tags"></i> OIDs</label>
|
||||
<textarea rows="4" cols="60" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0" style="width:70%;"></textarea>
|
||||
@ -31,6 +122,9 @@
|
||||
<p>Simple SNMP oid or oid list fetcher. Triggered by any input.</p>
|
||||
<p><code>msg.host</code> may contain the host.</p>
|
||||
<p><code>msg.community</code> may contain the community.</p>
|
||||
<p><code>msg.username</code> may contain the username. (V3 only)</p>
|
||||
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
|
||||
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
|
||||
<p><code>msg.oid</code> may contain a comma separated list of oids to request. (no spaces)</p>
|
||||
<p>OIDs must be numeric. iso. is the same a 1. </p>
|
||||
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
|
||||
@ -42,11 +136,19 @@
|
||||
color: "YellowGreen",
|
||||
defaults: {
|
||||
host: { value: "127.0.0.1" },
|
||||
community: { value: "public" },
|
||||
version: { value: "1", required: true },
|
||||
oids: { value: "" },
|
||||
timeout: { value: 5 },
|
||||
name: { value: "" }
|
||||
community: { value: "public" },
|
||||
auth: { value: "noAuthNoPriv", required: true },
|
||||
authprot: { value: "MD5", required: true },
|
||||
privprot: { value: "DES", required: true },
|
||||
oids: { value: "" },
|
||||
name: { value: "" },
|
||||
},
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
@ -56,6 +158,9 @@
|
||||
},
|
||||
labelStyle: function () {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
node_snmp_common.oneditprepare(this);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -65,22 +170,61 @@
|
||||
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
|
||||
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
|
||||
<select type="text" id="node-input-version" style="width:150px;">
|
||||
<option value="1">v1</option>
|
||||
<option value="2c">v2c</option>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<option value="3">v3</option>
|
||||
<!-- End of unique data for V3 -->
|
||||
</select>
|
||||
<span style="margin-left:50px;">Timeout</span>
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;"> S
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> S
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv1v2">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-input-username" placeholder="username">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
|
||||
<select type="text" id="node-input-auth" style="width:150px;">
|
||||
<option value="noAuthNoPriv">noAuthNoPriv</option>
|
||||
<option value="authNoPriv">authNoPriv</option>
|
||||
<option value="authPriv">authPriv</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
|
||||
<select type="text" id="node-input-authprot" style="width:150px;">
|
||||
<option value="MD5">MD5</option>
|
||||
<option value="SHA">SHA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
|
||||
<input type="password" id="node-input-authkey" placeholder="Authentication key">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
|
||||
<select type="text" id="node-input-privprot" style="width:150px;">
|
||||
<option value="DES">DES</option>
|
||||
<option value="AES">AES</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
|
||||
<input type="password" id="node-input-privkey" placeholder="Encryption key">
|
||||
</div>
|
||||
<!-- End of unique data for V3 -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-varbinds"><i class="fa fa-tags"></i> Varbinds</label>
|
||||
<textarea rows="10" cols="60" id="node-input-varbinds" placeholder="e.g. [ { "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>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@ -94,15 +238,18 @@
|
||||
<p>Simple snmp Set node. Trigger by any input</p>
|
||||
<p><code>msg.host</code> may contain the host.</p>
|
||||
<p><code>msg.community</code> may contain the community.</p>
|
||||
<p><code>msg.username</code> may contain the username. (V3 only)</p>
|
||||
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
|
||||
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
|
||||
<p><code>msg.varbinds</code> may contain varbinds as an array of json objects containing multiple oids, types and values.
|
||||
<pre>[
|
||||
{
|
||||
"oid": "1.3.6.1.2.1.1.5.0",
|
||||
"type": "OctetString",
|
||||
"value": "host1"
|
||||
},
|
||||
{ "oid": ... }
|
||||
]</pre>
|
||||
<code style="font-size: smaller;"><pre style="white-space: pre;">[
|
||||
{
|
||||
"oid": "1.3.6.1.2.1.1.5.0",
|
||||
"type": "OctetString",
|
||||
"value": "host1"
|
||||
},
|
||||
{ "oid": ... }
|
||||
]</pre></code>
|
||||
<p>Any numeric inputs must be numbers, not strings, e.g. 1 not "1".</p>
|
||||
<p>OIDs must be numeric. iso. is the same a 1.</p>
|
||||
</p>
|
||||
@ -114,12 +261,27 @@
|
||||
color: "YellowGreen",
|
||||
defaults: {
|
||||
host: { value: "127.0.0.1" },
|
||||
community: { value: "public" },
|
||||
version: { value: "1", required: true },
|
||||
varbinds: { value: "" },
|
||||
timeout: { value: 5 },
|
||||
community: { value: "public" },
|
||||
auth: { value: "noAuthNoPriv", required: true },
|
||||
authprot: { value: "MD5", required: true },
|
||||
privprot: { value: "DES", required: true },
|
||||
oids: { value: "" },
|
||||
varbinds: { value: "", validate:function(v) {
|
||||
try {
|
||||
return !v || !!JSON.parse(v);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}},
|
||||
name: { value: "" }
|
||||
},
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 0,
|
||||
icon: "snmp.png",
|
||||
@ -128,6 +290,9 @@
|
||||
},
|
||||
labelStyle: function () {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
node_snmp_common.oneditprepare(this);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -137,19 +302,58 @@
|
||||
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
|
||||
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
|
||||
<select type="text" id="node-input-version" style="width:150px;">
|
||||
<option value="1">v1</option>
|
||||
<option value="2c">v2c</option>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<option value="3">v3</option>
|
||||
<!-- End of unique data for V3 -->
|
||||
</select>
|
||||
<span style="margin-left:50px;">Timeout</span>
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;"> S
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> S
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv1v2">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-input-username" placeholder="username">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
|
||||
<select type="text" id="node-input-auth" style="width:150px;">
|
||||
<option value="noAuthNoPriv">noAuthNoPriv</option>
|
||||
<option value="authNoPriv">authNoPriv</option>
|
||||
<option value="authPriv">authPriv</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
|
||||
<select type="text" id="node-input-authprot" style="width:150px;">
|
||||
<option value="MD5">MD5</option>
|
||||
<option value="SHA">SHA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
|
||||
<input type="password" id="node-input-authkey" placeholder="Authentication key">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
|
||||
<select type="text" id="node-input-privprot" style="width:150px;">
|
||||
<option value="DES">DES</option>
|
||||
<option value="AES">AES</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
|
||||
<input type="password" id="node-input-privkey" placeholder="Encryption key">
|
||||
</div>
|
||||
<!-- End of unique data for V3 -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label>
|
||||
<input type="text" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0">
|
||||
@ -165,6 +369,9 @@
|
||||
<p>Simple SNMP oid table fetcher. Triggered by any input.</p>
|
||||
<p><code>msg.host</code> may contain the host.</p>
|
||||
<p><code>msg.community</code> may contain the community.</p>
|
||||
<p><code>msg.username</code> may contain the username. (V3 only)</p>
|
||||
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
|
||||
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
|
||||
<p><code>msg.oid</code> may contain the oid of a table to request.</p>
|
||||
<p>OID must be numeric. iso. is the same a 1.</p>
|
||||
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
|
||||
@ -176,12 +383,20 @@
|
||||
color: "YellowGreen",
|
||||
defaults: {
|
||||
host: { value: "127.0.0.1" },
|
||||
community: { value: "public" },
|
||||
version: { value: "1", required: true },
|
||||
oids: { value: "" },
|
||||
timeout: { value: 5 },
|
||||
community: { value: "public" },
|
||||
auth: { value: "noAuthNoPriv", required: true },
|
||||
authprot: { value: "MD5", required: true },
|
||||
privprot: { value: "DES", required: true },
|
||||
oids: { value: "" },
|
||||
name: { value: "" }
|
||||
},
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "snmp.png",
|
||||
@ -190,6 +405,9 @@
|
||||
},
|
||||
labelStyle: function () {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
node_snmp_common.oneditprepare(this);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -199,19 +417,58 @@
|
||||
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
|
||||
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
|
||||
<select type="text" id="node-input-version" style="width:150px;">
|
||||
<option value="1">v1</option>
|
||||
<option value="2c">v2c</option>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<option value="3">v3</option>
|
||||
<!-- End of unique data for V3 -->
|
||||
</select>
|
||||
<span style="margin-left:50px;">Timeout</span>
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;"> S
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> S
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv1v2">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-input-username" placeholder="username">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
|
||||
<select type="text" id="node-input-auth" style="width:150px;">
|
||||
<option value="noAuthNoPriv">noAuthNoPriv</option>
|
||||
<option value="authNoPriv">authNoPriv</option>
|
||||
<option value="authPriv">authPriv</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
|
||||
<select type="text" id="node-input-authprot" style="width:150px;">
|
||||
<option value="MD5">MD5</option>
|
||||
<option value="SHA">SHA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
|
||||
<input type="password" id="node-input-authkey" placeholder="Authentication key">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
|
||||
<select type="text" id="node-input-privprot" style="width:150px;">
|
||||
<option value="DES">DES</option>
|
||||
<option value="AES">AES</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
|
||||
<input type="password" id="node-input-privkey" placeholder="Encryption key">
|
||||
</div>
|
||||
<!-- End of unique data for V3 -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label>
|
||||
<input type="text" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0">
|
||||
@ -227,6 +484,9 @@
|
||||
<p>Simple SNMP oid subtree fetcher. Triggered by any input. Reads all OIDS at and below the current base OID.</p>
|
||||
<p><code>msg.host</code> may contain the host.</p>
|
||||
<p><code>msg.community</code> may contain the community.</p>
|
||||
<p><code>msg.username</code> may contain the username. (V3 only)</p>
|
||||
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
|
||||
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
|
||||
<p><code>msg.oid</code> may contain the oid of a table to request.</p>
|
||||
<p>OID must be numeric. iso. is the same a 1. </p>
|
||||
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
|
||||
@ -238,12 +498,20 @@
|
||||
color: "YellowGreen",
|
||||
defaults: {
|
||||
host: { value: "127.0.0.1" },
|
||||
community: { value: "public" },
|
||||
version: { value: "1", required: true },
|
||||
oids: { value: "" },
|
||||
timeout: { value: 5 },
|
||||
community: { value: "public" },
|
||||
auth: { value: "noAuthNoPriv", required: true },
|
||||
authprot: { value: "MD5", required: true },
|
||||
privprot: { value: "DES", required: true },
|
||||
oids: { value: "" },
|
||||
name: { value: "" }
|
||||
},
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "snmp.png",
|
||||
@ -252,6 +520,9 @@
|
||||
},
|
||||
labelStyle: function () {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
node_snmp_common.oneditprepare(this);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -262,19 +533,58 @@
|
||||
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
|
||||
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
|
||||
<select type="text" id="node-input-version" style="width:150px;">
|
||||
<option value="1">v1</option>
|
||||
<option value="2c">v2c</option>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<option value="3">v3</option>
|
||||
<!-- End of unique data for V3 -->
|
||||
</select>
|
||||
<span style="margin-left:50px;">Timeout</span>
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;"> S
|
||||
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; vertical-align:baseline;"> S
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv1v2">
|
||||
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
|
||||
<input type="text" id="node-input-community" placeholder="public">
|
||||
</div>
|
||||
<!-- Following Data is used for V3 Only -->
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
|
||||
<input type="text" id="node-input-username" placeholder="username">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3">
|
||||
<label for="node-input-auth"><i class="fa fa-user-secret"></i> Auth.</label>
|
||||
<select type="text" id="node-input-auth" style="width:150px;">
|
||||
<option value="noAuthNoPriv">noAuthNoPriv</option>
|
||||
<option value="authNoPriv">authNoPriv</option>
|
||||
<option value="authPriv">authPriv</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authprot"><i class="fa fa-shield"></i> Auth.Prot.</label>
|
||||
<select type="text" id="node-input-authprot" style="width:150px;">
|
||||
<option value="MD5">MD5</option>
|
||||
<option value="SHA">SHA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-auth">
|
||||
<label for="node-input-authkey"><i class="fa fa-key"></i> Auth.Key</label>
|
||||
<input type="password" id="node-input-authkey" placeholder="Authentication key">
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privprot"><i class="fa fa-shield"></i> Priv.Prot.</label>
|
||||
<select type="text" id="node-input-privprot" style="width:150px;">
|
||||
<option value="DES">DES</option>
|
||||
<option value="AES">AES</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-snmpv3 form-row-snmpv3-priv">
|
||||
<label for="node-input-privkey"><i class="fa fa-key"></i> Priv.Key</label>
|
||||
<input type="password" id="node-input-privkey" placeholder="Encryption key">
|
||||
</div>
|
||||
<!-- End of unique data for V3 -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-oids"><i class="fa fa-tags"></i> OID</label>
|
||||
<input type="text" id="node-input-oids" placeholder="e.g. 1.3.6.1.2.1.1.5.0">
|
||||
@ -287,14 +597,17 @@
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="snmp walker">
|
||||
<p>Simple SNMP oid walker fetcher. Triggered by any input.
|
||||
<p>Simple SNMP oid walker fetcher. Triggered by any input.
|
||||
Fetches all nodes from this OID to the end of the table.</p>
|
||||
<p><code>msg.host</code> may contain the host.</p>
|
||||
<p><code>msg.community</code> may contain the community.</p>
|
||||
<p><code>msg.username</code> may contain the username. (V3 only)</p>
|
||||
<p><code>msg.authkey</code> may contain the digest security key. (V3 only)</p>
|
||||
<p><code>msg.privkey</code> may contain the encryption security key. (V3 only)</p>
|
||||
<p><code>msg.oid</code> may contain the oid of a table to request.</p>
|
||||
<p>OID must be numeric. iso. is the same a 1. </p>
|
||||
<p>The node will output <code>msg.payload</code> and <code>msg.oid</code>.</p>
|
||||
<p><b>Note</b>: This node does indeed "walk" down the tree. This is different behaviour to
|
||||
<p><b>Note</b>: This node does indeed "walk" down the tree. This is different behaviour to
|
||||
the typical snmpwalk command line app.</p>
|
||||
</script>
|
||||
|
||||
@ -304,12 +617,20 @@
|
||||
color: "YellowGreen",
|
||||
defaults: {
|
||||
host: { value: "127.0.0.1" },
|
||||
community: { value: "public" },
|
||||
version: { value: "1", required: true },
|
||||
oids: { value: "" },
|
||||
timeout: { value: 5 },
|
||||
community: { value: "public" },
|
||||
auth: { value: "noAuthNoPriv", required: true },
|
||||
authprot: { value: "MD5", required: true },
|
||||
privprot: { value: "DES", required: true },
|
||||
oids: { value: "" },
|
||||
name: { value: "" }
|
||||
},
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: "snmp.png",
|
||||
@ -318,6 +639,9 @@
|
||||
},
|
||||
labelStyle: function () {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
node_snmp_common.oneditprepare(this);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
538
io/snmp/snmp.js
538
io/snmp/snmp.js
@ -1,262 +1,480 @@
|
||||
|
||||
module.exports = function (RED) {
|
||||
"use strict";
|
||||
var snmp = require("net-snmp");
|
||||
|
||||
var sessions = {};
|
||||
|
||||
function getSession(host, community, version, timeout) {
|
||||
var sessionKey = host + ":" + community + ":" + version;
|
||||
var port = 161;
|
||||
if (host.indexOf(":") !== -1) {
|
||||
port = host.split(":")[1];
|
||||
host = host.split(":")[0];
|
||||
const SNMP = require("net-snmp");
|
||||
const sessions = {};
|
||||
function generateUUID() {
|
||||
let d = Date.now();
|
||||
let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || (Date.now() * Math.random() * 100000);//Time in microseconds since load
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
let r = Math.random() * 16;//random number between 0 and 16
|
||||
if (d > 0) {//Use timestamp until depleted
|
||||
r = (d + r) % 16 | 0;
|
||||
d = Math.floor(d / 16);
|
||||
} else {//Use microseconds since page-load if supported
|
||||
r = (d2 + r) % 16 | 0;
|
||||
d2 = Math.floor(d2 / 16);
|
||||
}
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
}
|
||||
function openSession(sessionid, host, user, options) {
|
||||
// SNMPv3 call
|
||||
if (options.version === SNMP.Version3) {
|
||||
sessions[sessionid] = SNMP.createV3Session(host, user, options);
|
||||
}
|
||||
if (!(sessionKey in sessions)) {
|
||||
sessions[sessionKey] = snmp.createSession(host, community, { port:port, version:version, timeout:(timeout || 5000) });
|
||||
// SNMPv1 or SNMPv2c call
|
||||
else {
|
||||
sessions[sessionid] = SNMP.createSession(host, user.community, options);
|
||||
}
|
||||
return sessions[sessionKey];
|
||||
return sessions[sessionid];
|
||||
}
|
||||
|
||||
function SnmpNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.community = n.community;
|
||||
this.host = n.host;
|
||||
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
|
||||
this.oids = n.oids.replace(/\s/g, "");
|
||||
this.timeout = Number(n.timeout || 5) * 1000;
|
||||
var node = this;
|
||||
// Any session needs to be closed after completion
|
||||
function closeSession(sessionid) {
|
||||
try {
|
||||
sessions[sessionid].removeAllListeners();
|
||||
} catch (e) { }
|
||||
try {
|
||||
sessions[sessionid].close();
|
||||
} catch (e) { }
|
||||
delete sessions[sessionid];
|
||||
}
|
||||
|
||||
this.on("input", function (msg) {
|
||||
var host = node.host || msg.host;
|
||||
var community = node.community || msg.community;
|
||||
var oids = node.oids || msg.oid;
|
||||
function initSnmpNode(node, config) {
|
||||
node.community = config.community;
|
||||
node.host = config.host;
|
||||
node.version = config.version;
|
||||
node.auth = config.auth;
|
||||
node.authprot = config.authprot;
|
||||
node.privprot = config.privprot;
|
||||
if (node.credentials) {
|
||||
node.username = node.credentials.username;
|
||||
node.authkey = node.credentials.authkey;
|
||||
node.privkey = node.credentials.privkey;
|
||||
}
|
||||
node.timeout = Number(config.timeout || 5) * 1000;
|
||||
}
|
||||
|
||||
function prepareSnmpOptions(node, msg) {
|
||||
let host = node.host || msg.host;
|
||||
const sessionid = generateUUID();
|
||||
const user = {}
|
||||
const options = {};
|
||||
const compat = { "v1": "1", "v2": "2c", "v2c": "2c", "v3": "3" };
|
||||
if(compat[node.version]) {
|
||||
node.version = compat[node.version];
|
||||
} else if(["1","2c","3"].indexOf(node.version) < 0) {
|
||||
node.version = "1";
|
||||
}
|
||||
options.version = node.version;
|
||||
if (node.version === "1") {
|
||||
options.version = SNMP.Version1;
|
||||
user.community = node.community || msg.community;
|
||||
} else if (node.version === "2c") {
|
||||
options.version = SNMP.Version2c;
|
||||
user.community = node.community || msg.community;
|
||||
} else if (node.version === "3") {
|
||||
user.name = node.username || msg.username || "";
|
||||
user.level = SNMP.SecurityLevel.noAuthNoPriv;
|
||||
user.authProtocol = SNMP.AuthProtocols.none;
|
||||
user.authKey = "";
|
||||
user.privProtocol = SNMP.PrivProtocols.none;
|
||||
user.privKey = "";
|
||||
options.version = SNMP.Version3;
|
||||
if (node.auth === "authNoPriv" || node.auth === "authPriv") {
|
||||
user.level = SNMP.SecurityLevel.authNoPriv;
|
||||
user.authProtocol = (node.authprot === "SHA") ? SNMP.AuthProtocols.sha : SNMP.AuthProtocols.md5;
|
||||
user.authKey = node.authkey || msg.authkey || "";
|
||||
if (node.auth === "authPriv") {
|
||||
user.level = SNMP.SecurityLevel.authPriv;
|
||||
if (node.privprot === "DES" || node.privprot === "AES") {
|
||||
user.privProtocol = (node.privprot === "AES") ? SNMP.PrivProtocols.aes : SNMP.PrivProtocols.des;
|
||||
user.privKey = node.privkey || msg.privkey || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.timeout = node.timeout;
|
||||
options.debug = msg.debug || undefined;
|
||||
options.port = options.port || 161;
|
||||
options.retries = options.retries || 1;
|
||||
|
||||
if (msg.engineID) {
|
||||
options.engineID = msg.engineID;//The engineID used for SNMPv3 communications, given as a hex string - defaults to a system-generated engineID containing elements of random
|
||||
}
|
||||
if (msg.backoff) {
|
||||
options.backoff = msg.backoff;//The factor by which to increase the timeout for every retry, defaults to 1 for no increase
|
||||
}
|
||||
if (msg.backwardsGetNexts) {
|
||||
options.backwardsGetNexts = msg.backwardsGetNexts;//boolean to allow GetNext operations to retrieve lexicographically preceding OIDs
|
||||
}
|
||||
if (msg.idBitsSize === 16 || msg.idBitsSize === 32) {
|
||||
options.idBitsSize = msg.idBitsSize;//Either 16 or 32, defaults to 32. Used to reduce the size of the generated id for compatibility with some older devices.
|
||||
}
|
||||
const ipv = parseIP(host);
|
||||
if (ipv.version === 4) {
|
||||
host = ipv.ip;
|
||||
options.port = ipv.port || options.port;
|
||||
options.transport = 'udp4';
|
||||
} else if (ipv.version === 6) {
|
||||
host = ipv.ip;
|
||||
options.port = ipv.port || options.port;
|
||||
options.transport = 'udp6';
|
||||
} else {
|
||||
//probably a host name
|
||||
if (host.indexOf(":") > 0) {
|
||||
host = host.split(":")[0];
|
||||
options.port = host.split(":")[1];
|
||||
}
|
||||
}
|
||||
return {
|
||||
host: host,
|
||||
sessionid: sessionid,
|
||||
user: user,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
function parseIP(ip) {
|
||||
const IPV4_PAT = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::(\d+)){0,1}$/g;
|
||||
const IPV6_DOUBLE_COL_PAT = /^\[{0,1}([0-9a-f:]*)::([0-9a-f:]*)(?:\]:(\d+)){0,1}$/g;
|
||||
const ipv4Matcher = IPV4_PAT.exec(ip);
|
||||
let hex = "";
|
||||
let port;
|
||||
let ipOnly = [];
|
||||
try {
|
||||
|
||||
if (ipv4Matcher && ipv4Matcher.length) {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
ipOnly.push(ipv4Matcher[i]);
|
||||
hex += toHex4(ipv4Matcher[i]);
|
||||
}
|
||||
if (ipv4Matcher[5]) {
|
||||
port = parseInt(ipv4Matcher[5]);
|
||||
}
|
||||
return { ip: ipOnly.join("."), hex, port, version: 4 };
|
||||
}
|
||||
|
||||
// IPV6 Must be colons format (a:b:c:d:e:A.B.C.D not currently supported)
|
||||
let ipv6Pattern = "^\\[{0,1}";
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
ipv6Pattern += "([0-9a-f]+):";
|
||||
}
|
||||
ipv6Pattern += "([0-9a-f]+)(?:\\]:(\\d+)){0,1}$";
|
||||
const IPV6_PAT = new RegExp(ipv6Pattern);
|
||||
|
||||
|
||||
// IPV6, double colon
|
||||
const ipv6DoubleColonMatcher = IPV6_DOUBLE_COL_PAT.exec(ip);
|
||||
if (ipv6DoubleColonMatcher && ipv6DoubleColonMatcher.length) {
|
||||
let p1 = ipv6DoubleColonMatcher[1];
|
||||
if (!p1) {
|
||||
p1 = "0";
|
||||
}
|
||||
let p2 = ipv6DoubleColonMatcher[2];
|
||||
if (!p2) {
|
||||
p2 = "0";
|
||||
}
|
||||
p1 = p1.padStart(4, "0");
|
||||
p2 = p2.padStart(4, "0");
|
||||
ip = p1 + getZeros(8 - numCount(p1) - numCount(p2)) + p2;
|
||||
if (ipv6DoubleColonMatcher[3]) {
|
||||
ip = "[" + ip + "]:" + ipv6DoubleColonMatcher[3];
|
||||
}
|
||||
}
|
||||
|
||||
// IPV6
|
||||
const ipv6Matcher = IPV6_PAT.exec(ip);
|
||||
if (ipv6Matcher && ipv6Matcher.length) {
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
const p = toHex6(ipv6Matcher[i]).padStart(4, "0");
|
||||
ipOnly.push(p);
|
||||
hex += p;
|
||||
}
|
||||
if (ipv6Matcher[9]) {
|
||||
port = parseInt(ipv6Matcher[9]);
|
||||
}
|
||||
return { ip: ipOnly.join(":"), hex, port, version: 6 };
|
||||
}
|
||||
|
||||
throw new Error("Unknown address: " + ip);
|
||||
} catch (error) {
|
||||
return { ip, hex, port, version: null, error: error };
|
||||
}
|
||||
|
||||
function numCount(/** @type {string} */s) {
|
||||
return s.split(":").length;
|
||||
}
|
||||
function getZeros(/** @type {number} */ count) {
|
||||
const sb = [":"];
|
||||
while (count > 0) {
|
||||
sb.push("0000:");
|
||||
count--;
|
||||
}
|
||||
return sb.join("");
|
||||
}
|
||||
function toHex4(/** @type {string} */ s) {
|
||||
const val = parseInt(s);
|
||||
if (val < 0 || val > 255) {
|
||||
throw new Error("Invalid value : " + s);
|
||||
}
|
||||
return val.toString(16).padStart(2, "0");
|
||||
}
|
||||
function toHex6(/** @type {string} */ s) {
|
||||
const val = parseInt(s, 16);
|
||||
if (val < 0 || val > 65536) {
|
||||
throw new Error("Invalid hex value : " + s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
function SnmpNode(n) {
|
||||
const node = this;
|
||||
RED.nodes.createNode(node, n);
|
||||
initSnmpNode(node, n);
|
||||
node.oids = n.oids ? n.oids.replace(/\s/g, "") : "";
|
||||
|
||||
node.on("input", function (msg) {
|
||||
const oids = node.oids || msg.oid;
|
||||
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
|
||||
if (oids) {
|
||||
getSession(host, community, node.version, node.timeout).get(oids.split(","), function (error, varbinds) {
|
||||
let sess = openSession(sessionid, host, user, options);
|
||||
sess.on("error", function (err) {
|
||||
node.error(err, msg);
|
||||
})
|
||||
sess.get(oids.split(","), function (error, varbinds) {
|
||||
if (error) {
|
||||
node.error(error.toString(), msg);
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < varbinds.length; i++) {
|
||||
if (snmp.isVarbindError(varbinds[i])) {
|
||||
node.error(snmp.varbindError(varbinds[i]), msg);
|
||||
}
|
||||
else {
|
||||
if (varbinds[i].type == 4) { varbinds[i].value = varbinds[i].value.toString(); }
|
||||
varbinds[i].tstr = snmp.ObjectType[varbinds[i].type];
|
||||
//node.log(varbinds[i].oid + "|" + varbinds[i].tstr + "|" + varbinds[i].value);
|
||||
} else {
|
||||
for (let i = 0; i < varbinds.length; i++) {
|
||||
let vb = varbinds[i];
|
||||
if (SNMP.isVarbindError(vb)) {
|
||||
node.error(SNMP.varbindError(vb), msg);
|
||||
vb._error = SNMP.varbindError(vb); //add _error to msg so users can determine the varbind is not valid
|
||||
}
|
||||
// else {
|
||||
// if (vb.type == 4) { vb.value = vb.value.toString(); }
|
||||
// }
|
||||
vb.tstr = SNMP.ObjectType[vb.type];
|
||||
}
|
||||
msg.oid = oids;
|
||||
msg.payload = varbinds;
|
||||
msg.oid = oids;
|
||||
node.send(msg);
|
||||
}
|
||||
closeSession(sessionid); // Needed to close the session else a bad or good read could affect future readings
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
node.warn("No oid(s) to search for");
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("snmp", SnmpNode);
|
||||
RED.nodes.registerType("snmp", SnmpNode, {
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
}
|
||||
});
|
||||
|
||||
function SnmpSNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.community = n.community;
|
||||
this.host = n.host;
|
||||
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
|
||||
this.varbinds = n.varbinds;
|
||||
this.timeout = Number(n.timeout || 5) * 1000;
|
||||
if (this.varbinds && this.varbinds.trim().length === 0) { delete this.varbinds; }
|
||||
var node = this;
|
||||
this.on("input", function (msg) {
|
||||
var host = node.host || msg.host;
|
||||
var community = node.community || msg.community;
|
||||
var varbinds = (node.varbinds) ? JSON.parse(node.varbinds) : msg.varbinds;
|
||||
const node = this;
|
||||
RED.nodes.createNode(node, n);
|
||||
initSnmpNode(node, n);
|
||||
node.varbinds = n.varbinds;
|
||||
if (node.varbinds && node.varbinds.trim().length === 0) { delete node.varbinds; }
|
||||
node.on("input", function (msg) {
|
||||
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
|
||||
const varbinds = (node.varbinds) ? JSON.parse(node.varbinds) : msg.varbinds;
|
||||
if (varbinds) {
|
||||
for (var i = 0; i < varbinds.length; i++) {
|
||||
varbinds[i].type = snmp.ObjectType[varbinds[i].type];
|
||||
for (let i = 0; i < varbinds.length; i++) {
|
||||
varbinds[i].type = SNMP.ObjectType[varbinds[i].type];
|
||||
}
|
||||
getSession(host, community, node.version, node.timeout).set(varbinds, function (error, varbinds) {
|
||||
let sess = openSession(sessionid, host, user, options);
|
||||
sess.on("error", function (err) {
|
||||
node.error(err, msg);
|
||||
})
|
||||
sess.set(varbinds, function (error, varbinds) {
|
||||
if (error) {
|
||||
node.error(error.toString(), msg);
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < varbinds.length; i++) {
|
||||
} else {
|
||||
for (let i = 0; i < varbinds.length; i++) {
|
||||
// for version 2c we must check each OID for an error condition
|
||||
if (snmp.isVarbindError(varbinds[i])) {
|
||||
node.error(snmp.varbindError(varbinds[i]), msg);
|
||||
if (SNMP.isVarbindError(varbinds[i])) {
|
||||
node.error(SNMP.varbindError(varbinds[i]), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
closeSession(sessionid);
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
node.warn("No varbinds to set");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
RED.nodes.registerType("snmp set", SnmpSNode);
|
||||
RED.nodes.registerType("snmp set", SnmpSNode, {
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function SnmpTNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.community = n.community;
|
||||
this.host = n.host;
|
||||
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
|
||||
this.oids = n.oids.replace(/\s/g, "");
|
||||
this.timeout = Number(n.timeout || 5) * 1000;
|
||||
var node = this;
|
||||
var maxRepetitions = 20;
|
||||
const node = this;
|
||||
RED.nodes.createNode(node, n);
|
||||
initSnmpNode(node, n);
|
||||
node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
|
||||
const maxRepetitions = 20;
|
||||
|
||||
function sortInt(a, b) {
|
||||
if (a > b) { return 1; }
|
||||
else if (b > a) { return -1; }
|
||||
else { return 0; }
|
||||
else if (b > a) { return -1; } else { return 0; }
|
||||
}
|
||||
|
||||
this.on("input", function (msg) {
|
||||
var host = node.host || msg.host;
|
||||
var community = node.community || msg.community;
|
||||
var oids = node.oids || msg.oid;
|
||||
node.on("input", function (msg) {
|
||||
const oids = node.oids || msg.oid;
|
||||
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
|
||||
if (oids) {
|
||||
msg.oid = oids;
|
||||
getSession(host, community, node.version, node.timeout).table(oids, maxRepetitions, function (error, table) {
|
||||
let sess = openSession(sessionid, host, user, options);
|
||||
sess.on("error", function (err) {
|
||||
node.error(err, msg);
|
||||
})
|
||||
sess.table(oids, maxRepetitions, function (error, table) {
|
||||
if (error) {
|
||||
node.error(error.toString(), msg);
|
||||
}
|
||||
else {
|
||||
var indexes = [];
|
||||
for (var index in table) {
|
||||
} else {
|
||||
const indexes = [];
|
||||
for (let index in table) {
|
||||
if (table.hasOwnProperty(index)) {
|
||||
indexes.push(parseInt(index));
|
||||
}
|
||||
}
|
||||
indexes.sort(sortInt);
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
var columns = [];
|
||||
for (var column in table[indexes[i]]) {
|
||||
for (let i = 0; i < indexes.length; i++) {
|
||||
const columns = [];
|
||||
for (let column in table[indexes[i]]) {
|
||||
if (table[indexes[i]].hasOwnProperty(column)) {
|
||||
columns.push(parseInt(column));
|
||||
}
|
||||
}
|
||||
columns.sort(sortInt);
|
||||
// console.log("row index = " + indexes[i]);
|
||||
// for (var j = 0; j < columns.length; j++) {
|
||||
// console.log(" column " + columns[j] + " = " + table[indexes[i]][columns[j]]);
|
||||
// }
|
||||
}
|
||||
msg.payload = table;
|
||||
node.send(msg);
|
||||
}
|
||||
closeSession(sessionid);
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
node.warn("No oid to search for");
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("snmp table", SnmpTNode);
|
||||
RED.nodes.registerType("snmp table", SnmpTNode, {
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function SnmpSubtreeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.community = n.community;
|
||||
this.host = n.host;
|
||||
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
|
||||
this.oids = n.oids.replace(/\s/g, "");
|
||||
this.timeout = Number(n.timeout || 5) * 1000;
|
||||
var node = this;
|
||||
var maxRepetitions = 20;
|
||||
var response = [];
|
||||
const node = this;
|
||||
RED.nodes.createNode(node, n);
|
||||
initSnmpNode(node, n);
|
||||
node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
|
||||
const maxRepetitions = 20;
|
||||
|
||||
function feedCb(varbinds) {
|
||||
for (var i = 0; i < varbinds.length; i++) {
|
||||
if (snmp.isVarbindError(varbinds[i])) {
|
||||
node.error(snmp.varbindError(varbinds[i]), msg);
|
||||
}
|
||||
else {
|
||||
//console.log(varbinds[i].oid + "|" + varbinds[i].value);
|
||||
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
|
||||
node.on("input", function (msg) {
|
||||
const oids = node.oids || msg.oid;
|
||||
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
|
||||
const response = [];
|
||||
function feedCb(varbinds) {
|
||||
for (let i = 0; i < varbinds.length; i++) {
|
||||
if (SNMP.isVarbindError(varbinds[i])) {
|
||||
node.error(SNMP.varbindError(varbinds[i]), msg);
|
||||
} else {
|
||||
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.on("input", function (msg) {
|
||||
var host = node.host || msg.host;
|
||||
var community = node.community || msg.community;
|
||||
var oids = node.oids || msg.oid;
|
||||
if (oids) {
|
||||
msg.oid = oids;
|
||||
getSession(host, community, node.version, node.timeout).subtree(msg.oid, maxRepetitions, feedCb, function (error) {
|
||||
let sess = openSession(sessionid, host, user, options);
|
||||
sess.on("error", function (err) {
|
||||
node.error(err, msg);
|
||||
})
|
||||
sess.subtree(msg.oid, maxRepetitions, feedCb, function (error) {
|
||||
if (error) {
|
||||
node.error(error.toString(), msg);
|
||||
}
|
||||
else {
|
||||
// Clone the array
|
||||
msg.payload = response.slice(0);
|
||||
} else {
|
||||
msg.payload = response;
|
||||
node.send(msg);
|
||||
//Clears response
|
||||
response.length = 0;
|
||||
}
|
||||
closeSession(sessionid);
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
node.warn("No oid to search for");
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("snmp subtree", SnmpSubtreeNode);
|
||||
|
||||
RED.nodes.registerType("snmp subtree", SnmpSubtreeNode, {
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
}
|
||||
});
|
||||
|
||||
function SnmpWalkerNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.community = n.community;
|
||||
this.host = n.host;
|
||||
this.version = (n.version === "2c") ? snmp.Version2c : snmp.Version1;
|
||||
this.oids = n.oids.replace(/\s/g, "");
|
||||
this.timeout = Number(n.timeout || 5) * 1000;
|
||||
var node = this;
|
||||
var maxRepetitions = 20;
|
||||
var response = [];
|
||||
const node = this;
|
||||
RED.nodes.createNode(node, n);
|
||||
initSnmpNode(node, n);
|
||||
node.oids = n.oids ? n.oids.replace(/\s/g, "") : ""
|
||||
const maxRepetitions = 20;
|
||||
|
||||
function feedCb(varbinds) {
|
||||
for (var i = 0; i < varbinds.length; i++) {
|
||||
if (snmp.isVarbindError(varbinds[i])) {
|
||||
node.error(snmp.varbindError(varbinds[i]), msg);
|
||||
}
|
||||
else {
|
||||
//console.log(varbinds[i].oid + "|" + varbinds[i].value);
|
||||
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
|
||||
node.on("input", function (msg) {
|
||||
const oids = node.oids || msg.oid;
|
||||
const { host, sessionid, user, options } = prepareSnmpOptions(node, msg);
|
||||
const response = [];
|
||||
function feedCb(varbinds) {
|
||||
for (let i = 0; i < varbinds.length; i++) {
|
||||
if (SNMP.isVarbindError(varbinds[i])) {
|
||||
node.error(SNMP.varbindError(varbinds[i]), msg);
|
||||
} else {
|
||||
response.push({ oid: varbinds[i].oid, value: varbinds[i].value });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.on("input", function (msg) {
|
||||
node.msg = msg;
|
||||
var oids = node.oids || msg.oid;
|
||||
var host = node.host || msg.host;
|
||||
var community = node.community || msg.community;
|
||||
if (oids) {
|
||||
msg.oid = oids;
|
||||
getSession(host, community, node.version, node.timeout).walk(msg.oid, maxRepetitions, feedCb, function (error) {
|
||||
let sess = openSession(sessionid, host, user, options);
|
||||
sess.on("error", function (err) {
|
||||
node.error(err, msg);
|
||||
})
|
||||
sess.walk(msg.oid, maxRepetitions, feedCb, function (error) {
|
||||
if (error) {
|
||||
node.error(error.toString(), msg);
|
||||
}
|
||||
else {
|
||||
// Clone the array
|
||||
msg.payload = response.slice(0);
|
||||
} else {
|
||||
msg.payload = response;
|
||||
node.send(msg);
|
||||
//Clears response
|
||||
response.length = 0;
|
||||
}
|
||||
closeSession(sessionid);
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
node.warn("No oid to search for");
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("snmp walker", SnmpWalkerNode);
|
||||
RED.nodes.registerType("snmp walker", SnmpWalkerNode, {
|
||||
credentials: {
|
||||
username: { type: "text" },
|
||||
authkey: { type: "password" },
|
||||
privkey: { type: "password" }
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -57,6 +57,10 @@ module.exports = function(RED) {
|
||||
node.warn("reconnecting");
|
||||
});
|
||||
|
||||
node.client.on("reconnect", function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
|
||||
node.client.on("error", function(error) {
|
||||
node.status({fill:"grey",shape:"dot",text:"error"});
|
||||
node.warn(error);
|
||||
@ -124,6 +128,10 @@ module.exports = function(RED) {
|
||||
node.warn("reconnecting");
|
||||
});
|
||||
|
||||
node.client.on("reconnect", function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
|
||||
node.client.on("error", function(error) {
|
||||
node.status({fill:"grey",shape:"dot",text:"error"});
|
||||
node.warn(error);
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-stomp",
|
||||
"version" : "0.0.12",
|
||||
"version" : "0.0.14",
|
||||
"description" : "A Node-RED node to publish and subscribe to/from a Stomp server",
|
||||
"dependencies" : {
|
||||
"stomp-client" : "^0.9.0"
|
||||
@ -19,7 +19,7 @@
|
||||
},
|
||||
"author": {
|
||||
"name": "Dave Conway-Jones",
|
||||
"email": "ceejay@vnet.ibm.com",
|
||||
"email": "dceejay@gmail.com",
|
||||
"url": "http://nodered.org"
|
||||
}
|
||||
}
|
||||
|
16
package.json
16
package.json
@ -33,29 +33,29 @@
|
||||
"devDependencies": {
|
||||
"exif": "^0.6.0",
|
||||
"feedparser": "^2.2.10",
|
||||
"grunt": "^1.4.1",
|
||||
"grunt": "^1.6.1",
|
||||
"grunt-cli": "^1.4.3",
|
||||
"grunt-contrib-jshint": "^2.1.0",
|
||||
"grunt-jscs": "^3.0.1",
|
||||
"grunt-lint-inline": "^1.0.0",
|
||||
"grunt-simple-mocha": "^0.4.1",
|
||||
"imap": "^0.8.19",
|
||||
"mailparser": "^3.4.0",
|
||||
"markdown-it": "^12.3.0",
|
||||
"mailparser": "^3.6.4",
|
||||
"markdown-it": "^12.3.2",
|
||||
"mocha": "~6.2.3",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"multilang-sentiment": "^1.2.0",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-red": "^2.1.4",
|
||||
"node-red-node-test-helper": "^0.2.7",
|
||||
"nodemailer": "^6.7.2",
|
||||
"poplib": "^0.1.7",
|
||||
"node-red": "^3.0.2",
|
||||
"node-red-node-test-helper": "^0.3.0",
|
||||
"nodemailer": "^6.9.1",
|
||||
"node-pop3": "^0.8.0",
|
||||
"proxyquire": "^2.1.3",
|
||||
"pushbullet": "^2.4.0",
|
||||
"sentiment": "^2.1.0",
|
||||
"should": "^13.2.3",
|
||||
"sinon": "~7.5.0",
|
||||
"smtp-server": "^3.10.0",
|
||||
"smtp-server": "^3.11.0",
|
||||
"supertest": "^4.0.2",
|
||||
"when": "^3.7.8"
|
||||
},
|
||||
|
44
parsers/cbor/70-cbor.html
Normal file
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",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "A Node-RED node to convert a markdown string to html.",
|
||||
"dependencies": {
|
||||
"markdown-it": "^12.3.2"
|
||||
"markdown-it": "^13.0.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -16,6 +16,7 @@
|
||||
"markdown"
|
||||
],
|
||||
"node-red": {
|
||||
"version": ">=1.0.0",
|
||||
"nodes": {
|
||||
"markdown": "70-markdown.js"
|
||||
}
|
||||
@ -24,5 +25,8 @@
|
||||
"name": "Dave Conway-Jones",
|
||||
"email": "ceejay@vnet.ibm.com",
|
||||
"url": "http://nodered.org"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
|
@ -40,15 +40,32 @@
|
||||
<span data-i18n="email.label.useSecureConnection"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-authtype"><i class="fa fa-tasks"></i> <span data-i18n="email.label.authtype"></span></label>
|
||||
<select type="text" id="node-input-authtype">
|
||||
<option value="BASIC">Basic</option>
|
||||
<option value="XOAUTH2">XOAuth2</option>
|
||||
<option value="NONE">None</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-userid">
|
||||
<label for="node-input-userid"><i class="fa fa-user"></i> <span data-i18n="email.label.userid"></span></label>
|
||||
<input type="text" id="node-input-userid">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-row node-input-password">
|
||||
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="email.label.password"></span></label>
|
||||
<input type="password" id="node-input-password">
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-saslformat" style="display: none;">
|
||||
<label for="node-input-saslformat"><i class="fa fa-code"></i> <span data-i18n="email.label.saslformat"></span></label>
|
||||
<input type="checkbox" id="node-input-saslformat" style="width: auto;">
|
||||
</div>
|
||||
<div class="form-row node-input-token" style="display: none;">
|
||||
<label for="node-input-token"><i class="fa fa-lock"></i> <span data-i18n="email.label.token"></span></label>
|
||||
<input type="text" id="node-input-token" placeholder="oauth2Response.access_token">
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<div class="form-row node-input-useTLS">
|
||||
<label for="node-input-useTLS"><i class="fa fa-lock"></i> <span data-i18n="email.label.useTLS"></label>
|
||||
<input type="checkbox" id="node-input-tls" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<span data-i18n="email.label.rejectUnauthorised"></span>
|
||||
@ -68,6 +85,9 @@
|
||||
defaults: {
|
||||
server: {value:"smtp.gmail.com",required:true},
|
||||
port: {value:"465",required:true},
|
||||
authtype: {value: "BASIC"},
|
||||
saslformat: {value: true},
|
||||
token: {value: "oauth2Response.access_token"},
|
||||
secure: {value: true},
|
||||
tls: {value: true},
|
||||
name: {value:""},
|
||||
@ -92,11 +112,43 @@
|
||||
return (this.dname)?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.authtype === undefined) {
|
||||
this.authtype = "BASIC";
|
||||
$("#node-input-authtype").val('BASIC');
|
||||
}
|
||||
$("#node-input-authtype").change(function() {
|
||||
var protocol = $("#node-input-authtype").val();
|
||||
if (protocol === "BASIC") {
|
||||
$(".node-input-userid").show();
|
||||
$(".node-input-password").show();
|
||||
$(".node-input-saslformat").hide();
|
||||
$(".node-input-token").hide();
|
||||
$(".node-input-useTLS").show();
|
||||
} else if (protocol === "NONE") {
|
||||
$(".node-input-userid").hide();
|
||||
$(".node-input-password").hide();
|
||||
$(".node-input-saslformat").hide();
|
||||
$(".node-input-token").hide();
|
||||
$(".node-input-useTLS").hide();
|
||||
} else {
|
||||
$(".node-input-userid").show();
|
||||
$(".node-input-password").hide();
|
||||
$(".node-input-saslformat").show();
|
||||
$(".node-input-token").show();
|
||||
$("#node-input-fetch").val("trigger");
|
||||
$("#node-input-fetch").change();
|
||||
$(".node-input-useTLS").show();
|
||||
}
|
||||
});
|
||||
if (this.credentials.global) {
|
||||
$('#node-tip').show();
|
||||
} else {
|
||||
$('#node-tip').hide();
|
||||
}
|
||||
$("#node-input-token").typedInput({
|
||||
type:'msg',
|
||||
types:['msg']
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
@ -143,13 +195,29 @@
|
||||
<input type="text" id="node-input-port" placeholder="993">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-authtype"><i class="fa fa-tasks"></i> <span data-i18n="email.label.authtype"></span></label>
|
||||
<select type="text" id="node-input-authtype">
|
||||
<option value="BASIC">Basic</option>
|
||||
<option value="XOAUTH2">XOAuth2</option>
|
||||
<option value="NONE">None</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-userid" id="node-userid">
|
||||
<label for="node-input-userid"><i class="fa fa-user"></i> <span data-i18n="email.label.userid"></span></label>
|
||||
<input type="text" id="node-input-userid">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-row node-input-password" id="node-password">
|
||||
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="email.label.password"></span></label>
|
||||
<input type="password" id="node-input-password">
|
||||
</div>
|
||||
<div class="form-row node-input-saslformat" style="display: none;">
|
||||
<label for="node-input-saslformat"><i class="fa fa-code"></i> <span data-i18n="email.label.saslformat"></span></label>
|
||||
<input type="checkbox" id="node-input-saslformat" style="width: auto;">
|
||||
</div>
|
||||
<div class="form-row node-input-token" style="display: none;">
|
||||
<label for="node-input-token"><i class="fa fa-lock"></i> <span data-i18n="email.label.token"></span></label>
|
||||
<input type="text" id="node-input-token" placeholder="oauth2Response.access_token">
|
||||
</div>
|
||||
<div class="form-row node-input-box">
|
||||
<label for="node-input-box"><i class="fa fa-inbox"></i> <span data-i18n="email.label.folder"></span></label>
|
||||
<input type="text" id="node-input-box">
|
||||
@ -222,6 +290,29 @@
|
||||
}
|
||||
checkPorts();
|
||||
});
|
||||
|
||||
$("#node-input-authtype").change(function() {
|
||||
var protocol = $("#node-input-authtype").val();
|
||||
if (protocol === "BASIC") {
|
||||
$(".node-input-userid").show();
|
||||
$(".node-input-password").show();
|
||||
$(".node-input-saslformat").hide();
|
||||
$(".node-input-token").hide();
|
||||
} else if (protocol === "NONE") {
|
||||
$(".node-input-userid").hide();
|
||||
$(".node-input-password").hide();
|
||||
$(".node-input-saslformat").hide();
|
||||
$(".node-input-token").hide();
|
||||
}
|
||||
else {
|
||||
$(".node-input-userid").show();
|
||||
$(".node-input-password").hide();
|
||||
$(".node-input-saslformat").show();
|
||||
$(".node-input-token").show();
|
||||
$("#node-input-fetch").val("trigger");
|
||||
$("#node-input-fetch").change();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@ -237,6 +328,9 @@
|
||||
useSSL: {value: true},
|
||||
autotls: {value: "never"},
|
||||
port: {value:"993",required:true},
|
||||
authtype: {value: "BASIC"},
|
||||
saslformat: {value: true},
|
||||
token: {value: "oauth2Response.access_token"},
|
||||
box: {value:"INBOX"}, // For IMAP, The mailbox to process
|
||||
disposition: { value: "Read" }, // For IMAP, the disposition of the read email
|
||||
criteria: {value: "UNSEEN"},
|
||||
@ -263,6 +357,10 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
if (this.authtype === undefined) {
|
||||
this.authtype = "BASIC";
|
||||
$("#node-input-authtype").val('BASIC');
|
||||
}
|
||||
if (this.credentials.global) {
|
||||
$('#node-tip').show();
|
||||
} else {
|
||||
@ -289,6 +387,8 @@
|
||||
else {
|
||||
$('#node-repeatTime').show();
|
||||
that.inputs = 0;
|
||||
$("#node-input-authtype").val("BASIC");
|
||||
$("#node-input-authtype").change();
|
||||
}
|
||||
});
|
||||
$("#node-input-criteria").change(function() {
|
||||
@ -297,6 +397,10 @@
|
||||
$("#node-input-fetch").change();
|
||||
}
|
||||
});
|
||||
$("#node-input-token").typedInput({
|
||||
type:'msg',
|
||||
types:['msg']
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
@ -306,7 +410,46 @@
|
||||
<script type="text/html" data-template-name="e-mail mta">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-random"></i> <span data-i18n="email.label.port"></span></label>
|
||||
<input type="text" id="node-input-port" style="width:70%;"/>
|
||||
<input type="text" id="node-input-port" style="width:100px">
|
||||
<label style="width:40px"> </label>
|
||||
<input type="checkbox" id="node-input-secure" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<span data-i18n="email.label.enableSecure"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-starttls"><i class="fa fa-lock"></i> <span data-i18n="email.label.enableStarttls"></span></label>
|
||||
<input type="checkbox" id="node-input-starttls" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<span data-i18n="email.label.starttlsUpgrade"></span>
|
||||
</div>
|
||||
<div class="form-row" id="certRow">
|
||||
<label for="node-input-certFile"><i class="fa fa-file"></i>
|
||||
<span data-i18n="email.label.certFile"></span></label>
|
||||
<input type="text" id="node-input-certFile" placeholder="server.crt" style="width:100%">
|
||||
</div>
|
||||
<div class="form-row" id="keyRow">
|
||||
<label for="node-input-keyFile"><i class="fa fa-key"></i>
|
||||
<span data-i18n="email.label.keyFile"></span></label>
|
||||
<input type="text" id="node-input-keyFile" placeholder="private.key" style="width:100%">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-auth"><i class="fa fa-user"></i> <span data-i18n="email.label.users"></span></label>
|
||||
<input type="checkbox" id="node-input-auth" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<span data-i18n="email.label.auth"></span>
|
||||
</div>
|
||||
<div class="form-row node-input-email-users-container-row" style="margin-bottom: 0px;">
|
||||
<div id="node-input-email-users-container-div" style="box-sizing: border-box; border-radius: 5px;
|
||||
height: 200px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
||||
<ol id="node-input-email-users-container" style="list-style-type:none; margin: 0;"></ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<a href="#" class="editor-button editor-button-small" id="node-input-email-users-add" style="margin-top: 4px;">
|
||||
<i class="fa fa-plus"></i>
|
||||
<span data-i18n="email.label.addButton"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-expert"><i class="fa fa-cog"></i> <span data-i18n="email.label.expert"></span></label>
|
||||
<input type="text" id="node-input-expert">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
||||
@ -316,22 +459,148 @@
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('e-mail mta',{
|
||||
RED.nodes.registerType('e-mail mta', {
|
||||
category: 'social',
|
||||
color:"#c7e9c0",
|
||||
color: "#c7e9c0",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
port: {value:"1025",required:true},
|
||||
name: { value: "" },
|
||||
port: { value: "1025", required: true, validate: RED.validators.number() },
|
||||
secure: { value: false },
|
||||
starttls: { value: false },
|
||||
certFile: { value: "" },
|
||||
keyFile: { value: "" },
|
||||
users: { value: [] },
|
||||
auth: { value: false },
|
||||
expert: { value: '{"logger":false}' }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
inputs: 0,
|
||||
outputs: 1,
|
||||
icon: "envelope.png",
|
||||
paletteLabel: function() { return this._("email.email") + " MTA" },
|
||||
label: function() {
|
||||
return this.name||this._("email.email") + " MTA";
|
||||
paletteLabel: function () { return this._("email.email") + " MTA" },
|
||||
label: function () {
|
||||
return this.name || this._("email.email") + " MTA";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
labelStyle: function () {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
let node = this;
|
||||
// Certificate settings
|
||||
$("#node-input-secure").change(secVisibility);
|
||||
$("#node-input-starttls").change(secVisibility);
|
||||
function secVisibility() {
|
||||
if ($("#node-input-secure").is(":checked") || $("#node-input-starttls").is(":checked")) {
|
||||
$("#certRow").show();
|
||||
$("#keyRow").show();
|
||||
} else {
|
||||
$("#certRow").hide();
|
||||
$("#keyRow").hide();
|
||||
}
|
||||
}
|
||||
// User Management
|
||||
let cacheItemCount = 0;
|
||||
if (node.users && node.users.length > 0) {
|
||||
cacheItemCount = node.users.length;
|
||||
node.users.forEach(function (element, index, array) {
|
||||
generateUserEntry(element, index);
|
||||
});
|
||||
}
|
||||
function generateUserEntry(user, id) {
|
||||
let container = $("<li/>", {
|
||||
style: "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"
|
||||
});
|
||||
let row = $('<div id="row' + id + '"/>').appendTo(container);
|
||||
|
||||
$('<i style="color: #eee; cursor: move;" class="node-input-email-users-handle fa fa-bars"></i>').appendTo(row);
|
||||
|
||||
let userField = $("<input/>", {
|
||||
id: "node-input-email-users-name" + id,
|
||||
class: "userName",
|
||||
type: "text",
|
||||
style: "margin-left:5px;width:100px;",
|
||||
placeholder: "name"
|
||||
}).appendTo(row);
|
||||
|
||||
let passwordField = $("<input/>", {
|
||||
id: "node-input-email-users-password" + id,
|
||||
class: "userPassword",
|
||||
type: "password",
|
||||
style: "margin: 0 auto;width:50%;min-width:20px;margin-left:5px",
|
||||
placeholder: "password"
|
||||
}).appendTo(row);
|
||||
|
||||
userField.val(user.name);
|
||||
passwordField.val(user.password);
|
||||
|
||||
let finalspan = $("<span/>", {
|
||||
style: "float: right;margin-right: 10px;"
|
||||
}).appendTo(row);
|
||||
|
||||
let removeUserButton = $("<a/>", {
|
||||
href: "#",
|
||||
id: "node-button-user-remove" + id,
|
||||
class: "editor-button editor-button-small",
|
||||
style: "margin-top: 7px; margin-left: 5px;"
|
||||
}).appendTo(finalspan);
|
||||
|
||||
$("<i/>", { class: "fa fa-remove" }).appendTo(removeUserButton);
|
||||
|
||||
removeUserButton.click(function () {
|
||||
container.css({ background: "#fee" });
|
||||
container.fadeOut(300, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
});
|
||||
|
||||
$("#node-input-email-users-container").append(container);
|
||||
}
|
||||
|
||||
$("#node-input-email-users-container").sortable({
|
||||
axis: "y",
|
||||
handle: ".node-input-email-users-handle",
|
||||
cursor: "move"
|
||||
});
|
||||
|
||||
$("#node-input-email-users-container .node-input-email-users-handle").disableSelection();
|
||||
|
||||
$("#node-input-email-users-add").click(function () {
|
||||
if (!cacheItemCount || cacheItemCount < 0) {
|
||||
cacheItemCount = 0;
|
||||
}
|
||||
generateUserEntry({ name: "", password: "" }, cacheItemCount++);
|
||||
$("#node-input-email-users-container-div").scrollTop(
|
||||
$("#node-input-email-users-container-div").get(0).scrollHeight
|
||||
);
|
||||
});
|
||||
$("#node-input-auth").change(function () {
|
||||
if ($("#node-input-auth").is(":checked")) {
|
||||
$("#node-input-email-users-add").show();
|
||||
$("#node-input-email-users-container-div").show();
|
||||
} else {
|
||||
$("#node-input-email-users-add").hide();
|
||||
$("#node-input-email-users-container-div").hide();
|
||||
}
|
||||
});
|
||||
// Expert settings
|
||||
$("#node-input-expert").typedInput({
|
||||
type: "json",
|
||||
types: ["json"]
|
||||
})
|
||||
},
|
||||
oneditsave: function () {
|
||||
let node = this;
|
||||
let cacheUsers = $("#node-input-email-users-container").children();
|
||||
node.users = [];
|
||||
cacheUsers.each(function () {
|
||||
node.users.push({
|
||||
name: $(this)
|
||||
.find(".userName")
|
||||
.val(),
|
||||
password: $(this)
|
||||
.find(".userPassword")
|
||||
.val()
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
@ -1,10 +1,12 @@
|
||||
/* eslint-disable indent */
|
||||
|
||||
const { domainToUnicode } = require("url");
|
||||
|
||||
/**
|
||||
* POP3 protocol - RFC1939 - https://www.ietf.org/rfc/rfc1939.txt
|
||||
*
|
||||
* Dependencies:
|
||||
* * poplib - https://www.npmjs.com/package/poplib
|
||||
* * node-pop3 - https://www.npmjs.com/package/node-pop3
|
||||
* * nodemailer - https://www.npmjs.com/package/nodemailer
|
||||
* * imap - https://www.npmjs.com/package/imap
|
||||
* * mailparser - https://www.npmjs.com/package/mailparser
|
||||
@ -14,22 +16,17 @@ module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var Imap = require('imap');
|
||||
var POP3Client = require("./poplib.js");
|
||||
var Pop3Command = require("node-pop3");
|
||||
var nodemailer = require("nodemailer");
|
||||
var simpleParser = require("mailparser").simpleParser;
|
||||
var SMTPServer = require("smtp-server").SMTPServer;
|
||||
//var microMTA = require("micromta").microMTA;
|
||||
var fs = require('fs');
|
||||
|
||||
if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) {
|
||||
throw "Error : Requires nodejs version >= 8.";
|
||||
}
|
||||
|
||||
try {
|
||||
var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
|
||||
function EmailNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.topic = n.topic;
|
||||
@ -38,26 +35,33 @@ module.exports = function(RED) {
|
||||
this.outport = n.port;
|
||||
this.secure = n.secure;
|
||||
this.tls = true;
|
||||
var flag = false;
|
||||
this.authtype = n.authtype || "BASIC";
|
||||
if (this.authtype !== "BASIC") {
|
||||
this.inputs = 1;
|
||||
this.repeat = 0;
|
||||
}
|
||||
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
|
||||
this.userid = this.credentials.userid;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.userid = globalkeys.user;
|
||||
flag = true;
|
||||
}
|
||||
else if (this.authtype !== "NONE") {
|
||||
this.error(RED._("email.errors.nouserid"));
|
||||
}
|
||||
if (this.authtype === "BASIC" ) {
|
||||
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
||||
this.password = this.credentials.password;
|
||||
}
|
||||
else {
|
||||
this.error(RED._("email.errors.nopassword"));
|
||||
}
|
||||
}
|
||||
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
||||
this.password = this.credentials.password;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.password = globalkeys.pass;
|
||||
flag = true;
|
||||
else if (this.authtype === "XOAUTH2") {
|
||||
this.saslformat = n.saslformat;
|
||||
if (n.token !== "") {
|
||||
this.token = n.token;
|
||||
} else {
|
||||
this.error(RED._("email.errors.notoken"));
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
|
||||
}
|
||||
if (n.tls === false) { this.tls = false; }
|
||||
var node = this;
|
||||
|
||||
@ -68,12 +72,28 @@ module.exports = function(RED) {
|
||||
tls: {rejectUnauthorized: node.tls}
|
||||
}
|
||||
|
||||
if (this.userid && this.password) {
|
||||
if (node.authtype === "BASIC" ) {
|
||||
smtpOptions.auth = {
|
||||
user: node.userid,
|
||||
pass: node.password
|
||||
};
|
||||
}
|
||||
else if (node.authtype === "XOAUTH2") {
|
||||
var value = RED.util.getMessageProperty(msg,node.token);
|
||||
if (value !== undefined) {
|
||||
if (node.saslformat) {
|
||||
//Make base64 string for access - compatible with outlook365 and gmail
|
||||
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
|
||||
} else {
|
||||
saslxoauth2 = value;
|
||||
}
|
||||
}
|
||||
smtpOptions.auth = {
|
||||
type: "OAuth2",
|
||||
user: node.userid,
|
||||
accessToken: saslxoauth2
|
||||
};
|
||||
}
|
||||
var smtpTransport = nodemailer.createTransport(smtpOptions);
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
@ -95,7 +115,8 @@ module.exports = function(RED) {
|
||||
sendopts.headers = msg.headers;
|
||||
sendopts.priority = msg.priority;
|
||||
}
|
||||
sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line
|
||||
if (msg.hasOwnProperty("topic") && msg.topic === '') { sendopts.subject = ""; }
|
||||
else { sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; } // subject line
|
||||
if (msg.hasOwnProperty("header") && msg.header.hasOwnProperty("message-id")) {
|
||||
sendopts.inReplyTo = msg.header["message-id"];
|
||||
sendopts.subject = "Re: " + sendopts.subject;
|
||||
@ -116,7 +137,8 @@ module.exports = function(RED) {
|
||||
sendopts.attachments[0].contentType = msg.headers["content-type"];
|
||||
}
|
||||
// Create some body text..
|
||||
sendopts.text = RED._("email.default-message",{filename:fname, description:(msg.description||"")});
|
||||
if (msg.hasOwnProperty("description")) { sendopts.text = msg.description; }
|
||||
else { sendopts.text = RED._("email.default-message",{filename:fname}); }
|
||||
}
|
||||
else {
|
||||
var payload = RED.util.ensureString(msg.payload);
|
||||
@ -173,6 +195,7 @@ module.exports = function(RED) {
|
||||
// Setup the EmailInNode
|
||||
function EmailInNode(n) {
|
||||
var imap;
|
||||
var pop3;
|
||||
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
@ -187,43 +210,45 @@ module.exports = function(RED) {
|
||||
this.repeat = 1500;
|
||||
}
|
||||
if (this.inputs === 1) { this.repeat = 0; }
|
||||
this.inserver = n.server || (globalkeys && globalkeys.server) || "imap.gmail.com";
|
||||
this.inport = n.port || (globalkeys && globalkeys.port) || "993";
|
||||
this.inserver = n.server || "imap.gmail.com";
|
||||
this.inport = n.port || "993";
|
||||
this.box = n.box || "INBOX";
|
||||
this.useSSL= n.useSSL;
|
||||
this.autotls= n.autotls;
|
||||
this.protocol = n.protocol || "IMAP";
|
||||
this.disposition = n.disposition || "None"; // "None", "Delete", "Read"
|
||||
this.criteria = n.criteria || "UNSEEN"; // "ALL", "ANSWERED", "FLAGGED", "SEEN", "UNANSWERED", "UNFLAGGED", "UNSEEN"
|
||||
|
||||
var flag = false;
|
||||
this.authtype = n.authtype || "BASIC";
|
||||
if (this.authtype !== "BASIC") {
|
||||
this.inputs = 1;
|
||||
this.repeat = 0;
|
||||
}
|
||||
|
||||
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
|
||||
this.userid = this.credentials.userid;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.userid = globalkeys.user;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error(RED._("email.errors.nouserid"));
|
||||
}
|
||||
}
|
||||
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
||||
this.password = this.credentials.password;
|
||||
} else {
|
||||
if (globalkeys) {
|
||||
this.password = globalkeys.pass;
|
||||
flag = true;
|
||||
} else {
|
||||
else if (this.authtype !== "NONE") {
|
||||
this.error(RED._("email.errors.nouserid"));
|
||||
}
|
||||
if (this.authtype === "BASIC" ) {
|
||||
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
||||
this.password = this.credentials.password;
|
||||
}
|
||||
else {
|
||||
this.error(RED._("email.errors.nopassword"));
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
|
||||
else if (this.authtype === "XOAUTH2") {
|
||||
this.saslformat = n.saslformat;
|
||||
if (n.token !== "") {
|
||||
this.token = n.token;
|
||||
} else {
|
||||
this.error(RED._("email.errors.notoken"));
|
||||
}
|
||||
}
|
||||
|
||||
var node = this;
|
||||
this.interval_id = null;
|
||||
node.interval_id = null;
|
||||
|
||||
// Process a new email message by building a Node-RED message to be passed onwards
|
||||
// in the message flow. The parameter called `msg` is the template message we
|
||||
@ -252,103 +277,90 @@ module.exports = function(RED) {
|
||||
// Check the POP3 email mailbox for any new messages. For any that are found,
|
||||
// retrieve each message, call processNewMessage to process it and then delete
|
||||
// the messages from the server.
|
||||
function checkPOP3(msg) {
|
||||
var currentMessage;
|
||||
var maxMessage;
|
||||
//node.log("Checking POP3 for new messages");
|
||||
async function checkPOP3(msg,send,done) {
|
||||
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
|
||||
var saslxoauth2 = "";
|
||||
var currentMessage = 1;
|
||||
var maxMessage = 0;
|
||||
var nextMessage;
|
||||
|
||||
// Form a new connection to our email server using POP3.
|
||||
var pop3Client = new POP3Client(
|
||||
node.inport, node.inserver,
|
||||
{enabletls: node.useSSL} // Should we use SSL to connect to our email server?
|
||||
);
|
||||
|
||||
// If we have a next message to retrieve, ask to retrieve it otherwise issue a
|
||||
// quit request.
|
||||
function nextMessage() {
|
||||
if (currentMessage > maxMessage) {
|
||||
pop3Client.quit();
|
||||
setInputRepeatTimeout();
|
||||
return;
|
||||
}
|
||||
pop3Client.retr(currentMessage);
|
||||
currentMessage++;
|
||||
} // End of nextMessage
|
||||
|
||||
pop3Client.on("stat", function(status, data) {
|
||||
// Data contains:
|
||||
// {
|
||||
// count: <Number of messages to be read>
|
||||
// octect: <size of messages to be read>
|
||||
// }
|
||||
if (status) {
|
||||
currentMessage = 1;
|
||||
maxMessage = data.count;
|
||||
nextMessage();
|
||||
} else {
|
||||
node.log(util.format("stat error: %s %j", status, data));
|
||||
}
|
||||
pop3 = new Pop3Command({
|
||||
"host": node.inserver,
|
||||
"tls": node.useSSL,
|
||||
"timeout": tout,
|
||||
"port": node.inport
|
||||
});
|
||||
try {
|
||||
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"});
|
||||
await pop3.connect();
|
||||
if (node.authtype === "XOAUTH2") {
|
||||
var value = RED.util.getMessageProperty(msg,node.token);
|
||||
if (value !== undefined) {
|
||||
if (node.saslformat) {
|
||||
//Make base64 string for access - compatible with outlook365 and gmail
|
||||
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
|
||||
} else {
|
||||
saslxoauth2 = value;
|
||||
}
|
||||
}
|
||||
await pop3.command('AUTH', "XOAUTH2");
|
||||
await pop3.command(saslxoauth2);
|
||||
|
||||
pop3Client.on("error", function(err) {
|
||||
} else if (node.authtype === "BASIC") {
|
||||
await pop3.command('USER', node.userid);
|
||||
await pop3.command('PASS', node.password);
|
||||
}
|
||||
} catch(err) {
|
||||
node.error(err.message,err);
|
||||
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
|
||||
setInputRepeatTimeout();
|
||||
node.log("error: " + JSON.stringify(err));
|
||||
});
|
||||
|
||||
pop3Client.on("connect", function() {
|
||||
//node.log("We are now connected");
|
||||
pop3Client.login(node.userid, node.password);
|
||||
});
|
||||
|
||||
pop3Client.on("login", function(status, rawData) {
|
||||
//node.log("login: " + status + ", " + rawData);
|
||||
if (status) {
|
||||
pop3Client.stat();
|
||||
} else {
|
||||
node.log(util.format("login error: %s %j", status, rawData));
|
||||
pop3Client.quit();
|
||||
setInputRepeatTimeout();
|
||||
done();
|
||||
return;
|
||||
}
|
||||
maxMessage = (await pop3.STAT()).split(" ")[0];
|
||||
if (maxMessage>0) {
|
||||
node.status({fill:"blue", shape:"dot", text:"email.status.fetching"});
|
||||
while(currentMessage<=maxMessage) {
|
||||
try {
|
||||
nextMessage = await pop3.RETR(currentMessage);
|
||||
} catch(err) {
|
||||
node.error(RED._("email.errors.fetchfail", err.message),err);
|
||||
node.status({fill:"red",shape:"ring",text:"email.status.fetcherror"});
|
||||
setInputRepeatTimeout();
|
||||
done();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// We have now received a new email message. Create an instance of a mail parser
|
||||
// and pass in the email message. The parser will signal when it has parsed the message.
|
||||
simpleParser(nextMessage, {}, function(err, parsed) {
|
||||
//node.log(util.format("simpleParser: on(end): %j", mailObject));
|
||||
if (err) {
|
||||
node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
|
||||
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
|
||||
}
|
||||
else {
|
||||
processNewMessage(msg, parsed);
|
||||
}
|
||||
});
|
||||
//processNewMessage(msg, nextMessage);
|
||||
} catch(err) {
|
||||
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
|
||||
node.status({fill:"red",shape:"ring",text:"email.status.parseerror"});
|
||||
setInputRepeatTimeout();
|
||||
done();
|
||||
return;
|
||||
}
|
||||
await pop3.DELE(currentMessage);
|
||||
currentMessage++;
|
||||
}
|
||||
});
|
||||
await pop3.QUIT();
|
||||
node.status({fill:"green",shape:"dot",text:"finished"});
|
||||
setTimeout(status_clear, 5000);
|
||||
setInputRepeatTimeout();
|
||||
done();
|
||||
}
|
||||
|
||||
pop3Client.on("retr", function(status, msgNumber, data, rawData) {
|
||||
// node.log(util.format("retr: status=%s, msgNumber=%d, data=%j", status, msgNumber, data));
|
||||
if (status) {
|
||||
|
||||
// We have now received a new email message. Create an instance of a mail parser
|
||||
// and pass in the email message. The parser will signal when it has parsed the message.
|
||||
simpleParser(data, {}, function(err, parsed) {
|
||||
//node.log(util.format("simpleParser: on(end): %j", mailObject));
|
||||
if (err) {
|
||||
node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
|
||||
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
|
||||
}
|
||||
else {
|
||||
processNewMessage(msg, parsed);
|
||||
}
|
||||
});
|
||||
pop3Client.dele(msgNumber);
|
||||
}
|
||||
else {
|
||||
node.log(util.format("retr error: %s %j", status, rawData));
|
||||
pop3Client.quit();
|
||||
setInputRepeatTimeout();
|
||||
}
|
||||
});
|
||||
|
||||
pop3Client.on("invalid-state", function(cmd) {
|
||||
node.log("Invalid state: " + cmd);
|
||||
});
|
||||
|
||||
pop3Client.on("locked", function(cmd) {
|
||||
node.log("We were locked: " + cmd);
|
||||
});
|
||||
|
||||
// When we have deleted the last processed message, we can move on to
|
||||
// processing the next message.
|
||||
pop3Client.on("dele", function(status, msgNumber) {
|
||||
nextMessage();
|
||||
});
|
||||
} // End of checkPOP3
|
||||
|
||||
|
||||
@ -358,7 +370,50 @@ module.exports = function(RED) {
|
||||
// Check the email sever using the IMAP protocol for new messages.
|
||||
var s = false;
|
||||
var ss = false;
|
||||
function checkIMAP(msg) {
|
||||
function checkIMAP(msg,send,done) {
|
||||
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
|
||||
var saslxoauth2 = "";
|
||||
if (node.authtype === "XOAUTH2") {
|
||||
var value = RED.util.getMessageProperty(msg,node.token);
|
||||
if (value !== undefined) {
|
||||
if (node.saslformat) {
|
||||
//Make base64 string for access - compatible with outlook365 and gmail
|
||||
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
|
||||
} else {
|
||||
saslxoauth2 = value;
|
||||
}
|
||||
}
|
||||
imap = new Imap({
|
||||
xoauth2: saslxoauth2,
|
||||
host: node.inserver,
|
||||
port: node.inport,
|
||||
tls: node.useSSL,
|
||||
autotls: node.autotls,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
connTimeout: tout,
|
||||
authTimeout: tout
|
||||
});
|
||||
} else {
|
||||
imap = new Imap({
|
||||
user: node.userid,
|
||||
password: node.password,
|
||||
host: node.inserver,
|
||||
port: node.inport,
|
||||
tls: node.useSSL,
|
||||
autotls: node.autotls,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
connTimeout: tout,
|
||||
authTimeout: tout
|
||||
});
|
||||
}
|
||||
imap.on('error', function(err) {
|
||||
if (err.errno !== "ECONNRESET") {
|
||||
s = false;
|
||||
node.error(err.message,err);
|
||||
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
|
||||
}
|
||||
setInputRepeatTimeout();
|
||||
});
|
||||
//console.log("Checking IMAP for new messages");
|
||||
// We get back a 'ready' event once we have connected to imap
|
||||
s = true;
|
||||
@ -391,6 +446,7 @@ module.exports = function(RED) {
|
||||
imap.end();
|
||||
s = false;
|
||||
setInputRepeatTimeout();
|
||||
done(err);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@ -406,6 +462,7 @@ module.exports = function(RED) {
|
||||
imap.end();
|
||||
s = false;
|
||||
setInputRepeatTimeout();
|
||||
done(err);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@ -416,6 +473,8 @@ module.exports = function(RED) {
|
||||
imap.end();
|
||||
s = false;
|
||||
setInputRepeatTimeout();
|
||||
msg.payload = 0;
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -453,11 +512,13 @@ module.exports = function(RED) {
|
||||
imap.end();
|
||||
s = false;
|
||||
setInputRepeatTimeout();
|
||||
msg.payload = results.length;
|
||||
done();
|
||||
};
|
||||
if (node.disposition === "Delete") {
|
||||
imap.addFlags(results, "\Deleted", cleanup);
|
||||
imap.addFlags(results, '\\Deleted', imap.expunge(cleanup) );
|
||||
} else if (node.disposition === "Read") {
|
||||
imap.addFlags(results, "\Seen", cleanup);
|
||||
imap.addFlags(results, '\\Seen', cleanup);
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
@ -468,6 +529,7 @@ module.exports = function(RED) {
|
||||
imap.end();
|
||||
s = false;
|
||||
setInputRepeatTimeout();
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
}); // End of imap->search
|
||||
@ -477,6 +539,7 @@ module.exports = function(RED) {
|
||||
node.error(e.toString(),e);
|
||||
s = ss = false;
|
||||
imap.end();
|
||||
done(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -485,6 +548,7 @@ module.exports = function(RED) {
|
||||
node.error(RED._("email.errors.bad_criteria"),msg);
|
||||
s = ss = false;
|
||||
imap.end();
|
||||
done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -496,42 +560,20 @@ module.exports = function(RED) {
|
||||
|
||||
|
||||
// Perform a check of the email inboxes using either POP3 or IMAP
|
||||
function checkEmail(msg) {
|
||||
function checkEmail(msg,send,done) {
|
||||
if (node.protocol === "POP3") {
|
||||
checkPOP3(msg);
|
||||
checkPOP3(msg,send,done);
|
||||
} else if (node.protocol === "IMAP") {
|
||||
if (s === false && ss == false) { checkIMAP(msg); }
|
||||
if (s === false && ss == false) { checkIMAP(msg,send,done); }
|
||||
}
|
||||
} // End of checkEmail
|
||||
|
||||
if (node.protocol === "IMAP") {
|
||||
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
|
||||
imap = new Imap({
|
||||
user: node.userid,
|
||||
password: node.password,
|
||||
host: node.inserver,
|
||||
port: node.inport,
|
||||
tls: node.useSSL,
|
||||
autotls: node.autotls,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
connTimeout: tout,
|
||||
authTimeout: tout
|
||||
});
|
||||
imap.on('error', function(err) {
|
||||
if (err.errno !== "ECONNRESET") {
|
||||
node.log(err);
|
||||
s = false;
|
||||
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
|
||||
}
|
||||
setInputRepeatTimeout();
|
||||
});
|
||||
}
|
||||
|
||||
this.on("input", function(msg) {
|
||||
checkEmail(msg);
|
||||
node.on("input", function(msg, send, done) {
|
||||
send = send || function() { node.send.apply(node,arguments) };
|
||||
checkEmail(msg,send,done);
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
node.on("close", function() {
|
||||
if (this.interval_id != null) {
|
||||
clearTimeout(this.interval_id);
|
||||
}
|
||||
@ -542,6 +584,10 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
|
||||
function status_clear() {
|
||||
node.status({});
|
||||
}
|
||||
|
||||
function setInputRepeatTimeout() {
|
||||
// Set the repetition timer as needed
|
||||
if (!isNaN(node.repeat) && node.repeat > 0) {
|
||||
@ -565,47 +611,81 @@ module.exports = function(RED) {
|
||||
|
||||
|
||||
function EmailMtaNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
RED.nodes.createNode(this, n);
|
||||
this.port = n.port;
|
||||
this.secure = n.secure;
|
||||
this.starttls = n.starttls;
|
||||
this.certFile = n.certFile;
|
||||
this.keyFile = n.keyFile;
|
||||
this.users = n.users;
|
||||
this.auth = n.auth;
|
||||
try {
|
||||
this.options = JSON.parse(n.expert);
|
||||
} catch (error) {
|
||||
this.options = {};
|
||||
}
|
||||
var node = this;
|
||||
if (!Array.isArray(node.options.disabledCommands)) {
|
||||
node.options.disabledCommands = [];
|
||||
}
|
||||
node.options.secure = node.secure;
|
||||
if (node.certFile) {
|
||||
node.options.cert = fs.readFileSync(node.certFile);
|
||||
}
|
||||
if (node.keyFile) {
|
||||
node.options.key = fs.readFileSync(node.keyFile);
|
||||
}
|
||||
if (!node.starttls) {
|
||||
node.options.disabledCommands.push("STARTTLS");
|
||||
}
|
||||
if (!node.auth) {
|
||||
node.options.disabledCommands.push("AUTH");
|
||||
}
|
||||
|
||||
node.mta = new SMTPServer({
|
||||
secure: false,
|
||||
logger: false,
|
||||
disabledCommands: ['AUTH', 'STARTTLS'],
|
||||
|
||||
onData: function (stream, session, callback) {
|
||||
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
|
||||
if (err) { node.error(RED._("email.errors.parsefail"),err); }
|
||||
else {
|
||||
node.status({fill:"green", shape:"dot", text:""});
|
||||
var msg = {}
|
||||
msg.payload = parsed.text;
|
||||
msg.topic = parsed.subject;
|
||||
msg.date = parsed.date;
|
||||
msg.header = {};
|
||||
parsed.headers.forEach((v, k) => {msg.header[k] = v;});
|
||||
if (parsed.html) { msg.html = parsed.html; }
|
||||
if (parsed.to) {
|
||||
if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; }
|
||||
else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; }
|
||||
}
|
||||
if (parsed.cc) {
|
||||
if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; }
|
||||
else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; }
|
||||
}
|
||||
if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; }
|
||||
if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; }
|
||||
if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; }
|
||||
if (parsed.attachments) { msg.attachments = parsed.attachments; }
|
||||
else { msg.attachments = []; }
|
||||
node.send(msg); // Propagate the message down the flow
|
||||
setTimeout(function() { node.status({})}, 500);
|
||||
node.options.onData = function (stream, session, callback) {
|
||||
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
|
||||
if (err) { node.error(RED._("email.errors.parsefail"),err); }
|
||||
else {
|
||||
node.status({fill:"green", shape:"dot", text:""});
|
||||
var msg = {}
|
||||
msg.payload = parsed.text;
|
||||
msg.topic = parsed.subject;
|
||||
msg.date = parsed.date;
|
||||
msg.header = {};
|
||||
parsed.headers.forEach((v, k) => {msg.header[k] = v;});
|
||||
if (parsed.html) { msg.html = parsed.html; }
|
||||
if (parsed.to) {
|
||||
if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; }
|
||||
else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; }
|
||||
}
|
||||
callback();
|
||||
});
|
||||
if (parsed.cc) {
|
||||
if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; }
|
||||
else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; }
|
||||
}
|
||||
if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; }
|
||||
if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; }
|
||||
if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; }
|
||||
if (parsed.attachments) { msg.attachments = parsed.attachments; }
|
||||
else { msg.attachments = []; }
|
||||
node.send(msg); // Propagate the message down the flow
|
||||
setTimeout(function() { node.status({})}, 500);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
node.options.onAuth = function (auth, session, callback) {
|
||||
let id = node.users.findIndex(function (item) {
|
||||
return item.name === auth.username;
|
||||
});
|
||||
if (id >= 0 && node.users[id].password === auth.password) {
|
||||
callback(null, { user: id + 1 });
|
||||
} else {
|
||||
callback(new Error("Invalid username or password"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
node.mta = new SMTPServer(node.options);
|
||||
|
||||
node.mta.listen(node.port);
|
||||
|
||||
|
@ -9,7 +9,11 @@ Pre-requisite
|
||||
You will need valid email credentials for your email server. For GMail this may mean
|
||||
getting an application password if you have two-factor authentication enabled.
|
||||
|
||||
**Note :** Version 1.x of this node requires **Node.js v8** or newer.
|
||||
For Exchange and Outlook 365 you must use OAuth2.0.
|
||||
|
||||
**Notes **:
|
||||
Version 2.x of this node required **Node.js v12** or newer.
|
||||
Version 1.x of this node requires **Node.js v8** or newer.
|
||||
|
||||
Install
|
||||
-------
|
||||
@ -26,6 +30,12 @@ GMail users
|
||||
If you are accessing GMail you may need to either enable <a target="_new" href="https://support.google.com/mail/answer/185833?hl=en">an application password</a>,
|
||||
or enable <a target="_new" href="https://support.google.com/accounts/answer/6010255?hl=en">less secure access</a> via your Google account settings.</p>
|
||||
|
||||
Office 365 users
|
||||
----------------
|
||||
|
||||
If you are accessing Exchnage you will need to register an application through their platform and use OAuth2.0.
|
||||
<a target="_new" href="https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#get-an-access-token">Details on how to do this can be found here.</a>
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
@ -42,6 +52,9 @@ If there is text/html then that is returned in `msg.html`. `msg.from` and
|
||||
Additionally `msg.header` contains the complete header object including
|
||||
**to**, **cc** and other potentially useful properties.
|
||||
|
||||
Modern authentication through OAuth2.0 is supported, but must be triggered by an incoming access token and
|
||||
can only be automatically triggered upstream.
|
||||
|
||||
### Output node
|
||||
|
||||
Sends the `msg.payload` as an email, with a subject of `msg.topic`.
|
||||
|
@ -36,7 +36,7 @@
|
||||
"always": "immer",
|
||||
"rejectUnauthorised": "Überprüfen sie, ob das serverzertifikat gültig ist"
|
||||
},
|
||||
"default-message": "__description__\n\nDatei von Node-RED ist angehängt: __filename__",
|
||||
"default-message": "\nDatei von Node-RED ist angehängt: __filename__",
|
||||
"tip": {
|
||||
"cred": "<b>Hinweis</b>: Berechtigungen von globaler emailkeys.js-Datei kopiert",
|
||||
"recent": "Tipp: Es wird nur die letzte E-Mail abgerufen",
|
||||
|
@ -7,8 +7,17 @@
|
||||
<p>You may optionally set <code>msg.from</code> in the payload which will override the <code>userid</code>
|
||||
default value.</p>
|
||||
<h3>Gmail users</h3>
|
||||
<p>If you are accessing Gmail you may need to either enable <a target="_new" href="https://support.google.com/mail/answer/185833?hl=en">an application password</a>,
|
||||
or enable <a target="_new" href="https://support.google.com/accounts/answer/6010255?hl=en">less secure access</a> via your Google account settings.</p>
|
||||
<p>If you are accessing Gmail you may need to either enable <a target="_new" href="https://support.google.com/mail/answer/185833?hl=en">an application password</a>.</p>
|
||||
<h3>Authentication</h3>
|
||||
<p>When connecting to a SMTP server, two authentication types are available: Basic and XOAuth2.</p>
|
||||
<ul>
|
||||
<li><b>Basic:</b> requires a username and password to be entered</li>
|
||||
<li><b>XOAuth2:</b> requires a username and a <code>msg</code> property to extract the access token</li>
|
||||
</ul>
|
||||
<h3>SASL Formatting:</h3>
|
||||
<p>SASL XOAuth2 tokens are created by combining the username and token, encoding it in base64, and passing it to the mail server in the following format:</p>
|
||||
<pre>base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")</pre>
|
||||
<p>If the checkbox is unticked, flow creators can format the token themselves before passing it to the node.</p>
|
||||
<h3>Details</h3>
|
||||
<p>The payload can be html format. You may supply a separate plaintext version using <code>msg.plaintext</code>.
|
||||
If you don't and <code>msg.payload</code> contains html, it will also be used for the plaintext.
|
||||
@ -26,43 +35,78 @@
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="e-mail in">
|
||||
<p>Repeatedly gets emails from a POP3 or IMAP server and forwards on as a msg if not already seen.</p>
|
||||
<p>The subject is loaded into <code>msg.topic</code> and <code>msg.payload</code> is the plain text body.
|
||||
If there is text/html then that is returned in <code>msg.html</code>. <code>msg.from</code> and <code>msg.date</code> are also set if you need them.</p>
|
||||
<p>Additionally <code>msg.header</code> contains the complete header object including
|
||||
<i>to</i>, <i>cc</i> and other potentially useful properties.</p>
|
||||
<p>It can optionally mark the message as Read (default), Delete it, or leave unmarked (None).</p>
|
||||
<p>Uses the <a href="https://github.com/mscdex/node-imap/blob/master/README.md" target="_new">node-imap module</a> - see that page for
|
||||
information on the <code>msg.criteria</code> format if needed.</p>
|
||||
<p>Any attachments supplied in the incoming email can be found in the <code>msg.attachments</code> property. This will be an array of objects where
|
||||
each object represents a specific attachments. The format of the object is:</p>
|
||||
<pre>
|
||||
{
|
||||
contentType: // The MIME content description
|
||||
fileName: // A suggested file name associated with this attachment
|
||||
transferEncoding: // How was the original email attachment encodded?
|
||||
contentDisposition: // Unknown
|
||||
generatedFileName: // A suggested file name associated with this attachment
|
||||
contentId: // A unique generated ID for this attachment
|
||||
checksum: // A checksum against the data
|
||||
length: // Size of data in bytes
|
||||
content: // The actual content of the data contained in a Node.js Buffer object
|
||||
// We can turn this into a base64 data string with content.toString('base64')
|
||||
}
|
||||
</pre>
|
||||
<p><b>Note</b>: For POP3, the default port numbers are 110 for plain TCP and 995 for SSL. For IMAP the port numbers are 143 for plain TCP and 993 for SSL.</p>
|
||||
<p><b>Note</b>: With option 'STARTTLS' an established plain connection is upgraded to an encrypted one. Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading.</p>
|
||||
<p><b>Note</b>: The maximum refresh interval is 2147483 seconds (24.8 days).</p>
|
||||
<h3>Overview</h3>
|
||||
<p>The e-mail in node retrieves emails from a POP3 or IMAP server and forwards the email data as a message if it has not already been seen.</p>
|
||||
|
||||
<h3>Message Properties</h3>
|
||||
<p>The following properties are set on the message object:</p>
|
||||
<ul>
|
||||
<li><code>msg.topic</code> - the subject of the email</li>
|
||||
<li><code>msg.payload</code> - the plain text body of the email</li>
|
||||
<li><code>msg.html</code> - the HTML body of the email (if present)</li>
|
||||
<li><code>msg.from</code> - the sender of the email</li>
|
||||
<li><code>msg.date</code> - the date the email was sent</li>
|
||||
<li><code>msg.header</code> - the complete header object including information such as the "to" and "cc" recipients</li>
|
||||
<li><code>msg.attachments</code> - an array of objects representing any attachments included in the email</li>
|
||||
</ul>
|
||||
|
||||
<h3>Module Used</h3>
|
||||
<p>The e-mail in node uses the <a href="https://github.com/mscdex/node-imap/blob/master/README.md" target="_new">node-imap module</a>, see that page for information on the <code>msg.criteria</code> format if needed.</p>
|
||||
<p>It also makes use of <a href="https://github.com/node-pop3/node-pop3#readme" target="_new">node-pop3 module</a></p>
|
||||
|
||||
<h3>Attachment Format</h3>
|
||||
<p>Each object in the <code>msg.attachments</code> array is formatted as follows:</p>
|
||||
<pre>
|
||||
{
|
||||
contentType: // The MIME content description
|
||||
fileName: // A suggested file name associated with this attachment
|
||||
transferEncoding: // How was the original email attachment encoded?
|
||||
contentDisposition: // Unknown
|
||||
generatedFileName: // A suggested file name associated with this attachment
|
||||
contentId: // A unique generated ID for this attachment
|
||||
checksum: // A checksum against the data
|
||||
length: // Size of data in bytes
|
||||
content: // The actual content of the data contained in a Node.js Buffer object
|
||||
// We can turn this into a base64 data string with content.toString('base64')
|
||||
}
|
||||
</pre>
|
||||
<h3>Authentication</h3>
|
||||
<p>When connecting to a POP3 or IMAP server, two authentication types are available: Basic and XOAuth2.</p>
|
||||
<ul>
|
||||
<li><b>Basic:</b> requires a username and password to be entered</li>
|
||||
<li><b>XOAuth2:</b> requires a username and a <code>msg</code> property to extract the access token</li>
|
||||
</ul>
|
||||
<p>With XOAuth2 authentication, periodic fetching is not available. The node will only attemp to login when a new token is receieved.</p>
|
||||
<h3>SASL Formatting:</h3>
|
||||
<p>SASL XOAuth2 tokens are created by combining the username and token, encoding it in base64, and passing it to the mail server in the following format:</p>
|
||||
<pre>base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")</pre>
|
||||
<p>If the checkbox is unticked, flow creators can format the token themselves before passing it to the node.</p>
|
||||
|
||||
<h3>Notes</h3>
|
||||
<ul>
|
||||
<li>For POP3, the default port numbers are 110 for plain TCP and 995 for SSL. For IMAP the port numbers are 143 for plain TCP and 993 for SSL.</li>
|
||||
<li>With option 'STARTTLS' an established plain connection is upgraded to an encrypted one. Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading.</li>
|
||||
<li>The maximum refresh interval is 2147483 seconds (24.8 days).</li>
|
||||
</ul>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="e-mail mta">
|
||||
<p>Mail Transfer Agent - listens on a port for incoming SMTP mails.</p>
|
||||
<p><b>Note</b>: "NOT for production use" as there is no security built in.
|
||||
<p><b>Note</b>: Default configuration is "NOT for production use" as security is not enabled.
|
||||
This is primarily for local testing of outbound mail sending, but could be used
|
||||
as a mail forwarder to a real email service if required.</p>
|
||||
<p>To use ports below 1024, for example 25 or 465, you may need to get privileged access.
|
||||
On linux systems this can be done by running
|
||||
<pre>sudo setcap 'cap_net_bind_service=+eip' $(which node)</pre>
|
||||
and restarting Node-RED. Be aware - this gives all node applications access to all ports.</p>
|
||||
<h3>Security</h3>
|
||||
<p>When <i>Secure connection</i> is checked, the connection will use TLS.
|
||||
If not it is still possible to upgrade clear text socket to TLS socket by checking <i>Start TLS</i>.
|
||||
In most cases when using port 465, check <i>Secure connection</i>. For port 587 or 25 keep it disabled, use <i>Start TLS</i> instead.</p>
|
||||
<p>If you do no specify your own certificate (path to file) then a pregenerated self-signed certificate is used. Any respectful client refuses to accept such certificate.</p>
|
||||
<h3>Authentication</h3>
|
||||
<p>Authentication can be enabled (PLAIN or LOGIN). Add at least one user.</p>
|
||||
<h3>Expert</h3>
|
||||
<p>All options as described in <a href="https://nodemailer.com/extras/smtp-server/" target="_new">nodemailer SMTP server</a> can be made here.</p>
|
||||
</script>
|
||||
|
@ -31,12 +31,24 @@
|
||||
"unflagged": "Unflagged",
|
||||
"unseen": "Unseen",
|
||||
"autotls": "Start TLS?",
|
||||
"authtype": "Auth type",
|
||||
"saslformat": "Format to SASL",
|
||||
"token": "Token",
|
||||
"never": "never",
|
||||
"required": "if required",
|
||||
"always": "always",
|
||||
"rejectUnauthorised": "Check server certificate is valid"
|
||||
"rejectUnauthorised": "Check server certificate is valid",
|
||||
"enableSecure": "Secure connection",
|
||||
"enableStarttls": "Start TLS",
|
||||
"starttlsUpgrade": "Upgrade cleartext connection with STARTTLS",
|
||||
"certFile": "Certificate",
|
||||
"keyFile":"Private key",
|
||||
"users": "Users",
|
||||
"auth": "Authenticate users",
|
||||
"addButton": "Add",
|
||||
"expert": "Expert"
|
||||
},
|
||||
"default-message": "__description__\n\nFile from Node-RED is attached: __filename__",
|
||||
"default-message": "\nFile from Node-RED is attached: __filename__",
|
||||
"tip": {
|
||||
"cred": "<b>Note:</b> Copied credentials from global emailkeys.js file.",
|
||||
"recent": "Tip: Only retrieves the single most recent email.",
|
||||
@ -60,6 +72,7 @@
|
||||
"errors": {
|
||||
"nouserid": "No e-mail userid set",
|
||||
"nopassword": "No e-mail password set",
|
||||
"notoken": "No token property set",
|
||||
"nocredentials": "No Email credentials found. See info panel.",
|
||||
"nosmtptransport": "No SMTP transport. See info panel.",
|
||||
"nopayload": "No payload to send",
|
||||
|
@ -36,7 +36,7 @@
|
||||
"always": "常時",
|
||||
"rejectUnauthorised": "チェックサーバ証明書は有効です"
|
||||
},
|
||||
"default-message": "__description__\n\nNode-REDからファイルが添付されました: __filename__",
|
||||
"default-message": "\nNode-REDからファイルが添付されました: __filename__",
|
||||
"tip": {
|
||||
"cred": "<b>注釈:</b> emailkeys.jsファイルから認証情報をコピーしました。",
|
||||
"recent": "注釈: 最新のメールを1件のみ取得します。",
|
||||
|
@ -1,15 +1,17 @@
|
||||
{
|
||||
"name": "node-red-node-email",
|
||||
"version": "1.15.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Node-RED nodes to send and receive simple emails.",
|
||||
"dependencies": {
|
||||
"imap": "^0.8.19",
|
||||
"mailparser": "^3.4.0",
|
||||
"nodemailer": "^6.7.3",
|
||||
"smtp-server": "^3.10.0"
|
||||
"node-pop3": "^0.8.0",
|
||||
"mailparser": "^3.6.4",
|
||||
"nodemailer": "^6.9.1",
|
||||
"smtp-server": "^3.11.0"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"imap",
|
||||
"node-pop3",
|
||||
"mailparser",
|
||||
"nodemailer",
|
||||
"smtp-server"
|
||||
@ -26,9 +28,12 @@
|
||||
"gmail",
|
||||
"imap",
|
||||
"pop",
|
||||
"smtp",
|
||||
"smtp-server",
|
||||
"mta"
|
||||
],
|
||||
"node-red": {
|
||||
"version": ">=1.0.0",
|
||||
"nodes": {
|
||||
"email": "61-email.js"
|
||||
}
|
||||
|
@ -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>
|
||||
<input type="text" id="node-input-interval" style="width:60px"> <span data-i18n="feedparse.label.minutes"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-ignorefirst" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<span data-i18n="feedparse.label.ignorefirst"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
||||
@ -20,7 +25,8 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
url: {value:"", required:true},
|
||||
interval: { value:15, required:true, validate:function(v) {return (!isNaN(parseInt(v)) && (parseInt(v) <= 35790))} }
|
||||
interval: { value:15, required:true, validate:function(v) {return (!isNaN(parseInt(v)) && (parseInt(v) <= 35790))} },
|
||||
ignorefirst: { value:false }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
|
@ -11,11 +11,13 @@ module.exports = function(RED) {
|
||||
if (n.interval > 35790) { this.warn(RED._("feedparse.errors.invalidinterval")) }
|
||||
this.interval = (parseInt(n.interval)||15) * 60000;
|
||||
this.interval_id = null;
|
||||
this.ignorefirst = n.ignorefirst || false;
|
||||
this.seen = {};
|
||||
this.donefirst = false;
|
||||
var node = this;
|
||||
var parsedUrl = url.parse(this.url);
|
||||
if (!(parsedUrl.host || (parsedUrl.hostname && parsedUrl.port)) && !parsedUrl.isUnix) {
|
||||
node.error(RED._("feedparse.errors.invalidurl"));
|
||||
node.error(RED._("feedparse.errors.invalidurl"),RED._("feedparse.errors.invalidurl"));
|
||||
}
|
||||
else {
|
||||
var getFeed = function() {
|
||||
@ -33,19 +35,24 @@ module.exports = function(RED) {
|
||||
else { res.pipe(feedparser); }
|
||||
});
|
||||
|
||||
feedparser.on('error', function(error) { node.error(error); });
|
||||
feedparser.on('error', function(error) { node.error(error,error); });
|
||||
|
||||
feedparser.on('readable', function () {
|
||||
var stream = this, article;
|
||||
while (article = stream.read()) { // jshint ignore:line
|
||||
if (!(article.guid in node.seen) || ( node.seen[article.guid] !== 0 && node.seen[article.guid] != article.date.getTime())) {
|
||||
node.seen[article.guid] = article.date?article.date.getTime():0;
|
||||
node.seen[article.guid] = article.date ? article.date.getTime() : 0;
|
||||
var msg = {
|
||||
topic: article.origlink || article.link,
|
||||
payload: article.description,
|
||||
article: article
|
||||
};
|
||||
node.send(msg);
|
||||
if (node.ignorefirst === true && node.donefirst === false) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -53,7 +60,7 @@ module.exports = function(RED) {
|
||||
feedparser.on('meta', function (meta) {});
|
||||
feedparser.on('end', function () {});
|
||||
};
|
||||
node.interval_id = setInterval(function() { getFeed(); }, node.interval);
|
||||
node.interval_id = setInterval(function() { node.donefirst = true; getFeed(); }, node.interval);
|
||||
getFeed();
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,8 @@
|
||||
"label": {
|
||||
"feedurl": "Feed url",
|
||||
"refresh": "Refresh",
|
||||
"minutes": "minutes"
|
||||
"minutes": "minutes",
|
||||
"ignorefirst": "Ignore any stories older than restart"
|
||||
},
|
||||
"errors": {
|
||||
"badstatuscode": "error - Bad status code",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-node-feedparser",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.0",
|
||||
"description": "A Node-RED node to get RSS Atom feeds.",
|
||||
"dependencies": {
|
||||
"feedparser": "^2.2.10",
|
||||
@ -15,7 +15,8 @@
|
||||
"keywords": [
|
||||
"node-red",
|
||||
"atom",
|
||||
"rss"
|
||||
"rss",
|
||||
"feed"
|
||||
],
|
||||
"node-red": {
|
||||
"nodes": {
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "node-red-node-notify",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "A Node-RED node to send local popup Notify alerts",
|
||||
"dependencies": {
|
||||
"node-notifier": "^5.4.5"
|
||||
"node-notifier": "^10.0.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -221,8 +221,14 @@ module.exports = function(RED) {
|
||||
msg.payload = incoming.iden;
|
||||
}
|
||||
else if (incoming.type === 'sms_changed') {
|
||||
msg.topic = "SMS: "+ incoming.notifications[0].title;
|
||||
msg.payload = incoming.notifications[0].body;
|
||||
if (incoming.notifications && incoming.notifications.length > 0) {
|
||||
msg.topic = "SMS: "+ incoming.notifications[0].title;
|
||||
msg.payload = incoming.notifications[0].body;
|
||||
}
|
||||
else {
|
||||
msg.topic = "SMS: ";
|
||||
msg.payload = "";
|
||||
}
|
||||
msg.message = incoming;
|
||||
}
|
||||
else {
|
||||
@ -244,12 +250,11 @@ module.exports = function(RED) {
|
||||
|
||||
function PushbulletOut(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
var self = this;
|
||||
|
||||
this.title = n.title;
|
||||
this.chan = n.chan;
|
||||
this.pushtype = n.pushtype;
|
||||
this.pusher = null;
|
||||
var self = this;
|
||||
|
||||
var configNode;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-pushbullet",
|
||||
"version" : "0.0.17",
|
||||
"version" : "0.0.19",
|
||||
"description" : "A Node-RED node to send alerts via Pushbullet",
|
||||
"dependencies" : {
|
||||
"pushbullet": "^2.4.0",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.3 KiB |
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "node-red-node-pusher",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"description": "A Node-RED node to send and receive messages using Pusher.com",
|
||||
"dependencies": {
|
||||
"pusher": "^1.5.1",
|
||||
"pusher-client": "^1.1.0"
|
||||
"pusher": "^5.1.2",
|
||||
"pusher-js": "^8.0.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -25,5 +25,11 @@
|
||||
"name": "Dave Conway-Jones",
|
||||
"email": "ceejay@vnet.ibm.com",
|
||||
"url": "http://nodered.org"
|
||||
}
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Shaqaruden",
|
||||
"email": "shaqaruden@gmail.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
<script type="text/x-red" data-template-name="pusher in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
|
||||
<input type="text" id="node-input-channel" placeholder="my_channel">
|
||||
<input type="text" id="node-input-channel" placeholder="channel">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-eventname"><i class="fa fa-tasks"></i> Event</label>
|
||||
<input type="text" id="node-input-eventname" placeholder="test_event_name">
|
||||
<input type="text" id="node-input-eventname" placeholder="event name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-pusherappkeysub"><i class="fa fa-lock"></i> App Key</label>
|
||||
@ -15,10 +15,15 @@
|
||||
<div class="form-row">
|
||||
<label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label>
|
||||
<select type="text" id="node-input-cluster">
|
||||
<option value="mt1">us-east-1 (US - default)</option>
|
||||
<option value="eu">eu-west-1 (Europe)</option>
|
||||
<option value="ap1">ap-southeast-1 (Singapore)</option>
|
||||
<option value="ap2">ap-south-1 (Mumbai)</option>
|
||||
<option value="mt1">N. Virginia (US - default)</option>
|
||||
<option value="us2">Ohio (US)</option>
|
||||
<option value="us3">Oregon (US)</option>
|
||||
<option value="eu">Ireland (Europe)</option>
|
||||
<option value="ap1">Singapore (Asia)</option>
|
||||
<option value="ap2">Mumbai (India)</option>
|
||||
<option value="ap3">Tokyo (Asia)</option>
|
||||
<option value="ap4">Sydney (Australia)</option>
|
||||
<option value="sa1">São Paulo (Brazil)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@ -87,10 +92,15 @@
|
||||
<div class="form-row">
|
||||
<label for="node-input-cluster"><i class="fa fa-server"></i> Cluster</label>
|
||||
<select type="text" id="node-input-cluster">
|
||||
<option value="mt1">us-east-1 (US - default)</option>
|
||||
<option value="eu">eu-west-1 (Europe)</option>
|
||||
<option value="ap1">ap-southeast-1 (Singapore)</option>
|
||||
<option value="ap2">ap-south-1 (Mumbai)</option>
|
||||
<option value="mt1">N. Virginia (US - default)</option>
|
||||
<option value="us2">Ohio (US)</option>
|
||||
<option value="us3">Oregon (US)</option>
|
||||
<option value="eu">Ireland (Europe)</option>
|
||||
<option value="ap1">Singapore (Asia)</option>
|
||||
<option value="ap2">Mumbai (India)</option>
|
||||
<option value="ap3">Tokyo (Asia)</option>
|
||||
<option value="ap4">Sydney (Australia)</option>
|
||||
<option value="sa1">São Paulo (Brazil)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
module.exports = function (RED) {
|
||||
"use strict";
|
||||
var Pusher = require('pusher');
|
||||
var PusherClient = require('pusher-client');
|
||||
const Pusher = require("pusher")
|
||||
const PusherClient = require('pusher-js');
|
||||
|
||||
//node for subscribing to an event/channel
|
||||
function PusherNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
RED.nodes.createNode(this, n);
|
||||
this.channel = n.channel;
|
||||
this.eventname = n.eventname;
|
||||
this.cluster = n.cluster || "mt1";
|
||||
@ -19,19 +19,21 @@ module.exports = function(RED) {
|
||||
else { this.error("No Pusher app key set for input node"); }
|
||||
|
||||
//create a subscription to the channel and event defined by user
|
||||
var socket = new PusherClient(''+node.appkey, {cluster:node.cluster, encrypted:true});
|
||||
var chan = socket.subscribe(''+node.channel);
|
||||
chan.bind(''+node.eventname,
|
||||
function(data) {
|
||||
var msg = {topic:node.eventname, channel:node.channel};
|
||||
if (data.hasOwnProperty("payload")) { msg.payload = data.payload; }
|
||||
else { msg.payload = data; }
|
||||
node.send(msg);
|
||||
}
|
||||
);
|
||||
// var socket = new PusherClient(''+node.appkey, {cluster:node.cluster, encrypted:true});
|
||||
const pusher = new PusherClient('' + node.appkey, {
|
||||
cluster: node.cluster
|
||||
});
|
||||
const channel = pusher.subscribe('' + node.channel);
|
||||
|
||||
node.on("close", function() {
|
||||
socket.disconnect();
|
||||
channel.bind('' + node.eventname, function (data) {
|
||||
var msg = { topic: node.eventname, channel: node.channel };
|
||||
if (data.hasOwnProperty("payload")) { msg.payload = data.payload; }
|
||||
else { msg.payload = data; }
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
node.on("close", function () {
|
||||
pusher.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
@ -39,7 +41,7 @@ module.exports = function(RED) {
|
||||
//Node for sending Pusher events
|
||||
function PusherNodeSend(n) {
|
||||
// Create a RED node
|
||||
RED.nodes.createNode(this,n);
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
var credentials = this.credentials;
|
||||
@ -56,33 +58,35 @@ module.exports = function(RED) {
|
||||
this.eventname = n.eventname;
|
||||
this.cluster = n.cluster || "mt1";
|
||||
|
||||
var pusherd = new Pusher({
|
||||
var pusher = new Pusher({
|
||||
appId: this.appid,
|
||||
key: this.appkey,
|
||||
secret: this.appsecret,
|
||||
cluster: this.cluster
|
||||
});
|
||||
|
||||
node.on("input", function(msg) {
|
||||
pusherd.trigger(this.channel, this.eventname, {
|
||||
|
||||
|
||||
node.on("input", function (msg) {
|
||||
pusher.trigger(this.channel, this.eventname, {
|
||||
"payload": msg.payload
|
||||
});
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.on("close", function () {
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("pusher in",PusherNode,{
|
||||
RED.nodes.registerType("pusher in", PusherNode, {
|
||||
credentials: {
|
||||
pusherappkeysub: "text"
|
||||
}
|
||||
});
|
||||
RED.nodes.registerType("pusher out",PusherNodeSend,{
|
||||
RED.nodes.registerType("pusher out", PusherNodeSend, {
|
||||
credentials: {
|
||||
pusherappid: {type:"text"},
|
||||
pusherappkey: {type:"text"},
|
||||
pusherappsecret: {type:"password"}
|
||||
pusherappid: { type: "text" },
|
||||
pusherappkey: { type: "text" },
|
||||
pusherappsecret: { type: "password" }
|
||||
},
|
||||
encrypted: true
|
||||
});
|
||||
|
@ -157,13 +157,17 @@
|
||||
<input type="text" id="node-config-input-user" placeholder="joe@blah.im">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-nickname"><i class="fa fa-user"></i> Nickname</label>
|
||||
<label for="node-config-input-nickname"><i class="fa fa-heart"></i> Nickname</label>
|
||||
<input type="text" id="node-config-input-nickname" placeholder="Joe (optional)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
|
||||
<input type="password" id="node-config-input-password">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-resource"><i class="fa fa-globe"></i> Resource</label>
|
||||
<input type="text" id="node-config-input-resource" placeholder="optional resource id">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -173,7 +177,8 @@
|
||||
server: {value:"", required:false},
|
||||
port: {value:5222, required:false, validate:RED.validators.number()},
|
||||
user: {value:""},
|
||||
nickname: {value:""}
|
||||
nickname: {value:""},
|
||||
resource: {value:""}
|
||||
},
|
||||
credentials: {
|
||||
password: {type:"password"}
|
||||
|
@ -30,6 +30,8 @@ module.exports = function(RED) {
|
||||
else {
|
||||
this.port = parseInt(n.port);
|
||||
}
|
||||
this.domain = this.jid.split('@')[1] || this.server;
|
||||
this.resource = n.resource || "";
|
||||
|
||||
// The password is obfuscated and stored in a separate location
|
||||
var credentials = this.credentials;
|
||||
@ -45,12 +47,15 @@ module.exports = function(RED) {
|
||||
if (RED.settings.verbose || LOGITALL) {
|
||||
this.log("Setting up connection xmpp: {service: "+proto+"://"+this.server+":"+this.port+", username: "+this.username+", password: "+this.password+"}");
|
||||
}
|
||||
this.client = client({
|
||||
var opts = {
|
||||
service: proto+'://' + this.server + ':' + this.port,
|
||||
domain: this.domain,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
timeout: 60000
|
||||
});
|
||||
}
|
||||
if (this.resource !== "") { opts.resource = this.resource; }
|
||||
this.client = client(opts);
|
||||
|
||||
this.client.timeout = 60000;
|
||||
// helper variable for checking against later, maybe we should be using the client
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-node-xmpp",
|
||||
"version": "0.5.10",
|
||||
"version": "0.6.0",
|
||||
"description": "A Node-RED node to talk to an XMPP server",
|
||||
"dependencies": {
|
||||
"@xmpp/client": "^0.13.1"
|
||||
|
@ -65,19 +65,26 @@ module.exports = function(RED) {
|
||||
});
|
||||
}
|
||||
|
||||
this.connect = function() {
|
||||
if (!this.connected && !this.connecting) {
|
||||
node.connect = function() {
|
||||
if (!node.connected && !node.connecting) {
|
||||
doConnect();
|
||||
}
|
||||
}
|
||||
|
||||
this.on('close', function(done) {
|
||||
if (this.tick) { clearTimeout(this.tick); }
|
||||
if (this.check) { clearInterval(this.check); }
|
||||
node.connected = false;
|
||||
node.on('close', function(done) {
|
||||
if (node.tick) { clearTimeout(node.tick); }
|
||||
if (node.check) { clearInterval(node.check); }
|
||||
// node.connection.release();
|
||||
node.emit("state"," ");
|
||||
node.pool.end(function(err) { done(); });
|
||||
if (node.connected) {
|
||||
node.connected = false;
|
||||
node.pool.end(function(err) { done(); });
|
||||
}
|
||||
else {
|
||||
delete node.pool;
|
||||
done();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("MySQLdatabase",MySQLNode, {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-node-mysql",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "A Node-RED node to read and write to a MySQL database",
|
||||
"dependencies": {
|
||||
"mysql2": "^2.3.3"
|
||||
|
@ -16,6 +16,7 @@ module.exports = function(RED) {
|
||||
|
||||
var fileTail = function() {
|
||||
if (fs.existsSync(node.filename)) {
|
||||
node.status({ });
|
||||
if (node.filetype === "text") {
|
||||
node.tail = new Tail(node.filename,{separator:node.split, flushAtEOF:true});
|
||||
}
|
||||
@ -40,6 +41,7 @@ module.exports = function(RED) {
|
||||
node.tail.on("error", function(err) {
|
||||
node.status({ fill: "red",shape:"ring", text: "node-red:common.status.error" });
|
||||
node.error(err.toString());
|
||||
if (err.code ==="ENOENT") { scheduleRestart(); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
@ -51,6 +53,8 @@ module.exports = function(RED) {
|
||||
var scheduleRestart = function() {
|
||||
node.tout = setTimeout(function() {
|
||||
node.tout = null;
|
||||
if (node.tail) { node.tail.unwatch(); }
|
||||
delete node.tail;
|
||||
fileTail();
|
||||
}, 10000);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-node-tail",
|
||||
"version": "0.3.2",
|
||||
"version": "0.4.0",
|
||||
"description": "A node to tail files for Node-RED",
|
||||
"dependencies": {
|
||||
"tail": "^2.2.4"
|
||||
|
@ -15,257 +15,297 @@ describe('random node', function() {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i1 (integer) - DEFAULT no overrides defaults to 1 and 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "" , high:"" , inte:true, wires:[["n2"]] },
|
||||
it ("Test i1 (integer) - DEFAULT no overrides defaults to 1 and 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "" , high:"" , inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(1,10);
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(1,10);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // see if it's really an integer and not a float...
|
||||
done();
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i1"});
|
||||
n1.emit("input", {"test":"Test i1"});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f1 (float) - DEFAULT no overrides defaults to 1 and 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low:"" , high:"" , inte:false, wires:[["n2"]] },
|
||||
it ("Test f1 (float) - DEFAULT no overrides defaults to 1 and 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low:"" , high:"" , inte:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(1.0,9.999);
|
||||
//msg.payload.toString().indexOf(".").should.not.equal(-1);
|
||||
done();
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(1.0,9.999);
|
||||
//msg.payload.toString().indexOf(".").should.not.equal(-1);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f-1"});
|
||||
n1.emit("input", {"test":"Test f-1"});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i2 (integer) - FLIP node From = 3 To = -3", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:true, wires:[["n2"]] },
|
||||
it ("Test i2 (integer) - FLIP node From = 3 To = -3", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(-3,3);
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(-3,3);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||
done();
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i2"});
|
||||
n1.emit("input", {"test":"Test i2"});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f2 (float) - FLIP node From = 3 To = -3", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:false, wires:[["n2"]] },
|
||||
it ("Test f2 (float) - FLIP node From = 3 To = -3", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 3, high: -3, inte:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(-3.0,3.0);
|
||||
done();
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(-3.0,3.0);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f2"});
|
||||
n1.emit("input", {"test":"Test f2"});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i3 (integer) - values in msg From = 2 To = '5', node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
it ("Test i3 (integer) - values in msg From = 2 To = '5', node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2,5);
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2,5);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||
done();
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i3", "from":2, "to":'5'});
|
||||
n1.emit("input", {"test":"Test i3", "from":2, "to":'5'});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f3 (float) - values in msg From = 2 To = '5', node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||
it ("Test f3 (float) - values in msg From = 2 To = '5', node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2.0,5.0);
|
||||
done();
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2.0,5.0);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f3", "from":2, "to":'5'});
|
||||
n1.emit("input", {"test":"Test f3", "from":2, "to":'5'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i4 (integer) - value in msg From = 2, node From = 5 To = '' - node overides From = 5 To defaults to 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:true, wires:[["n2"]] },
|
||||
it ("Test i4 (integer) - value in msg From = 2, node From = 5 To = '' - node overides From = 5 To defaults to 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(5,10);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1);
|
||||
done();
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(5,10);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i4", "from": 2});
|
||||
n1.emit("input", {"test":"Test i4", "from": 2});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f4 (float) - value in msg From = 2, node From = 5 To = '' - node wins 'To' defaults to 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:false, wires:[["n2"]] },
|
||||
it ("Test f4 (float) - value in msg From = 2, node From = 5 To = '' - node wins 'To' defaults to 10", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: 5, high:"", inte:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(5.0,10.0);
|
||||
done();
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(5.0,10.0);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f4", "from": 2});
|
||||
n1.emit("input", {"test":"Test f4", "from": 2});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i5 (integer) - msg From = '6' To = '9' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
it ("Test i5 (integer) - msg From = '6' To = '9' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(6,9);
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(6,9);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||
done();
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i5", "from": '6', "to": '9'});
|
||||
n1.emit("input", {"test":"Test i5", "from": '6', "to": '9'});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f5 (float) - msg From = '6' To = '9' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||
it ("Test i5a (integer) - msg From = '0' To = '2' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(6.0,9.0);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f5", "from": '6', "to": '9'});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i6 (integer) - msg From = 2.4 To = '7.3' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2,7);
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(0,2);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||
done();
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i6", "from": 2.4, "to": '7.3'});
|
||||
n1.emit("input", {"test":"Test i5", "from": '0', "to": '2'});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f6 (float) - msg From = 2.4 To = '7.3' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||
it ("Test i5b (integer) - msg From = '-3' To = '0' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2.4,7.3);
|
||||
done();
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(-3,0);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f6", "from": 2.4, "to": '7.3'});
|
||||
n1.emit("input", {"test":"Test i5", "from": '-3', "to": '0'});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f5 (float) - msg From = '6' To = '9' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(6.0,9.0);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f5", "from": '6', "to": '9'});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
it ("Test i6 (integer) - msg From = 2.4 To = '7.3' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:true, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2,7);
|
||||
msg.payload.toString().indexOf(".").should.equal(-1); // slightly dumb test to see if it really is an integer and not a float...
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test i6", "from": 2.4, "to": '7.3'});
|
||||
});
|
||||
});
|
||||
|
||||
it ("Test f6 (float) - msg From = 2.4 To = '7.3' node no entries", function(done) {
|
||||
var flow = [{id:"n1", type:"random", low: "", high: "", inte:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"} ];
|
||||
helper.load(testNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
//console.log(msg);
|
||||
msg.should.have.a.property("payload");
|
||||
msg.payload.should.be.within(2.4,7.3);
|
||||
done();
|
||||
}
|
||||
catch(err) { done(err); }
|
||||
});
|
||||
n1.emit("input", {"test":"Test f6", "from": 2.4, "to": '7.3'});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
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("inserver", "imap.gmail.com");
|
||||
n1.should.have.property("inport", "993");
|
||||
n1.should.have.property("authtype", "BASIC");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should force input on XOAuth2', function (done) {
|
||||
var flow = [{
|
||||
id: "n1",
|
||||
type: "e-mail in",
|
||||
name: "emailin",
|
||||
authtype: "XOAUTH2",
|
||||
wires: [
|
||||
[]
|
||||
]
|
||||
}];
|
||||
helper.load(emailNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
n1.should.have.property("repeat", 0);
|
||||
n1.should.have.property("inputs", 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -51,6 +70,7 @@ describe('email Node', function () {
|
||||
helper.load(emailNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
n1.should.have.property('name', "emailout");
|
||||
n1.should.have.property("authtype", "BASIC");
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -83,7 +103,7 @@ describe('email Node', function () {
|
||||
//console.log(helper.log());
|
||||
//logEvents.should.have.length(3);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("email.errors.nopayload");
|
||||
logEvents[2][0].msg.toString().should.startWith("email.errors.nopayload");
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
@ -134,7 +154,7 @@ describe('email Node', function () {
|
||||
// console.log(logEvents[0][0].msg.toString());
|
||||
//logEvents.should.have.length(3);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("Error:");
|
||||
logEvents[2][0].msg.toString().should.startWith("Error:");
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
@ -179,7 +199,7 @@ describe('email Node', function () {
|
||||
//console.log(helper.log().args);
|
||||
//logEvents.should.have.length(3);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("Error:");
|
||||
logEvents[2][0].msg.toString().should.startWith("Error:");
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
|
@ -1,44 +1,44 @@
|
||||
|
||||
<script type="text/html" data-template-name="sunrise">
|
||||
<div class="form-row">
|
||||
<label for="node-input-lat"><i class="fa fa-globe"></i> Latitude</label>
|
||||
<input type="text" id="node-input-lat" placeholder="51.025">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-lon"><i class="fa fa-globe"></i> Longitude</label>
|
||||
<input type="text" id="node-input-lon" placeholder="-1.4">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-start"><i class="fa fa-clock-o"></i> Start</label>
|
||||
<select id="node-input-start" style='width:70%'>
|
||||
<option value="nightEnd">Morning astronomical twilight starts</option>
|
||||
<option value="nauticalDawn">Morning nautical twilight starts</option>
|
||||
<option value="dawn">Dawn, morning civil twilight starts</option>
|
||||
<option value="sunrise">Sunrise</option>
|
||||
<option value="sunriseEnd">Sunrise end</option>
|
||||
<option value="goldenHourEnd">End of morning golden hour</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-end"><i class="fa fa-clock-o"></i> End</label>
|
||||
<select id="node-input-end" style='width:70%'>
|
||||
<option value="goldenHour">Start of evening golden hour</option>
|
||||
<option value="sunsetStart">Sunset start</option>
|
||||
<option value="sunset">Sunset, civil twilight starts</option>
|
||||
<option value="dusk">Dusk, Evening astronomical twilight starts</option>
|
||||
<option value="nauticalDusk">Evening nautical twilight starts</option>
|
||||
<option value="night">Dark enough for astronomy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-arrows-h"></i> Offset</label>
|
||||
<span style="margin-right:4px">start</span> <input type="text" id="node-input-soff" placeholder="minutes" style='width:60px;'> mins
|
||||
<span style="margin-left:14px; margin-right:4px">end</span> <input type="text" id="node-input-eoff" placeholder="minutes" style='width:60px;'> mins
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-lat"><i class="fa fa-globe"></i><span data-i18n="sunrise.label.latitude"></span></label>
|
||||
<input type="text" id="node-input-lat" placeholder="51.025">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-lon"><i class="fa fa-globe"></i><span data-i18n="sunrise.label.longitude"></span></label>
|
||||
<input type="text" id="node-input-lon" placeholder="-1.4">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-start"><i class="fa fa-clock-o"></i><span data-i18n="sunrise.label.start"></label>
|
||||
<select id="node-input-start" style='width:70%'>
|
||||
<option value="nightEnd" data-i18n="sunrise.nightEnd"></option>
|
||||
<option value="nauticalDawn" data-i18n="sunrise.nauticalDawn"></option>
|
||||
<option value="dawn" data-i18n="sunrise.dawn"></option>
|
||||
<option value="sunrise" data-i18n="sunrise.sunrise"></option>
|
||||
<option value="sunriseEnd" data-i18n="sunrise.sunriseEnd"></option>
|
||||
<option value="goldenHourEnd" data-i18n="sunrise.goldenHourEnd"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-end"><i class="fa fa-clock-o"></i><span data-i18n="sunrise.label.end"></label>
|
||||
<select id="node-input-end" style='width:70%'>
|
||||
<option value="goldenHour" data-i18n="sunrise.goldenHour"></option>
|
||||
<option value="sunsetStart" data-i18n="sunrise.sunsetStart"></option>
|
||||
<option value="sunset" data-i18n="sunrise.sunset"></option>
|
||||
<option value="dusk" data-i18n="sunrise.dusk"></option>
|
||||
<option value="nauticalDusk" data-i18n="sunrise.nauticalDusk"></option>
|
||||
<option value="night" data-i18n="sunrise.night"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-arrows-h"></i><span data-i18n="sunrise.label.offset"></label>
|
||||
<span style="margin-right:4px" data-i18n="sunrise.start"></span> <input type="text" id="node-input-soff" placeholder="minutes" style='width:60px;' > <span data-i18n="sunrise.mins"></span>
|
||||
<span style="margin-left:14px; margin-right:4px" data-i18n="sunrise.end"></span> <input type="text" id="node-input-eoff" placeholder="minutes" style='width:60px;'><span data-i18n="sunrise.mins"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i><span data-i18n="sunrise.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]sunrise.label.name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -56,10 +56,15 @@
|
||||
},
|
||||
inputs:0,
|
||||
outputs:2,
|
||||
outputLabels: ["once per minute","only on change"],
|
||||
outputLabels: function(i) {
|
||||
return [
|
||||
this._("sunrise.onePerMin"),
|
||||
this._("sunrise.onse")
|
||||
][i];
|
||||
},
|
||||
icon: "sun.png",
|
||||
label: function() {
|
||||
return this.name||"Sun rise/set";
|
||||
return this.name||this._("sunrise.sunName");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@ -49,8 +49,8 @@ module.exports = function(RED) {
|
||||
var msg = {payload:0, topic:"sun", sun:sun, moon:moon, start:s1, end:s2, now:now};
|
||||
if ((e1 > 0) & (e2 < 0)) { msg.payload = 1; }
|
||||
if (oldval == null) { oldval = msg.payload; }
|
||||
if (msg.payload == 1) { node.status({fill:"yellow",shape:"dot",text:"day"}); }
|
||||
else { node.status({fill:"blue",shape:"dot",text:"night"}); }
|
||||
if (msg.payload == 1) { node.status({fill:"yellow",shape:"dot",text:"sunrise.dayState"}); }
|
||||
else { node.status({fill:"blue",shape:"dot",text:"sunrise.nightState"}); }
|
||||
if (msg.payload != oldval) {
|
||||
oldval = msg.payload;
|
||||
node.send([msg,msg]);
|
||||
|
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",
|
||||
"version" : "1.0.1",
|
||||
"version" : "1.1.0",
|
||||
"description" : "A Node-RED node to provide a signal at sunrise and sunset",
|
||||
"dependencies" : {
|
||||
"suncalc" : "^1.8.0"
|
||||
|
@ -7,9 +7,11 @@ simple timeswitch node to schedule daily on/off events.
|
||||
Install
|
||||
-------
|
||||
|
||||
Run the following command in your Node-RED user directory - typically `~/.node-red`
|
||||
You can install by using the `Menu - Manage Palette` option, or running the following command in your
|
||||
Node-RED user directory - typically `~/.node-red`
|
||||
|
||||
npm install node-red-node-timeswitch
|
||||
cd ~/.node-red
|
||||
npm i node-red-node-timeswitch
|
||||
|
||||
Usage
|
||||
-----
|
||||
@ -24,9 +26,11 @@ or dusk, and negatively (-ve) for minutes before dawn or dusk..
|
||||
The output emits a `msg.payload` of *1* or *0* every minute depending on
|
||||
whether the current time is during the selected on time or off time.
|
||||
|
||||
If you just need the transitions from 0->1 or 1->0 then follow this node with an RBE node.
|
||||
If you just need the transitions from 0->1 or 1->0 then follow this node with a `filter (RBE)` node.
|
||||
|
||||
You may also optionally specify a `msg.topic` if required.
|
||||
|
||||
**Note**: For a more complex version with more built-in options see Pete Scargill's
|
||||
[node-red-contrib-bigtimer](http://flows.nodered.org/node/node-red-contrib-bigtimer) node.
|
||||
**Note**: For a more complex version with more built-in options see
|
||||
[node-red-contrib-bigtimer](http://flows.nodered.org/node/node-red-contrib-bigtimer) node, or
|
||||
for multiple schedules and a nice visual interface to cron then use Steve's
|
||||
[node-red-contrib-cron-plus](https://flows.nodered.org/node/node-red-contrib-cron-plus) node.
|
||||
|
@ -7,6 +7,6 @@
|
||||
or negatively (-ve) for minutes earlier.</p>
|
||||
<p>The output emits a <code>msg.payload</code> of <i>1</i> or <i>0</i> every minute depending on
|
||||
whether the current time is during the selected on time or off time.</p>
|
||||
<p>If you just need the transitions from 0->1 or 1->0 then follow this node with an RBE node.</p>
|
||||
<p>If you just need the transitions from 0->1 or 1->0 then follow this node with a filter (RBE) node.</p>
|
||||
<p>You may also optionally specify a <code>msg.topic</code> if required.</p>
|
||||
</script>
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name" : "node-red-node-timeswitch",
|
||||
"version" : "0.1.0",
|
||||
"version" : "1.0.0",
|
||||
"description" : "A Node-RED node to provide a simple timeswitch to schedule daily on/off events.",
|
||||
"dependencies" : {
|
||||
"spacetime": "^6.12.5",
|
||||
"spacetime": "^7.4.0",
|
||||
"suncalc": "^1.8.0"
|
||||
},
|
||||
"repository" : {
|
||||
@ -25,7 +25,19 @@
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "@pmacostapdi"
|
||||
"name": "@dceejay"
|
||||
},
|
||||
{
|
||||
"name": "@pmacostapdi"
|
||||
},
|
||||
{
|
||||
"name": "@heikokue"
|
||||
},
|
||||
{
|
||||
"name": "@sammachin"
|
||||
},
|
||||
{
|
||||
"name": "@jdmallen"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
module.exports = function (RED) {
|
||||
"use strict";
|
||||
var SunCalc = require('suncalc');
|
||||
const spacetime = require("spacetime")
|
||||
const SUNRISE_KEY = "sunrise";
|
||||
const SUNSET_KEY = "sunset";
|
||||
|
||||
function TimeswitchNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.lat = n.lat;
|
||||
this.lon = n.lon;
|
||||
this.start = n.start || "sunrise";
|
||||
this.end = n.end || "sunset";
|
||||
this.startt = n.starttime;
|
||||
this.endt = n.endtime;
|
||||
this.duskoff = n.duskoff;
|
||||
this.dawnoff = n.dawnoff;
|
||||
this.sunriseOffset = n.dawnoff;
|
||||
this.sunsetOffset = n.duskoff;
|
||||
this.mytopic = n.mytopic;
|
||||
this.timezone = n.timezone || "UTC";
|
||||
|
||||
@ -24,6 +23,7 @@ module.exports = function(RED) {
|
||||
this.thu = n.thu;
|
||||
this.fri = n.fri;
|
||||
this.sat = n.sat;
|
||||
|
||||
this.jan = n.jan;
|
||||
this.feb = n.feb;
|
||||
this.mar = n.mar;
|
||||
@ -38,118 +38,195 @@ module.exports = function(RED) {
|
||||
this.dec = n.dec;
|
||||
|
||||
var node = this;
|
||||
var ison = 0;
|
||||
var newendtime = 0;
|
||||
|
||||
this.on("input", function(msg2) {
|
||||
if (msg2.payload === "reset") { ison = 0; }
|
||||
this.on("input", function () {
|
||||
// current global time
|
||||
const now = spacetime.now();
|
||||
const nowNative = now.toNativeDate();
|
||||
|
||||
var timeOffset = spacetime(Date.now()).goto(this.timezone.toLowerCase()).timezone().current.offset * 60 * 60 * 1000;
|
||||
var now = new Date(Date.now() + timeOffset);
|
||||
var nowMillis = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), 0);
|
||||
var midnightMillis = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0);
|
||||
var today = Math.round((nowMillis - midnightMillis) / 60000) % 1440;
|
||||
var starttime = Number(node.startt);
|
||||
var endtime = Number(node.endt);
|
||||
// all sun events for the given lat/long
|
||||
const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon);
|
||||
let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute");
|
||||
let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute");
|
||||
|
||||
if ((starttime >= 5000) || (endtime == 5000) || (endtime == 6000)) {
|
||||
var times = SunCalc.getTimes(now, node.lat, node.lon);
|
||||
var startMillis = Date.UTC(times[node.start].getUTCFullYear(), times[node.start].getUTCMonth(), times[node.start].getUTCDate(), times[node.start].getUTCHours(), times[node.start].getUTCMinutes());
|
||||
var endMillis = Date.UTC(times[node.end].getUTCFullYear(), times[node.end].getUTCMonth(), times[node.end].getUTCDate(), times[node.end].getUTCHours(), times[node.end].getUTCMinutes());
|
||||
var dawn = ((startMillis - midnightMillis) / 60000) + Number(node.dawnoff);
|
||||
var dusk = ((endMillis - midnightMillis) / 60000) + Number(node.duskoff);
|
||||
if (starttime == 5000) { starttime = dawn; }
|
||||
if (starttime == 6000) { starttime = dusk; }
|
||||
if (endtime == 5000) { endtime = dawn; }
|
||||
if (endtime == 6000) { endtime = dusk; }
|
||||
if (RED.settings.verbose) { node.log("Dawn " + parseInt(dawn / 60) + ":" + dawn % 60 + " - Dusk " + parseInt(dusk / 60) + ":" + dusk % 60); }
|
||||
// add optional sun event offset, if specified
|
||||
sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
|
||||
sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes");
|
||||
|
||||
// check if sun event has already occurred today
|
||||
if (now.isAfter(sunriseDateTime)) {
|
||||
// get tomorrow's sunrise, since it'll be different
|
||||
sunriseDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNRISE_KEY]).nearest("minute");
|
||||
// add optional sun event offset, if specified (again)
|
||||
sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
|
||||
}
|
||||
if (now.isAfter(sunsetDateTime)) {
|
||||
// get tomorrow's sunset, since it'll be different
|
||||
sunsetDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNSET_KEY]).nearest("minute");
|
||||
// add optional sun event offset, if specified (again)
|
||||
sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes");
|
||||
}
|
||||
|
||||
var proceed = 0;
|
||||
switch (now.getDay()) {
|
||||
case 0 : { if (node.sun) { proceed++; } break; }
|
||||
case 1 : { if (node.mon) { proceed++; } break; }
|
||||
case 2 : { if (node.tue) { proceed++; } break; }
|
||||
case 3 : { if (node.wed) { proceed++; } break; }
|
||||
case 4 : { if (node.thu) { proceed++; } break; }
|
||||
case 5 : { if (node.fri) { proceed++; } break; }
|
||||
case 6 : { if (node.sat) { proceed++; } break; }
|
||||
// log sun events
|
||||
if (RED.settings.verbose) {
|
||||
node.log(`Sunrise ${sunriseDateTime.format("time")} - Sunset ${sunsetDateTime.format("time")} `);
|
||||
}
|
||||
|
||||
if (proceed) {
|
||||
switch (now.getMonth()) {
|
||||
case 0 : { if (node.jan) { proceed++; } break; }
|
||||
case 1 : { if (node.feb) { proceed++; } break; }
|
||||
case 2 : { if (node.mar) { proceed++; } break; }
|
||||
case 3 : { if (node.apr) { proceed++; } break; }
|
||||
case 4 : { if (node.may) { proceed++; } break; }
|
||||
case 5 : { if (node.jun) { proceed++; } break; }
|
||||
case 6 : { if (node.jul) { proceed++; } break; }
|
||||
case 7 : { if (node.aug) { proceed++; } break; }
|
||||
case 8 : { if (node.sep) { proceed++; } break; }
|
||||
case 9 : { if (node.oct) { proceed++; } break; }
|
||||
case 10: { if (node.nov) { proceed++; } break; }
|
||||
case 11: { if (node.dec) { proceed++; } break; }
|
||||
// apply selected timezone to selected times (not to sunrise/sunset-- those are based on lat/long)
|
||||
const currentTimeZone = now.timezone();
|
||||
const selectedTimeZone = spacetime(now.epoch, this.timezone.toLowerCase()).timezone();
|
||||
|
||||
// handler function to convert minute strings (from <option> tags) to spacetime objects, called below
|
||||
let getSelectedTimeFromMinuteString = minuteString => {
|
||||
const selectedTimeInMinutesAfterMidnight = Number(minuteString);
|
||||
let selectedTime = spacetime.now();
|
||||
// if less than 1440, what are the time values for the next start and stop time?
|
||||
if (selectedTimeInMinutesAfterMidnight < 1440) {
|
||||
// determine offset to get from selected time zone to current timezone
|
||||
// e.g. current (EDT) is -4, selected (PDT) is -7
|
||||
// in order to get from PDT to EDT, you must add 3
|
||||
// (-4) - (-7) = +3
|
||||
const offset = currentTimeZone.current.offset - selectedTimeZone.current.offset;
|
||||
const selectedHourValue = Math.floor(selectedTimeInMinutesAfterMidnight / 60);
|
||||
const selectedMinuteValue = Math.floor(selectedTimeInMinutesAfterMidnight % 60);
|
||||
selectedTime = selectedTime.hour(selectedHourValue).minute(selectedMinuteValue).second(0).millisecond(0);
|
||||
selectedTime = selectedTime.add(offset, "hours");
|
||||
// select the next time if it's in the past
|
||||
if (now.isAfter(selectedTime)) {
|
||||
selectedTime = selectedTime.add(1, "day");
|
||||
}
|
||||
} else if (selectedTimeInMinutesAfterMidnight == 5000) { // sunrise
|
||||
selectedTime = sunriseDateTime;
|
||||
} else if (selectedTimeInMinutesAfterMidnight == 6000) { // sunset
|
||||
selectedTime = sunsetDateTime;
|
||||
}
|
||||
return selectedTime;
|
||||
};
|
||||
|
||||
// our definitive next ON time
|
||||
let selectedOnTime = getSelectedTimeFromMinuteString(node.startt);
|
||||
// our definitive next OFF time
|
||||
let selectedOffTime = getSelectedTimeFromMinuteString(node.endt);
|
||||
|
||||
// handle the "Start + X Minutes" cases
|
||||
if (node.endt >= 10000) {
|
||||
// even though the next start time might be tomorrow,
|
||||
// the start time + X minutes might still be coming today,
|
||||
// so we need to go back a day first
|
||||
const selectedOnTimeMinus1Day = selectedOnTime.subtract(1, "day");
|
||||
selectedOffTime = selectedOnTimeMinus1Day.add(node.endt - 10000, "minutes");
|
||||
// _now_ we can check if the off time is in the past
|
||||
if (now.isAfter(selectedOffTime)) {
|
||||
selectedOffTime = selectedOffTime.add(1, "day");
|
||||
}
|
||||
}
|
||||
|
||||
if (proceed >= 2) { proceed = 1; }
|
||||
else { proceed = 0; }
|
||||
|
||||
newendtime = endtime;
|
||||
if (endtime > 10000) { newendtime = starttime + (endtime - 10000); }
|
||||
|
||||
if (proceed) { // have to handle midnight wrap
|
||||
if (starttime <= newendtime) {
|
||||
if ((today >= starttime) && (today <= newendtime)) { proceed++; }
|
||||
// handler function for the node payload, called below
|
||||
let sendPayload = (payload, nextTime) => {
|
||||
// var o = nextTime.goto(selectedTimeZone.name).offset()/60;
|
||||
// if (o > 0) { o = "+" + o; }
|
||||
// else {o = "-" + o; }
|
||||
if (payload == 1) {
|
||||
node.status({
|
||||
fill: "yellow",
|
||||
shape: "dot",
|
||||
text: `on until ${nextTime.goto(selectedTimeZone.name).format("time-24")}`
|
||||
});
|
||||
} else {
|
||||
node.status({
|
||||
fill: "blue",
|
||||
shape: "dot",
|
||||
text: `off until ${nextTime.goto(selectedTimeZone.name).format("time-24")}`
|
||||
});
|
||||
}
|
||||
else {
|
||||
if ((today >= starttime) || (today <= newendtime)) { proceed++; }
|
||||
var msg = {};
|
||||
if (node.mytopic) {
|
||||
msg.topic = node.mytopic;
|
||||
}
|
||||
msg.payload = payload;
|
||||
node.send(msg);
|
||||
};
|
||||
|
||||
var proceed = true;
|
||||
|
||||
// if today is not among the selected days of the week, stop here
|
||||
switch (nowNative.getDay()) {
|
||||
case 0 : { if (!node.sun) { proceed &= false; } break; }
|
||||
case 1 : { if (!node.mon) { proceed &= false; } break; }
|
||||
case 2 : { if (!node.tue) { proceed &= false; } break; }
|
||||
case 3 : { if (!node.wed) { proceed &= false; } break; }
|
||||
case 4 : { if (!node.thu) { proceed &= false; } break; }
|
||||
case 5 : { if (!node.fri) { proceed &= false; } break; }
|
||||
case 6 : { if (!node.sat) { proceed &= false; } break; }
|
||||
}
|
||||
|
||||
if (proceed >= 2) {
|
||||
var duration = newendtime - today;
|
||||
if (today > newendtime) { duration += 1440; }
|
||||
//node.status({fill:"yellow",shape:"dot",text:"on for " + duration + " mins"});
|
||||
node.status({fill:"yellow", shape:"dot", text:"on until " + parseInt(newendtime / 60) + ":" + ("0" + newendtime % 60).substr(-2)});
|
||||
if (!proceed) {
|
||||
sendPayload(0, selectedOnTime);
|
||||
return;
|
||||
}
|
||||
//else { node.status({fill:"blue",shape:"dot",text:"off"}); }
|
||||
else { node.status({fill:"blue", shape:"dot", text:"off until " + parseInt(starttime / 60) + ":" + ("0" + starttime % 60).substr(-2)}); }
|
||||
|
||||
var msg = {};
|
||||
if (node.mytopic) { msg.topic = node.mytopic; }
|
||||
msg.payload = (proceed >= 2) ? 1 : 0;
|
||||
node.send(msg);
|
||||
// if this month is not among the selected months, stop here
|
||||
switch (nowNative.getMonth()) {
|
||||
case 0 : { if (!node.jan) { proceed &= false; } break; }
|
||||
case 1 : { if (!node.feb) { proceed &= false; } break; }
|
||||
case 2 : { if (!node.mar) { proceed &= false; } break; }
|
||||
case 3 : { if (!node.apr) { proceed &= false; } break; }
|
||||
case 4 : { if (!node.may) { proceed &= false; } break; }
|
||||
case 5 : { if (!node.jun) { proceed &= false; } break; }
|
||||
case 6 : { if (!node.jul) { proceed &= false; } break; }
|
||||
case 7 : { if (!node.aug) { proceed &= false; } break; }
|
||||
case 8 : { if (!node.sep) { proceed &= false; } break; }
|
||||
case 9 : { if (!node.oct) { proceed &= false; } break; }
|
||||
case 10: { if (!node.nov) { proceed &= false; } break; }
|
||||
case 11: { if (!node.dec) { proceed &= false; } break; }
|
||||
}
|
||||
|
||||
if (!proceed) {
|
||||
sendPayload(0, selectedOnTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the chronological order is NOW --> ON --> OFF, then now should be OFF
|
||||
if (proceed && selectedOffTime.isAfter(selectedOnTime)) {
|
||||
sendPayload(0, selectedOnTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the chronological order is NOW --> OFF --> ON, then now should be ON
|
||||
if (proceed && selectedOffTime.isBefore(selectedOnTime)) {
|
||||
sendPayload(1, selectedOffTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: we already ensured that all ON or OFF times would be in the future,
|
||||
// so there is no midnight wrapping issue.
|
||||
});
|
||||
|
||||
var tock = setTimeout(function() {
|
||||
var tock = setTimeout(function () {
|
||||
node.emit("input", {});
|
||||
}, 2000); // wait 2 secs before starting to let things settle down – e.g. UI connect
|
||||
|
||||
var tick = setInterval(function() {
|
||||
var tick = setInterval(function () {
|
||||
node.emit("input", {});
|
||||
}, 60000); // trigger every 60 secs
|
||||
|
||||
this.on("close", function() {
|
||||
this.on("close", function () {
|
||||
if (tock) { clearTimeout(tock); }
|
||||
if (tick) { clearInterval(tick); }
|
||||
});
|
||||
}
|
||||
|
||||
RED.httpAdmin.post("/timeswitch/:id", RED.auth.needsPermission("timeswitch.write"), function(req, res) {
|
||||
RED.httpAdmin.post("/timeswitch/:id", RED.auth.needsPermission("timeswitch.write"), function (req, res) {
|
||||
var node = RED.nodes.getNode(req.params.id);
|
||||
if (node != null) {
|
||||
try {
|
||||
node.emit("input", {payload:"reset"});
|
||||
node.emit("input", { payload: "reset" });
|
||||
res.sendStatus(200);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
res.sendStatus(500);
|
||||
node.error("Inject failed:" + err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
@ -34,7 +34,7 @@ to restart the command automatically.
|
||||
|
||||
Setting `msg.kill` to a signal name (e.g. SIGINT, SIGHUP) will stop the process - but if the restart flag is set it will then auto restart.
|
||||
|
||||
Sending `msg.start` will also re-start the process.
|
||||
Sending `msg.start` will also re-start the process. Additional arguments can be specified in `msg.args`.
|
||||
|
||||
**Note:** Some applications will automatically buffer lines of output. It is advisable to turn off this behaviour.
|
||||
For example, if running a Python app, the `-u` parameter will stop the output being buffered.
|
||||
|
@ -15,16 +15,21 @@ module.exports = function(RED) {
|
||||
this.closer = n.closer || "SIGKILL";
|
||||
this.autorun = true;
|
||||
if (n.autorun === false) { this.autorun = false; }
|
||||
if (this.args.match(/^\[.*\]$/)) {
|
||||
try { this.args = JSON.parse(this.args); }
|
||||
catch(e) {
|
||||
node.warn(RED._("daemon.errors.badparams"))
|
||||
}
|
||||
}
|
||||
else { this.args = this.args.match(/("[^"]*")|[^ ]+/g); }
|
||||
this.args = parseArgs(this.args);
|
||||
var node = this;
|
||||
var lastmsg = {};
|
||||
|
||||
function parseArgs(args) {
|
||||
if (args.match(/^\[.*\]$/)) {
|
||||
try { args = JSON.parse(args); }
|
||||
catch(e) {
|
||||
node.warn(RED._("daemon.errors.badparams"))
|
||||
}
|
||||
}
|
||||
else { args = args.match(/("[^"]*")|[^ ]+/g); }
|
||||
return args;
|
||||
}
|
||||
|
||||
function inputlistener(msg) {
|
||||
if (msg != null) {
|
||||
if (msg.hasOwnProperty("kill") && node.running) {
|
||||
@ -32,7 +37,11 @@ module.exports = function(RED) {
|
||||
node.child.kill(msg.kill.toUpperCase());
|
||||
}
|
||||
else if (msg.hasOwnProperty("start") && !node.running) {
|
||||
runit();
|
||||
let args = "";
|
||||
if (msg.hasOwnProperty("args") && msg.args.length > 0) {
|
||||
args = parseArgs(msg.args.trim());
|
||||
}
|
||||
runit(args);
|
||||
}
|
||||
else {
|
||||
if (!Buffer.isBuffer(msg.payload)) {
|
||||
@ -41,22 +50,27 @@ module.exports = function(RED) {
|
||||
if (node.cr === true) { msg.payload += "\n"; }
|
||||
}
|
||||
node.debug("inp: "+msg.payload);
|
||||
lastmsg = msg;
|
||||
if (node.child !== null && node.running) { node.child.stdin.write(msg.payload); }
|
||||
else { node.warn(RED._("daemon.errors.notrunning")); }
|
||||
lastmsg = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runit() {
|
||||
function runit(appendArgs) {
|
||||
var line = "";
|
||||
if (!node.cmd || (typeof node.cmd !== "string") || (node.cmd.length < 1)) {
|
||||
node.status({fill:"grey",shape:"ring",text:RED._("daemon.status.nocommand")});
|
||||
return;
|
||||
}
|
||||
let args = node.args;
|
||||
if (appendArgs !== undefined && appendArgs.length > 0) {
|
||||
args = args.concat(appendArgs);
|
||||
}
|
||||
|
||||
try {
|
||||
node.child = spawn(node.cmd, node.args);
|
||||
node.debug(node.cmd+" "+JSON.stringify(node.args));
|
||||
node.child = spawn(node.cmd, args);
|
||||
node.debug(node.cmd+" "+JSON.stringify(args));
|
||||
node.status({fill:"green",shape:"dot",text:RED._("daemon.status.running")});
|
||||
node.running = true;
|
||||
|
||||
@ -106,6 +120,12 @@ module.exports = function(RED) {
|
||||
else { node.log('error: ' + err); }
|
||||
node.status({fill:"red",shape:"ring",text:RED._("daemon.status.error")});
|
||||
});
|
||||
|
||||
node.child.stdin.on('error', function (err) {
|
||||
if (err.errno === "EPIPE") { node.error(RED._("daemon.errors.pipeclosed"),lastmsg); }
|
||||
else { node.log('error: ' + err); }
|
||||
node.status({fill:"red",shape:"ring",text:RED._("daemon.status.error")});
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
if (e.errno === "ENOENT") { node.warn(RED._("daemon.errors.notfound")); }
|
||||
|
@ -5,7 +5,7 @@
|
||||
<p>Parameters can be space separated, space separated with quotes, or a javascript array. For example `aa bb` or `"cc dd"` or `["aa","bb cc""]`.</p>
|
||||
<p>If the called program stops (i.e. a return code is produced), this node can attempt to restart the command.</p>
|
||||
<p>Setting <code>msg.kill</code> to a signal name (e.g. SIGINT, SIGHUP) will stop the process - but if the
|
||||
restart flag is set it will then auto restart. Sending <code>msg.start</code> will also re-start the process.</p>
|
||||
restart flag is set it will then auto restart. Sending <code>msg.start</code> will also re-start the process. Additional arguments can be specified in <code>msg.args</code>.</p>
|
||||
<p><b>Note:</b> Some applications will automatically buffer lines of output. It is advisable to turn off this behaviour.
|
||||
For example, if running a Python app, the <code>-u</code> parameter will stop the output being buffered.</p>
|
||||
</script>
|
||||
|
@ -37,7 +37,8 @@
|
||||
"notrunning": "Command not running",
|
||||
"notfound": "Command not found",
|
||||
"notexecutable": "Command not executable",
|
||||
"restarting": "Restarting"
|
||||
"restarting": "Restarting",
|
||||
"pipeclosed": "Process closed"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-daemon",
|
||||
"version" : "0.4.0",
|
||||
"version" : "0.5.1",
|
||||
"description" : "A Node-RED node that runs and monitors a long running system command.",
|
||||
"dependencies" : {
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user